PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 4.4.9 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 All 142 releases
learnpress / inc / class-lp-helper.php

class-lp-helper.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.10, at inc/class-lp-helper.php

658 lines 16.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Class LP_Helper
5 */
6
7 use LearnPress\Helpers\Template;
8
9 defined( 'ABSPATH' ) || exit;
10
11 class LP_Helper {
12
13 /**
14 * Wrap function unserialize to fix issues with UTF-8 chars when encoding/decoding
15 * of serialize function.
16 *
17 * @param string $string
18 *
19 * @return mixed
20 */
21 public static function maybe_unserialize( $string ) {
22 if ( is_string( $string ) && is_serialized( $string ) ) {
23 $string = trim( $string );
24 $unserialized = @unserialize( $string, array( 'allowed_classes' => false ) );
25 if ( false === $unserialized && 'b:0;' !== $string ) {
26 $string = preg_replace_callback(
27 '!s:(\d+):"(.*?)";!s',
28 array( __CLASS__, '_unserialize_replace_callback' ),
29 $string
30 );
31
32 $unserialized = @unserialize( $string, array( 'allowed_classes' => false ) );
33 }
34
35 $string = $unserialized;
36 }
37
38 return $string;
39 }
40
41 public static function _unserialize_replace_callback( $m ) {
42 $len = strlen( $m[2] );
43 $result = "s:$len:\"{$m[2]}\";";
44
45 return $result;
46 }
47
48 /**
49 * Shuffle array and keep the keys
50 *
51 * @param array $array
52 *
53 * @return bool
54 */
55 public static function shuffle_assoc( &$array ) {
56 $keys = array_keys( $array );
57 shuffle( $keys );
58 $new = array();
59 foreach ( $keys as $key ) {
60 $new[ $key ] = $array[ $key ];
61 }
62 $array = $new;
63
64 return true;
65 }
66
67 /**
68 * MD5 an array.
69 *
70 * @param array $array
71 *
72 * @return string
73 */
74 public static function array_to_md5( $array ) {
75 settype( $array, 'array' );
76 ksort( $array );
77
78 return md5( serialize( $array ) );
79 }
80
81 /**
82 * Load posts from database into cache by ids
83 *
84 * @param array|int $ids
85 *
86 * @Todo: tungnx - need to review code - addon h5p v4.0.3 still use
87 * @deprecated 4.1.6.9
88 */
89 public static function cache_posts( $ids ) {
90 //_deprecated_function( __FUNCTION__, '4.1.6.9' );
91 }
92
93 /**
94 * Merge two or more classes into one.
95 *
96 * @return array
97 */
98 public static function merge_class() {
99 if ( func_num_args() == 1 ) {
100 return func_get_arg( 0 );
101 } elseif ( func_num_args() == 0 ) {
102 return null;
103 }
104
105 $classes = array();
106 foreach ( func_get_args() as $class ) {
107 if ( is_string( $class ) ) {
108 $cls = explode( ' ', $class );
109 $classes = array_merge( $classes, $cls );
110 } else {
111 $classes = array_merge( $classes, $class );
112 }
113 }
114
115 $classes = array_filter( $classes );
116 $classes = array_unique( $classes );
117
118 return $classes;
119 }
120
121 /**
122 * Sanitize order statuses.
123 * Add prefix lp- into each status if it is not exists.
124 *
125 * @param $statuses
126 *
127 * @return array|mixed
128 */
129 public static function sanitize_order_status( &$statuses ) {
130 if ( is_array( $statuses ) ) {
131 foreach ( $statuses as $k => $status ) {
132 if ( false === strpos( $status, 'lp-' ) ) {
133 $statuses[ $k ] = "lp-{$status}";
134 }
135 }
136 } else {
137 $statuses = preg_split( '#\s+#', $statuses );
138 self::sanitize_order_status( $statuses );
139 if ( sizeof( $statuses ) == 1 ) {
140 $statuses = reset( $statuses );
141 }
142 }
143
144 return $statuses;
145 }
146
147 /**
148 * Merge two arrays recursive.
149 *
150 * @param array $array1
151 * @param array $array2
152 *
153 * @return array
154 */
155 public static function array_merge_recursive( &$array1, &$array2 ) {
156 $merged = $array1;
157
158 if ( is_array( $array1 ) && is_array( $array2 ) ) {
159 foreach ( $array2 as $key => & $value ) {
160 if ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
161 $merged[ $key ] = self::array_merge_recursive( $merged[ $key ], $value );
162 } elseif ( is_numeric( $key ) ) {
163 if ( ! in_array( $value, $merged ) ) {
164 $merged[] = $value;
165 }
166 } else {
167 $merged[ $key ] = $value;
168 }
169 }
170 } elseif ( is_array( $array2 ) ) {
171 $merged = $array2;
172 }
173
174 return $merged;
175 }
176
177 /**
178 * Encode the object to json format.
179 * Replace number, "false", "true" in couple of quotes with
180 * original value.
181 * Example:
182 * Input: {
183 * number: "1234",
184 * true_value: "true",
185 * false_value: "false"
186 * }
187 * Output: {
188 * number: 1234,
189 * true_value: true,
190 * false_value: false
191 * }
192 *
193 * @param array $data
194 *
195 * @return false|mixed|string
196 */
197 public static function json_encode( $data ) {
198 $data = wp_json_encode( $data );
199 $data = preg_replace_callback(
200 '~:"(([0-9]+)([.,]?)([0-9]?)|true|false)"~',
201 array(
202 __CLASS__,
203 '_valid_json_value',
204 ),
205 $data
206 );
207
208 return $data;
209 }
210
211 /**
212 * Callback function for json_encode method "json_encode".
213 *
214 * @param array $m
215 *
216 * @return string
217 */
218 public static function _valid_json_value( $m ) {
219 return str_replace( array( ':"', '"' ), array( ':', '' ), $m[0] );
220 }
221
222 /**
223 * Create LP static page.
224 *
225 * @param array $args
226 * @param string $key_option
227 *
228 * @return bool|int|WP_Error
229 */
230 public static function create_page( array $args, string $key_option ) {
231 $page_id = 0;
232
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 );
253
254 if ( ! in_array( $key_option, $key_pages_allow ) ) {
255 throw new Exception( __( 'Invalid key page', 'learnpress' ) );
256 }
257
258 if ( ! isset( $args['post_title'] ) ) {
259 throw new Exception( __( 'Missing post title', 'learnpress' ) );
260 }
261
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 -->';
268 }
269
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 );
282
283 $page_id = wp_insert_post( $args );
284 if ( ! $page_id ) {
285 return false;
286 }
287
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() );
293 }
294
295 return $page_id;
296 }
297
298 /**
299 * Get the current url
300 *
301 * @return string
302 * @since 3.2.6.8
303 * @author tungnx
304 */
305 public static function getUrlCurrent(): string {
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' ) ?? '' );
311
312 return untrailingslashit( $schema . $http_host . $request_uri );
313 }
314
315 /**
316 * Check request is rest api
317 *
318 * @return bool
319 * @author tungnx
320 * @since 4.1.6.6
321 */
322 public static function isRestApiLP(): bool {
323 $restPrefix = '/' . rest_get_url_prefix();
324 return strpos( self::getUrlCurrent(), $restPrefix . '/lp/' ) || strpos( self::getUrlCurrent(), $restPrefix . '/learnpress/' );
325 }
326
327 /**
328 * Sanitize string and array
329 *
330 * @param array|string $value
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.
334 * @return array|string
335 * @since 3.2.7.1
336 * @author tungnx
337 */
338 public static function sanitize_params_submitted( $value, string $type_content = 'text', $unslash = true ) {
339 $value = $unslash ? wp_unslash( $value ) : $value;
340
341 if ( is_string( $value ) ) {
342 switch ( $type_content ) {
343 case 'html':
344 $value = Template::sanitize_html_content( $value );
345 break;
346 case 'textarea':
347 $value = sanitize_textarea_field( $value );
348 break;
349 case 'key':
350 $value = sanitize_key( $value );
351 break;
352 case 'int':
353 $value = (int) $value;
354 break;
355 case 'float':
356 $value = (float) $value;
357 break;
358 case 'email':
359 $value = sanitize_email( $value );
360 break;
361 default:
362 if ( is_callable( $type_content ) ) {
363 $value = call_user_func( $type_content, $value );
364 } else {
365 $value = sanitize_text_field( $value );
366 }
367 }
368 } elseif ( is_array( $value ) ) {
369 foreach ( $value as $k => $v ) {
370 unset( $value[ $k ] );
371 $value[ sanitize_text_field( $k ) ] = self::sanitize_params_submitted( $v, $type_content, $unslash );
372 }
373 }
374
375 return $value;
376 }
377
378 public static function db_format_array( array $arr, string $format = '%d' ): string {
379 $arr_formatted = array_fill( 0, sizeof( $arr ), $format );
380
381 return join( ',', $arr_formatted );
382 }
383
384 /**
385 * Calculate percent of progress rows on db
386 *
387 * @param int $offset .
388 * @param int $limit .
389 * @param int $total_row .
390 *
391 * @return float
392 */
393 public static function progress_percent( int $offset, int $limit, int $total_row ): float {
394 if ( $total_row <= 0 ) {
395 return 0;
396 }
397
398 $percent = ( $offset + $limit ) * 100 / $total_row;
399
400 $percent = min( $percent, 100 );
401
402 return floatval( number_format( $percent, 2 ) );
403 }
404
405 /**
406 * Convert array to string
407 * Ex: array("publish", "pending") to post_status IN(%s, %s)
408 *
409 * @param array $arr
410 *
411 * @return string
412 */
413 public static function format_query_IN( array $arr ): string {
414 $format = array_fill( 0, count( $arr ), '%s' );
415
416 return join( ',', $format );
417 }
418
419 /**
420 * Get link lp checkout page
421 * without cache - because some cache(redis) will cache page with user anonymous
422 */
423 public static function get_link_no_cache( string $link ): string {
424 return esc_url_raw( add_query_arg( 'no-cache', uniqid(), $link ) );
425 }
426
427 /**
428 * Check string is json
429 *
430 * @param string $str
431 * @param null $associative
432 *
433 * @return mixed
434 * @throws Exception
435 * @since 4.1.6.4
436 * @version 1.0.1
437 */
438 public static function json_decode( string $str, $associative = null ) {
439 $obj = json_decode( $str, $associative );
440 if ( json_last_error() !== JSON_ERROR_NONE ) {
441 throw new Exception( 'JSON decode: ' . json_last_error_msg() );
442 }
443
444 return $obj;
445 }
446
447 /**
448 * Handle permalink structure for LP
449 *
450 * @return string
451 * @since 4.2.2
452 * @version 1.0.4
453 */
454 public static function handle_lp_permalink_structure( $post_link, $post ) {
455 if ( false === strpos( $post_link, '%' ) ) {
456 return $post_link;
457 }
458
459 $find = [];
460 $replace = [];
461 if ( ! empty( $post->post_date_gmt ) ) {
462 $find = array(
463 '%year%',
464 '%monthnum%',
465 '%day%',
466 '%hour%',
467 '%minute%',
468 '%second%',
469 '%post_id%',
470 );
471
472 $time = strtotime( $post->post_date_gmt );
473
474 $replace = array(
475 date_i18n( 'Y', $time ),
476 date_i18n( 'm', $time ),
477 date_i18n( 'd', $time ),
478 date_i18n( 'H', $time ),
479 date_i18n( 'i', $time ),
480 date_i18n( 's', $time ),
481 $post->ID,
482 );
483 }
484
485 if ( strpos( $post_link, '%course_category%' ) && get_post_type( $post ) === LP_COURSE_CPT ) {
486 // Get the custom taxonomy terms in use by this post
487 $terms = get_the_terms( $post->ID, 'course_category' );
488
489 if ( ! empty( $terms ) ) {
490 $terms = wp_list_sort( $terms, 'term_id' );
491 // order by IDF
492 $category_object = apply_filters(
493 'learn_press_course_post_type_link_course_category',
494 $terms[0],
495 $terms,
496 $post
497 );
498 $category_object = get_term( $category_object, 'course_category' );
499 if ( ! $category_object instanceof WP_Term ) {
500 return $post_link;
501 }
502
503 $course_category = $category_object->slug;
504 $parent = $category_object->parent;
505 if ( $parent ) {
506 $ancestors = get_ancestors( $category_object->term_id, 'course_category' );
507 foreach ( $ancestors as $ancestor ) {
508 $ancestor_object = get_term( $ancestor, 'course_category' );
509 if ( $ancestor_object instanceof WP_Term ) {
510 $course_category = $ancestor_object->slug . '/' . $course_category;
511 }
512 }
513 }
514 } else {
515 // If no terms are assigned to this post, use a string instead (can't leave the placeholder there)
516 $course_category = _x( 'uncategorized', 'slug', 'learnpress' );
517 }
518
519 $find[] = '%course_category%';
520 $replace[] = urldecode( $course_category );
521 }
522
523 return apply_filters(
524 'learn-press/single-course/permalink',
525 str_replace( $find, $replace, $post_link ),
526 $post
527 );
528 }
529
530 /**
531 * Print variable script inline script tag.
532 * If $name_variable_script is empty,
533 * the script will be print as json with set $tag_args['type'] = application/json.
534 *
535 * @param string $name_variable_script
536 * @param array $data
537 * @param array $tag_args as ['type' => 'text/javascript', 'id' => '']
538 *
539 * @return void
540 * @version 1.0.2
541 * @since 4.2.5.5
542 */
543 public static function print_inline_script_tag( string $name_variable_script, array $data, array $tag_args = [] ) {
544 /**
545 * Comment block code reason by security
546 * wp_json_encode() already produces a valid JavaScript string,
547 * and there is no reason to decode HTML entities inside data that will be re-inserted with insertAdjacentHTML
548 * @comment since 4.4.8
549 */
550 /*foreach ( $data as $key => $value ) {
551 if ( ! is_scalar( $value ) ) {
552 continue;
553 }
554
555 $data[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' );
556 }*/
557
558 $data_json = wp_json_encode( $data );
559 $script = '';
560 if ( ! empty( $name_variable_script ) ) {
561 $script = "var {$name_variable_script} = {$data_json};";
562 } elseif ( isset( $tag_args['type'] ) && $tag_args['type'] === 'application/json' ) {
563 $script = $data_json;
564 }
565 wp_print_inline_script_tag( $script, $tag_args );
566 }
567
568 /**
569 * Get translation string single/plural
570 *
571 * @param float $number
572 * @param string $string_value
573 * @param bool $include_number
574 *
575 * @return string
576 * @since 4.2.7.4
577 * @version 1.0.0
578 */
579 public static function get_i18n_string_plural( float $number, string $string_value = '', bool $include_number = true ): string {
580 switch ( $string_value ) {
581 case LP_COURSE_CPT:
582 $plural = sprintf(
583 _n( 'Course', 'Courses', $number, 'learnpress' ),
584 $number
585 );
586 break;
587 case LP_LESSON_CPT:
588 $plural = sprintf(
589 _n( 'Lesson', 'Lessons', $number, 'learnpress' ),
590 $number
591 );
592 break;
593 case LP_QUIZ_CPT:
594 $plural = sprintf(
595 _n( 'Quiz', 'Quizzes', $number, 'learnpress' ),
596 $number
597 );
598 break;
599 case LP_QUESTION_CPT:
600 $plural = sprintf(
601 _n( 'Question', 'Questions', $number, 'learnpress' ),
602 $number
603 );
604 break;
605 case 'lp_assignment':
606 $plural = sprintf(
607 _n( 'Assignment', 'Assignments', $number, 'learnpress' ),
608 $number
609 );
610 break;
611 case 'lp_h5p':
612 $plural = sprintf(
613 _n( 'H5P', 'H5Ps', $number, 'learnpress' ),
614 $number
615 );
616 break;
617 default:
618 $plural = $string_value;
619 break;
620 }
621
622 if ( $include_number ) {
623 $plural = sprintf( '%s %s', $number, $plural );
624 }
625
626 return apply_filters( 'learn-press/i18n/plural', $plural, $number, $string_value, $include_number );
627 }
628
629 /**
630 * Get client ip address
631 *
632 * @return mixed|string
633 * @since 4.2.7.9
634 * @version 1.0.0
635 */
636 public static function get_client_ip(): string {
637 $ipaddress = 'ip-unknown';
638 if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
639 $ipaddress = $_SERVER['HTTP_CLIENT_IP'];
640 } elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
641 $ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
642 } elseif ( isset( $_SERVER['HTTP_X_FORWARDED'] ) ) {
643 $ipaddress = $_SERVER['HTTP_X_FORWARDED'];
644 } elseif ( isset( $_SERVER['HTTP_FORWARDED_FOR'] ) ) {
645 $ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
646 } elseif ( isset( $_SERVER['HTTP_FORWARDED'] ) ) {
647 $ipaddress = $_SERVER['HTTP_FORWARDED'];
648 } elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
649 $ipaddress = $_SERVER['REMOTE_ADDR'];
650 }
651
652 $ipaddress = (string) $ipaddress;
653 $ipaddress = 'gip-' . $ipaddress;
654
655 return $ipaddress;
656 }
657 }
658