includes/llms.functions.core.php
<?php
/**
* Core LifterLMS functions file
*
* @package LifterLMS/Functions
*
* @since 1.0.0
* @version 7.5.0
*/
defined( 'ABSPATH' ) || exit;
require_once 'functions/llms-functions-l10n.php';
require_once 'functions/llms-functions-access-plans.php';
require_once 'functions/llms-functions-deprecated.php';
require_once 'functions/llms-functions-forms.php';
require_once 'functions/llms-functions-locale.php';
require_once 'functions/llms-functions-options.php';
require_once 'functions/llms-functions-progression.php';
require_once 'functions/llms-functions-user-information-fields.php';
require_once 'functions/llms-functions-wrappers.php';
require_once 'functions/llms.functions.access.php';
require_once 'functions/llms.functions.certificate.php';
require_once 'functions/llms.functions.course.php';
require_once 'functions/llms.functions.currency.php';
require_once 'functions/llms.functions.log.php';
require_once 'functions/llms.functions.notice.php';
require_once 'functions/llms.functions.order.php';
require_once 'functions/llms.functions.page.php';
require_once 'functions/llms.functions.person.php';
require_once 'functions/llms.functions.privacy.php';
require_once 'functions/llms.functions.quiz.php';
require_once 'functions/llms.functions.template.php';
require_once 'functions/llms.functions.user.postmeta.php';
require_once 'functions/llms.functions.favorite.php';
if ( ! function_exists( 'llms_anonymize_string' ) ) {
/**
* Anonymize a string.
*
* Masks the characters in a string with the specified character leaving a small number
* of characters visible. For example `llms_anonymize_string( 'MY_SECRET_STRING' ) will return
* 'MY************NG'.
*
* The number of retained original characters is dependent on the string's length:
*
* | Length | At start | At end | Example |
* | ------------- | -------- | ------ | ------------ |
* | 1 | 0 | 0 | * |
* | >= 2 && <= 6 | 0 | 1 | *****A |
* | >= 7 && <= 10 | 0 | 2 | ********AA |
* | >= 11 | 2 | 2 | AA*******AA |
*
* Any string that validates as an email address using `is_email()` will be split at the `@` symbol
* and each part of the email address will be anonymized separately, for example:
* `llms_anonymize_string( 'help@lifterlms.com' )` will return '***p@li*********om'.
*
* @since 6.4.0
*
* @param string $string The input string to be anonymized.
* @param string $char The character used to mask the string.
* @return string
*/
function llms_anonymize_string( $string, $char = '*' ) {
if ( is_email( $string ) ) {
$parts = explode( '@', $string );
return llms_anonymize_string( $parts[0] ) . '@' . llms_anonymize_string( $parts[1] );
}
$len = strlen( $string );
$at_front = 2;
$at_back = 2;
if ( 1 === $len ) {
return $char;
} elseif ( $len <= 6 ) {
$at_front = 0;
$at_back = 1;
} elseif ( $len <= 10 ) {
$at_front = 0;
}
$start = substr( $string, 0, $at_front );
$body = str_repeat( $char, strlen( $string ) - ( $at_front + $at_back ) );
$end = substr( $string, - $at_back );
return "{$start}{$body}{$end}";
}
}
/**
* Insert elements into an associative array after a specific array key
*
* If the requested key doesn't exit, the new item will be added to the end of the array.
* If you need to insert at the beginning of an array use array_merge( $new_item, $orig_item ).
*
* @since 3.21.0
*
* @param array $array Original associative array.
* @param string $after_key Key name in original array to insert new item after.
* @param string $insert_key Key name of the item to be inserted.
* @param mixed $insert_item Value to be inserted.
* @return array
*/
function llms_assoc_array_insert( $array, $after_key, $insert_key, $insert_item ) {
$res = array();
$new_item = array(
$insert_key => $insert_item,
);
$index = array_search( $after_key, array_keys( $array ) );
if ( false !== $index ) {
$index++;
$res = array_merge(
array_slice( $array, 0, $index, true ),
$new_item,
array_slice( $array, $index, count( $array ) - 1, true )
);
} else {
$res = array_merge( $array, $new_item );
}
return $res;
}
/**
* Do apply_filters( 'the_content', $content ) without actions adding their own content onto us...
*
* @param string $content Optional. The content. Default is empty string.
* @return string
* @since 3.16.10
* @version 3.19.2
*/
if ( ! function_exists( 'llms_content' ) ) {
function llms_content( $content = '' ) {
$content = do_shortcode( shortcode_unautop( wpautop( convert_chars( wptexturize( $content ) ) ) ) );
global $wp_embed;
if ( $wp_embed && method_exists( $wp_embed, 'autoembed' ) ) {
$content = $wp_embed->autoembed( $content );
}
return $content;
}
}
/**
* Mark a function as deprecated and inform when it is used.
*
* This function uses WP core's `_deprecated_function()`, logging to the LifterLMS log file
* located at `wp-content/updloads/llms-logs/llms-{$hash}.log` instead of `wp-content/debug.log`.
*
* @since 2.6.0
* @since 3.6.0 Unknown.
* @since 4.4.0 Uses WP `_deprecated_function()` instead of duplicating its logic.
*
* @param string $function Name of the deprecated function.
* @param string $version LifterLMS version that deprecated the function.
* @param string $replacement Optional. Replacement function. Default is `null`.
* @return void
*/
function llms_deprecated_function( $function, $version, $replacement = null ) {
_deprecated_function( $function, $version, $replacement );
}
/**
* Cron function to cleanup files in the LLMS_TMP_DIR
*
* Removes any files that are more than a day old.
*
* @since 3.18.0
* @since 4.10.1 Use strict type comparisons.
*
* @return void
*/
function llms_cleanup_tmp() {
$max_age = llms_current_time( 'timestamp' ) - apply_filters( 'llms_tmpfile_max_age', DAY_IN_SECONDS );
$exclude = array( '.htaccess', 'index.html' );
foreach ( glob( LLMS_TMP_DIR . '*' ) as $file ) {
// Don't cleanup index and .htaccess.
if ( in_array( basename( $file ), $exclude, true ) ) {
continue;
}
if ( filemtime( $file ) < $max_age ) {
wp_delete_file( $file );
}
}
}
add_action( 'llms_cleanup_tmp', 'llms_cleanup_tmp' );
/**
* Escape and add quotes to a string, useful for array mapping when building queries.
*
* @since 6.0.0
*
* @param string $str Input string.
* @return string Escaped string wrapped in quotation marks.
*/
function llms_esc_and_quote_str( $str ) {
return "'" . esc_sql( $str ) . "'";
}
/**
* Retrieve an array of post types which can be completed by students
*
* @since 4.2.0
*
* @return string[]
*/
function llms_get_completable_post_types() {
/**
* Filter the list of post types which can be completed by students.
*
* @since Unknown
*
* @param string[] $post_types WP_Post post type names.
*/
return apply_filters( 'llms_completable_post_types', array( 'course', 'section', 'lesson' ) );
}
/**
* Retrieve an array of taxonomies which can be completed by students
*
* @since 4.2.0
*
* @return string[]
*/
function llms_get_completable_taxonomies() {
/**
* Filter the list of taxonomies which can be completed by students.
*
* @since 4.2.0
*
* @param string[] $taxonomies Taxonomy names.
*/
return apply_filters( 'llms_completable_taxonomies', array( 'course_track' ) );
}
/**
* Retrieve an array of post types whose name doesn't start with the prefix 'llms_'.
*
* @since 4.10.1
*
* @return string[]
*/
function llms_get_unprefixed_post_types() {
/**
* Filter the list of post types whose name doesn't start with the prefix 'llms_'.
*
* @since 4.10.1
*
* @param string[] $post_types WP_Post post type names.
*/
return apply_filters( 'llms_unprefixed_post_types', array( 'course', 'section', 'lesson' ) );
}
/**
* Get themes natively supported by LifterLMS
*
* @since 3.0.0
*
* @return array
*/
function llms_get_core_supported_themes() {
return array(
'canvas',
'Divi',
'genesis',
'twentyseventeen',
'twentysixteen',
'twentyfifteen',
'twentyfourteen',
'twentythirteen',
'twentyeleven',
'twentytwelve',
'twentyten',
);
}
/**
* Get human readable time difference between 2 dates
*
* Return difference between 2 dates in year, month, hour, minute or second
* The $precision caps the number of time units used: for instance if
* $time1 - $time2 = 3 days, 4 hours, 12 minutes, 5 seconds
* - with precision = 1 : 3 days
* - with precision = 2 : 3 days, 4 hours
* - with precision = 3 : 3 days, 4 hours, 12 minutes.
*
* @since Unknown
* @since 3.24.0 Unknown.
*
* @source http://www.if-not-true-then-false.com/2010/php-calculate-real-differences-between-two-dates-or-timestamps/
*
* @param mixed $time1 A time (string or timestamp).
* @param mixed $time2 A time (string or timestamp).
* @param integer $precision Optional precision. Default is 2.
* @return string time difference
*/
function llms_get_date_diff( $time1, $time2, $precision = 2 ) {
// If not numeric then convert timestamps.
if ( ! is_numeric( $time1 ) ) {
$time1 = strtotime( $time1 );
}
if ( ! is_numeric( $time2 ) ) {
$time2 = strtotime( $time2 );
}
// If time1 > time2 then swap the 2 values.
if ( $time1 > $time2 ) {
list( $time1, $time2 ) = array( $time2, $time1 );
}
// Set up intervals and diffs arrays.
$intervals = array( 'year', 'month', 'day', 'hour', 'minute', 'second' );
$l18n_singular = array(
'year' => __( 'year', 'lifterlms' ),
'month' => __( 'month', 'lifterlms' ),
'day' => __( 'day', 'lifterlms' ),
'hour' => __( 'hour', 'lifterlms' ),
'minute' => __( 'minute', 'lifterlms' ),
'second' => __( 'second', 'lifterlms' ),
);
$l18n_plural = array(
'year' => __( 'years', 'lifterlms' ),
'month' => __( 'months', 'lifterlms' ),
'day' => __( 'days', 'lifterlms' ),
'hour' => __( 'hours', 'lifterlms' ),
'minute' => __( 'minutes', 'lifterlms' ),
'second' => __( 'seconds', 'lifterlms' ),
);
$diffs = array();
foreach ( $intervals as $interval ) {
// Create temp time from time1 and interval.
$ttime = strtotime( '+1 ' . $interval, $time1 );
// Set initial values.
$add = 1;
$looped = 0;
// Loop until temp time is smaller than time2.
while ( $time2 >= $ttime ) {
// Create new temp time from time1 and interval.
$add++;
$ttime = strtotime( '+' . $add . ' ' . $interval, $time1 );
$looped++;
}
$time1 = strtotime( '+' . $looped . ' ' . $interval, $time1 );
$diffs[ $interval ] = $looped;
}
$count = 0;
$times = array();
foreach ( $diffs as $interval => $value ) {
// Break if we have needed precision.
if ( $count >= $precision ) {
break;
}
// Add value and interval if value is bigger than 0.
if ( $value > 0 ) {
if ( 1 != $value ) {
$text = $l18n_plural[ $interval ];
} else {
$text = $l18n_singular[ $interval ];
}
// Add value and interval to times array.
$times[] = $value . ' ' . $text;
$count++;
}
}
// Return string with times.
return implode( ', ', $times );
}
/**
* Instantiate an instance of DOMDocument with an HTML string
*
* This function suppresses PHP warnings that would be thrown by DOMDocument when
* loading a partial string or an HTML string with errors.
*
* @see LLMS_DOM_Document->load().
*
* @since 4.7.0
* @since 4.8.0 Remove reliance on `mb_convert_encoding()`.
* @since 4.13.0 Add back partial reliance on `mb_convert_encoding()` but keep the previous implementation as a fall-back.
* Also fix a potential fatal in the fall-back which tried to manipulate a non existent node.
* Wrapper for `LLMS_Dom_Document:load()`.
*
* @param string $string An HTML string, either a full HTML document or a partial string.
* @return DOMDocument|WP_Error Returns an instance of DOMDocument with `$string` loaded into it
* or an error object when DOMDocument isn't available or an error is encountered during loading.
*/
function llms_get_dom_document( $string ) {
$llms_dom = new LLMS_DOM_Document( $string );
$load = $llms_dom->load();
return is_wp_error( $load ) ? $load : $llms_dom->dom();
}
/**
* Retrieve the HTML for a donut chart
*
* Note that this must be used in conjunction with some JS to initialize the chart!
*
* @since 3.9.0
* @since 3.24.0 Unknown.
*
* @param mixed $percentage Percentage to display
* @param string $text Optional. Text/caption to display (short). Default is empty string.
* @param string $size Optional. Size of the chart (mini, small, default, large). Default is 'default'.
* @param array $classes Optional. Additional custom css classes to add to the chart element. Default is empty array.
* @return string
*/
function llms_get_donut( $percentage, $text = '', $size = 'default', $classes = array() ) {
$percentage = is_numeric( $percentage ) ? $percentage : 0;
$classes = array_merge( array( 'llms-donut', $size ), $classes );
$classes = implode( ' ', $classes );
$percentage = 'mini' === $size ? round( $percentage, 0 ) : llms()->grades()->round( $percentage );
return '
<div class="' . $classes . '" data-perc="' . $percentage . '">
<div class="inside">
<div class="percentage">
' . $percentage . '<small>%</small>
<div class="caption">' . $text . '</div>
</div>
</div>
</div>';
}
/**
* Get a list of registered engagement triggers
*
* @return array
* @since 3.1.0
* @since 3.24.1
*/
function llms_get_engagement_triggers() {
/**
* Filter the engagement triggers
*
* @since Unknown
*
* @param array $engagement_triggers An associative array of engagement triggers. Keys are the engagement trigger slugs, values are their description.
*/
return apply_filters(
'lifterlms_engagement_triggers',
array(
'user_registration' => __( 'Student creates a new account', 'lifterlms' ),
'access_plan_purchased' => __( 'Student Purchases an Access Plan', 'lifterlms' ),
'course_enrollment' => __( 'Student enrolls in a course', 'lifterlms' ),
'course_purchased' => __( 'Student purchases a course', 'lifterlms' ),
'course_completed' => __( 'Student completes a course', 'lifterlms' ),
// 'days_since_login' => __( 'Days since user last logged in', 'lifterlms' ), // @todo.
'lesson_completed' => __( 'Student completes a lesson', 'lifterlms' ),
'quiz_completed' => __( 'Student completes a quiz', 'lifterlms' ),
'quiz_passed' => __( 'Student passes a quiz', 'lifterlms' ),
'quiz_failed' => __( 'Student fails a quiz', 'lifterlms' ),
'section_completed' => __( 'Student completes a section', 'lifterlms' ),
'course_track_completed' => __( 'Student completes a course track', 'lifterlms' ),
'membership_enrollment' => __( 'Student enrolls in a membership', 'lifterlms' ),
'membership_purchased' => __( 'Student purchases a membership', 'lifterlms' ),
)
);
}
/**
* Get a list of registered engagement types
*
* @return array
* @since 3.1.0
* @version 3.24.0
*/
function llms_get_engagement_types() {
/**
* Filter the engagement types
*
* @since Unknown
*
* @param array $engagement_types An associative array of engagement types. Keys are the engagement type slugs, values are their description.
*/
return apply_filters(
'lifterlms_engagement_types',
array(
'achievement' => __( 'Award an Achievement', 'lifterlms' ),
'certificate' => __( 'Award a Certificate', 'lifterlms' ),
'email' => __( 'Send an Email', 'lifterlms' ),
)
);
}
/**
* Retrieve a list of post types which users can be enrolled into.
*
* @since 4.4.1
*
* @return string[] A list of post type names.
*/
function llms_get_enrollable_post_types() {
/**
* Customize the post types which users can be enrolled into.
*
* This filter differs slightly from `llms_user_enrollment_status_allowed_post_types`. This filter
* determines which post types a user can be physically associated with through enrollment while
* `llms_user_enrollment_status_allowed_post_types` allows checking of user enrollment based on
* posts which are associated with a post type.
*
* @since 3.37.9
*
* @see llms_user_enrollment_status_allowed_post_types
*
* @param string[] $post_types Array of post type names.
*/
return apply_filters( 'llms_user_enrollment_allowed_post_types', array( 'course', 'llms_membership' ) );
}
/**
* Retrieve a list of post types that can be used to check a users enrollment status in an enroll-able post type.
*
* @since 4.4.1
*
* @return string[] A list of post type names.
*/
function llms_get_enrollable_status_check_post_types() {
/**
* Customize the post types that can be used to check a user's enrollment status.
*
* This filter differs slightly from `llms_user_enrollment_allowed_post_types`. The difference is that
* a user can be enrolled into a course but we can check their course enrollment status using the ID of a child (section or lesson).
*
* When adding a new post type for custom enrollment functionality the post type should be registered with
* both of these filters.
*
* @since 3.37.9
*
* @see llms_user_enrollment_allowed_post_types
*
* @param string[] $post_types List of allowed post types names.
*/
return apply_filters( 'llms_user_enrollment_status_allowed_post_types', array( 'course', 'section', 'lesson', 'llms_membership' ) );
}
/**
* Retrieve an HTML anchor for an option page
*
* @since 3.18.0
*
* @param string $option_name Option name.
* @param string $target Optional. HTML target attribute. Defaults to _blank.
* @return string
*/
function llms_get_option_page_anchor( $option_name, $target = '_blank' ) {
$page_id = get_option( $option_name );
if ( ! $page_id ) {
return '';
}
$target = $target ? ' target="' . esc_attr( $target ) . '"' : '';
return sprintf(
'<a href="%1$s"%2$s>%3$s</a>',
get_the_permalink( $page_id ),
$target,
get_the_title( $page_id )
);
}
/**
* Get a list of available product (course & membership) catalog visibility options
*
* @since 3.6.0
*
* @return array
*/
function llms_get_product_visibility_options() {
/**
* Filter the product visibility options
*
* @since 3.6.0
*
* @param array $product_visibility_options. An associative array representing of visibility options. Keys are the engagement type slugs, values are their description.
*/
return apply_filters(
'lifterlms_product_visibility_options',
array(
'catalog_search' => __( 'Catalog & Search', 'lifterlms' ),
'catalog' => __( 'Catalog only', 'lifterlms' ),
'search' => __( 'Search only', 'lifterlms' ),
'hidden' => __( 'Hidden', 'lifterlms' ),
)
);
}
/**
* Get an array of student IDs based on enrollment status a course or membership
*
* @since 3.0.0
* @since 3.8.0 Unknown.
* @since 4.10.2 Instantiate the student query passing `no_found_rows` arg as `true`,
* as we don't need (and do not return) pagination info, e.g. max_pages.
* @since 6.0.0 Don't access `LLMS_Student_Query` properties directly.
*
* @param int $post_id WP_Post id of a course or membership.
* @param string|array $statuses List of enrollment statuses to query by status query is an OR relationship. Default is 'enrolled'.
* @param integer $limit Number of results.
* @param integer $skip Number of results to skip (for pagination).
* @return array
*/
function llms_get_enrolled_students( $post_id, $statuses = 'enrolled', $limit = 50, $skip = 0 ) {
$query = new LLMS_Student_Query(
array(
'post_id' => $post_id,
'statuses' => $statuses,
'page' => ( 0 === $skip ) ? 1 : ( $skip / $limit ) + 1,
'per_page' => $limit,
'sort' => array(
'id' => 'ASC',
),
'no_found_rows' => true,
)
);
if ( $query->has_results() ) {
return wp_list_pluck( $query->get_results(), 'id' );
}
return array();
}
/**
* Retrieve default instructor data structure.
*
* @since 3.25.0
*
* @return array
*/
function llms_get_instructors_defaults() {
/**
* Filter the instructor's default data structure.
*
* @since 3.25.0
*
* @param array $product_visibility_options. An associative array representing the instructor's default data structure.
*/
return apply_filters(
'llms_post_instructors_get_defaults',
array(
'label' => __( 'Author', 'lifterlms' ),
'visibility' => 'visible',
'id' => '',
)
);
}
/**
* Function used to sanitize user input in a manner similar to the (deprecated) FILTER_SANITIZE_STRING.
*
* This function retrieves the raw user input via `llms_filter_input()` using the FILTER_UNSAFE_RAW filter, strips
* all tags, and then encodes single and double quotes with the relevant HTML entity codes.
*
* In many cases, the usage of `FILTER_SANITIZE_STRING` can be easily replaced with `FILTER_SANITIZE_FULL_SPECIAL_CHARS` but
* in some cases, especially when storing the user input, encoding all special characters can result in an stored XSS injection
* so this function can be used to preserve the pre PHP 8.1 behavior where sanitization is expected during the retrieval
* of user input.
*
* @since 5.9.0
*
* @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV.
* @param string $variable_name Name of a variable to retrieve.
* @param int[] $flags Array of supported filter options and flags.
* Accepts `FILTER_REQUIRE_ARRAY` in order to require the input to be an array.
* Accepts `FILTER_FLAG_NO_ENCODE_QUOTES` to prevent encoding of quotes.
* @return string|string[]|null|boolean Value of the requested variable on success, `false` if the filter fails, or `null` if the `$variable_name` variable is not set.
*/
function llms_filter_input_sanitize_string( $type, $variable_name, $flags = array() ) {
$require_array = in_array( FILTER_REQUIRE_ARRAY, $flags, true );
$string = llms_filter_input( $type, $variable_name, FILTER_UNSAFE_RAW, $require_array ? FILTER_REQUIRE_ARRAY : array() );
// If we have an empty string or the input var isn't found we can return early.
if ( empty( $string ) ) {
return $string;
}
$string = $require_array ? array_map( 'wp_strip_all_tags', $string ) : wp_strip_all_tags( $string );
if ( ! in_array( FILTER_FLAG_NO_ENCODE_QUOTES, $flags, true ) ) {
$string = str_replace(
array( "'", '"' ),
array( ''', '"' ),
$string
);
}
return $string;
}
/**
* Get the most recently created coupon ID for a given code
*
* @param string $code Optional. The coupon's code (title). Default is empty string.
* @param int $dupcheck_id Optional. Coupon id that can be passed which will be excluded during the query
* this is used to dupcheck the coupon code during coupon creation. Default is 0.
* @return int
* @since 3.0.0
* @version 3.0.0
*/
function llms_find_coupon( $code = '', $dupcheck_id = 0 ) {
global $wpdb;
return $wpdb->get_var(
$wpdb->prepare(
"SELECT ID
FROM {$wpdb->posts}
WHERE post_title = %s
AND post_type = 'llms_coupon'
AND post_status = 'publish'
AND ID != %d
ORDER BY ID desc;
",
array( $code, $dupcheck_id )
)
); // no-cache ok.
}
/**
* Get a list of available course / membership enrollment statuses
*
* @since 3.0.0
*
* @return array
*/
function llms_get_enrollment_statuses() {
/**
* Filter the enrollment statuses
*
* @since 3.0.0
*
* @param array $enrollment_statuses An associative array representing the enrollment statuses. Keys are the statuses, values are their human readable labels (names).
*/
return apply_filters(
'llms_get_enrollment_statuses',
array(
'cancelled' => __( 'Cancelled', 'lifterlms' ),
'enrolled' => __( 'Enrolled', 'lifterlms' ),
'expired' => __( 'Expired', 'lifterlms' ),
)
);
}
/**
* Get the human readable (and translated) name of an enrollment status
*
* @since 3.0.0
* @since 3.6.0 Unknown.
*
* @param string $status Enrollment status key.
* @return string
*/
function llms_get_enrollment_status_name( $status ) {
$status = strtolower( $status ); // Backwards compatibility.
$statuses = llms_get_enrollment_statuses();
if ( is_array( $statuses ) && isset( $statuses[ $status ] ) ) {
$status = $statuses[ $status ];
}
/**
* Filter the enrollment status name
*
* @since Unknown
*
* @param array $enrollment_status The enrollment status name.
*/
return apply_filters( 'lifterlms_get_enrollment_status_name', $status );
}
/**
* Retrieve an IP Address for the current user
*
* @since 3.0.0
* @since 3.35.0 Sanitize superglobal input.
*
* @return string
*/
function llms_get_ip_address() {
$ip = '';
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Look below you.
// phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Look below you.
if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
// Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2.
// Make sure we always only send through the first IP in the list which should always be the client IP.
$ip = trim( current( explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
} elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
$ip = $_SERVER['REMOTE_ADDR'];
}
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
$ip = sanitize_text_field( wp_unslash( $ip ) );
if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
return '';
}
return $ip;
}
/**
* Retrieves and filters the value open registration option
*
* @since 5.0.0
*
* @return string The value of the open registration status. Either "yes" for enabled or "no" for disabled.
*/
function llms_get_open_registration_status() {
$status = get_option( 'lifterlms_enable_myaccount_registration', 'no' );
/**
* Filter the value of the open registration setting
*
* @since 3.37.10
*
* @param string $status The current value of the open registration option. Either "yes" for enabled or "no" for disabled.
*/
return apply_filters( 'llms_enable_open_registration', $status );
}
/**
* Retrieve the LLMS Post Model for a give post by ID or WP_Post Object
*
* @since 3.3.0
* @since 3.16.11 Unknown.
* @since 4.10.1 Made sure to only instantiate LifterLMS classes.
*
* @param WP_Post|int $post Instance of WP_Post or a WP Post ID.
* @param mixed $error Determine what to return if the LLMS class isn't found.
* post = WP_Post
* falsy = false.
* @return LLMS_Post_Model|WP_Post|null|false LLMS_Post_Model extended object,
* null if WP get_post() fails,
* WP_Post if LLMS_Post_Model extended class isn't found and $error = 'post'
* false if LLMS_Post_Model extended class isn't found and $error != 'post'.
*/
function llms_get_post( $post, $error = false ) {
$post = get_post( $post );
if ( ! $post ) {
return $post;
}
$class = '';
// Check whether it's an llms post candidate: `post_type` starts with the 'llms_' prefix, or is one of the unprefixed ones.
if ( 0 === strpos( $post->post_type, 'llms_' ) || in_array( $post->post_type, llms_get_unprefixed_post_types(), true ) ) {
$post_type = explode( '_', str_replace( 'llms_', '', $post->post_type ) );
$class = 'LLMS';
foreach ( $post_type as $part ) {
$class .= '_' . ucfirst( $part );
}
}
if ( $class && class_exists( $class ) ) {
return new $class( $post );
} elseif ( 'post' === $error ) {
return $post;
}
return false;
}
/**
* Retrieve the parent course for a section, lesson, or quiz
*
* @since 3.6.0
* @since 3.17.7 Unknown.
* @since 3.37.14 Bail if `$post` is not an istance of `LLMS_Post_Model`.
* Use strict comparison.
*
* @param WP_Post|int $post WP Post ID or instance of WP_Post.
* @return LLMS_Course|null Instance of the LLMS_Course or null.
*/
function llms_get_post_parent_course( $post ) {
$post = llms_get_post( $post );
if ( ! $post || ! is_a( $post, 'LLMS_Post_Model' ) ) {
return null;
}
/**
* Filter the course children post types
*
* @since Unknown
*
* @param $post_type string[] Names of the post types that can be children of a course.
*/
$post_types = apply_filters( 'llms_course_children_post_types', array( 'section', 'lesson', 'llms_quiz' ) );
if ( ! in_array( $post->get( 'type' ), $post_types, true ) ) {
return null;
}
/** @var LLMS_Section|LLMS_Lesson|LLMS_Quiz $post */
return $post->get_course();
}
/**
* Retrieve an array of existing transaction statuses
*
* @since 3.0.0
*
* @return array
*/
function llms_get_transaction_statuses() {
/**
* Filter the transaction statuses
*
* @since Unknown
*
* @param $statuses string[] Names of the possible transaction statuses.
*/
return apply_filters(
'llms_get_transaction_statuses',
array(
'llms-txn-failed',
'llms-txn-pending',
'llms-txn-refunded',
'llms-txn-succeeded',
)
);
}
/**
* Determine is request is an ajax request
*
* @since 3.0.1
* @since 4.0.0 Use WP core `wp_doing_ajax()`.
*
* @return bool
*/
function llms_is_ajax() {
return wp_doing_ajax();
}
/**
* Determine if request is a REST request
*
* @since 3.27.0
*
* @return bool
*/
function llms_is_rest() {
/**
* Filters whether the current request is a REST request.
*
* @since 5.4.0
*
* @param $is_rest Whether the current request is a REST request.
*/
return apply_filters( 'llms_is_rest', ( defined( 'REST_REQUEST' ) && REST_REQUEST ) );
}
/**
* Determine whether the current theme is a block theme.
*
* Just a wrapper for WordPress core `wp_is_block_theme()` so to filter for testing purposes.
*
* @since 6.0.0
*
* @return string
*/
function llms_is_block_theme() {
/**
* Filters whether the current theme is a block theme.
*
* @since 6.0.0
*
* @param $is_block_theme Whether the current theme is a block theme.
*/
return apply_filters( 'llms_is_block_theme', function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() );
}
/**
* Checks if the current admin page is the block editor.
*
* @since 7.2.0
*
* @return bool
*/
function llms_is_block_editor(): bool {
if ( function_exists( 'is_gutenberg_page' ) && is_gutenberg_page() ) {
return true;
}
$current_screen = get_current_screen();
if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
return true;
}
return false;
}
/**
* Determine if the current request is a block rendering request in the editor.
*
* @since 7.2.0
*
* @return bool
*/
function llms_is_editor_block_rendering() {
if ( ! defined( 'REST_REQUEST' ) || ! is_user_logged_in() ) {
return false;
}
global $wp;
if ( ! $wp instanceof WP || empty( $wp->query_vars['rest_route'] ) ) {
return false;
}
$route = $wp->query_vars['rest_route'];
return false !== strpos( $route, '/block-renderer/' );
}
/**
* Check if the home URL is https. If it is, we don't need to do things such as 'force ssl'.
*
* @thanks woocommerce <3.
*
* @since 3.0.0
*
* @return bool
*/
function llms_is_site_https() {
return false !== strstr( get_option( 'home' ), 'https:' );
}
/**
* Create an array that can be passed to metabox select elements configured as an llms-select2-post query-ier
*
* @since 3.0.0
* @since 3.6.0 Unknown
*
* @param array $post_ids Optional. Indexed array of WordPress Post IDs. Defayult is empty array.
* @param string $template Optional. A template to customize the way the results look. Default is empty string.
* {title} and {id} can be passed into the template
* and will be replaced with the post title and post id respectively.
* @return array
*/
function llms_make_select2_post_array( $post_ids = array(), $template = '' ) {
if ( ! $template ) {
$template = '{title} (' . __( 'ID#', 'lifterlms' ) . ' {id})';
}
if ( ! is_array( $post_ids ) ) {
$post_ids = array( $post_ids );
}
$ret = array();
foreach ( $post_ids as $id ) {
$title = str_replace( array( '{title}', '{id}' ), array( get_the_title( $id ), $id ), $template );
$ret[] = array(
'key' => $id,
'title' => $title,
);
}
/**
* Filter the select2 post array
*
* @since Unknown
*
* @param array Associative array of representing select2 post elements.
* @param array $post_ids Optional. Indexed array of WordPress Post IDs.
*/
return apply_filters( 'llms_make_select2_post_array', $ret, $post_ids );
}
/**
* Create an array that can be passed to metabox select elements configured as an llms-select2-student query-ier.
*
* @since 3.10.1
* @version 3.23.0
*
* @param array $user_ids Optional. Indexed array of WordPress User IDs. Default is empty array.
* @param string $template Optional. A template to customize the way the results look. Default is empty string.
* %1$s = student name
* %2$s = student email.
* @return array
*/
function llms_make_select2_student_array( $user_ids = array(), $template = '' ) {
if ( ! $template ) {
$template = '%1$s <%2$s>';
}
if ( ! is_array( $user_ids ) ) {
$user_ids = array( $user_ids );
}
$ret = array();
foreach ( $user_ids as $id ) {
$student = llms_get_student( $id );
if ( ! $student ) {
continue;
}
$ret[] = array(
'key' => $id,
'title' => sprintf( $template, $student->get_name(), $student->get( 'user_email' ) ),
);
}
/**
* Filter the select2 student array
*
* @since Unknown
*
* @param array $elements Associative array representing select2 student elements.
* @param array $post_ids Optional. Indexed array of WordPress Post IDs.
*/
return apply_filters( 'llms_make_select2_student_array', $ret, $user_ids );
}
/**
* Define a constant if it's not already defined
*
* @since 3.15.0
*
* @param string $name Constant name.
* @param mixed $value Constant values.
* @return void
*/
function llms_maybe_define_constant( $name, $value ) {
if ( ! defined( $name ) ) {
define( $name, $value );
}
}
/**
* Parse booleans
*
* Mostly used to parse yes/no bools stored in various meta data fields
*
* @since 3.16.0
*
* @param mixed $val Value to parse.
* @return bool
*/
function llms_parse_bool( $val ) {
return filter_var( $val, FILTER_VALIDATE_BOOLEAN );
}
/**
* Convert a PHP error constant to a human readable error code
*
* @since 4.9.0
*
* @link https://www.php.net/manual/en/errorfunc.constants.php
*
* @param int $code A predefined php error constant.
* @return string A human readable string version of the constant.
*/
function llms_php_error_constant_to_code( $code ) {
$codes = array(
E_ERROR => 'E_ERROR', // 1.
E_WARNING => 'E_WARNING', // 2.
E_PARSE => 'E_PARSE', // 4.
E_NOTICE => 'E_NOTICE', // 8.
E_CORE_ERROR => 'E_CORE_ERROR', // 16.
E_CORE_WARNING => 'E_CORE_WARNING', // 32.
E_COMPILE_ERROR => 'E_COMPILE_ERROR', // 64.
E_COMPILE_WARNING => 'E_COMPILE_WARNING', // 128.
E_USER_ERROR => 'E_USER_ERROR', // 256.
E_USER_WARNING => 'E_USER_WARNING', // 512.
E_USER_NOTICE => 'E_USER_NOTICE', // 1024.
E_STRICT => 'E_STRICT', // 2048.
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', // 4096.
E_DEPRECATED => 'E_DEPRECATED', // 8192.
E_USER_DEPRECATED => 'E_USER_DEPRECATED', // 16384.
);
return isset( $codes[ $code ] ) ? $codes[ $code ] : $code;
}
/**
* Wrapper for set_time_limit to ensure it's enabled before calling
*
* @since 3.16.5
*
* @source thanks WooCommerce <3
*
* @param int $limit Optional. Script time limit. Default is 0 = no time limit.
* @return void
*/
function llms_set_time_limit( $limit = 0 ) {
if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
@set_time_limit( $limit ); // @phpcs:ignore
}
}
/**
* Strips a list of prefixes from the start of a string.
*
* By default, strips `llms_`, `lifterlms_`, 'llms-', or 'lifterlms-'. Other prefixes may be provided.
*
* Will strip only the first prefix found from the list of supplied prefixes.
*
* @since 6.0.0
* @since 7.0.0 Added `llms-` and `lifterlms-` as additional default prefixes to strip.
*
* @param string $string String to modify.
* @param string[] $prefixes List of prefixs.
* @return string The modified string. If no prefixes were found, the original string is returned without modification.
*/
function llms_strip_prefixes( $string, $prefixes = array() ) {
$prefixes = empty( $prefixes ) ? array( 'llms_', 'lifterlms_', 'llms-', 'lifterlms-' ) : $prefixes;
foreach ( $prefixes as $prefix ) {
if ( 0 === strpos( $string, $prefix ) ) {
$string = substr( $string, strlen( $prefix ) );
/**
* Most of the time we'll be using this to replace `llms_` as we don't often use `lifterlms_` for
* prefixing (anymore).
*
* Also, while it's probably not ever in use, this will prevent double-stripping if, for example,
* the string was `llms_lifterlms_something`. If we did want to strip that, the `$prefixes` should
* be overwritten to have both these items stripped.
*
* So once we find a prefix, we'll break the loop and return the string with the stripped prefix.
*/
break;
}
}
return $string;
}
/**
* Trim a string and append a suffix
*
* @since 3.0.0
*
* @source thank you WooCommerce <3
*
* @param string $string Input string.
* @param int $chars Optional. Max number of characters. Default is 200.
* @param string $suffix Optional. A suffix to append. Default is '...'.
* @return string
*/
function llms_trim_string( $string, $chars = 200, $suffix = '...' ) {
if ( strlen( $string ) > $chars ) {
if ( function_exists( 'mb_substr' ) ) {
$string = mb_substr( $string, 0, ( $chars - mb_strlen( $suffix ) ) ) . $suffix;
} else {
$string = substr( $string, 0, ( $chars - strlen( $suffix ) ) ) . $suffix;
}
}
return $string;
}
/**
* Verify nonce with additional checks to confirm request method
*
* Skips verification if the nonce is not set
* Useful for checking nonce for various LifterLMS forms which check for the form submission on init actions.
*
* @since 3.8.0
* @since 3.35.0 Sanitize nonce field before verification.
*
* @param string $nonce Name of the nonce field.
* @param string $action Name of the action.
* @param string $request_method Optional. Name of the intended request method. Default is 'POST'.
* @return null|false|int
*/
function llms_verify_nonce( $nonce, $action, $request_method = 'POST' ) {
if ( strtoupper( getenv( 'REQUEST_METHOD' ) ) !== $request_method ) {
return;
}
if ( empty( $_REQUEST[ $nonce ] ) ) {
return;
}
return wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST[ $nonce ] ) ), $action );
}
/**
* Check that the test value is a member of a specific array for sanitization purposes.
*
* @param mixed $needle Value to be tested.
* @param array $safelist Array of safelist values.
* @param mixed $default Default value to return if the needle is not in the safelist. Defaults to the first value in the safelist array if not provided.
* @since 7.6.0
*/
function llms_sanitize_with_safelist( $needle, $safelist, $default = null ) {
if ( ! in_array( $needle, $safelist ) ) {
if ( isset( $default ) ) {
return $default;
} else {
return $safelist[0];
}
} else {
return $needle;
}
}