PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.0
Tutor LMS – eLearning and online course solution v4.0.0
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 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / includes / tutor-general-functions.php
tutor / includes Last commit date
droip 1 week ago kirki 1 week ago theme-compatibility 2 days ago country.php 2 days ago ecommerce-functions.php 2 days ago tinymce_translate.php 2 days ago translate-text.php 2 days ago tutor-general-functions.php 2 days ago tutor-template-functions.php 2 days ago tutor-template-hook.php 4 years ago
tutor-general-functions.php
2032 lines
1 <?php
2 /**
3 * Tutor general functions
4 *
5 * @package TutorFunctions
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 1.0.0
9 */
10
11 defined( 'ABSPATH' ) || exit;
12
13 use TUTOR\Assets;
14 use Tutor\Cache\FlashMessage;
15 use Tutor\Components\Alert;
16 use Tutor\Ecommerce\Ecommerce;
17 use Tutor\Ecommerce\OptionKeys;
18 use Tutor\Ecommerce\Settings;
19 use TUTOR\Icon;
20 use TUTOR\Input;
21 use Tutor\Models\CourseModel;
22 use Tutor\Models\EnrollmentModel;
23
24
25 if ( ! function_exists( 'tutor' ) ) {
26 /**
27 * Tutor helper function to get configuration like version, path etc.
28 *
29 * @since 1.0.0
30 * @since 3.7.0 updated with config class.
31 *
32 * @return \TUTOR\Config
33 */
34 function tutor(): \TUTOR\Config {
35 return \TUTOR\Config::get_instance();
36 }
37 }
38
39 if ( ! function_exists( 'tutor_utils' ) ) {
40 /**
41 * Access tutor utils functions.
42 *
43 * @since 1.0.0
44 *
45 * @return \TUTOR\Utils
46 */
47 function tutor_utils() {
48 if ( ! isset( $GLOBALS['tutor_utils_object'] ) ) {
49 // Use runtime cache.
50 $GLOBALS['tutor_utils_object'] = new \TUTOR\Utils();
51 }
52
53 return $GLOBALS['tutor_utils_object'];
54 }
55 }
56
57
58 if ( ! function_exists( 'tutils' ) ) {
59 /**
60 * Alias of tutor_utils().
61 *
62 * @since 1.3.4
63 *
64 * @return \TUTOR\Utils
65 */
66 function tutils() {
67 return tutor_utils();
68 }
69 }
70
71 if ( ! function_exists( 'tutor_sanitize_data' ) ) {
72 /**
73 * Escaping for Sanitize data.
74 *
75 * @since 1.9.13
76 *
77 * @param string $input input data.
78 * @param string $type type of input.
79 *
80 * @return string|array|object
81 */
82 function tutor_sanitize_data( $input = null, $type = null ) {
83 $array = array();
84 $object = new stdClass();
85
86 if ( is_string( $input ) ) {
87
88 if ( 'textarea' === $type ) {
89 $input = sanitize_textarea_field( $input );
90 } elseif ( 'kses' === $type ) {
91 $input = wp_kses_post( $input );
92 } else {
93 $input = sanitize_text_field( $input );
94 }
95
96 return $input;
97
98 } elseif ( is_object( $input ) && count( get_object_vars( $input ) ) ) {
99
100 foreach ( $input as $key => $value ) {
101 if ( is_object( $value ) ) {
102 $object->$key = tutor_sanitize_data( $value );
103 } else {
104 $key = sanitize_text_field( $key );
105 $value = sanitize_text_field( $value );
106 $object->$key = $value;
107 }
108 }
109 return $object;
110 } elseif ( is_array( $input ) && count( $input ) ) {
111 foreach ( $input as $key => $value ) {
112 if ( is_array( $value ) ) {
113 $array[ $key ] = tutor_sanitize_data( $value );
114 } else {
115 $key = sanitize_text_field( $key );
116 $value = sanitize_text_field( $value );
117 $array[ $key ] = $value;
118 }
119 }
120
121 return $array;
122 }
123 }
124 }
125
126 if ( ! function_exists( 'tutor_placeholder_img_src' ) ) {
127 /**
128 * Get placeholder image src.
129 *
130 * @since 1.0.0
131 *
132 * @return string
133 */
134 function tutor_placeholder_img_src() {
135 $src = tutor()->url . 'assets/images/placeholder.svg';
136 return apply_filters( 'tutor_placeholder_img_src', $src );
137 }
138 }
139
140 if ( ! function_exists( 'tutor_course_categories_dropdown' ) ) {
141 /**
142 * Get course categories selecting UI.
143 *
144 * @since 1.3.4
145 *
146 * @param int $post_ID post ID.
147 * @param array $args arguments.
148 *
149 * @return string
150 */
151 function tutor_course_categories_dropdown( $post_ID = 0, $args = array() ) {
152
153 $default = array(
154 'classes' => '',
155 'name' => 'tax_input[course-category]',
156 'multiple' => true,
157 );
158
159 $args = apply_filters( 'tutor_course_categories_dropdown_args', array_merge( $default, $args ) );
160
161 $multiple_select = '';
162
163 if ( tutor_utils()->array_get( 'multiple', $args ) ) {
164 if ( isset( $args['name'] ) ) {
165 $args['name'] = $args['name'] . '[]';
166 }
167 $multiple_select = "multiple='multiple'";
168 }
169
170 extract( $args ); //phpcs:ignore
171
172 $classes = (array) $classes;
173 $classes = implode( ' ', $classes );
174
175 $categories = tutor_utils()->get_course_categories();
176
177 $output = '';
178 $output .= '<select name="' . $name . '" ' . $multiple_select . ' class="' . $classes . '" data-placeholder="' . __( 'Search Course Category. ex. Design, Development, Business', 'tutor' ) . '">';
179 $output .= '<option value="">' . __( 'Select a category', 'tutor' ) . '</option>';
180 $output .= _generate_categories_dropdown_option( $post_ID, $categories, $args );
181 $output .= '</select>';
182
183 return $output;
184 }
185 }
186
187
188 if ( ! function_exists( 'tutor_course_tags_dropdown' ) ) {
189 /**
190 * Get course tags selecting UI.
191 *
192 * @since 1.3.4
193 *
194 * @param int $post_ID post ID.
195 * @param array $args arguments.
196 *
197 * @return string
198 */
199 function tutor_course_tags_dropdown( $post_ID = 0, $args = array() ) {
200
201 $default = array(
202 'classes' => '',
203 'name' => 'tax_input[course-tag]',
204 'multiple' => true,
205 );
206
207 $args = apply_filters( 'tutor_course_tags_dropdown_args', array_merge( $default, $args ) );
208
209 $multiple_select = '';
210
211 if ( tutor_utils()->array_get( 'multiple', $args ) ) {
212 if ( isset( $args['name'] ) ) {
213 $args['name'] = $args['name'] . '[]';
214 }
215 $multiple_select = "multiple='multiple'";
216 }
217
218 extract( $args ); //phpcs:ignore
219
220 $classes = (array) $classes;
221 $classes = implode( ' ', $classes );
222
223 $tags = tutor_utils()->get_course_tags();
224
225 $output = '';
226 $output .= '<select name=' . $name . ' ' . $multiple_select . ' class="' . $classes . '" data-placeholder="' . __( 'Search Course Tags. ex. Design, Development, Business', 'tutor' ) . '">';
227 $output .= '<option value="">' . __( 'Select a tag', 'tutor' ) . '</option>';
228 $output .= _generate_tags_dropdown_option( $post_ID, $tags, $args );
229 $output .= '</select>';
230
231 return $output;
232 }
233 }
234
235 if ( ! function_exists( '_generate_categories_dropdown_option' ) ) {
236 /**
237 * Get selecting options, recursive supports.
238 *
239 * @since 1.3.4
240 *
241 * @param int $post_ID post ID.
242 * @param array $categories categories.
243 * @param array $args arguments.
244 * @param int $depth depth.
245 *
246 * @return string
247 */
248 function _generate_categories_dropdown_option( $post_ID = 0, $categories = array(), $args = array(), $depth = 0 ) {
249 $output = '';
250
251 if ( ! tutor_utils()->count( $categories ) ) {
252 return $output;
253 }
254
255 if ( ! is_numeric( $post_ID ) || $post_ID < 1 ) {
256 return $output;
257 }
258
259 foreach ( $categories as $category_id => $category ) {
260 if ( ! $category->parent ) {
261 $depth = 0;
262 }
263
264 $childrens = tutor_utils()->array_get( 'children', $category );
265 $has_in_term = has_term( $category->term_id, 'course-category', $post_ID );
266
267 $depth_seperator = '';
268 if ( $depth ) {
269 for ( $depth_i = 0; $depth_i < $depth; $depth_i++ ) {
270 $depth_seperator .= '-';
271 }
272 }
273
274 $output .= '<option value="' . $category->term_id . '" ' . selected( $has_in_term, true, false ) . '> ' . $depth_seperator . ' ' . $category->name . '</option>';
275
276 if ( tutor_utils()->count( $childrens ) ) {
277 $depth++;
278 $output .= _generate_categories_dropdown_option( $post_ID, $childrens, $args, $depth );
279 }
280 }
281
282 return $output;
283 }
284 }
285
286 if ( ! function_exists( '_generate_tags_dropdown_option' ) ) {
287 /**
288 * Get selecting options, recursive supports
289 *
290 * @since 1.3.4
291 *
292 * @param int $post_ID post ID.
293 * @param array $tags tags.
294 * @param array $args arguments.
295 * @param int $depth depth.
296 *
297 * @return string
298 */
299 function _generate_tags_dropdown_option( $post_ID = 0, $tags = array(), $args = array(), $depth = 0 ) {
300 $output = '';
301
302 if ( ! tutor_utils()->count( $tags ) ) {
303 return $output;
304 }
305
306 if ( ! is_numeric( $post_ID ) || $post_ID < 1 ) {
307 return $output;
308 }
309
310 foreach ( $tags as $tag ) {
311
312 $has_in_term = has_term( $tag->term_id, CourseModel::COURSE_TAG, $post_ID );
313
314 $output .= '<option value="' . esc_attr( $tag->name ) . '" ' . selected( $has_in_term, true, false ) . '>' . esc_html( $tag->name ) . '</option>';
315
316 }
317
318 return $output;
319 }
320 }
321
322 if ( ! function_exists( 'tutor_course_categories_checkbox' ) ) {
323 /**
324 * Generate course categories checkbox
325 *
326 * @since 1.3.4
327 *
328 * @param int $post_ID post ID.
329 * @param array $args arguments.
330 *
331 * @return string
332 */
333 function tutor_course_categories_checkbox( $post_ID = 0, $args = array() ) {
334 $default = array(
335 'name' => 'tax_input[course-category]',
336 );
337
338 $args = apply_filters( 'tutor_course_categories_checkbox_args', array_merge( $default, $args ) );
339
340 if ( isset( $args['name'] ) ) {
341 $args['name'] = $args['name'] . '[]';
342 }
343
344 extract( $args ); //phpcs:ignore
345
346 $categories = tutor_utils()->get_course_categories();
347 $output = '';
348 $output .= tutor_generate_categories_checkbox( $post_ID, $categories, $args );
349
350 return $output;
351 }
352 }
353
354 if ( ! function_exists( 'tutor_course_tags_checkbox' ) ) {
355 /**
356 * Generate course tags checkbox.
357 *
358 * @since 1.3.4
359 *
360 * @param int $post_ID post ID.
361 * @param array $args arguments.
362 *
363 * @return string
364 */
365 function tutor_course_tags_checkbox( $post_ID = 0, $args = array() ) {
366 $default = array(
367 'name' => 'tax_input[course-tag]',
368 );
369
370 $args = apply_filters( 'tutor_course_tags_checkbox_args', array_merge( $default, $args ) );
371
372 if ( isset( $args['name'] ) ) {
373 $args['name'] = $args['name'] . '[]';
374 }
375
376 extract( $args ); //phpcs:ignore
377
378 $tags = tutor_utils()->get_course_tags();
379 $output = '';
380 $output .= tutor_generate_tags_checkbox( $post_ID, $tags, $args );
381
382 return $output;
383 }
384 }
385
386 if ( ! function_exists( 'tutor_generate_categories_checkbox' ) ) {
387 /**
388 * Internal function to generate course categories checkbox.
389 *
390 * @since 1.3.4
391 *
392 * @param int $post_ID post ID.
393 * @param array $categories categories list.
394 * @param array $args arguments.
395 *
396 * @return string
397 */
398 function tutor_generate_categories_checkbox( $post_ID = 0, $categories = array(), $args = array() ) {
399
400 $output = '';
401 $input_name = tutor_utils()->array_get( 'name', $args );
402
403 if ( tutor_utils()->count( $categories ) ) {
404 $output .= '<ul class="tax-input-course-category">';
405 foreach ( $categories as $category_id => $category ) {
406 $childrens = tutor_utils()->array_get( 'children', $category );
407 $has_in_term = has_term( $category->term_id, 'course-category', $post_ID );
408
409 $output .= '<li class="tax-input-course-category-item tax-input-course-category-item-' . $category->term_id . '"><label class="course-category-checkbox"> <input type="checkbox" name="' . $input_name . '" value="' . $category->term_id . '" ' . checked( $has_in_term, true, false ) . '/> <span>' . $category->name . '</span> </label>';
410
411 if ( tutor_utils()->count( $childrens ) ) {
412 $output .= tutor_generate_categories_checkbox( $post_ID, $childrens, $args );
413 }
414 $output .= ' </li>';
415 }
416 $output .= '</ul>';
417 }
418
419 return $output;
420 }
421 }
422 if ( ! function_exists( 'tutor_generate_tags_checkbox' ) ) {
423 /**
424 * Internal function to generate course tags checkbox.
425 *
426 * @since 1.3.4
427 *
428 * @param int $post_ID post ID.
429 * @param array $tags tags list.
430 * @param array $args arguments.
431 *
432 * @return string
433 */
434 function tutor_generate_tags_checkbox( $post_ID = 0, $tags = array(), $args = array() ) {
435
436 $output = '';
437 $input_name = tutor_utils()->array_get( 'name', $args );
438
439 if ( tutor_utils()->count( $tags ) ) {
440 $output .= '<ul class="tax-input-course-tag">';
441 foreach ( $tags as $tag ) {
442 $has_in_term = has_term( $tag->term_id, CourseModel::COURSE_TAG, $post_ID );
443
444 $output .= '<li class="tax-input-course-tag-item tax-input-course-tag-item-' . $tag->term_id . '"><label class="course-tag-checkbox"> <input type="checkbox" name="' . $input_name . '" value="' . $tag->term_id . '" ' . checked( $has_in_term, true, false ) . ' /> <span>' . $tag->name . '</span> </label>';
445
446 $output .= ' </li>';
447 }
448 $output .= '</ul>';
449 }
450
451 return $output;
452 }
453 }
454
455 if ( ! function_exists( 'get_tutor_header' ) ) {
456 /**
457 * Get Tutor Header.
458 *
459 * @since 1.0.0
460 *
461 * @param bool $full_screen whether it is full screen.
462 *
463 * @return void
464 */
465 function get_tutor_header( $full_screen = false ) {
466 $enable_spotlight_mode = tutor_utils()->get_option( 'enable_spotlight_mode' );
467
468 if ( $enable_spotlight_mode || $full_screen ) {
469 ?>
470 <!doctype html>
471 <html <?php language_attributes(); ?>>
472
473 <head>
474 <meta charset="<?php bloginfo( 'charset' ); ?>" />
475 <meta name="viewport" content="width=device-width, initial-scale=1" />
476 <link rel="profile" href="https://gmpg.org/xfn/11" />
477 <?php wp_head(); ?>
478 </head>
479
480 <body <?php body_class(); ?>>
481 <?php wp_body_open(); ?>
482 <div id="tutor-page-wrap" class="tutor-site-wrap site">
483 <?php
484 } else {
485 tutor_utils()->tutor_custom_header();
486 }
487 }
488 }
489
490 if ( ! function_exists( 'get_tutor_footer' ) ) {
491 /**
492 * Get Tutor Footer.
493 *
494 * @since 1.0.0
495 *
496 * @param bool $full_screen whether it is full screen.
497 *
498 * @return void
499 */
500 function get_tutor_footer( $full_screen = false ) {
501 $enable_spotlight_mode = tutor_utils()->get_option( 'enable_spotlight_mode' );
502 if ( $enable_spotlight_mode || $full_screen ) {
503 ?>
504 </div>
505 <?php wp_footer(); ?>
506
507 </body>
508
509 </html>
510 <?php
511 } else {
512 tutor_utils()->tutor_custom_footer();
513 }
514 }
515 }
516
517 if ( ! function_exists( 'get_tutor_option' ) ) {
518 /**
519 * Get tutor option by this helper function.
520 *
521 * @since 1.3.6
522 *
523 * @param string $key option key.
524 * @param mixed $default default value.
525 *
526 * @return array|bool|mixed
527 */
528 function get_tutor_option( $key = null, $default = false ) {
529 return tutor_utils()->get_option( $key, $default );
530 }
531 }
532
533 if ( ! function_exists( 'update_tutor_option' ) ) {
534 /**
535 * Update tutor option by this helper function.
536 *
537 * @since 1.3.6
538 *
539 * @param string $key option key.
540 * @param mixed $value option value.
541 *
542 * @return void
543 */
544 function update_tutor_option( $key = null, $value = false ) {
545 tutor_utils()->update_option( $key, $value );
546 }
547 }
548 if ( ! function_exists( 'get_tutor_course_settings' ) ) {
549 /**
550 * Get tutor course settings by course ID.
551 *
552 * @since 1.4.1
553 *
554 * @param int $course_id course ID.
555 * @param string $key setting key.
556 * @param mixed $default default value.
557 *
558 * @return array|bool|mixed
559 */
560 function get_tutor_course_settings( $course_id = 0, $key = null, $default = false ) {
561 return tutor_utils()->get_course_settings( $course_id, $key, $default );
562 }
563 }
564
565 if ( ! function_exists( 'get_item_content_drip_settings' ) ) {
566 /**
567 * Get lesson content drip settings.
568 *
569 * @since 1.0.0
570 *
571 * @param int $lesson_id lesson ID.
572 * @param string $key setting key.
573 * @param mixed $default default value.
574 *
575 * @return array|bool|mixed
576 */
577 function get_item_content_drip_settings( $lesson_id = 0, $key = null, $default = false ) {
578 return tutor_utils()->get_item_content_drip_settings( $lesson_id, $key, $default );
579 }
580 }
581
582 if ( ! function_exists( 'tutor_alert' ) ) {
583 /**
584 * Print Alert by tutor_alert()
585 *
586 * @since 1.4.1
587 * @since 4.0.0 Added $use_component parameter.
588 *
589 * @param null $msg Message to display.
590 * @param string $type Type of alert.
591 * @param bool $echo Whether to echo the alert.
592 * @param bool $use_component Whether to use new core PHP component.
593 *
594 * @return string
595 */
596 function tutor_alert( $msg = null, $type = 'warning', $echo = true, $use_component = false ) {
597 if ( ! $msg ) {
598 if ( 'any' === $type ) {
599 if ( ! $msg ) {
600 $type = 'warning';
601 $msg = tutor_flash_get( $type );
602 }
603 if ( ! $msg ) {
604 $type = 'danger';
605 $msg = tutor_flash_get( $type );
606 }
607 if ( ! $msg ) {
608 $type = 'success';
609 $msg = tutor_flash_get( $type );
610 }
611 } else {
612 $msg = tutor_flash_get( $type );
613 }
614 }
615
616 if ( ! $msg ) {
617 return $msg;
618 }
619
620 if ( $use_component ) {
621 if ( 'danger' === $type ) {
622 $type = Alert::ERROR;
623 }
624
625 switch ( $type ) {
626 case Alert::SUCCESS:
627 $icon = Icon::CHECK;
628 break;
629 case Alert::WARNING:
630 case Alert::ERROR:
631 $icon = Icon::WARNING;
632 break;
633 default:
634 $icon = Icon::INFO;
635 break;
636 }
637
638 $html = Alert::make()->variant( $type )->text( $msg )->icon( $icon );
639 return $echo ? $html->render() : $html;
640 }
641
642 $html = '<div class="tutor-alert tutor-' . esc_attr( $type ) . '">
643 <div class="tutor-alert-text">
644 <span class="tutor-alert-icon tutor-fs-4 tutor-icon-circle-info tutor-mr-12"></span>
645 <span>' . wp_kses( $msg, array( 'div', 'span' ) ) . '</span>
646 </div>
647 </div>';
648 if ( $echo ) {
649 echo tutor_kses_html( $html ); //phpcs:ignore
650 }
651 return $html;
652 }
653 }
654
655
656 if ( ! function_exists( 'tutor_nonce_field' ) ) {
657 /**
658 * Simply call tutor_nonce_field() to generate nonce field.
659 *
660 * @since 1.4.2
661 *
662 * @param bool $echo whether to echo.
663 *
664 * @return void
665 */
666 function tutor_nonce_field( $echo = true ) {
667 wp_nonce_field( tutor()->nonce_action, tutor()->nonce, $echo );
668 }
669 }
670
671 if ( ! function_exists( 'tutor_flash_set' ) ) {
672 /**
673 * Set Flash Message.
674 *
675 * @since 1.4.2
676 *
677 * @param string $key message key.
678 * @param string $message message content.
679 *
680 * @return void
681 */
682 function tutor_flash_set( $key = null, $message = '' ) {
683 if ( ! $key ) {
684 return;
685 }
686 // ensure session is started.
687 if ( session_status() !== PHP_SESSION_ACTIVE ) {
688 session_start();
689 }
690 $_SESSION[ $key ] = $message;
691 }
692 }
693
694 if ( ! function_exists( 'tutor_flash_get' ) ) {
695 /**
696 * Get flash message.
697 *
698 * @since 1.4.2
699 *
700 * @param string $key message key.
701 *
702 * @return array|bool|mixed|null
703 */
704 function tutor_flash_get( $key = null ) {
705 if ( $key ) {
706 // ensure session is started.
707 if ( session_status() !== PHP_SESSION_ACTIVE ) {
708 @session_start();
709 }
710 if ( empty( $_SESSION ) ) {
711 return null;
712 }
713 $message = tutor_utils()->array_get( $key, $_SESSION );
714 if ( $message ) {
715 unset( $_SESSION[ $key ] );
716 }
717 return $message;
718 }
719 return $key;
720 }
721 }
722
723 if ( ! function_exists( 'tutor_redirect_back' ) ) {
724 /**
725 * Redirect to back or a specific URL and terminate.
726 *
727 * @since 1.4.3
728 *
729 * @param string $url redirect URL.
730 *
731 * @return void
732 */
733 function tutor_redirect_back( $url = null ) {
734 if ( ! $url ) {
735 $url = tutor_utils()->referer();
736 }
737 wp_safe_redirect( $url );
738 exit();
739 }
740 }
741
742 if ( ! function_exists( 'tutor_action_field' ) ) {
743 /**
744 * Tutor action field.
745 *
746 * @since 1.4.3
747 *
748 * @param string $action action name.
749 * @param bool $echo whether to echo.
750 *
751 * @return string|void
752 */
753 function tutor_action_field( $action = '', $echo = true ) {
754 $output = '';
755 if ( $action ) {
756 $output = '<input type="hidden" name="tutor_action" value="' . esc_attr( $action ) . '">';
757 }
758
759 if ( $echo ) {
760 echo wp_kses(
761 $output,
762 array(
763 'input' => array(
764 'type' => true,
765 'name' => true,
766 'value' => true,
767 ),
768 )
769 );
770 } else {
771 return $output;
772 }
773 }
774 }
775
776
777 if ( ! function_exists( 'tutor_time' ) ) {
778 /**
779 * Return current Time from WordPress time.
780 *
781 * @since 1.4.3
782 *
783 * @return int|string
784 */
785 function tutor_time() {
786 $gmt_offset = get_option( 'gmt_offset' );
787 return time() + ( $gmt_offset * HOUR_IN_SECONDS );
788 }
789 }
790
791 if ( ! function_exists( 'tutor_maintenance_mode' ) ) {
792 /**
793 * Toggle maintenance mode for the site.
794 *
795 * Creates/deletes the maintenance file to enable/disable maintenance mode.
796 *
797 * @since 1.4.6
798 *
799 * @param bool $enable true to enable maintenance mode, false to disable.
800 *
801 * @return void
802 */
803 function tutor_maintenance_mode( $enable = false ) {
804 $file = ABSPATH . '.tutor_maintenance';
805 if ( $enable ) {
806 // Create maintenance file to signal that we are upgrading.
807 $maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
808
809 if ( ! file_exists( $file ) ) {
810 file_put_contents( $file, $maintenance_string );
811 }
812 } else {
813 if ( file_exists( $file ) ) {
814 unlink( $file );
815 }
816 }
817 }
818 }
819
820 if ( ! function_exists( 'is_single_course' ) ) {
821 /**
822 * Check if the current page is course single page.
823 *
824 * @since 1.6.0
825 *
826 * @param bool $check_spotlight whether to check spotlight.
827 *
828 * @return bool
829 */
830 function is_single_course( $check_spotlight = false ) {
831 global $wp_query;
832 $course_post_type = tutor()->course_post_type;
833
834 $post_types = array( $course_post_type );
835 if ( $check_spotlight ) {
836 $post_types = array_merge(
837 $post_types,
838 array(
839 'lesson',
840 'tutor_quiz',
841 'tutor_assignments',
842 'tutor_zoom_meeting',
843 )
844 );
845 }
846
847 if ( is_single() && ! empty( $wp_query->query['post_type'] ) && in_array( $wp_query->query['post_type'], $post_types ) ) {
848 return true;
849 }
850 return false;
851 }
852 }
853
854 if ( ! function_exists( 'tutor_js_date_format_against_wp' ) ) {
855 /**
856 * Require wp_date form return js date format.
857 * This is helpful for date picker.
858 *
859 * @since 1.9.7
860 *
861 * @return string
862 */
863 function tutor_js_date_format_against_wp() {
864 $wp_date_format = get_option( 'date_format' );
865 $default_format = 'Y-M-d';
866
867 $formats = array(
868 'Y-m-d' => 'Y-M-d',
869 'm/d/Y' => 'M-d-Y',
870 'd/m/Y' => 'd-M-Y',
871 'F j, Y' => 'MMMM d, yyyy',
872 'j F Y' => 'MMMM d, yyyy',
873 );
874 return isset( $formats[ $wp_date_format ] ) ? $formats[ $wp_date_format ] : $default_format;
875 }
876 }
877
878 if ( ! function_exists( 'tutor_get_formated_date' ) ) {
879 /**
880 * Convert date to desired format.
881 *
882 * NOTE: mysql query use formatted date from here
883 * that's why date_i18n need to be ignore.
884 *
885 * @since 1.0.0
886 *
887 * @param string $require_format if empty Y-m-d is used.
888 * @param string $user_date date.
889 *
890 * @return string
891 */
892 function tutor_get_formated_date( string $require_format = '', string $user_date = '' ) {
893 $require_format = empty( $require_format ) ? 'Y-m-d' : $require_format;
894
895 $date = date_create( str_replace( '/', '-', $user_date ) );
896 if ( is_a( $date, 'DateTime' ) ) {
897 $formatted_date = date_format( $date, $require_format );
898 } else {
899 $formatted_date = gmdate( $require_format, strtotime( $user_date ) );
900 }
901 return $formatted_date;
902 }
903 }
904
905 if ( ! function_exists( 'tutor_i18n_get_formated_date' ) ) {
906 /**
907 * Get translated date.
908 *
909 * @since 2.0.2
910 *
911 * @param string $date date in string from to translate & format.
912 * @param string $format optional date format, default is wp date time format.
913 *
914 * @return string translated date.
915 */
916 function tutor_i18n_get_formated_date( string $date, string $format = '' ) {
917 if ( '' === $format ) {
918 $format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
919 }
920 return date_i18n( $format, strtotime( $date ) );
921 }
922 }
923
924 if ( ! function_exists( '_tutor_search_by_title_only' ) ) {
925 /**
926 * Search SQL filter for matching against post title only.
927 *
928 * @since 1.0.0
929 * @link http://wordpress.stackexchange.com/a/11826/1685
930 *
931 * @param string $search search string.
932 * @param WP_Query $wp_query wp query.
933 *
934 * @return string
935 */
936 function _tutor_search_by_title_only( $search, $wp_query ) {
937 if ( ! empty( $search ) && ! empty( $wp_query->query_vars['search_terms'] ) ) {
938 global $wpdb;
939
940 $q = $wp_query->query_vars;
941 $n = ! empty( $q['exact'] ) ? '' : '%';
942
943 $search = array();
944
945 foreach ( (array) $q['search_terms'] as $term ) {
946 $search[] = $wpdb->prepare( "$wpdb->posts.post_title LIKE %s", $n . $wpdb->esc_like( $term ) . $n );
947 }
948
949 if ( ! is_user_logged_in() ) {
950 $search[] = "$wpdb->posts.post_password = ''";
951 }
952
953 $search = ' AND ' . implode( ' AND ', $search );
954 }
955
956 return $search;
957 }
958 }
959
960 if ( ! function_exists( 'get_request' ) ) {
961 /**
962 * Function to get_request.
963 *
964 * @since 1.0.0
965 *
966 * @param mixed $var variable name.
967 *
968 * @return mixed
969 */
970 function get_request( $var ) {
971 return isset( $_REQUEST[ $var ] ) ? sanitize_text_field( $_REQUEST[ $var ] ) : false;//phpcs:ignore
972
973 }
974 }
975
976 if ( ! function_exists( 'tutor_kses_allowed_html' ) ) {
977 /**
978 * Add custom allowed tags to KSES.
979 *
980 * @since 1.0.0
981 *
982 * @param array $allowed_tags allowed tags.
983 * @param string $context context.
984 *
985 * @return array
986 */
987 function tutor_kses_allowed_html( $allowed_tags, $context ) {
988 $tags = array( 'input', 'style', 'script', 'select', 'form', 'option', 'optgroup', 'iframe', 'bdi', 'source', 'a' );
989 $atts = array( 'min', 'max', 'maxlength', 'type', 'method', 'enctype', 'action', 'selected', 'class', 'id', 'disabled', 'checked', 'readonly', 'name', 'aria-*', 'style', 'role', 'placeholder', 'value', 'data-*', 'src', 'width', 'height', 'frameborder', 'allow', 'fullscreen', 'title', 'multiple', 'tutor-hide-course-single-sidebar', 'href' );
990
991 foreach ( $tags as $tag ) {
992 $tag_attrs = array();
993
994 foreach ( $atts as $att ) {
995 $tag_attrs[ $att ] = true;
996 }
997
998 $allowed_tags[ $tag ] = $tag_attrs;
999 }
1000
1001 return $allowed_tags;
1002 }
1003 }
1004
1005 if ( ! function_exists( 'tutor_kses_allowed_css' ) ) {
1006 /**
1007 * Add custom allowed CSS to KSES.
1008 *
1009 * @since 1.0.0
1010 *
1011 * @param array $styles styles.
1012 *
1013 * @return array
1014 */
1015 function tutor_kses_allowed_css( $styles ) {
1016 $styles[] = 'display';
1017 $styles[] = '--progress-value';
1018 return $styles;
1019 }
1020 }
1021
1022 if ( ! function_exists( 'tutor_kses_html' ) ) {
1023 /**
1024 * Tutor KSES HTML wrapper.
1025 *
1026 * @since 1.0.0
1027 *
1028 * @param string $content HTML content.
1029 *
1030 * @return string
1031 */
1032 function tutor_kses_html( $content ) {
1033
1034 return $content;
1035 add_filter( 'wp_kses_allowed_html', 'tutor_kses_allowed_html', 10, 2 );
1036 add_filter( 'safe_style_css', 'tutor_kses_allowed_css' );
1037
1038 $content = preg_replace( '/<!--(.|\s)*?-->/', '', $content );
1039 $content = wp_kses_post( $content );
1040 $content = str_replace( '&amp;', '&', $content );
1041
1042 remove_filter( 'safe_style_css', 'tutor_kses_allowed_css' );
1043 remove_filter( 'wp_kses_allowed_html', 'tutor_kses_allowed_html' );
1044
1045 return $content;
1046 }
1047 }
1048
1049 if ( ! function_exists( 'get_tutor_all_withdrawal_methods' ) ) {
1050 /**
1051 * Get all Withdraw Methods available on this system.
1052 *
1053 * @since 1.5.7
1054 *
1055 * @return array
1056 */
1057 function get_tutor_all_withdrawal_methods() {
1058 return apply_filters( 'tutor_withdrawal_methods_all', array() );
1059 }
1060 }
1061
1062
1063 if ( ! function_exists( 'tutor_log' ) ) {
1064 /**
1065 * Logging data.
1066 *
1067 * @since 1.0.0
1068 * @since 3.0.0 exception logging support added.
1069 *
1070 * @return void
1071 */
1072 function tutor_log() {
1073 $arg_list = func_get_args();
1074
1075 foreach ( $arg_list as $data ) {
1076 ob_start();
1077
1078 if ( $data instanceof Exception ) {
1079 var_dump( $data->getMessage() );
1080 var_dump( $data->getTraceAsString() );
1081 } else {
1082 var_dump( $data );
1083 }
1084
1085 error_log( ob_get_clean() );
1086 }
1087 }
1088 }
1089
1090 if ( ! function_exists( 'tutor_wc_price_currency_format' ) ) {
1091 /**
1092 * WooCommerce price currency format.
1093 *
1094 * @since 1.0.0
1095 *
1096 * @param mixed $amount amount.
1097 *
1098 * @return string
1099 */
1100 function tutor_wc_price_currency_format( $amount ) {
1101
1102 $symbol = get_woocommerce_currency_symbol();
1103 $position = get_option( 'woocommerce_currency_pos', 'left' );
1104
1105 switch ( $position ) {
1106 case 'left':
1107 $amount = $symbol . $amount;
1108 break;
1109 case 'left_space':
1110 $amount = $symbol . ' ' . $amount;
1111 break;
1112
1113 case 'right':
1114 $amount = $amount . $symbol;
1115 break;
1116 case 'right_space':
1117 $amount = $amount . ' ' . $symbol;
1118 break;
1119
1120 default:
1121 $amount = $symbol . $amount;
1122 break;
1123 }
1124
1125 return $amount;
1126 }
1127 }
1128
1129 if ( ! function_exists( 'tutor_meta_box_wrapper' ) ) {
1130 /**
1131 * Tutor meta box wrapper
1132 *
1133 * @since v2.0.2
1134 *
1135 * @param string $id id of meta box.
1136 * @param string $title meta box title.
1137 * @param mixed $callback callback function that meta box will call.
1138 * @param string $screen which screen meta box should appear.
1139 * @param string $context optional param. Where meta box should appear.
1140 * @param string $priority optional.
1141 * @param string $custom_class optional. If provide it will add this class along
1142 * with div id.
1143 *
1144 * @return void if class provided then filter hook will return class.
1145 */
1146 function tutor_meta_box_wrapper(
1147 $id,
1148 $title,
1149 $callback,
1150 $screen,
1151 $context = 'advanced',
1152 $priority = 'default',
1153 $custom_class = ''
1154 ) {
1155 add_meta_box(
1156 $id,
1157 $title,
1158 $callback,
1159 $screen,
1160 $context,
1161 $priority
1162 );
1163 if ( '' !== $custom_class ) {
1164 $post_type = tutor()->course_post_type;
1165 add_filter(
1166 "postbox_classes_{$post_type}_{$id}",
1167 function ( $classes ) use ( $custom_class ) {
1168 if ( ! in_array( $custom_class, $classes ) ) {
1169 $classes[] = $custom_class;
1170 }
1171 return $classes;
1172 }
1173 );
1174 }
1175 }
1176 }
1177
1178 if ( ! function_exists( 'tutor_closeable_alert_msg' ) ) {
1179 /**
1180 * Create a close-able alert message
1181 *
1182 * @since 2.1.9
1183 * @since 3.7.1 param css_class added to pass any custom css class.
1184 *
1185 * @param string $message alert message.
1186 * @param string $alert alert key like: success, warning, danger, etc.
1187 * @param array $allowed_tags allowed tags to use with WP_KSES.
1188 * @param string $css_class custom css class.
1189 *
1190 * @return void
1191 */
1192 function tutor_closeable_alert_msg( string $message, string $alert = 'success', $allowed_tags = array(), $css_class = '' ) {
1193 if ( Assets::should_load_legacy_scripts() ) {
1194 ?>
1195 <div class="tutor-alert tutor-<?php echo esc_attr( $alert ); ?> <?php echo esc_attr( $css_class ); ?> tutor-mb-12 tutor-alert tutor-success tutor-mb-12 tutor-d-flex tutor-align-center tutor-justify-between">
1196 <span>
1197 <?php echo is_array( $allowed_tags ) && count( $allowed_tags ) ? wp_kses( $message, $allowed_tags ) : esc_html( $message ); ?>
1198 </span>
1199 <span class="tutor-icon-times" aria-hidden="true" onclick="this.closest('div').remove()" style="cursor: pointer;"></span>
1200 </div>
1201 <?php
1202 } else {
1203 $message = is_array( $allowed_tags ) && count( $allowed_tags ) ? wp_kses( $message, $allowed_tags ) : esc_html( $message );
1204 $alert = 'danger' === $alert ? Alert::ERROR : $alert;
1205
1206 Alert::make()
1207 ->text( $message )
1208 ->variant( $alert )
1209 ->attr( 'class', $css_class . ' tutor-mb-5' )
1210 ->render();
1211 }
1212 }
1213 }
1214
1215 if ( ! function_exists( 'tutor_set_flash_message' ) ) {
1216 /**
1217 * Utility API Set flash message to show somewhere
1218 *
1219 * It will call set_cache method of FlashMessage class to set cache
1220 *
1221 * @param mixed $message message to show.
1222 * @param string $alert alert type as FlashMessage::$alert_types.
1223 *
1224 * @return void
1225 */
1226 function tutor_set_flash_message( $message = '', $alert = 'success' ) {
1227 $flash_msg = new FlashMessage( $message, $alert );
1228 $flash_msg->set_cache();
1229 }
1230 }
1231
1232
1233 if ( ! function_exists( 'tutor_snackbar' ) ) {
1234 /**
1235 * Reuseable snackbar to show on the frontend
1236 *
1237 * Create a snackbar based on title, action buttons
1238 *
1239 * @since 2.2.0
1240 *
1241 * @param string $title title to show.
1242 * @param array $action_buttons 2 dimensional array of action buttons to show.
1243 * Supported attrs: [ [title => title, id => '', class => '' url => '', target => ''] ].
1244 * @param string $title_icon_class title icon to show before title.
1245 *
1246 * @return void
1247 */
1248 function tutor_snackbar( string $title, array $action_buttons = array(), $title_icon_class = '' ) {
1249 ?>
1250 <div id="tutor-reuseable-snackbar" class="tutor-snackbar-wrapper">
1251 <div class="tutor-snackbar">
1252 <p>
1253 <?php if ( ! empty( $title_icon_class ) ) : ?>
1254 <i class="tutor-snackbar-title-icon <?php echo esc_attr( $title_icon_class ); ?>"></i>
1255 <?php endif; ?>
1256 <?php echo esc_html( $title ); ?>
1257 </p>
1258 <div>
1259 <?php foreach ( $action_buttons as $attr => $button ) : ?>
1260 <a
1261 <?php foreach ( $button as $attr => $value ) : ?>
1262 <?php if ( ! empty( $value ) ) : ?>
1263 <?php echo esc_attr( $attr ) . '="' . esc_attr( $value ) . '" '; ?>
1264 <?php endif; ?>
1265 <?php endforeach; ?>
1266 >
1267 <?php echo esc_html( isset( $button['title'] ) ? $button['title'] : '' ); ?>
1268 </a>
1269 <?php endforeach; ?>
1270 <span class="tutor-icon-times" aria-hidden="true" onclick="this.closest('#tutor-reuseable-snackbar').remove()" style="cursor: pointer;"></span>
1271 </div>
1272 </div>
1273 </div>
1274 <?php
1275 }
1276 }
1277
1278 if ( ! function_exists( 'tutor_is_rest' ) ) {
1279 /**
1280 * Checks if the current request is a WP REST API request.
1281 *
1282 * @since 2.6.0
1283 *
1284 * Case #1: After WP_REST_Request initialisation
1285 * Case #2: Support "plain" permalink settings and check if `rest_route` starts with `/`
1286 * Case #3: It can happen that WP_Rewrite is not yet initialized,
1287 * so do this (wp-settings.php)
1288 * Case #4: URL Path begins with wp-json/ (your REST prefix)
1289 * Also supports WP installations in subfolders
1290 *
1291 * @see https://wordpress.stackexchange.com/questions/221202/does-something-like-is-rest-exist
1292 * @returns boolean
1293 */
1294 function tutor_is_rest() {
1295 $rest_route = Input::get( 'rest_route' );
1296 if ( defined( 'REST_REQUEST' ) && REST_REQUEST || $rest_route && strpos( $rest_route, '/', 0 ) === 0 ) {
1297 return true;
1298 }
1299
1300 // (#3)
1301 global $wp_rewrite;
1302 if ( null === $wp_rewrite ) {
1303 $wp_rewrite = new WP_Rewrite(); //phpcs:ignore
1304 }
1305
1306 // (#4)
1307 $rest_url = wp_parse_url( trailingslashit( rest_url() ) );
1308 $current_url = wp_parse_url( add_query_arg( array() ) );
1309 return strpos( $current_url['path'] ?? '/', $rest_url['path'], 0 ) === 0;
1310 }
1311 }
1312
1313 if ( ! function_exists( 'tutor_getallheaders' ) ) {
1314 /**
1315 * Wrapper of PHP getallheaders with a fallback if getallheaders not available
1316 *
1317 * @since 2.6.0
1318 *
1319 * @see https://www.php.net/manual/en/function.getallheaders.php
1320 *
1321 * @return array of headers
1322 */
1323 function tutor_getallheaders() {
1324 $headers = array();
1325 if ( function_exists( 'getallheaders' ) ) {
1326 $headers = getallheaders();
1327 }
1328
1329 if ( ! $headers ) {
1330 foreach ( $_SERVER as $name => $value ) {
1331 if ( substr( $name, 0, 5 ) == 'HTTP_' ) {
1332 $headers[ str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', substr( $name, 5 ) ) ) ) ) ] = $value;
1333 }
1334 }
1335 }
1336
1337 return $headers;
1338 }
1339 }
1340
1341 if ( ! function_exists( 'tutor_entry_box_buttons' ) ) {
1342 /**
1343 * Tutor conditional buttons for the enrollment box
1344 *
1345 * @since 2.6.0
1346 *
1347 * @param int $course_id course id.
1348 * @param int $user_id user id.
1349 *
1350 * @return object
1351 */
1352 function tutor_entry_box_buttons( int $course_id = 0, int $user_id = 0 ) {
1353 $conditional_buttons = (object) array(
1354 'show_enroll_btn' => false,
1355 'show_add_to_cart_btn' => false,
1356 'show_view_cart_btn' => false,
1357 'show_start_learning_btn' => false,
1358 'show_continue_learning_btn' => false,
1359 'show_complete_course_btn' => false,
1360 'show_retake_course_btn' => false,
1361 'show_certificate_view_btn' => false,
1362 'show_course_fully_booked_btn' => false,
1363 );
1364
1365 $course_id = tutor_utils()->get_post_id( $course_id );
1366 $user_id = tutor_utils()->get_user_id( $user_id );
1367
1368 $has_course_access = tutor_utils()->has_user_course_content_access( $user_id, $course_id );
1369
1370 $is_public_course = get_post_meta( $course_id, '_tutor_is_public_course', true );
1371
1372 $is_enabled_retake = tutor_utils()->get_option( 'course_retake_feature' );
1373
1374 $is_enrolled = EnrollmentModel::is_enrolled( $course_id, $user_id );
1375
1376 if ( 'yes' === $is_public_course ) {
1377 $conditional_buttons->show_start_learning_btn = true;
1378 } else {
1379 // Admin & instructor can manage posts.
1380 if ( $is_enrolled || $has_course_access ) {
1381 $can_complete_course = CourseModel::can_complete_course( $course_id, $user_id );
1382 $is_completed_course = tutor_utils()->is_completed_course( $course_id, $user_id );
1383 $course_progress = (int) tutor_utils()->get_course_completed_percent( $course_id, $user_id );
1384
1385 if ( $course_progress > 0 && $course_progress < 100 ) {
1386 $conditional_buttons->show_continue_learning_btn = true;
1387 }
1388
1389 if ( 0 === $course_progress ) {
1390 $conditional_buttons->show_start_learning_btn = true;
1391 }
1392
1393 if ( $can_complete_course ) {
1394 $conditional_buttons->show_complete_course_btn = true;
1395 }
1396
1397 if ( $is_completed_course ) {
1398 $conditional_buttons->show_certificate_view_btn = true;
1399 }
1400
1401 if ( $is_enabled_retake && $is_completed_course ) {
1402 $conditional_buttons->show_retake_course_btn = true;
1403 }
1404 } else {
1405 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
1406 $is_paid_course = tutor_utils()->is_course_purchasable( $course_id );
1407 if ( 'free' !== $monetize_by && $is_paid_course ) {
1408 if ( tutor_is_item_in_cart( $course_id ) ) {
1409 $conditional_buttons->show_view_cart_btn = true;
1410 } else {
1411 $conditional_buttons->show_add_to_cart_btn = true;
1412 }
1413 } else {
1414 $conditional_buttons->show_enroll_btn = true;
1415 }
1416 }
1417 }
1418
1419 if ( ! $is_public_course && ! ( $is_enrolled || $has_course_access ) ) {
1420 $is_fully_booked = tutor_utils()->is_course_fully_booked( $course_id );
1421 if ( $is_fully_booked ) {
1422 $conditional_buttons->show_course_fully_booked_btn = true;
1423 }
1424 }
1425
1426 return apply_filters( 'tutor_enrollment_buttons', $conditional_buttons, $course_id, $user_id );
1427 }
1428 }
1429
1430 if ( ! function_exists( 'tutor_global_timezone_lists' ) ) {
1431 /**
1432 * Get list of global timezones.
1433 *
1434 * @since 1.0.0
1435 *
1436 * @return array
1437 */
1438 function tutor_global_timezone_lists() {
1439 return array(
1440 'Pacific/Midway' => '(GMT-11:00) Midway Island, Samoa ',
1441 'Pacific/Pago_Pago' => '(GMT-11:00) Pago Pago ',
1442 'Pacific/Honolulu' => '(GMT-10:00) Hawaii ',
1443 'America/Anchorage' => '(GMT-8:00) Alaska ',
1444 'America/Vancouver' => '(GMT-7:00) Vancouver ',
1445 'America/Los_Angeles' => '(GMT-7:00) Pacific Time (US and Canada) ',
1446 'America/Tijuana' => '(GMT-7:00) Tijuana ',
1447 'America/Phoenix' => '(GMT-7:00) Arizona ',
1448 'America/Edmonton' => '(GMT-6:00) Edmonton ',
1449 'America/Denver' => '(GMT-6:00) Mountain Time (US and Canada) ',
1450 'America/Mazatlan' => '(GMT-6:00) Mazatlan ',
1451 'America/Regina' => '(GMT-6:00) Saskatchewan ',
1452 'America/Guatemala' => '(GMT-6:00) Guatemala ',
1453 'America/El_Salvador' => '(GMT-6:00) El Salvador ',
1454 'America/Managua' => '(GMT-6:00) Managua ',
1455 'America/Costa_Rica' => '(GMT-6:00) Costa Rica ',
1456 'America/Tegucigalpa' => '(GMT-6:00) Tegucigalpa ',
1457 'America/Winnipeg' => '(GMT-5:00) Winnipeg ',
1458 'America/Chicago' => '(GMT-5:00) Central Time (US and Canada) ',
1459 'America/Mexico_City' => '(GMT-5:00) Mexico City ',
1460 'America/Panama' => '(GMT-5:00) Panama ',
1461 'America/Bogota' => '(GMT-5:00) Bogota ',
1462 'America/Lima' => '(GMT-5:00) Lima ',
1463 'America/Caracas' => '(GMT-4:30) Caracas ',
1464 'America/Montreal' => '(GMT-4:00) Montreal ',
1465 'America/New_York' => '(GMT-4:00) Eastern Time (US and Canada) ',
1466 'America/Indianapolis' => '(GMT-4:00) Indiana (East) ',
1467 'America/Puerto_Rico' => '(GMT-4:00) Puerto Rico ',
1468 'America/Santiago' => '(GMT-4:00) Santiago ',
1469 'America/Halifax' => '(GMT-3:00) Halifax ',
1470 'America/Montevideo' => '(GMT-3:00) Montevideo ',
1471 'America/Araguaina' => '(GMT-3:00) Brasilia ',
1472 'America/Argentina/Buenos_Aires' => '(GMT-3:00) Buenos Aires, Georgetown ',
1473 'America/Sao_Paulo' => '(GMT-3:00) Sao Paulo ',
1474 'Canada/Atlantic' => '(GMT-3:00) Atlantic Time (Canada) ',
1475 'America/St_Johns' => '(GMT-2:30) Newfoundland and Labrador ',
1476 'America/Godthab' => '(GMT-2:00) Greenland ',
1477 'Atlantic/Cape_Verde' => '(GMT-1:00) Cape Verde Islands ',
1478 'Atlantic/Azores' => '(GMT+0:00) Azores ',
1479 'UTC' => '(GMT+0:00) Universal Time UTC ',
1480 'Etc/Greenwich' => '(GMT+0:00) Greenwich Mean Time ',
1481 'Atlantic/Reykjavik' => '(GMT+0:00) Reykjavik ',
1482 'Africa/Nouakchott' => '(GMT+0:00) Nouakchott ',
1483 'Europe/Dublin' => '(GMT+1:00) Dublin ',
1484 'Europe/London' => '(GMT+1:00) London ',
1485 'Europe/Lisbon' => '(GMT+1:00) Lisbon ',
1486 'Africa/Casablanca' => '(GMT+1:00) Casablanca ',
1487 'Africa/Bangui' => '(GMT+1:00) West Central Africa ',
1488 'Africa/Algiers' => '(GMT+1:00) Algiers ',
1489 'Africa/Tunis' => '(GMT+1:00) Tunis ',
1490 'Europe/Belgrade' => '(GMT+2:00) Belgrade, Bratislava, Ljubljana ',
1491 'CET' => '(GMT+2:00) Sarajevo, Skopje, Zagreb ',
1492 'Europe/Oslo' => '(GMT+2:00) Oslo ',
1493 'Europe/Copenhagen' => '(GMT+2:00) Copenhagen ',
1494 'Europe/Brussels' => '(GMT+2:00) Brussels ',
1495 'Europe/Berlin' => '(GMT+2:00) Amsterdam, Berlin, Rome, Stockholm, Vienna ',
1496 'Europe/Amsterdam' => '(GMT+2:00) Amsterdam ',
1497 'Europe/Rome' => '(GMT+2:00) Rome ',
1498 'Europe/Stockholm' => '(GMT+2:00) Stockholm ',
1499 'Europe/Vienna' => '(GMT+2:00) Vienna ',
1500 'Europe/Luxembourg' => '(GMT+2:00) Luxembourg ',
1501 'Europe/Paris' => '(GMT+2:00) Paris ',
1502 'Europe/Zurich' => '(GMT+2:00) Zurich ',
1503 'Europe/Madrid' => '(GMT+2:00) Madrid ',
1504 'Africa/Harare' => '(GMT+2:00) Harare, Pretoria ',
1505 'Europe/Warsaw' => '(GMT+2:00) Warsaw ',
1506 'Europe/Prague' => '(GMT+2:00) Prague Bratislava ',
1507 'Europe/Budapest' => '(GMT+2:00) Budapest ',
1508 'Africa/Tripoli' => '(GMT+2:00) Tripoli ',
1509 'Africa/Cairo' => '(GMT+2:00) Cairo ',
1510 'Africa/Johannesburg' => '(GMT+2:00) Johannesburg ',
1511 'Europe/Helsinki' => '(GMT+3:00) Helsinki ',
1512 'Africa/Nairobi' => '(GMT+3:00) Nairobi ',
1513 'Europe/Sofia' => '(GMT+3:00) Sofia ',
1514 'Europe/Istanbul' => '(GMT+3:00) Istanbul ',
1515 'Europe/Athens' => '(GMT+3:00) Athens ',
1516 'Europe/Bucharest' => '(GMT+3:00) Bucharest ',
1517 'Asia/Nicosia' => '(GMT+3:00) Nicosia ',
1518 'Asia/Beirut' => '(GMT+3:00) Beirut ',
1519 'Asia/Damascus' => '(GMT+3:00) Damascus ',
1520 'Asia/Jerusalem' => '(GMT+3:00) Jerusalem ',
1521 'Asia/Amman' => '(GMT+3:00) Amman ',
1522 'Europe/Moscow' => '(GMT+3:00) Moscow ',
1523 'Asia/Baghdad' => '(GMT+3:00) Baghdad ',
1524 'Asia/Kuwait' => '(GMT+3:00) Kuwait ',
1525 'Asia/Riyadh' => '(GMT+3:00) Riyadh ',
1526 'Asia/Bahrain' => '(GMT+3:00) Bahrain ',
1527 'Asia/Qatar' => '(GMT+3:00) Qatar ',
1528 'Asia/Aden' => '(GMT+3:00) Aden ',
1529 'Africa/Khartoum' => '(GMT+3:00) Khartoum ',
1530 'Africa/Djibouti' => '(GMT+3:00) Djibouti ',
1531 'Africa/Mogadishu' => '(GMT+3:00) Mogadishu ',
1532 'Europe/Kiev' => '(GMT+3:00) Kiev ',
1533 'Asia/Dubai' => '(GMT+4:00) Dubai ',
1534 'Asia/Muscat' => '(GMT+4:00) Muscat ',
1535 'Asia/Tehran' => '(GMT+4:30) Tehran ',
1536 'Asia/Kabul' => '(GMT+4:30) Kabul ',
1537 'Asia/Baku' => '(GMT+5:00) Baku, Tbilisi, Yerevan ',
1538 'Asia/Yekaterinburg' => '(GMT+5:00) Yekaterinburg ',
1539 'Asia/Tashkent' => '(GMT+5:00) Tashkent ',
1540 'Asia/Karachi' => '(GMT+5:00) Islamabad, Karachi ',
1541 'Asia/Calcutta' => '(GMT+5:30) India ',
1542 'Asia/Kolkata' => '(GMT+5:30) Mumbai, Kolkata, New Delhi ',
1543 'Asia/Kathmandu' => '(GMT+5:45) Kathmandu ',
1544 'Asia/Novosibirsk' => '(GMT+6:00) Novosibirsk ',
1545 'Asia/Almaty' => '(GMT+6:00) Almaty ',
1546 'Asia/Dacca' => '(GMT+6:00) Dacca ',
1547 'Asia/Dhaka' => '(GMT+6:00) Astana, Dhaka ',
1548 'Asia/Krasnoyarsk' => '(GMT+7:00) Krasnoyarsk ',
1549 'Asia/Bangkok' => '(GMT+7:00) Bangkok ',
1550 'Asia/Saigon' => '(GMT+7:00) Vietnam ',
1551 'Asia/Jakarta' => '(GMT+7:00) Jakarta ',
1552 'Asia/Irkutsk' => '(GMT+8:00) Irkutsk, Ulaanbaatar ',
1553 'Asia/Shanghai' => '(GMT+8:00) Beijing, Shanghai ',
1554 'Asia/Hong_Kong' => '(GMT+8:00) Hong Kong ',
1555 'Asia/Taipei' => '(GMT+8:00) Taipei ',
1556 'Asia/Kuala_Lumpur' => '(GMT+8:00) Kuala Lumpur ',
1557 'Asia/Singapore' => '(GMT+8:00) Singapore ',
1558 'Australia/Perth' => '(GMT+8:00) Perth ',
1559 'Asia/Yakutsk' => '(GMT+9:00) Yakutsk ',
1560 'Asia/Seoul' => '(GMT+9:00) Seoul ',
1561 'Asia/Tokyo' => '(GMT+9:00) Osaka, Sapporo, Tokyo ',
1562 'Australia/Darwin' => '(GMT+9:30) Darwin ',
1563 'Australia/Adelaide' => '(GMT+9:30) Adelaide ',
1564 'Asia/Vladivostok' => '(GMT+10:00) Vladivostok ',
1565 'Pacific/Port_Moresby' => '(GMT+10:00) Guam, Port Moresby ',
1566 'Australia/Brisbane' => '(GMT+10:00) Brisbane ',
1567 'Australia/Sydney' => '(GMT+10:00) Canberra, Melbourne, Sydney ',
1568 'Australia/Hobart' => '(GMT+10:00) Hobart ',
1569 'Asia/Magadan' => '(GMT+10:00) Magadan ',
1570 'SST' => '(GMT+11:00) Solomon Islands ',
1571 'Pacific/Noumea' => '(GMT+11:00) New Caledonia ',
1572 'Asia/Kamchatka' => '(GMT+12:00) Kamchatka ',
1573 'Pacific/Fiji' => '(GMT+12:00) Fiji Islands, Marshall Islands ',
1574 'Pacific/Auckland' => '(GMT+12:00) Auckland, Wellington',
1575 );
1576 }
1577
1578 if ( ! function_exists( 'tutor_get_all_active_payment_gateways' ) ) {
1579 /**
1580 * Get all active payment gateways including manual & automate.
1581 *
1582 * @since 3.0.0
1583 *
1584 * @return array
1585 */
1586 function tutor_get_all_active_payment_gateways() {
1587 $payment_settings = Settings::get_payment_settings();
1588 $payment_methods = ! empty( $payment_settings['payment_methods'] ) ? $payment_settings['payment_methods'] : array();
1589
1590 $active_gateways = array();
1591
1592 foreach ( $payment_methods as $method ) {
1593 $is_active = $method['is_active'] ?? false;
1594 $is_manual = $method['is_manual'] ?? false;
1595 if ( ! $is_active ) {
1596 continue;
1597 }
1598
1599 if ( ! Ecommerce::is_payment_gateway_configured( $method['name'] ) ) {
1600 continue;
1601 }
1602
1603 $fields = $method['fields'];
1604 unset( $method['fields'] );
1605
1606 $gateway = $method;
1607 if ( $is_manual ) {
1608 foreach ( $fields as $field ) {
1609 if ( 'payment_instructions' === $field['name'] || 'additional_details' === $field['name'] ) {
1610 $gateway[ $field['name'] ] = $field['value'];
1611 }
1612 }
1613 }
1614
1615 $active_gateways[] = $gateway;
1616 }
1617
1618 return $active_gateways;
1619 }
1620 }
1621
1622 if ( ! function_exists( 'tutor_get_subscription_supported_payment_gateways' ) ) {
1623 /**
1624 * Get all supported gateways.
1625 *
1626 * This function will return only subscription supported gateways if
1627 * plan id provided.
1628 *
1629 * @since 3.0.0
1630 * @since 3.4.0 plan_id param removed.
1631 *
1632 * @return array
1633 */
1634 function tutor_get_subscription_supported_payment_gateways() {
1635 $payment_gateways = tutor_get_all_active_payment_gateways();
1636
1637 $supported_gateways = array();
1638 foreach ( $payment_gateways as $gateway ) {
1639 $support_subscription = $gateway['support_subscription'] ?? false;
1640
1641 if ( ! $support_subscription ) {
1642 continue;
1643 }
1644
1645 $supported_gateways[] = array(
1646 'name' => $gateway['name'] ?? '',
1647 'label' => $gateway['label'] ?? '',
1648 'icon' => $gateway['icon'] ?? '',
1649 'support_subscription' => $gateway['support_subscription'] ?? '',
1650 'is_manual' => $gateway['is_manual'] ?? '',
1651 'additional_details' => $gateway['additional_details'] ?? '',
1652 'payment_instructions' => $gateway['payment_instructions'] ?? '',
1653 );
1654 }
1655
1656 return $supported_gateways;
1657 }
1658 }
1659
1660 if ( ! function_exists( 'tutor_get_manual_payment_gateways' ) ) {
1661 /**
1662 * Get manual payment gateways.
1663 *
1664 * @since 3.0.0
1665 *
1666 * @return array
1667 */
1668 function tutor_get_manual_payment_gateways() {
1669 $payments = tutor_utils()->get_option( 'payment_settings' );
1670 $payments = json_decode( stripslashes( $payments ) );
1671
1672 $manual_methods = array();
1673
1674 if ( $payments ) {
1675 foreach ( $payments->payment_methods as $method ) {
1676 if ( isset( $method->is_manual ) && 1 === (int) $method->is_manual ) {
1677 $manual_methods[] = $method;
1678 }
1679 }
1680 }
1681
1682 return apply_filters( 'tutor_manual_payment_methods', $manual_methods );
1683 }
1684 }
1685 }
1686
1687 if ( ! function_exists( 'tutor_get_course_formatted_price_html' ) ) {
1688 /**
1689 * Get course formatted price.
1690 * Only for monetized by tutor.
1691 *
1692 * @since 3.0.0
1693 *
1694 * @param int $course_id course ID.
1695 * @param bool $echo whether to echo content.
1696 *
1697 * @return string|void
1698 */
1699 function tutor_get_course_formatted_price_html( $course_id, $echo = true ) {
1700 $price_data = tutor_utils()->get_raw_course_price( $course_id );
1701
1702 if ( ! $price_data->regular_price ) {
1703 return;
1704 }
1705 ob_start();
1706 ?>
1707 <div class="list-item-price tutor-item-price">
1708 <?php if ( $price_data->sale_price ) : ?>
1709 <span><?php tutor_print_formatted_price( $price_data->display_price ); ?></span>
1710 <del><?php tutor_print_formatted_price( $price_data->regular_price ); ?></del>
1711 <?php else : ?>
1712 <span><?php tutor_print_formatted_price( $price_data->display_price ); ?></span>
1713 <?php endif; ?>
1714 </div>
1715 <?php if ( $price_data->show_incl_tax_label ) : ?>
1716 <div class="tutor-course-price-tax tutor-fs-8 tutor-fw-normal tutor-color-black"><?php esc_html_e( 'Incl. tax', 'tutor' ); ?></div>
1717 <?php endif; ?>
1718 <?php
1719 $content = apply_filters( 'tutor_course_formatted_price', ob_get_clean() );
1720 if ( $echo ) {
1721 echo $content; // PHPCS:ignore
1722 } else {
1723 return $content;
1724 }
1725 }
1726 }
1727
1728 if ( ! function_exists( 'tutor_get_formatted_price' ) ) {
1729 /**
1730 * Get course formatted price.
1731 *
1732 * Formatting as per ecommerce price settings.
1733 *
1734 * @since 3.0.0
1735 *
1736 * @param mixed $price Raw price.
1737 *
1738 * @return string|void
1739 */
1740 function tutor_get_formatted_price( $price ) {
1741 $price = floatval( Input::sanitize( $price ) );
1742 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
1743 if ( Ecommerce::MONETIZE_BY === $monetize_by ) {
1744 $currency_symbol = Settings::get_currency_symbol_by_code( tutor_utils()->get_option( OptionKeys::CURRENCY_CODE, 'USD' ) );
1745 $currency_position = tutor_utils()->get_option( OptionKeys::CURRENCY_POSITION, 'left' );
1746 $thousand_separator = tutor_utils()->get_option( OptionKeys::THOUSAND_SEPARATOR, ',' );
1747 $decimal_separator = tutor_utils()->get_option( OptionKeys::DECIMAL_SEPARATOR, '.' );
1748 $no_of_decimal = tutor_utils()->get_option( OptionKeys::NUMBER_OF_DECIMALS, '2' );
1749
1750 $price = number_format( $price, $no_of_decimal, $decimal_separator, $thousand_separator );
1751 $price = 'left' === $currency_position ? $currency_symbol . $price : $price . $currency_symbol;
1752 } elseif ( 'wc' === $monetize_by ) {
1753 $price = wc_price( $price );
1754 } elseif ( 'edd' === $monetize_by ) {
1755 $price = edd_currency_filter( edd_format_amount( $price ) );
1756 } elseif ( 'pmpro' === $monetize_by ) {
1757 $price = pmpro_formatPrice( $price );
1758 }
1759
1760 return $price;
1761 }
1762 }
1763
1764 if ( ! function_exists( 'tutor_print_formatted_price' ) ) {
1765 /**
1766 * A clone copy of `tutor_get_formatted_price` helper.
1767 * To print formatted price with output escaping.
1768 *
1769 * @since 3.0.0
1770 *
1771 * @param mixed $price price.
1772 *
1773 * @return void
1774 */
1775 function tutor_print_formatted_price( $price ) {
1776 echo esc_html( tutor_get_formatted_price( $price ) );
1777 }
1778 }
1779
1780 if ( ! function_exists( 'tutor_get_locale_price' ) ) {
1781 /**
1782 * Get price as per locale format.
1783 *
1784 * For locale settings currency code will be used.
1785 *
1786 * @since 3.0.0
1787 *
1788 * @param mixed $price raw price.
1789 *
1790 * @return mixed raw price.
1791 */
1792 function tutor_get_locale_price( $price ) {
1793 // TODO: implement price formation.
1794 return $price;
1795 }
1796 }
1797
1798 if ( ! function_exists( 'tutor_is_json' ) ) {
1799 /**
1800 * Check if a string is valid JSON.
1801 *
1802 * @since 1.0.0
1803 *
1804 * @param string $string input string.
1805 *
1806 * @return bool
1807 */
1808 function tutor_is_json( $string ) {
1809 json_decode( $string );
1810 return json_last_error() === JSON_ERROR_NONE;
1811 }
1812 }
1813
1814 if ( ! function_exists( 'tutor_is_dev_mode' ) ) {
1815 /**
1816 * Check tutor is in development mode or not.
1817 *
1818 * @since 3.0.0
1819 *
1820 * @return bool True if the current environment is local, false otherwise.
1821 */
1822 function tutor_is_dev_mode() {
1823 return defined( 'TUTOR_DEV_MODE' ) && TUTOR_DEV_MODE;
1824 }
1825 }
1826
1827 if ( ! function_exists( 'tutor_redirect_after_payment' ) ) {
1828 /**
1829 * Redirect after payment with status and message.
1830 *
1831 * @since 3.0.0
1832 *
1833 * @param string $status success or error status of payment.
1834 * @param int $order_id order ID.
1835 * @param string $message success/error message to display.
1836 *
1837 * @return void
1838 */
1839 function tutor_redirect_after_payment( $status, $order_id, $message = '' ) {
1840 $query_params = array(
1841 'tutor_order_placement' => $status,
1842 'order_id' => $order_id,
1843 );
1844
1845 if ( $message ) {
1846 if ( 'success' === $status ) {
1847 $query_params['success_message'] = $message;
1848 } else {
1849 $query_params['error_message'] = $message;
1850 }
1851 }
1852
1853 wp_safe_redirect( apply_filters( 'tutor_redirect_url_after_checkout', add_query_arg( $query_params, home_url() ), $status, $order_id ) );
1854 exit();
1855 }
1856 }
1857
1858 if ( ! function_exists( 'tutor_split_amounts' ) ) {
1859 /**
1860 * Split amounts into parts for admin & instructor.
1861 *
1862 * Amount split will be proportionally based on
1863 * admin commission rate & instructor commission rate.
1864 *
1865 * @since 3.0.0
1866 *
1867 * @param array $amounts single amount or list of amount array. For ex: [12,20,100].
1868 *
1869 * @return array
1870 */
1871 function tutor_split_amounts( $amounts ) {
1872 $amounts = is_array( $amounts ) ? $amounts : array( $amounts );
1873
1874 $admin_amount = 0;
1875 $instructor_amount = 0;
1876
1877 $sharing_enabled = tutor_utils()->get_option( 'enable_revenue_sharing' );
1878 $instructor_rate = $sharing_enabled ? tutor_utils()->get_option( 'earning_instructor_commission' ) : 0;
1879 $admin_rate = $sharing_enabled ? tutor_utils()->get_option( 'earning_admin_commission' ) : 100;
1880
1881 foreach ( $amounts as $amount ) {
1882 $instructor_amount = $instructor_rate > 0 ? ( ( $amount * $instructor_rate ) / 100 ) : 0;
1883 $admin_amount = $admin_rate > 0 ? ( ( $amount * $admin_rate ) / 100 ) : 0;
1884 }
1885
1886 return array(
1887 'admin' => $admin_amount,
1888 'instructor' => $instructor_amount,
1889 );
1890 }
1891 }
1892
1893 if ( ! function_exists( 'tutor_is_local_env' ) ) {
1894 /**
1895 * Check if the current environment is local.
1896 *
1897 * @since 3.2.0
1898 *
1899 * @return bool True if the current environment is local, false otherwise.
1900 */
1901 function tutor_is_local_env() {
1902 $site_url = site_url();
1903 return (
1904 strpos( $site_url, '.local' ) !== false ||
1905 strpos( $site_url, 'localhost' ) !== false
1906 );
1907 }
1908 }
1909
1910
1911
1912 if ( ! function_exists( 'get_tutor_post_types' ) ) {
1913 /**
1914 * Get tutor post type list.
1915 *
1916 * @since 3.6.0
1917 *
1918 * @param string $post_type the post type to get single tutor valid post type.
1919 *
1920 * @return array|string
1921 */
1922 function get_tutor_post_types( $post_type = '' ) {
1923 $valid_post_types = array(
1924 'course' => tutor()->course_post_type,
1925 'bundle' => tutor()->bundle_post_type,
1926 'lesson' => tutor()->lesson_post_type,
1927 'topics' => tutor()->topics_post_type,
1928 'quiz' => tutor()->quiz_post_type,
1929 'assignment' => tutor()->assignment_post_type,
1930 'zoom' => tutor()->zoom_post_type,
1931 'meet' => tutor()->meet_post_type,
1932 'enrollment' => tutor()->enrollment_post_type,
1933 'announcement' => tutor()->announcement_post_type,
1934 );
1935
1936 if ( $post_type && isset( $valid_post_types[ $post_type ] ) ) {
1937 return $valid_post_types[ $post_type ];
1938 }
1939
1940 return $valid_post_types;
1941 }
1942 }
1943
1944 if ( ! function_exists( 'tutor_decode_unicode_sequences' ) ) {
1945 /**
1946 * Decode Unicode escape sequences in a string to their corresponding UTF-8 characters.
1947 *
1948 * Example:
1949 * decode_unicode_escapes('Hello\\u0020World') => 'Hello World'
1950 * decode_unicode_escapes('\\u{1F600}') => '😀'
1951 *
1952 * Notes:
1953 * - Input and output are expected to be UTF-8 encoded.
1954 * - Behavior for malformed sequences is to preserve the original bytes rather than throw.
1955 *
1956 * @since 3.6.0
1957 *
1958 * @param string $str the input string possibly containing Unicode escape sequences.
1959 *
1960 * @return string the string with Unicode escape sequences decoded into UTF-8 characters.
1961 */
1962 function tutor_decode_unicode_sequences( $str ) {
1963 if ( empty( $str ) ) {
1964 return '';
1965 }
1966
1967 // Step 1: Decode \uXXXX or uXXXX sequences.
1968 $str = preg_replace_callback(
1969 '/\\\\?u([0-9a-fA-F]{4})/',
1970 function ( $m ) {
1971 return mb_convert_encoding( pack( 'H*', $m[1] ), 'UTF-8', 'UCS-2BE' );
1972 },
1973 $str
1974 );
1975
1976 // Step 2: Decode HTML entities like &lt;, &nbsp;.
1977 $str = html_entity_decode( $str, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
1978
1979 return $str;
1980 }
1981 }
1982
1983 if ( ! function_exists( 'tutor_price_allowed_html' ) ) {
1984
1985 /**
1986 * Get the allowed HTML tags for price formatting.
1987 *
1988 * @since 4.0.0
1989 *
1990 * @return array Allowed HTML tags and their attributes for price output.
1991 */
1992 function tutor_price_allowed_html() {
1993
1994 if ( 'edd' === tutor_utils()->get_option( 'monetize_by' ) && function_exists( 'edd_get_allowed_tags' ) ) {
1995 return edd_get_allowed_tags();
1996 }
1997
1998 return array(
1999 'span' => array(
2000 'class' => true,
2001 'id' => true,
2002 'style' => true,
2003 'aria-hidden' => true,
2004 ),
2005 'del' => array(
2006 'class' => true,
2007 ),
2008 'ins' => array(
2009 'class' => true,
2010 ),
2011 'bdi' => array(
2012 'class' => true,
2013 ),
2014 'small' => array(
2015 'class' => true,
2016 ),
2017 'strong' => array(
2018 'class' => true,
2019 ),
2020 'em' => array(
2021 'class' => true,
2022 ),
2023 'sup' => array(
2024 'class' => true,
2025 ),
2026 'sub' => array(
2027 'class' => true,
2028 ),
2029 );
2030 }
2031 }
2032