` tags. [#2281](https://github.com/gocodebox/lifterlms/issues/2281)
-+ When an order post is restored from the trash its post status will now be "llms-pending" in favor of the default "draft" status.
-
-##### Bug Fixes
-
-+ Fixed unclosed checkout div wrapper on empty cart. [#2277](https://github.com/gocodebox/lifterlms/issues/2277)
-+ Don't attempt to lookup the default payment gateway from user meta data.
-+ Fixed required fields duplication when the form is a child of a `.wp-block-column` element. [#2134](https://github.com/gocodebox/lifterlms/issues/2134)
-+ Fixed an issue that prevented disabling the access plan’s option, Override Membership Redirects, once enabled. [#2234](https://github.com/gocodebox/lifterlms/issues/2234)
-+ Disabled `scroll-behavior: smooth` on checkout screen to address form element validity checking issues on Chromium-based browsers. [#2206](https://github.com/gocodebox/lifterlms/issues/2206)
-
-##### Deprecations
-
-+ Deprecated `LLMS_Controller_Orders::switch_payment_source()` in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Deprecated the `lifterlms_update_option_{$type}` action in favor of the `llms_update_option_{$type}` filter.
-+ Method `LLMS_Controller_Orders::confirm_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::confirm_pending_order()`.
-+ Method `LLMS_Controller_Orders::create_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::create_pending_order()`.
-+ Method `LLMS_Controller_Orders::switch_payment_source()` is deprecated in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Passing jQuery selections into the `window.LLMS.Spinner` functions is deprecated. Use JS `Elements` or selection strings parseable by `document.querySelector()` instead.
-+ Deprecated hook `llms_{$method}_title` in favor of `llms_{$method}_refund_title`.
-
-##### Developer Notes
-
-+ Added admin settings helper function, `llms_get_dashicon_link()`, intended to enable the addition of external resource helper links to settings field descriptions.
-+ The `LLMS_Student` object can be instantiated as an empty object and bypass current user autoloading. In the future this may affect integrations using the `lifterlms_new_pending_order` action hook which will receive an "empty" student object during order setup by gateways utilizing new AJAX-powered checkout endpoints.
-+ Added a filter, `llms_gateway_{$this->id}_logging_enabled`, which will allow force enabling/disabling of gateway logging functions.
-+ Improved payment gateway secure string logging by adding a method, `add_secure_string()` allowing developers to add secure strings during runtime without the necessity of registering the strings using filters.
-+ Introduces new function `llms_is_option_secure()` for determining if an "secured" option is defined in a "secure" manner.
-+ Implemented new gateway feature: `modify_recurring_payments`. [#2176](https://github.com/gocodebox/lifterlms/issues/2176)
-+ Added two new parameters to LLMS_Access_Plan::get_redirection_url() - `$encode` to optionally get a raw (not encoded) URL. - `$querystring_only` to optionally get only the redirect URL if set via NPUT_GET variable.
-+ Added new parameter `$querystring_only` to the filter hook `llms_plan_get_checkout_redirection`.
-+ Admin settings fields now display `after_html` for additional field types which support `desc`.
-+ The CSS for `.llms-spinning` and `.llms-spinner` elements is no longer loaded as part of the `lifterlms.css` and `admin.css` files, instead it is loaded dynamically when `window.LLMS.Spinner` functions are called. In some cases CSS overrides to these elements which relied on CSS rule load order may no longer successfully override the default CSS rules. These overrides may need to be updated to have more specific selectors in order to ensure the overrides are retained.
-+ The Javascript object, `window.LLMS.Spinner`, has been converted to a module accessible from the same variable.
-+ The `window.LLMS.Spinner` methods now accept JS Elements and selector strings parseable by `document.querySelector()` in addition to jQuery selections.
-+ Added new filter `llms_transaction_can_be_refunded` enabling custom refund restrictions to be applied to a transaction.
-
-##### Updated Templates
-
-+ [templates/block-templates/archive-course.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/archive-course.html)
-+ [templates/block-templates/archive-llms_membership.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/archive-llms_membership.html)
-+ [templates/block-templates/single-no-access.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/single-no-access.html)
-+ [templates/block-templates/taxonomy-course_cat.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/taxonomy-course_cat.html)
-+ [templates/block-templates/taxonomy-course_difficulty.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/taxonomy-course_difficulty.html)
-+ [templates/block-templates/taxonomy-course_tag.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/taxonomy-course_tag.html)
-+ [templates/block-templates/taxonomy-course_track.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/taxonomy-course_track.html)
-+ [templates/block-templates/taxonomy-membership_cat.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/taxonomy-membership_cat.html)
-+ [templates/block-templates/taxonomy-membership_tag.html](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/block-templates/taxonomy-membership_tag.html)
-+ [templates/checkout/form-gateways.php](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/checkout/form-gateways.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/checkout/form-switch-source.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/7.0.0/templates/myaccount/view-order-actions.php)
-
-
-v6.11.0 - 2022-09-22
---------------------
-
-##### Updates and Enhancements
-
-+ Since version 6.0.0, the Certificate Title Block provided the option to use four Google-hosted fonts. These fonts will now be served from the site's server in favor of serving them from the Google Fonts CDN. For more information about this change, please refer to https://make.wordpress.org/themes/2022/06/18/complying-with-gdpr-when-using-google-fonts/. If you wish to continue loading fonts from Google's CDN, add the following code to your functions.php file: `add_filter( 'llms_use_google_webfonts', '__return_true' );`. [#2189](https://github.com/gocodebox/lifterlms/issues/2189)
-+ Upgraded included library, `@woocommerce/action-scheduler`, to version [3.5.2](https://github.com/woocommerce/action-scheduler/releases/tag/3.5.2).
-
-##### Bug Fixes
-
-+ Fixed a division by zero error encountered on quiz reporting screens for quizzes with 0 total available points. [#2270](https://github.com/gocodebox/lifterlms/issues/2270)
-
-
-v7.0.0-rc.1 - 2022-09-14
-------------------------
-
-##### New Features
-
-+ Added handling for admin settings options that store their option values in a nested array.
-+ Added new AJAX checkout and payment source switching endpoints for payment gateways to utilize instead of the preexisting synchronous form submission methods.
-+ On purchase completed retrieve the redirection URL from the INPUT_POST 'redirect' variable, if no 'redirect' variable is passed via INPUT_GET. The INPUT_POST 'redirect' variable comes from the new checkout form's hidden field 'redirect' populated with LLMS_Access_Plan::get_redirection_url(). [#2229](https://github.com/gocodebox/lifterlms/issues/2229)
-
-##### Updates and Enhancements
-
-+ When an order post is restored from the trash its post status will now be "llms-pending" in favor of the default "draft" status.
-
-##### Bug Fixes
-
-+ Don't attempt to lookup the default payment gateway from user meta data.
-+ Fixed an issue that prevented disabling the access plan’s option, Override Membership Redirects, once enabled. [#2234](https://github.com/gocodebox/lifterlms/issues/2234)
-+ Disabled `scroll-behavior: smooth` on checkout screen to address form element validity checking issues on Chromium-based browsers. [#2206](https://github.com/gocodebox/lifterlms/issues/2206)
-
-##### Deprecations
-
-+ Deprecated `LLMS_Controller_Orders::switch_payment_source()` in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Deprecated the `lifterlms_update_option_{$type}` action in favor of the `llms_update_option_{$type}` filter.
-+ Method `LLMS_Controller_Orders::confirm_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::confirm_pending_order()`.
-+ Method `LLMS_Controller_Orders::create_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::create_pending_order()`.
-+ Method `LLMS_Controller_Orders::switch_payment_source()` is deprecated in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Passing jQuery selections into the `window.LLMS.Spinner` functions is deprecated. Use JS `Elements` or selection strings parseable by `document.querySelector()` instead.
-+ Deprecated hook `llms_{$method}_title` in favor of `llms_{$method}_refund_title`.
-
-##### Developer Notes
-
-+ Added admin settings helper function, `llms_get_dashicon_link()`, intended to enable the addition of external resource helper links to settings field descriptions.
-+ The `LLMS_Student` object can be instantiated as an empty object and bypass current user autoloading. In the future this may affect integrations using the `lifterlms_new_pending_order` action hook which will receive an "empty" student object during order setup by gateways utilizing new AJAX-powered checkout endpoints.
-+ Added a filter, `llms_gateway_{$this->id}_logging_enabled`, which will allow force enabling/disabling of gateway logging functions.
-+ Improved payment gateway secure string logging by adding a method, `add_secure_string()` allowing developers to add secure strings during runtime without the necessity of registering the strings using filters.
-+ Introduces new function `llms_is_option_secure()` for determining if an "secured" option is defined in a "secure" manner.
-+ Implemented new gateway feature: `modify_recurring_payments`. [#2176](https://github.com/gocodebox/lifterlms/issues/2176)
-+ Added two new parameters to LLMS_Access_Plan::get_redirection_url() - `$encode` to optionally get a raw (not encoded) URL. - `$querystring_only` to optionally get only the redirect URL if set via NPUT_GET variable.
-+ Added new parameter `$querystring_only` to the filter hook `llms_plan_get_checkout_redirection`.
-+ Admin settings fields now display `after_html` for additional field types which support `desc`.
-+ The CSS for `.llms-spinning` and `.llms-spinner` elements is no longer loaded as part of the `lifterlms.css` and `admin.css` files, instead it is loaded dynamically when `window.LLMS.Spinner` functions are called. In some cases CSS overrides to these elements which relied on CSS rule load order may no longer successfully override the default CSS rules. These overrides may need to be updated to have more specific selectors in order to ensure the overrides are retained.
-+ The Javascript object, `window.LLMS.Spinner`, has been converted to a module accessible from the same variable.
-+ The `window.LLMS.Spinner` methods now accept JS Elements and selector strings parseable by `document.querySelector()` in addition to jQuery selections.
-+ Added new filter `llms_transaction_can_be_refunded` enabling custom refund restrictions to be applied to a transaction.
-
-##### Updated Templates
-
-+ [templates/checkout/form-gateways.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-rc.1/templates/checkout/form-gateways.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-rc.1/templates/checkout/form-switch-source.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-rc.1/templates/myaccount/view-order-actions.php)
-
-
-v6.10.2 - 2022-09-14
---------------------
-
-##### Updates and Enhancements
-
-+ Updated `woocommerce/action-scheduler` to version [3.5.1](https://github.com/woocommerce/action-scheduler/releases/tag/3.5.1).
-
-##### Security Fixes
-
-+ Fixed a data sanitization issue related to achievement permalinks.
-
-
-v6.10.1 - 2022-09-07
---------------------
-
-##### Bug Fixes
-
-+ Fixed a PHP warning raised when logging errors during email notification dispatch. [#2250](https://github.com/gocodebox/lifterlms/issues/2250)
-+ Fixed issue preventing one-time orders for being included in membership revenue reporting widgets. [#2254](https://github.com/gocodebox/lifterlms/issues/2254)
-
-
-v7.0.0-beta.1 - 2022-08-29
---------------------------
-
-##### New Features
-
-+ Added handling for admin settings options that store their option values in a nested array.
-+ Added new AJAX checkout and payment source switching endpoints for payment gateways to utilize instead of the preexisting synchronous form submission methods.
-+ On purchase completed retrieve the redirection URL from the INPUT_POST 'redirect' variable, if no 'redirect' variable is passed via INPUT_GET. The INPUT_POST 'redirect' variable comes from the new checkout form's hidden field 'redirect' populated with LLMS_Access_Plan::get_redirection_url(). [#2229](https://github.com/gocodebox/lifterlms/issues/2229)
-
-##### Updates and Enhancements
-
-+ When an order post is restored from the trash its post status will now be "llms-pending" in favor of the default "draft" status.
-
-##### Bug Fixes
-
-+ Don't attempt to lookup the default payment gateway from user meta data.
-+ Fixed an issue that prevented disabling the access plan’s option, Override Membership Redirects, once enabled. [#2234](https://github.com/gocodebox/lifterlms/issues/2234)
-+ Disabled `scroll-behavior: smooth` on checkout screen to address form element validity checking issues on Chromium-based browsers. [#2206](https://github.com/gocodebox/lifterlms/issues/2206)
-
-##### Deprecations
-
-+ Deprecated `LLMS_Controller_Orders::switch_payment_source()` in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Deprecated the `lifterlms_update_option_{$type}` action in favor of the `llms_update_option_{$type}` filter.
-+ Method `LLMS_Controller_Orders::confirm_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::confirm_pending_order()`.
-+ Method `LLMS_Controller_Orders::create_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::create_pending_order()`.
-+ Method `LLMS_Controller_Orders::switch_payment_source()` is deprecated in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Passing jQuery selections into the `window.LLMS.Spinner` functions is deprecated. Use JS `Elements` or selection strings parseable by `document.querySelector()` instead.
-+ Deprecated hook `llms_{$method}_title` in favor of `llms_{$method}_refund_title`.
-
-##### Developer Notes
-
-+ Added admin settings helper function, `llms_get_dashicon_link()`, intended to enable the addition of external resource helper links to settings field descriptions.
-+ The `LLMS_Student` object can be instantiated as an empty object and bypass current user autoloading. In the future this may affect integrations using the `lifterlms_new_pending_order` action hook which will receive an "empty" student object during order setup by gateways utilizing new AJAX-powered checkout endpoints.
-+ Added a filter, `llms_gateway_{$this->id}_logging_enabled`, which will allow force enabling/disabling of gateway logging functions.
-+ Improved payment gateway secure string logging by adding a method, `add_secure_string()` allowing developers to add secure strings during runtime without the necessity of registering the strings using filters.
-+ Introduces new function `llms_is_option_secure()` for determining if an "secured" option is defined in a "secure" manner.
-+ Implemented new gateway feature: `modify_recurring_payments`. [#2176](https://github.com/gocodebox/lifterlms/issues/2176)
-+ Added two new parameters to LLMS_Access_Plan::get_redirection_url() - `$encode` to optionally get a raw (not encoded) URL. - `$querystring_only` to optionally get only the redirect URL if set via NPUT_GET variable.
-+ Added new parameter `$querystring_only` to the filter hook `llms_plan_get_checkout_redirection`.
-+ Admin settings fields now display `after_html` for additional field types which support `desc`.
-+ The CSS for `.llms-spinning` and `.llms-spinner` elements is no longer loaded as part of the `lifterlms.css` and `admin.css` files, instead it is loaded dynamically when `window.LLMS.Spinner` functions are called. In some cases CSS overrides to these elements which relied on CSS rule load order may no longer successfully override the default CSS rules. These overrides may need to be updated to have more specific selectors in order to ensure the overrides are retained.
-+ The Javascript object, `window.LLMS.Spinner`, has been converted to a module accessible from the same variable.
-+ The `window.LLMS.Spinner` methods now accept JS Elements and selector strings parseable by `document.querySelector()` in addition to jQuery selections.
-+ Added new filter `llms_transaction_can_be_refunded` enabling custom refund restrictions to be applied to a transaction.
-
-##### Updated Templates
-
-+ [templates/checkout/form-gateways.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-beta.1/templates/checkout/form-gateways.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-beta.1/templates/checkout/form-switch-source.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-beta.1/templates/myaccount/view-order-actions.php)
-
-
-v6.10.0 - 2022-08-29
---------------------
-
-##### Updates and Enhancements
-
-+ Updtaed woocommerce/action-scheduler to version [3.5.0](https://github.com/woocommerce/action-scheduler/releases/tag/3.5.0).
-+ Upgrades the bundled `quill-wordcount` module to version 2.0, addressing an issue encountered when counting words with non-Latin character languages.
-
-##### Bug Fixes
-
-+ Make `
` elements in quiz attempt results scrollable.
-+ Make sure the current user can edit the lesson, when changing its completion status from the admin reporting.
-+ Added missing textodmain for the string 'Move {post_title} to the Trash'. [#2224](https://github.com/gocodebox/lifterlms/issues/2224)
-+ Fixed PHP fatal error when quick editing an award. [#2231](https://github.com/gocodebox/lifterlms/issues/2231)
-+ Updated Spain's provinces list. [#2243](https://github.com/gocodebox/lifterlms/issues/2243)
-
-##### Deprecations
-
-+ The files `assets/vendor/quill/quill.module.wordcount.js` and `assets/vendor/quill/quill.module.wordcount.min.js` are to be removed in the next major release. Instead of loading these files directly, use `wp_enqueue_script( 'llms-quill-wordcount' )`.
-
-
-v6.9.0 - 2022-07-28
--------------------
-
-##### Updates and Enhancements
-
-+ Removed site-wide font-weight styles targeting `` through `` elements. [#2217](https://github.com/gocodebox/lifterlms/issues/2217)
-
-##### Bug Fixes
-
-+ Fixed issue preventing decimals from being used for coupon discount amounts. [#2149](https://github.com/gocodebox/lifterlms/issues/2149)
-+ Added AR (Arezzo) to Italy's states list. [#2214](https://github.com/gocodebox/lifterlms/issues/2214)
-
-
-v7.0.0-alpha.4 - 2022-07-18
----------------------------
-
-+ Fixed error causing recurring payment reschedules to fail with a fatal error.
-
-
-v7.0.0-alpha.3 - 2022-07-16
----------------------------
-
-+ Add max-length sanitization to admin settings which specify a max length.
-+ Fixed invalid user links on admin order screens when viewing incomplete orders missing a registered user.
-+ Added new function `llms_is_secure()`.
-+ Added `lifterlms-` and `llms-` as automatically stripped prefixed when using `llms_strip_prefixes()`.
-+ Added new temporary metadata, `temp_gateway_ids` to orders for use by gateways when switching payment methods.
-+ Moved `LLMS.Spinner` Javascript into an `@lifterlms/components` module and removed its reliance on jQuery.
-+ Disabled `scroll-behavior: scroll` on checkout screens to address a validity reporting issue on Chromium-based browsers.
-
-
-v6.8.0 - 2022-07-12
--------------------
-
-##### Bug Fixes
-
-+ Fixed Hello Theme's word-break and spacing for quiz answer options. [#2132](https://github.com/gocodebox/lifterlms/issues/2132)
-+ Fixed text/label alignment in Twenty-Twenty-Two theme.
-+ Fixed regression introduced in version 6.3.0 which prevented the Courses nav item from being customized in the BuddyPress profile nav menu. [#2142](https://github.com/gocodebox/lifterlms/issues/2142)
-
-##### Developer Notes
-
-+ Added new filter `llms_product_get_restrictions` hook to filter the list of restrictions placed on a given product. [#2201](https://github.com/gocodebox/lifterlms/issues/2201)
-
-
-v7.0.0-alpha.2 - 2022-06-23
----------------------------
-
-##### New Features
-
-+ Added handling for admin settings options that store their option values in a nested array.
-+ Added new AJAX checkout and payment source switching endpoints for payment gateways to utilize instead of the preexisting synchronous form submission methods.
-
-##### Bug Fixes
-
-+ Don't attempt to lookup the default payment gateway from user meta data.
-+ Fixes Hello Theme's word-break and spacing for quiz answer options. Also fixes text/label alignment in Twenty-Twenty-Two Theme. [#2132](https://github.com/gocodebox/lifterlms/issues/2132)
-
-##### Deprecations
-
-+ Deprecated `LLMS_Controller_Orders::switch_payment_source()` in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Deprecated the `lifterlms_update_option_{$type}` action in favor of the `llms_update_option_{$type}` filter.
-+ Method `LLMS_Controller_Orders::confirm_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::confirm_pending_order()`.
-+ Method `LLMS_Controller_Orders::create_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::create_pending_order()`.
-+ Method `LLMS_Controller_Orders::switch_payment_source()` is deprecated in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Deprecated hook `llms_{$method}_title` in favor of `llms_{$method}_refund_title`.
-
-##### Developer Notes
-
-+ Added admin settings helper function, `llms_get_dashicon_link()`, intended to enable the addition of external resource helper links to settings field descriptions.
-+ The `LLMS_Student` object can be instantiated as an empty object and bypass current user autoloading. In the future this may affect integrations using the `lifterlms_new_pending_order` action hook which will receive an "empty" student object during order setup by gateways utilizing new AJAX-powered checkout endpoints.
-+ Added a filter, `llms_gateway_{$this->id}_logging_enabled`, which will allow force enabling/disabling of gateway logging functions.
-+ Improved payment gateway secure string logging by adding a method, `add_secure_string()` allowing developers to add secure strings during runtime without the necessity of registering the strings using filters.
-+ Implemented new gateway feature: `modify_recurring_payments`. [#2176](https://github.com/gocodebox/lifterlms/issues/2176)
-+ Admin settings fields now display `after_html` for additional field types which support `desc`.
-+ Added new filter `llms_transaction_can_be_refunded` enabling custom refund restrictions to be applied to a transaction.
-
-##### Updated Templates
-
-+ [templates/checkout/form-gateways.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-alpha.2/templates/checkout/form-gateways.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-alpha.2/templates/checkout/form-switch-source.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-alpha.2/templates/myaccount/view-order-actions.php)
-
-
-v7.0.0-alpha.1 - 2022-06-15
----------------------------
-
-##### New Features
-
-+ Added new AJAX checkout and payment source switching endpoints for payment gateways to utilize instead of the preexisting synchronous form submission methods.
-
-##### Bug Fixes
-
-+ Don't attempt to lookup the default payment gateway from user meta data.
-
-##### Deprecations
-
-+ Deprecated `LLMS_Controller_Orders::switch_payment_source()` in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Method `LLMS_Controller_Orders::confirm_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::confirm_pending_order()`.
-+ Method `LLMS_Controller_Orders::create_pending_order()` is deprecated in favor of `LLMS_Controller_Checkout::create_pending_order()`.
-+ Method `LLMS_Controller_Orders::switch_payment_source()` is deprecated in favor of `LLMS_Controller_Checkout::switch_payment_source()`.
-+ Deprecated hook `llms_{$method}_title` in favor of `llms_{$method}_refund_title`.
-
-##### Developer Notes
-
-+ The `LLMS_Student` object can be instantiated as an empty object and bypass current user autoloading. In the future this may affect integrations using the `lifterlms_new_pending_order` action hook which will receive an "empty" student object during order setup by gateways utilizing new AJAX-powered checkout endpoints.
-+ Improved payment gateway secure string logging by adding a method, `add_secure_string()` allowing developers to add secure strings during runtime without the necessity of registering the strings using filters.
-+ Implemented new gateway feature: `modify_recurring_payments`. [#2176](https://github.com/gocodebox/lifterlms/issues/2176)
-+ Added new filter `llms_transaction_can_be_refunded` enabling custom refund restrictions to be applied to a transaction.
-
-##### Updated Templates
-
-+ [templates/checkout/form-gateways.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-alpha.1/templates/checkout/form-gateways.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-alpha.1/templates/checkout/form-switch-source.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/7.0.0-alpha.1/templates/myaccount/view-order-actions.php)
-
-
-v6.7.0 - 2022-06-09
--------------------
-
-##### Updates and Enhancements
-
-+ Update LifterLMS Blocks to [v2.4.3](https://make.lifterlms.com/2022/06/09/lifterlms-blocks-version-2-4-3/).
-+ Upgraded Action Scheduler to [v3.4.1](https://github.com/woocommerce/action-scheduler/releases/tag/3.4.1).
-+ Upgraded Action Scheduler to [v3.4.2](https://github.com/woocommerce/action-scheduler/releases/tag/3.4.2).
-
-##### Bug Fixes
-
-+ Fixed a fatal error on PHP 8+ when restoring a post type from revision. [#2164](https://github.com/gocodebox/lifterlms/issues/2164)
-
-
-v6.6.0 - 2022-05-23
--------------------
-
-##### PHP Minimum Required Version Change
-
-+ **Raised the minimum supported PHP version to 7.4.**
-
-##### WordPress Minimum Required Version Change
-
-+ **Raised the minimum supported WordPress core version to 5.6.**
-
-##### New Features
-
-+ Added support for WordPress 6.0.
-
-##### Bug Fixes
-
-+ Fixed the ability for 3rd party plugins to use the `lifterlms_external_engagement_handler_arguments` and `lifterlms_external_engagement_query_arguments` filters.
-+ Added automatic exclusion of "no cache" pages from the WP Engine server-side cache when using "pretty" permalinks. [#1717](https://github.com/gocodebox/lifterlms/issues/1717)
-+ Stop subtracting LifterLMS order note comments from global comment counts via the `wp_count_comments` filter on WordPress 6.0 and later. See related WordPress Trac ticket [#19901](https://core.trac.wordpress.org/ticket/19901)
-
-
-v6.5.0 - 2022-05-11
--------------------
-
-##### Upcoming PHP Version Requirement Change
-
-**This will be the last version of LifterLMS to support PHP 7.3. The next version of LifterLMS, expected before the end of May 2022, will raise the minimum supported PHP version to 7.4. PHP 7.3 reached its official [end of life](https://www.php.net/eol.php) on December 6, 2021. If you are still using PHP 7.3 please upgrade to PHP 7.4 or later as soon as possible.**
-
-##### Updates and Enhancements
-
-+ Updates LifterLMS Rest to [v1.0.0-beta.25](https://make.lifterlms.com/2022/05/11/lifterlms-rest-api-version-1-0-0-beta-25/).
-
-##### Bug Fixes
-
-+ Students who have already completed a lesson will now automatically bypass the lesson's drip restrictions. [#1835](https://github.com/gocodebox/lifterlms/issues/1835)
-+ Properly encode certificate JS localization data. [#2140](https://github.com/gocodebox/lifterlms/issues/2140)
-
-##### Developer Notes
-
-+ Added a new filter, `llms_lesson_drip_bypass_if_completed`, which controls the automatic bypass of drip restrictions for completed lessons. [#1835](https://github.com/gocodebox/lifterlms/issues/1835)
-+ Allow avoiding error return when updating an `LLMS_Post_Model` post meta with the same value as the one stored in the database. [#909](https://github.com/gocodebox/lifterlms/issues/909)
-
-
-v6.4.0 - 2022-04-19
--------------------
-
-##### Upcoming PHP Version Requirement Change
-
-**LifterLMS will drop support for PHP 7.3 by May, 2022. This will raise the minimum supported PHP version to 7.4. PHP 7.3 reached its official [end of life](https://www.php.net/eol.php) on December 6, 2021. If you are still using PHP 7.3 please upgrade to PHP 7.4 or later as soon as possible.**
-
-##### New Features
-
-+ Any "secure" payment gateway options will be automatically masked when written to debug log files.
-
-##### Updates and Enhancements
-
-+ When building notification content, only parse merge codes used in the notification. [#1465](https://github.com/gocodebox/lifterlms/issues/1465)
-+ Improved checks related to the number of quiz attempts allowed for each student.
-+ Prevent browser page caching on quizzes. [#2092](https://github.com/gocodebox/lifterlms/issues/2092)
-
-##### Bug Fixes
-
-+ Allowed classes extended from the manual payment gateway class to display payment instructions.
-+ Allowed the `LLMS_Shortcode_User_Info` class to be filtered by the `llms_load_shortcodes` and `llms_load_shortcode_path` hooks.
-+ Stop using the deprecated `FILTER_SANITIZE_STRING` constant.
-+ Fixed an issue that caused shortcodes to not be replaced in some engagement emails. [#2070](https://github.com/gocodebox/lifterlms/issues/2070)
-+ Improve core forms detection so to exclude duplicates. [#2052](https://github.com/gocodebox/lifterlms/issues/2052)
-+ Added Aosta (AO) to the list of Italian provinces. [#2098](https://github.com/gocodebox/lifterlms/issues/2098)
-+ Fixed a compatibility issue with the Elementor Pro Theme Builder encountered on course and membership catalogs. [#2111](https://github.com/gocodebox/lifterlms/issues/2111)
-+ Fixed an issue where merge codes in reusable blocks on certificate templates were not replaced when the template was displayed or when the certificate was awarded and published. [#2058](https://github.com/gocodebox/lifterlms/issues/2058)
-+ Fixed an issue with OceanWP and Twenty Twenty themes where the Terms and Conditions checkbox was displayed incorrectly. [#1938](https://github.com/gocodebox/lifterlms/issues/1938)
-
-##### Developer Notes
-
-+ Added a new filter, `llms_secure_strings` allowing developers to register strings that should be automatically masked when written to log files.
-+ Added new filter `llms_no_cache` to control whether or not LifterLMS will send nocache headers. [#2092](https://github.com/gocodebox/lifterlms/issues/2092)
-+ Added new filter `llms_template_loader_restricted_priority` to control the priority of the `template_include` hook callback used to load restricted content single templates.
-
-
-v6.3.0 - 2022-04-07
--------------------
-
-##### Upcoming PHP Version Requirement Change
-
-**LifterLMS will drop support for PHP 7.3 by May, 2022. This will raise the minimum supported PHP version to 7.4. PHP 7.3 reached its official [end of life](https://www.php.net/eol.php) on December 6, 2021. If you are still using PHP 7.3 please upgrade to PHP 7.4 or later as soon as possible.**
-
-##### New Features
-
-+ Automatically add student's dashboard endpoints to the BuddyPress profile nav. [#627](https://github.com/gocodebox/lifterlms/issues/627)
-
-##### Updates and Enhancements
-
-+ Upgraded LifterLMS Blocks to [v2.4.2](https://make.lifterlms.com/2022/04/07/lifterlms-blocks-version-2-4-2/).
-+ Updated LifterLMS Helper to [v3.4.2](https://make.lifterlms.com/2022/04/01/lifterlms-helper-version-3-4-2/).
-
-##### Bug Fixes
-
-+ Fixed paged queries in student dashboard not working when using plain permalinks.
-+ Fixed an issue that prevented searching students in some admin areas when WordPress was installed in a subdirectory. [#2096](https://github.com/gocodebox/lifterlms/issues/2096)
-+ Fixed lesson's comments status not reflecting default global setting when created with the course builder. [#2099](https://github.com/gocodebox/lifterlms/issues/2099)
-
-##### Deprecations
-
-+ Deprecated `LLMS_Integration_Buddypress::achievements_screen()` method with no replacement.
-+ Deprecated `LLMS_Integration_Buddypress::certificates_screen()` method with no replacement.
-+ Deprecated `LLMS_Integration_Buddypress::courses_screen()` method with no replacement.
-+ Deprecated `LLMS_Integration_Buddypress::memberships_screen()` method with no replacement.
-+ Deprecated `LLMS_Integration_Buddypress::remove_courses_paginate_links_filter()` method with no replacement.
-+ Deprecated `LLMS_Integration_Buddypress::modify_courses_paginate_links()` method with no replacement.
-
-##### Developer Notes
-
-+ Added `llms_get_paged_query_var()` function that returns the page number query var for the current request.
-+ Added new filter `llms_buddypress_profile_endpoints` to control the LifterLMS endpoints to be added to the BuddyPress profile.
-+ Added new filter `llms_buddypress_min_nav_item_slug` to control the LifterLMS main BuddyPress' nav item slug.
-+ Added new filter `llms_buddypress_min_nav_item_label` to control the LifterLMS main BuddyPress' nav item label.
-+ Added new filter `llms_buddypress_min_nav_item_position` to control the LifterLMS main BuddyPress' nav item position.
-
-
-v6.2.0 - 2022-03-30
--------------------
-
-##### Updates and Enhancements
-
-+ Changed the `llmsStudentsSelect2()` JavaScript function to use the LifterLMS REST API "list students" endpoint instead of the `LLMS_AJAX_Handler::query_students()` PHP function.
-+ Upgraded LifterLMS Blocks to [v2.4.1](https://make.lifterlms.com/2022/03/30/lifterlms-blocks-version-2-4-1/).
-
-##### Bug Fixes
-
-+ Fixed issue with hidden checkboxes on LifterLMS forms.
-+ Fixed a compatiblity issue with the Divi Theme Builder ignoring access restrictions when using template with custom body. [#2063](https://github.com/gocodebox/lifterlms/issues/2063)
-+ Fixed an error encountered on the Engagements > Certificates screen when using the BuddyBoss theme. [#2080](https://github.com/gocodebox/lifterlms/issues/2080)
-
-##### Deprecations
-
-+ Deprecated `LLMS_AJAX_Handler::query_students()`. Use the [REST API list students](https://developer.lifterlms.com/rest-api/#tag/Students/paths/~1students/get) endpoint instead.
-
-##### Developer Notes
-
-+ Added new filter `llms_template_loader_priority` to control the priority of the `template_include` hook callback used to load restricted content templates.
-
-
-v6.1.0 - 2022-03-23
--------------------
-
-##### Upcoming PHP Version Requirement Change
-
-**LifterLMS will drop support for PHP 7.3 by May, 2022. This will raise the minimum supported PHP version to 7.4. PHP 7.3 reached its official [end of life](https://www.php.net/eol.php) on December 6, 2021. If you are still using PHP 7.3 please upgrade to PHP 7.4 or later as soon as possible.**
-
-##### New Features
-
-+ Added the `{earned_date}` certificate merge code.
-
-##### Updates and Enhancements
-
-+ Changed the label for the `{current_date}` certificate merge code from 'Earned Date' to 'Current Date'.
-+ Updates LifterLMS REST to [v1.0.0-beta.24](https://make.lifterlms.com/2022/03/17/lifterlms-rest-api-version-1-0-0-beta-24/).
-
-##### Bug Fixes
-
-+ Fixed an issue encountered when editing an order with a completed payment plan. [#2067](https://github.com/gocodebox/lifterlms/issues/2067)
-+ Fixed access of protected LLMS_Abstract_Query properties.
-
-
-v6.0.0 - 2022-03-08
--------------------
-
-**This major release of LifterLMS focuses on improving the experience of creating, designing, and managing achievements and certificates: use the block editor to design certificates, sync awards with their templates, award achievements and certificates on demand without requiring an engagement trigger, and [much more](https://lifterlms.com/docs/getting-started-with-lifterlms-6-0/). In addition, this release removes a significant number of previously deprecated classes, methods, and functions. Please read the full Breaking Changes sections for more information on removed code.**
-
-##### New Features
-
-+ The block editor is now enabled by default for certificates when using WordPress versions 5.8 and later.
- + Existing certificates are marked as "legacy" and will continue to use the classic editor until migrated.
- + To migrate a certificate, click the "Migrate Certificate" button. This will force the certificate's content into blocks.
-+ A number of new settings are available to certificates when using the block editor:
- + Set the certificate's display (and print) size using common paper sizes such as US Letter, US Legal, A3, A4, and more.
- + Set the certificate's display orientation: portrait of landscape.
- + Set the certificate's inner margins.
- + Set the certificate's background color.
-+ A new block, the Certificate Title Block, has been made available to certificates.
- + The block works like a WordPress core Heading Block with added options for selecting from a few display fonts (provided by Google Web Fonts).
- + The block controls the title of awarded certificates.
-+ Added the ability for administrators and LMS managers to edit earned certificates/achievements from the students reporting screen, as well as award new certificates/achievements to students.
-+ Added the ability to sync awarded certificates with the template used to generate them. [#1078](https://github.com/gocodebox/lifterlms/issues/1078)
-+ The `post_name` of earned certificate posts will be generated with a randomized 3+ character string in favor of relying on sequential numbers.
-+ Added certificate global options for the default size of new certificates and certificate templates.
-+ Certificate and email template merge code buttons now include [llms-user] information shortcodes.
-+ Added certificate sequential ID functionality merge code.
-+ Added a link to return to the student dashboard when viewing an awarded certificate. [#1959](https://github.com/gocodebox/lifterlms/issues/1959)
-+ Provide additional information to hooks on the student single course reporting screen.
-
-##### Updates and Enhancements
-
-+ Added new default images for use with achievements and certificates.
- + The site-wide default images can be customized on the admin panel under Settings -> Engagements.
- + The old default images will automatically be used for legacy certificates and can be forced by filtering `llms_use_legacy_engagement_images`. [#1081](https://github.com/gocodebox/lifterlms/issues/1081)
-+ Certificates no longer use the `header.php` and `footer.php` files from the site's theme, instead custom templates (`templates/certificates/header.php` and `templates/certificates/footer.php`) are used instead. These templates are minimal and exclude theme wrappers which reduces the visual conflicts encountered from theme wrappers, backgrounds, and more, especially when printing certificates. [#463](https://github.com/gocodebox/lifterlms/issues/463)
-+ The achievements and certificates dashboard endpoints are now paginated. [#669](https://github.com/gocodebox/lifterlms/issues/669)
-+ Added pagination to achievement and certificate reporting pages.
-+ The URL of earned user certificates has been changed from "my_certificate" to "certificate". Requests to the old url are automatically redirected to the new url, including instances where the URL slug has been translated.
-+ The URL of certificate template previews has been changed from "certificate" to "certificate-template".
-+ The certificate merge code, `{first_name}`, now outputs an empty string in favor of falling back to the user's nickname when there is no first name for the user. [#1640](https://github.com/gocodebox/lifterlms/issues/1640)
-+ The look and behavior of the certificate {{MINI_CERTIFICATE}} pop-over notification merge code now displays a placeholder preview of the certificate in favor of attempting to render a tiny version of the actual certificate. [#1950](https://github.com/gocodebox/lifterlms/issues/1950)
-+ The coupon code in the student's order details table is now wrapped in a `` tag instead of an `` tag. [#2033](https://github.com/gocodebox/lifterlms/issues/2033)
-+ Updates LifterLMS REST to [v1.0.0-beta.23](https://make.lifterlms.com/2022/02/23/lifterlms-rest-api-version-1-0-0-beta-23/).
-+ Updated LifterLMS Blocks to [version 2.4.0](https://make.lifterlms.com/2022/02/25/lifterlms-blocks-version-2-4-0/).
-
-##### Bug Fixes
-
-+ Delayed engagements are automatically unscheduled when the related post is deleted.
-+ A disabled student dashboard endpoint will no longer display the endpoint's summary on the main dashboard page. [#535](https://github.com/gocodebox/lifterlms/issues/535)
-+ Prior to sending a delayed engagement the recipient's enrollment in the related post is verified resulting the engagement not being triggered if the recipient's enrollment has been terminated. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ Post search filter boxes on various post tables will now longer display a link to the selected post.
-+ Basic notification code is no longer loaded on the admin panel.
-+ Fixed the label hover on picture type quizzes in some themes. [#2015](https://github.com/gocodebox/lifterlms/issues/2015)
-
-##### Database Migration
-
-+ A database migration is required when upgrading from versions earlier than 6.0.0. A description of the required updates can be found at [https://lifterlms.com/docs/lifterlms-database-updates/#600](https://lifterlms.com/docs/lifterlms-database-updates/#600).
-
-##### Deprecations
-
-+ Public access to properties of the abstract `LLMS_Database_Query` has been deprecated.
- + Public access to class property `LLMS_Database_Query::$found_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_found_results()`.
- + Public access to class property `LLMS_Database_Query::$max_pages`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_max_pages()`.
- + Public access to class property `LLMS_Database_Query::$number_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_number_results()`.
- + Public access to class property `LLMS_Database_Query::$results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_results()`.
- + Public access to class property `LLMS_Database_Query::$query_vars`. The variable as a whole cannot be publicly accessed, instead use `LLMS_Database_Query::get()` and `LLMS_Database_Query::set()` to read and write to the array.
- + The above changes were made to the abstract class `LLMS_Database_Query` but the following concrete classes that utilize the abstract are also affected by this change: `LLMS_Query_User_Postmeta`, `LLMS_Student_Query`, `LLMS_Query_Quiz_Attempt`, `LLMS_Events_Query`, and `LLMS_Notifications_Query`.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Achievement::format_string()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_title()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Achievement::create()` is deprecated with no replacement.
-+ Method `LLMS_Achievements::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement()`.
-+ Class `LLMS_Certificate` is deprecated with no direct replacement.
- + Method `LLMS_Certificate::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Certificate::format_string()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
-+ Method `LLMS_Certificates::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate()`.
-+ Method `LLMS_Engagements::init()` is deprecated with no replacement.
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Method `LLMS_Database_Query::set_found_results()` is deprecated.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement_User::has_user_earned()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::init()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::get_content_html()` is deprecated with no replacement.
-+ Class `LLMS_Certificate_User` is deprecated with no direct replacement.
- + Method `LLMS_Certificate_User::init()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::set_shortcode_user()` is deprecated with no replacement.
-+ Engagement debug logging is removed. Use `llms_log()` directly instead.
-+ Filter `llms_db_query_get_default_args` is deprecated in favor of `llms_{$this->id}_query_get_default_args`.
-+ Filter `llms_certificate_has_user_earned` is deprecated in favor of `llms_earned_certificate_dupcheck`.
-+ Unused public class property `LLMS_Achievements::$content` is deprecated with no replacement.
-+ Method `LLMS_Admin_Post_Types::meta_metabox_init()` is deprecated with no replacement.
-+ The site options `lifterlms_certificate_bg_img_width`, `lifterlms_certificate_bg_img_height`, and `lifterlms_certificate_legacy_image_size` are now used only for certificates and certificate templates created using the classic editor.
- + The settings, found on the Engagements Settings screen, are hidden by default.
- + During the database upgrade from versions earlier than 6.x, an site option, `llms_has_legacy_certificates` is added when at least one certificate is found. This option will display the settings so they can continue to be used for legacy certificates.
- + After migrating all certificates on a site, the settings will still display. In order to remove them from the screen a developer can either delete the option `llms_has_legacy_certificates` or return `false` from the filter `llms_has_legacy_certificates`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ The constant `LLMS_ENGAGEMENT_DEBUG` is deprecated with no replacement.
-+ Engagement debugging via `LLMS_Engagements::log` is deprecated. Use `llms_log()` instead.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Filter `lifterlms_register_post_type_llms_my_certificate` is deprecated in favor of `lifterlms_register_post_type_my_certificate`.
-+ Deprecated the misspelled protected method `LLMS_Database_Query::preprare_query()` and replaced with `LLMS_Database_Query::prepare_query()`.
- + Class method `LLMS_Events_Query::preprare_query` replaced with `LLMS_Events_Query::prepare_query()`.
- + Class method `LLMS_Query_Quiz_Attempt::preprare_query` replaced with `LLMS_Query_Quiz_Attempt::prepare_query()`.
- + Class method `LLMS_Query_User_Postmeta::preprare_query` replaced with `LLMS_Query_User_Postmeta::prepare_query()`.
- + Class method `LLMS_Student_Query::preprare_query` replaced with `LLMS_Student_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`. [#859](https://github.com/gocodebox/lifterlms/issues/859)
-
-##### Breaking Changes
-
-+ Removed FSE template: `templates/block-templates/single-certificate.html`.
-+ Removed the deprecated `LLMS()` function in favor of the `llms()` function.
-+ Removed the deprecated the `LLMS_SendWP::do_remote_install()` method in favor of the `LLMS_Abstract_Email_Provider::do_remote_install()` method.
-+ Removed the deprecated `LLMS_Abstract_Email_Provider::output_css()` method.
-+ Removed the deprecated `LLMS_Abstract_Generator_Posts::increment()` method.
-+ Removed the deprecated `LLMS_Admin_Users_Table::load_dependencies()` method.
-+ Removed the deprecated `LLMS_Admin_Import::localize_stat()` method.
-+ Removed the deprecated `LLMS_Admin_Notices_Core::check_staging()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::generator_course_status()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::output_step_html()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::scripts()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::watch_course_generation()` method.
-+ Removed the deprecated `llms_format_decimal()` function.
-+ Removed the deprecated `llms_set_person_auth_cookie()` function.
-+ Removed the deprecated `LLMS_Course::sections` property.
-+ Removed the deprecated `LLMS_Course::sku` property.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_pw_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::is_inline_script_enqueued()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::format_date()` method.
-+ Removed the deprecated `LLMS_Generator::get_author_id_from_raw()` method.
-+ Removed the deprecated `LLMS_Generator::get_default_post_status()` method.
-+ Removed the deprecated `LLMS_Generator::get_generated_posts()` method.
-+ Removed the deprecated `LLMS_Generator::increment()` method.
-+ Removed the deprecated `llms__created` action hook from the `LLMS_Abstract_Database_Store::create()` method.
-+ Removed the deprecated `llms__deleted` action hook from the `LLMS_Abstract_Database_Store::delete()` method.
-+ Removed the deprecated `llms__updated` action hook from the `LLMS_Abstract_Database_Store::update()` method.
-+ Removed the deprecated `llms_user_removed_from_membership_level` action hook from the `LLMS_Student::unenroll()` method.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `lifterlms_template_pricing_table()` function.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `templates/product/pricing-table.php` file.
-+ Removed the deprecated `LLMS_Frontend_Password` class.
-+ Removed the deprecated `LLMS_Install::db_updates()` method.
-+ Removed the deprecated `LLMS_Install::update_notice()` method.
-+ Removed the deprecated `LLMS_Notifications::dispatch_processors()` method.
-+ Removed the deprecated `llms_processors_async_dispatching` filter hook from the `LLMS_Notifications::__construct()` method.
-+ Removed the deprecated `LLMS_Notifications::$_instance` property.
-+ Removed the deprecated `LLMS_Person_Handler::register()` method.
-+ Removed the deprecated `LLMS_Person_Handler::sanitize_field()` method.
-+ Removed the deprecated `LLMS_Person_Handler::update()` method.
-+ Removed the deprecated `LLMS_Person_Handler::validate_fields()` method.
-+ Removed the deprecated `LLMS_Person_Handler::voucher_toggle_script()` method.
-+ Removed the deprecated `templates/admin/notices/db-update.php` file.
-+ Removed the deprecated `templates/admin/notices/db-updating.php` file.
-+ Removed the deprecated `llms_usernames_blacklist` filter hook in the `llms_get_usernames_blocklist()` function.
-+ Removed the deprecated `includes/libraries/wp-background-processing/index.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-async-request.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-background-process.php` file.
-+ Removed the deprecated `LLMS_Section::get_next_available_lesson_order()` method.
-+ Removed the deprecated `LLMS_Section::get_order()` method.
-+ Removed the deprecated `LLMS_Section::get_parent_course()` method.
-+ Removed the deprecated `LLMS_Section::set_parent_course()` method.
-+ Removed the deprecated `LLMS_AJAX::check_voucher_duplicate()` method.
-+ Removed the deprecated `LLMS_AJAX::get_ajax_data()` method.
-+ Removed the deprecated `LLMS_AJAX::register_script()` method.
-+ Removed the deprecated `LLMS_Interface_Post_Audio` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Sales_Page` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Video` interface.
-+ Removed the deprecated `LLMS_Achievements::$_instance` property.
-+ Removed the deprecated `LLMS_Certificates::$_instance` property.
-+ Removed the deprecated `LLMS_Emails::$_instance` property.
-+ Removed the deprecated `LLMS_Engagements::$_instance` property.
-+ Removed the deprecated `LLMS_Events::$_instance` property.
-+ Removed the deprecated `LLMS_Grades::$_instance` property.
-+ Removed the deprecated `LLMS_Integrations::$_instance` property.
-+ Removed the deprecated `LLMS_Payment_Gateways::$_instance` property.
-+ Removed the deprecated `LLMS_Processors::$_instance` property.
-+ Removed the deprecated `LLMS_Sessions::$_instance` property.
-
-##### Developer Notes
-
-+ Added `LLMS_Awards_Query`, used for querying data about awarded certificates and achievements.
- + The method signature `LLMS_Student::get_achievements()` and `LLMS_Student::get_certificates()` now use this class under tho hood.
- + The previous method signature, which passed data into a direct SQL query, is now deprecated.
-+ Achievement and certificate data storage locations have been modified, primarily to reduce reliance on the `wp_postmeta` table which will result in a site-wide performance improvement, especially on large sites.
- + Meta properties `_llms_achievement_content` and `_llms_certificate_content` have been removed in favor of `WP_Post::$post_content`.
- + Meta properties `_llms_achievement_title` and `_llms_certificate_title` have been removed in favor of `WP_Post::$post_title`.
- + Meta properties `_llms_achievement_template` and `_llms_certificate_template` have been removed in favor of `WP_Post::$post_parent`.
- + Meta properties `_llms_achievement_image` and `_llms_certificate_image` have been moved the meta property `_thumbnail_id` in order to utilize the WordPress core's featured image functionality and internal APIs.
-+ Reliance on `lifterlms_user_postmeta` for achievement and certificate data will be removed in a future release.
- + User postmeta properties `_achievement_earned` and `_certificate_earned` will continue to be recorded but are no longer being used internally.
- + The `updated_date` is now accessible via `WP_Post::$post_date`.
- + The `user_id` is now accessible via `WP_Post::$post_author`.
-+ Added new Javascript UI components library, modeled after `@wordpress/components`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/components).
-+ Added a new SVG icon library, modeled after `@wordpress/icons`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/icons).
-+ The merge code button seen on certificate and email template editors is now an SVG image instead of a PNG.
-+ Added utility function for escaping and quoting strings. [#1027](https://github.com/gocodebox/lifterlms/issues/1027)
-+ Added the ability to force an admin metabox field value through the new `meta` arg. [#2016](https://github.com/gocodebox/lifterlms/issues/2016)
-+ Added new utility function for stripping prefixes from strings.
-
-##### Performance Improvements
-
-+ Increased the number of files that are autoloaded instead of manually loaded.
-
-##### Updated Templates
-
-+ [templates/achievements/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/achievements/loop.php)
-+ [templates/achievements/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/achievements/template.php)
-+ [templates/admin/notices/db-update.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/admin/notices/db-update.php)
-+ [templates/admin/notices/db-updating.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/admin/notices/db-updating.php)
-+ [templates/admin/reporting/reporting.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/admin/reporting/reporting.php)
-+ [templates/admin/reporting/tabs/students/courses-course.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/admin/reporting/tabs/students/courses-course.php)
-+ [templates/admin/reporting/tabs/students/information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/admin/reporting/tabs/students/information.php)
-+ [templates/block-templates/single-certificate.html](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/block-templates/single-certificate.html)
-+ [templates/certificates/actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/actions.php)
-+ [templates/certificates/content-legacy.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/content-legacy.php)
-+ [templates/certificates/content.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/content.php)
-+ [templates/certificates/dynamic-styles.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/dynamic-styles.php)
-+ [templates/certificates/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/footer.php)
-+ [templates/certificates/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/header.php)
-+ [templates/certificates/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/loop.php)
-+ [templates/certificates/preview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/preview.php)
-+ [templates/certificates/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/certificates/template.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/checkout/form-switch-source.php)
-+ [templates/content-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/content-certificate.php)
-+ [templates/emails/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/emails/footer.php)
-+ [templates/emails/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/emails/header.php)
-+ [templates/myaccount/my-grades-single-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/myaccount/my-grades-single-table.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/myaccount/view-order-actions.php)
-+ [templates/myaccount/view-order-information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/myaccount/view-order-information.php)
-+ [templates/myaccount/view-order-transactions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/myaccount/view-order-transactions.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/myaccount/view-order.php)
-+ [templates/product/pricing-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/product/pricing-table.php)
-+ [templates/single-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0/templates/single-certificate.php)
-
-
-v6.0.0-rc.1 - 2022-03-03
-------------------------
-
-##### New Features
-
-+ Added a link to return to the student dashboard when viewing an awarded certificate. [#1959](https://github.com/gocodebox/lifterlms/issues/1959)
-+ The block editor is now enabled by default for certificates when using WordPress versions 5.8 and later.
- + Existing certificates are marked as "legacy" and will continue to use the classic editor until migrated.
- + To migrate a certificate, click the "Migrate Certificate" button. This will force the certificate's content into blocks.
-+ A number of new settings are available to certificates when using the block editor:
- + Set the certificate's display (and print) size using common paper sizes such as US Letter, US Legal, A3, A4, and more.
- + Set the certificate's display orientation: portrait of landscape.
- + Set the certificate's inner margins.
- + Set the certificate's background color.
-+ A new block, the Certificate Title Block, has been made available to certificates.
- + The block works like a WordPress core Heading Block with added options for selecting from a few display fonts (provided by Google Web Fonts).
- + The block controls the title of awarded certificates.
-+ + Added the ability to sync awarded certificates with the template used to generate them. [#1078](https://github.com/gocodebox/lifterlms/issues/1078)
-+ The `post_name` of earned certificate posts will be generated with a randomized 3+ character string in favor of relying on sequential numbers.
-+ Added the ability for administrators and LMS managers to edit earned certificates/achievements from the students reporting screen, as well as award new certificates/achievements to students.
-+ + Added certificate global options for the default size of new certificates and certificate templates.
-+ Certificate and email template merge code buttons now include [llms-user] information shortcodes.
-+ Added certificate sequential ID functionality merge code. [Read more](@TODO).
-+ Provide additional information to hooks on the student single course reporting screen.
-
-##### Updates and Enhancements
-
-+ Added pagination to achievement and certificate reporting pages.
-+ Certificates no longer use the `header.php` and `footer.php` files from the site's theme, instead custom templates (`templates/certificates/header.php` and `templates/certificates/footer.php`) are used instead. These templates are minimal and exclude theme wrappers which reduces the visual conflicts encountered from theme wrappers, backgrounds, and more, especially when printing certificates. [#463](https://github.com/gocodebox/lifterlms/issues/463)
-+ The achievements and certificates dashboard endpoints are now paginated. [#669](https://github.com/gocodebox/lifterlms/issues/669)
-+ Added new default images for use with achievements and certificates.
- + The site-wide default images can be customized on the admin panel under Settings -> Engagements.
- + The old default images can be used by filtering `llms_use_legacy_engagement_images`. [#1081](https://github.com/gocodebox/lifterlms/issues/1081)
-+ The URL of earned user certificates has been changed from "my_certificate" to "certificate". Requests to the old url are automatically redirected to the new url, including instances where the URL slug has been translated.
-+ The URL of certificate template previews has been changed from "certificate" to "certificate-template".
-+ The certificate merge code, `{first_name}`, now outputs an empty string in favor of falling back to the user's nickname when there is no first name for the user. [#1640](https://github.com/gocodebox/lifterlms/issues/1640)
-+ Updates LifterLMS REST to [v1.0.0-beta.23](https://make.lifterlms.com/2022/02/23/lifterlms-rest-api-version-1-0-0-beta-23/).
-+ Updated LifterLMS Blocks to [version 2.4.0](https://make.lifterlms.com/2022/02/25/lifterlms-blocks-version-2-4-0/).
-+ The look and behavior of the certificate {{MINI_CERTIFICATE}} pop-over notification merge code now displays a placeholder preview of the certificate in favor of attempting to render a tiny version of the actual certificate. [#1950](https://github.com/gocodebox/lifterlms/issues/1950)
-
-##### Bug Fixes
-
-+ + Fixed how the protected `LLMS_Notifications_Query::$found_results` property is accessed in `LLMS_Abstract_Notification_Controller::has_subscriber_received()`. + Fixed how the protected `LLMS_Notifications_Query::$max_pages` property is accessed in `lifterlms_template_student_dashboard_my_notifications()`.
-+ Delayed engagements are automatically unscheduled when the related post is deleted.
-+ Prior to sending a delayed engagement the recipient's enrollment in the related post is verified resulting the engagement not being triggered if the recipient's enrollment has been terminated. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ A disabled student dashboard endpoint will no longer display the endpoint's summary on the main dashboard page. [#535](https://github.com/gocodebox/lifterlms/issues/535)
-+ Post search filter boxes on various post tables will now longer display a link to the selected post.
-+ Basic notification code is no longer loaded on the admin panel.
-
-##### Deprecations
-
-+ Public access to properties of the abstract `LLMS_Database_Query` has been deprecated.
- + Public access to class property `LLMS_Database_Query::$found_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_found_results()`.
- + Public access to class property `LLMS_Database_Query::$max_pages`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_max_pages()`.
- + Public access to class property `LLMS_Database_Query::$number_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_number_results()`.
- + Public access to class property `LLMS_Database_Query::$results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_results()`.
- + Public access to class property `LLMS_Database_Query::$query_vars`. The variable as a whole cannot be publicly accessed, instead use `LLMS_Database_Query::get()` and `LLMS_Database_Query::set()` to read and write to the array.
- + The above changes were made to the abstract class `LLMS_Database_Query` but the following concrete classes that utilize the abstract are also affected by this change: `LLMS_Query_User_Postmeta`, `LLMS_Student_Query`, `LLMS_Query_Quiz_Attempt`, `LLMS_Events_Query`, and `LLMS_Notifications_Query`.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Achievement::format_string()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_title()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Achievement::create()` is deprecated with no replacement.
-+ Method `LLMS_Achievements::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement()`.
-+ Class `LLMS_Certificate` is deprecated with no direct replacement.
- + Method `LLMS_Certificate::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Certificate::format_string()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
-+ Method `LLMS_Certificates::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate()`.
-+ Method `LLMS_Engagements::init()` is deprecated with no replacement.
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Method `LLMS_Database_Query::set_found_results()` is deprecated.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement_User::has_user_earned()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::init()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::get_content_html()` is deprecated with no replacement.
-+ Class `LLMS_Certificate_User` is deprecated with no direct replacement.
- + Method `LLMS_Certificate_User::init()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::set_shortcode_user()` is deprecated with no replacement.
-+ Engagement debug logging is removed. Use `llms_log()` directly instead.
-+ Filter `llms_db_query_get_default_args` is deprecated in favor of `llms_{$this->id}_query_get_default_args`.
-+ Filter `llms_certificate_has_user_earned` is deprecated in favor of `llms_earned_certificate_dupcheck`.
-+ Unused public class property `LLMS_Achievements::$content` is deprecated with no replacement.
-+ Method `LLMS_Admin_Post_Types::meta_metabox_init()` is deprecated with no replacement.
-+ The site options `lifterlms_certificate_bg_img_width`, `lifterlms_certificate_bg_img_height`, and `lifterlms_certificate_legacy_image_size` are now used only for certificates and certificate templates created using the classic editor.
- + The settings, found on the Engagements Settings screen, are hidden by default.
- + During the database upgrade from versions earlier than 6.x, an site option, `llms_has_legacy_certificates` is added when at least one certificate is found. This option will display the settings so they can continue to be used for legacy certificates.
- + After migrating all certificates on a site, the settings will still display. In order to remove them from the screen a developer can either delete the option `llms_has_legacy_certificates` or return `false` from the filter `llms_has_legacy_certificates`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ The constant `LLMS_ENGAGEMENT_DEBUG` is deprecated with no replacement.
-+ Engagement debugging via `LLMS_Engagements::log` is deprecated. Use `llms_log()` instead.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Filter `lifterlms_register_post_type_llms_my_certificate` is deprecated in favor of `lifterlms_register_post_type_my_certificate`.
-+ Deprecated the misspelled protected method `LLMS_Database_Query::preprare_query()` and replaced with `LLMS_Database_Query::prepare_query()`.
- + Class method `LLMS_Events_Query::preprare_query` replaced with `LLMS_Events_Query::prepare_query()`.
- + Class method `LLMS_Query_Quiz_Attempt::preprare_query` replaced with `LLMS_Query_Quiz_Attempt::prepare_query()`.
- + Class method `LLMS_Query_User_Postmeta::preprare_query` replaced with `LLMS_Query_User_Postmeta::prepare_query()`.
- + Class method `LLMS_Student_Query::preprare_query` replaced with `LLMS_Student_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`. [#859](https://github.com/gocodebox/lifterlms/issues/859)
-
-##### Breaking Changes
-
-+ Removed FSE template: `templates/block-templates/single-certificate.html`.
-+ Removed the deprecated `LLMS()` function in favor of the `llms()` function.
-+ Removed the deprecated the `LLMS_SendWP::do_remote_install()` method in favor of the `LLMS_Abstract_Email_Provider::do_remote_install()` method.
-+ Removed the deprecated `LLMS_Abstract_Email_Provider::output_css()` method.
-+ Removed the deprecated `LLMS_Abstract_Generator_Posts::increment()` method.
-+ Removed the deprecated `LLMS_Admin_Users_Table::load_dependencies()` method.
-+ Removed the deprecated `LLMS_Admin_Import::localize_stat()` method.
-+ Removed the deprecated `LLMS_Admin_Notices_Core::check_staging()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::generator_course_status()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::output_step_html()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::scripts()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::watch_course_generation()` method.
-+ Removed the deprecated `llms_format_decimal()` function.
-+ Removed the deprecated `llms_set_person_auth_cookie()` function.
-+ Removed the deprecated `LLMS_Course::sections` property.
-+ Removed the deprecated `LLMS_Course::sku` property.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_pw_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::is_inline_script_enqueued()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::format_date()` method.
-+ Removed the deprecated `LLMS_Generator::get_author_id_from_raw()` method.
-+ Removed the deprecated `LLMS_Generator::get_default_post_status()` method.
-+ Removed the deprecated `LLMS_Generator::get_generated_posts()` method.
-+ Removed the deprecated `LLMS_Generator::increment()` method.
-+ Removed the deprecated `llms__created` action hook from the `LLMS_Abstract_Database_Store::create()` method.
-+ Removed the deprecated `llms__deleted` action hook from the `LLMS_Abstract_Database_Store::delete()` method.
-+ Removed the deprecated `llms__updated` action hook from the `LLMS_Abstract_Database_Store::update()` method.
-+ Removed the deprecated `llms_user_removed_from_membership_level` action hook from the `LLMS_Student::unenroll()` method.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `lifterlms_template_pricing_table()` function.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `templates/product/pricing-table.php` file.
-+ Removed the deprecated `LLMS_Frontend_Password` class.
-+ Removed the deprecated `LLMS_Install::db_updates()` method.
-+ Removed the deprecated `LLMS_Install::update_notice()` method.
-+ Removed the deprecated `LLMS_Notifications::dispatch_processors()` method.
-+ Removed the deprecated `llms_processors_async_dispatching` filter hook from the `LLMS_Notifications::__construct()` method.
-+ Removed the deprecated `LLMS_Notifications::$_instance` property.
-+ Removed the deprecated `LLMS_Person_Handler::register()` method.
-+ Removed the deprecated `LLMS_Person_Handler::sanitize_field()` method.
-+ Removed the deprecated `LLMS_Person_Handler::update()` method.
-+ Removed the deprecated `LLMS_Person_Handler::validate_fields()` method.
-+ Removed the deprecated `LLMS_Person_Handler::voucher_toggle_script()` method.
-+ Removed the deprecated `templates/admin/notices/db-update.php` file.
-+ Removed the deprecated `templates/admin/notices/db-updating.php` file.
-+ Removed the deprecated `llms_usernames_blacklist` filter hook in the `llms_get_usernames_blocklist()` function.
-+ Removed the deprecated `includes/libraries/wp-background-processing/index.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-async-request.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-background-process.php` file.
-+ Removed the deprecated `LLMS_Section::get_next_available_lesson_order()` method.
-+ Removed the deprecated `LLMS_Section::get_order()` method.
-+ Removed the deprecated `LLMS_Section::get_parent_course()` method.
-+ Removed the deprecated `LLMS_Section::set_parent_course()` method.
-+ Removed the deprecated `LLMS_AJAX::check_voucher_duplicate()` method.
-+ Removed the deprecated `LLMS_AJAX::get_ajax_data()` method.
-+ Removed the deprecated `LLMS_AJAX::register_script()` method.
-+ Removed the deprecated `LLMS_Interface_Post_Audio` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Sales_Page` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Video` interface.
-+ Removed the deprecated `LLMS_Achievements::$_instance` property.
-+ Removed the deprecated `LLMS_Certificates::$_instance` property.
-+ Removed the deprecated `LLMS_Emails::$_instance` property.
-+ Removed the deprecated `LLMS_Engagements::$_instance` property.
-+ Removed the deprecated `LLMS_Events::$_instance` property.
-+ Removed the deprecated `LLMS_Grades::$_instance` property.
-+ Removed the deprecated `LLMS_Integrations::$_instance` property.
-+ Removed the deprecated `LLMS_Payment_Gateways::$_instance` property.
-+ Removed the deprecated `LLMS_Processors::$_instance` property.
-+ Removed the deprecated `LLMS_Sessions::$_instance` property.
-
-##### Developer Notes
-
-+ Added `LLMS_Awards_Query`, used for querying data about awarded certificates and achievements.
- + The method signature `LLMS_Student::get_achievements()` and `LLMS_Student::get_certificates()` now use this class under tho hood.
- + The previous method signature, which passed data into a direct SQL query, is now deprecated.
-+ Achievement and certificate data storage locations have been modified, primarily to reduce reliance on the `wp_postmeta` table which will result in a site-wide performance improvement, especially on large sites.
- + Meta properties `_llms_achievement_content` and `_llms_certificate_content` have been removed in favor of `WP_Post::$post_content`.
- + Meta properties `_llms_achievement_title` and `_llms_certificate_title` have been removed in favor of `WP_Post::$post_title`.
- + Meta properties `_llms_achievement_template` and `_llms_certificate_template` have been removed in favor of `WP_Post::$post_parent`.
- + Meta properties `_llms_achievement_image` and `_llms_certificate_image` have been moved the meta property `_thumbnail_id` in order to utilize the WordPress core's featured image functionality and internal APIs.
-+ Reliance on `lifterlms_user_postmeta` for achievement and certificate data will be removed in a future release.
- + User postmeta properties `_achievement_earned` and `_certificate_earned` will continue to be recorded but are no longer being used internally.
- + The `updated_date` is now accessible via `WP_Post::$post_date`.
- + The `user_id` is now accessible via `WP_Post::$post_author`.
-+ Added new Javascript UI components library, modeled after `@wordpress/components`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/components).
-+ Added a new SVG icon library, modeled after `@wordpress/icons`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/icons).
-+ The merge code button seen on certificate and email template editors is now an SVG image instead of a PNG.
-+ Added utility function for escaping and quoting strings. [#1027](https://github.com/gocodebox/lifterlms/issues/1027)
-+ Added the ability to force an admin metabox field value through the new `meta` arg. [#2016](https://github.com/gocodebox/lifterlms/issues/2016)
-+ Added new utility function for stripping prefixes from strings.
-
-##### Performance Improvements
-
-+ Increased the number of files that are autoloaded instead of manually loaded.
-
-##### Updated Templates
-
-+ [templates/achievements/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/achievements/loop.php)
-+ [templates/achievements/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/achievements/template.php)
-+ [templates/admin/notices/db-update.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/admin/notices/db-update.php)
-+ [templates/admin/notices/db-updating.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/admin/notices/db-updating.php)
-+ [templates/admin/reporting/reporting.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/admin/reporting/reporting.php)
-+ [templates/admin/reporting/tabs/students/courses-course.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/admin/reporting/tabs/students/courses-course.php)
-+ [templates/admin/reporting/tabs/students/information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/admin/reporting/tabs/students/information.php)
-+ [templates/block-templates/single-certificate.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/block-templates/single-certificate.html)
-+ [templates/certificates/actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/actions.php)
-+ [templates/certificates/content-legacy.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/content-legacy.php)
-+ [templates/certificates/content.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/content.php)
-+ [templates/certificates/dynamic-styles.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/dynamic-styles.php)
-+ [templates/certificates/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/footer.php)
-+ [templates/certificates/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/header.php)
-+ [templates/certificates/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/loop.php)
-+ [templates/certificates/preview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/preview.php)
-+ [templates/certificates/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/certificates/template.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/checkout/form-switch-source.php)
-+ [templates/content-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/content-certificate.php)
-+ [templates/emails/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/emails/footer.php)
-+ [templates/emails/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/emails/header.php)
-+ [templates/myaccount/my-grades-single-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/myaccount/my-grades-single-table.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/myaccount/view-order-actions.php)
-+ [templates/myaccount/view-order-information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/myaccount/view-order-information.php)
-+ [templates/myaccount/view-order-transactions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/myaccount/view-order-transactions.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/myaccount/view-order.php)
-+ [templates/product/pricing-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/product/pricing-table.php)
-+ [templates/single-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-rc.1/templates/single-certificate.php)
-
-
-v6.0.0-beta.2 - 2022-02-22
---------------------------
-
-##### New Features
-
-+ Added a link to return to the student dashboard when viewing an awarded certificate. [#1959](https://github.com/gocodebox/lifterlms/issues/1959)
-+ The block editor is now enabled by default for certificates when using WordPress versions 5.8 and later.
- + Existing certificates are marked as "legacy" and will continue to use the classic editor until migrated.
- + To migrate a certificate, click the "Migrate Certificate" button. This will force the certificate's content into blocks.
-+ A number of new settings are available to certificates when using the block editor:
- + Set the certificate's display (and print) size using common paper sizes such as US Letter, US Legal, A3, A4, and more.
- + Set the certificate's display orientation: portrait of landscape.
- + Set the certificate's inner margins.
- + Set the certificate's background color.
-+ A new block, the Certificate Title Block, has been made available to certificates.
- + The block works like a WordPress core Heading Block with added options for selecting from a few display fonts (provided by Google Web Fonts).
- + The block controls the title of awarded certificates.
-+ + Added the ability to sync awarded certificates with the template used to generate them. [#1078](https://github.com/gocodebox/lifterlms/issues/1078)
-+ The `post_name` of earned certificate posts will be generated with a randomized 3+ character string in favor of relying on sequential numbers.
-+ Added the ability for administrators and LMS managers to edit earned certificates/achievements from the students reporting screen, as well as award new certificates/achievements to students.
-+ + Added certificate global options for the default size of new certificates and certificate templates.
-+ Certificate and email template merge code buttons now include [llms-user] information shortcodes.
-+ Added certificate sequential ID functionality merge code. [Read more](@TODO).
-
-##### Updates and Enhancements
-
-+ Added pagination to achievement and certificate reporting pages.
-+ Certificates no longer use the `header.php` and `footer.php` files from the site's theme, instead custom templates (`templates/certificates/header.php` and `templates/certificates/footer.php`) are used instead. These templates are minimal and exclude theme wrappers which reduces the visual conflicts encountered from theme wrappers, backgrounds, and more, especially when printing certificates. [#463](https://github.com/gocodebox/lifterlms/issues/463)
-+ The achievements and certificates dashboard endpoints are now paginated. [#669](https://github.com/gocodebox/lifterlms/issues/669)
-+ Added new default images for use with achievements and certificates.
- + The site-wide default images can be customized on the admin panel under Settings -> Engagements.
- + The old default images can be used by filtering `llms_use_legacy_engagement_images`. [#1081](https://github.com/gocodebox/lifterlms/issues/1081)
-+ The URL of earned user certificates has been changed from "my_certificate" to "certificate". Requests to the old url are automatically redirected to the new url, including instances where the URL slug has been translated.
-+ The URL of certificate template previews has been changed from "certificate" to "certificate-template".
-+ The certificate merge code, `{first_name}`, now outputs an empty string in favor of falling back to the user's nickname when there is no first name for the user. [#1640](https://github.com/gocodebox/lifterlms/issues/1640)
-+ Updates LifterLMS REST to [v1.0.0-beta.22](https://make.lifterlms.com/2021/12/15/lifterlms-rest-api-version-1-0-0-beta-22/).
-+ The look and behavior of the certificate {{MINI_CERTIFICATE}} pop-over notification merge code now displays a placeholder preview of the certificate in favor of attempting to render a tiny version of the actual certificate. [#1950](https://github.com/gocodebox/lifterlms/issues/1950)
-
-##### Bug Fixes
-
-+ + Fixed how the protected `LLMS_Notifications_Query::$found_results` property is accessed in `LLMS_Abstract_Notification_Controller::has_subscriber_received()`. + Fixed how the protected `LLMS_Notifications_Query::$max_pages` property is accessed in `lifterlms_template_student_dashboard_my_notifications()`.
-+ Delayed engagements are automatically unscheduled when the related post is deleted.
-+ Prior to sending a delayed engagement the recipient's enrollment in the related post is verified resulting the engagement not being triggered if the recipient's enrollment has been terminated. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ A disabled student dashboard endpoint will no longer display the endpoint's summary on the main dashboard page. [#535](https://github.com/gocodebox/lifterlms/issues/535)
-+ Post search filter boxes on various post tables will now longer display a link to the selected post.
-+ Basic notification code is no longer loaded on the admin panel.
-
-##### Deprecations
-
-+ Public access to properties of the abstract `LLMS_Database_Query` has been deprecated.
- + Public access to class property `LLMS_Database_Query::$found_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_found_results()`.
- + Public access to class property `LLMS_Database_Query::$max_pages`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_max_pages()`.
- + Public access to class property `LLMS_Database_Query::$number_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_number_results()`.
- + Public access to class property `LLMS_Database_Query::$results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_results()`.
- + Public access to class property `LLMS_Database_Query::$query_vars`. The variable as a whole cannot be publicly accessed, instead use `LLMS_Database_Query::get()` and `LLMS_Database_Query::set()` to read and write to the array.
- + The above changes were made to the abstract class `LLMS_Database_Query` but the following concrete classes that utilize the abstract are also affected by this change: `LLMS_Query_User_Postmeta`, `LLMS_Student_Query`, `LLMS_Query_Quiz_Attempt`, `LLMS_Events_Query`, and `LLMS_Notifications_Query`.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Achievement::format_string()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_title()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Achievement::create()` is deprecated with no replacement.
-+ Method `LLMS_Achievements::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement()`.
-+ Class `LLMS_Certificate` is deprecated with no direct replacement.
- + Method `LLMS_Certificate::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Certificate::format_string()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
-+ Method `LLMS_Certificates::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate()`.
-+ Method `LLMS_Engagements::init()` is deprecated with no replacement.
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Method `LLMS_Database_Query::set_found_results()` is deprecated.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement_User::has_user_earned()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::init()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::get_content_html()` is deprecated with no replacement.
-+ Class `LLMS_Certificate_User` is deprecated with no direct replacement.
- + Method `LLMS_Certificate_User::init()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::set_shortcode_user()` is deprecated with no replacement.
-+ Engagement debug logging is removed. Use `llms_log()` directly instead.
-+ Filter `llms_db_query_get_default_args` is deprecated in favor of `llms_{$this->id}_query_get_default_args`.
-+ Filter `llms_certificate_has_user_earned` is deprecated in favor of `llms_earned_certificate_dupcheck`.
-+ Unused public class property `LLMS_Achievements::$content` is deprecated with no replacement.
-+ Method `LLMS_Admin_Post_Types::meta_metabox_init()` is deprecated with no replacement.
-+ The site options `lifterlms_certificate_bg_img_width`, `lifterlms_certificate_bg_img_height`, and `lifterlms_certificate_legacy_image_size` are now used only for certificates and certificate templates created using the classic editor.
- + The settings, found on the Engagements Settings screen, are hidden by default.
- + During the database upgrade from versions earlier than 6.x, an site option, `llms_has_legacy_certificates` is added when at least one certificate is found. This option will display the settings so they can continue to be used for legacy certificates.
- + After migrating all certificates on a site, the settings will still display. In order to remove them from the screen a developer can either delete the option `llms_has_legacy_certificates` or return `false` from the filter `llms_has_legacy_certificates`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ The constant `LLMS_ENGAGEMENT_DEBUG` is deprecated with no replacement.
-+ Engagement debugging via `LLMS_Engagements::log` is deprecated. Use `llms_log()` instead.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Filter `lifterlms_register_post_type_llms_my_certificate` is deprecated in favor of `lifterlms_register_post_type_my_certificate`.
-+ Deprecated the misspelled protected method `LLMS_Database_Query::preprare_query()` and replaced with `LLMS_Database_Query::prepare_query()`.
- + Class method `LLMS_Events_Query::preprare_query` replaced with `LLMS_Events_Query::prepare_query()`.
- + Class method `LLMS_Query_Quiz_Attempt::preprare_query` replaced with `LLMS_Query_Quiz_Attempt::prepare_query()`.
- + Class method `LLMS_Query_User_Postmeta::preprare_query` replaced with `LLMS_Query_User_Postmeta::prepare_query()`.
- + Class method `LLMS_Student_Query::preprare_query` replaced with `LLMS_Student_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`. [#859](https://github.com/gocodebox/lifterlms/issues/859)
-
-##### Breaking Changes
-
-+ Removed FSE template: `templates/block-templates/single-certificate.html`.
-+ Removed the deprecated `LLMS()` function in favor of the `llms()` function.
-+ Removed the deprecated the `LLMS_SendWP::do_remote_install()` method in favor of the `LLMS_Abstract_Email_Provider::do_remote_install()` method.
-+ Removed the deprecated `LLMS_Abstract_Email_Provider::output_css()` method.
-+ Removed the deprecated `LLMS_Abstract_Generator_Posts::increment()` method.
-+ Removed the deprecated `LLMS_Admin_Users_Table::load_dependencies()` method.
-+ Removed the deprecated `LLMS_Admin_Import::localize_stat()` method.
-+ Removed the deprecated `LLMS_Admin_Notices_Core::check_staging()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::generator_course_status()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::output_step_html()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::scripts()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::watch_course_generation()` method.
-+ Removed the deprecated `llms_format_decimal()` function.
-+ Removed the deprecated `llms_set_person_auth_cookie()` function.
-+ Removed the deprecated `LLMS_Course::sections` property.
-+ Removed the deprecated `LLMS_Course::sku` property.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_pw_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::is_inline_script_enqueued()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::format_date()` method.
-+ Removed the deprecated `LLMS_Generator::get_author_id_from_raw()` method.
-+ Removed the deprecated `LLMS_Generator::get_default_post_status()` method.
-+ Removed the deprecated `LLMS_Generator::get_generated_posts()` method.
-+ Removed the deprecated `LLMS_Generator::increment()` method.
-+ Removed the deprecated `llms__created` action hook from the `LLMS_Abstract_Database_Store::create()` method.
-+ Removed the deprecated `llms__deleted` action hook from the `LLMS_Abstract_Database_Store::delete()` method.
-+ Removed the deprecated `llms__updated` action hook from the `LLMS_Abstract_Database_Store::update()` method.
-+ Removed the deprecated `llms_user_removed_from_membership_level` action hook from the `LLMS_Student::unenroll()` method.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `lifterlms_template_pricing_table()` function.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `templates/product/pricing-table.php` file.
-+ Removed the deprecated `LLMS_Frontend_Password` class.
-+ Removed the deprecated `LLMS_Install::db_updates()` method.
-+ Removed the deprecated `LLMS_Install::update_notice()` method.
-+ Removed the deprecated `LLMS_Notifications::dispatch_processors()` method.
-+ Removed the deprecated `llms_processors_async_dispatching` filter hook from the `LLMS_Notifications::__construct()` method.
-+ Removed the deprecated `LLMS_Notifications::$_instance` property.
-+ Removed the deprecated `LLMS_Person_Handler::register()` method.
-+ Removed the deprecated `LLMS_Person_Handler::sanitize_field()` method.
-+ Removed the deprecated `LLMS_Person_Handler::update()` method.
-+ Removed the deprecated `LLMS_Person_Handler::validate_fields()` method.
-+ Removed the deprecated `LLMS_Person_Handler::voucher_toggle_script()` method.
-+ Removed the deprecated `templates/admin/notices/db-update.php` file.
-+ Removed the deprecated `templates/admin/notices/db-updating.php` file.
-+ Removed the deprecated `llms_usernames_blacklist` filter hook in the `llms_get_usernames_blocklist()` function.
-+ Removed the deprecated `includes/libraries/wp-background-processing/index.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-async-request.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-background-process.php` file.
-+ Removed the deprecated `LLMS_Section::get_next_available_lesson_order()` method.
-+ Removed the deprecated `LLMS_Section::get_order()` method.
-+ Removed the deprecated `LLMS_Section::get_parent_course()` method.
-+ Removed the deprecated `LLMS_Section::set_parent_course()` method.
-+ Removed the deprecated `LLMS_AJAX::check_voucher_duplicate()` method.
-+ Removed the deprecated `LLMS_AJAX::get_ajax_data()` method.
-+ Removed the deprecated `LLMS_AJAX::register_script()` method.
-+ Removed the deprecated `LLMS_Interface_Post_Audio` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Sales_Page` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Video` interface.
-+ Removed the deprecated `LLMS_Achievements::$_instance` property.
-+ Removed the deprecated `LLMS_Certificates::$_instance` property.
-+ Removed the deprecated `LLMS_Emails::$_instance` property.
-+ Removed the deprecated `LLMS_Engagements::$_instance` property.
-+ Removed the deprecated `LLMS_Events::$_instance` property.
-+ Removed the deprecated `LLMS_Grades::$_instance` property.
-+ Removed the deprecated `LLMS_Integrations::$_instance` property.
-+ Removed the deprecated `LLMS_Payment_Gateways::$_instance` property.
-+ Removed the deprecated `LLMS_Processors::$_instance` property.
-+ Removed the deprecated `LLMS_Sessions::$_instance` property.
-
-##### Developer Notes
-
-+ Added `LLMS_Awards_Query`, used for querying data about awarded certificates and achievements.
- + The method signature `LLMS_Student::get_achievements()` and `LLMS_Student::get_certificates()` now use this class under tho hood.
- + The previous method signature, which passed data into a direct SQL query, is now deprecated.
-+ Achievement and certificate data storage locations have been modified, primarily to reduce reliance on the `wp_postmeta` table which will result in a site-wide performance improvement, especially on large sites.
- + Meta properties `_llms_achievement_content` and `_llms_certificate_content` have been removed in favor of `WP_Post::$post_content`.
- + Meta properties `_llms_achievement_title` and `_llms_certificate_title` have been removed in favor of `WP_Post::$post_title`.
- + Meta properties `_llms_achievement_template` and `_llms_certificate_template` have been removed in favor of `WP_Post::$post_parent`.
- + Meta properties `_llms_achievement_image` and `_llms_certificate_image` have been moved the meta property `_thumbnail_id` in order to utilize the WordPress core's featured image functionality and internal APIs.
-+ Reliance on `lifterlms_user_postmeta` for achievement and certificate data will be removed in a future release.
- + User postmeta properties `_achievement_earned` and `_certificate_earned` will continue to be recorded but are no longer being used internally.
- + The `updated_date` is now accessible via `WP_Post::$post_date`.
- + The `user_id` is now accessible via `WP_Post::$post_author`.
-+ Added new Javascript UI components library, modeled after `@wordpress/components`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/components).
-+ Added a new SVG icon library, modeled after `@wordpress/icons`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/icons).
-+ The merge code button seen on certificate and email template editors is now an SVG image instead of a PNG.
-+ Added utility function for escaping and quoting strings. [#1027](https://github.com/gocodebox/lifterlms/issues/1027)
-+ Added new utility function for stripping prefixes from strings.
-
-##### Performance Improvements
-
-+ Increased the number of files that are autoloaded instead of manually loaded.
-
-##### Updated Templates
-
-+ [templates/achievements/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/achievements/loop.php)
-+ [templates/achievements/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/achievements/template.php)
-+ [templates/admin/notices/db-update.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/admin/notices/db-update.php)
-+ [templates/admin/notices/db-updating.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/admin/notices/db-updating.php)
-+ [templates/admin/reporting/reporting.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/admin/reporting/reporting.php)
-+ [templates/admin/reporting/tabs/courses/overview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/admin/reporting/tabs/courses/overview.php)
-+ [templates/admin/reporting/tabs/memberships/overview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/admin/reporting/tabs/memberships/overview.php)
-+ [templates/admin/reporting/tabs/quizzes/overview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/admin/reporting/tabs/quizzes/overview.php)
-+ [templates/admin/reporting/tabs/students/information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/admin/reporting/tabs/students/information.php)
-+ [templates/block-templates/archive-course.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/archive-course.html)
-+ [templates/block-templates/archive-llms_membership.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/archive-llms_membership.html)
-+ [templates/block-templates/single-no-access.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/single-no-access.html)
-+ [templates/block-templates/taxonomy-course_cat.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/taxonomy-course_cat.html)
-+ [templates/block-templates/taxonomy-course_difficulty.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/taxonomy-course_difficulty.html)
-+ [templates/block-templates/taxonomy-course_tag.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/taxonomy-course_tag.html)
-+ [templates/block-templates/taxonomy-course_track.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/taxonomy-course_track.html)
-+ [templates/block-templates/taxonomy-membership_cat.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/taxonomy-membership_cat.html)
-+ [templates/block-templates/taxonomy-membership_tag.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/block-templates/taxonomy-membership_tag.html)
-+ [templates/certificates/actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/actions.php)
-+ [templates/certificates/content-legacy.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/content-legacy.php)
-+ [templates/certificates/content.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/content.php)
-+ [templates/certificates/dynamic-styles.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/dynamic-styles.php)
-+ [templates/certificates/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/footer.php)
-+ [templates/certificates/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/header.php)
-+ [templates/certificates/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/loop.php)
-+ [templates/certificates/preview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/preview.php)
-+ [templates/certificates/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/certificates/template.php)
-+ [templates/checkout/form-confirm-payment.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/checkout/form-confirm-payment.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/checkout/form-switch-source.php)
-+ [templates/content-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/content-certificate.php)
-+ [templates/course/lesson-navigation.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/course/lesson-navigation.php)
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/course/lesson-preview.php)
-+ [templates/course/parent-course.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/course/parent-course.php)
-+ [templates/emails/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/emails/footer.php)
-+ [templates/emails/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/emails/header.php)
-+ [templates/loop-main.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/loop-main.php)
-+ [templates/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/loop.php)
-+ [templates/myaccount/my-grades-single-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/myaccount/my-grades-single-table.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/myaccount/view-order-actions.php)
-+ [templates/myaccount/view-order-information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/myaccount/view-order-information.php)
-+ [templates/myaccount/view-order-transactions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/myaccount/view-order-transactions.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/myaccount/view-order.php)
-+ [templates/product/pricing-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/product/pricing-table.php)
-+ [templates/quiz/questions/content-picture_choice.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/quiz/questions/content-picture_choice.php)
-+ [templates/quiz/results.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/quiz/results.php)
-+ [templates/single-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.2/templates/single-certificate.php)
-
-
-v5.10.0 - 2022-02-22
---------------------
-
-##### Updates and Enhancements
-
-+ Added an option to specify a custom checkout form title for free access plans. [#1774](https://github.com/gocodebox/lifterlms/issues/1774)
-+ Updated LifterLMS Blocks to [v2.3.2](https://make.lifterlms.com/2022/02/22/lifterlms-blocks-version-2-3-2/). [#1774](https://github.com/gocodebox/lifterlms/issues/1774)
-
-##### Bug Fixes
-
-+ Fixed ability to sort course students table by completed date. [#1969](https://github.com/gocodebox/lifterlms/issues/1969)
-+ Fixed reporting issue encountered when a course has no lessons. [#2012](https://github.com/gocodebox/lifterlms/issues/2012)
-+ Fixed broken checkout on Twenty Twenty-Two Theme when using the password strength meter. [#1997](https://github.com/gocodebox/lifterlms/issues/1997)
-+ Fixed block template slug generation from path in Windows environments. [#2001](https://github.com/gocodebox/lifterlms/issues/2001)
-+ Fixed an issue encountered when using the search box on the voucher admin posts list screen. [#2005](https://github.com/gocodebox/lifterlms/issues/2005)
-
-##### Updated Templates
-
-+ [templates/admin/reporting/tabs/courses/overview.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/admin/reporting/tabs/courses/overview.php)
-+ [templates/admin/reporting/tabs/memberships/overview.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/admin/reporting/tabs/memberships/overview.php)
-+ [templates/admin/reporting/tabs/quizzes/overview.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/admin/reporting/tabs/quizzes/overview.php)
-+ [templates/block-templates/archive-course.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/archive-course.html)
-+ [templates/block-templates/archive-llms_membership.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/archive-llms_membership.html)
-+ [templates/block-templates/single-certificate.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/single-certificate.html)
-+ [templates/block-templates/single-no-access.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/single-no-access.html)
-+ [templates/block-templates/taxonomy-course_cat.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/taxonomy-course_cat.html)
-+ [templates/block-templates/taxonomy-course_difficulty.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/taxonomy-course_difficulty.html)
-+ [templates/block-templates/taxonomy-course_tag.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/taxonomy-course_tag.html)
-+ [templates/block-templates/taxonomy-course_track.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/taxonomy-course_track.html)
-+ [templates/block-templates/taxonomy-membership_cat.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/taxonomy-membership_cat.html)
-+ [templates/block-templates/taxonomy-membership_tag.html](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/block-templates/taxonomy-membership_tag.html)
-+ [templates/checkout/form-confirm-payment.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/checkout/form-confirm-payment.php)
-+ [templates/course/lesson-navigation.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/course/lesson-navigation.php)
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/course/lesson-preview.php)
-+ [templates/course/parent-course.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/course/parent-course.php)
-+ [templates/loop-main.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/loop-main.php)
-+ [templates/loop.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/loop.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/myaccount/view-order.php)
-+ [templates/quiz/questions/content-picture_choice.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/quiz/questions/content-picture_choice.php)
-+ [templates/quiz/results.php](https://github.com/gocodebox/lifterlms/blob/5.10.0/templates/quiz/results.php)
-
-
-v6.0.0-beta.1 - 2022-02-16
---------------------------
-
-##### New Features
-
-+ Added a link to return to the student dashboard when viewing an awarded certificate. [#1959](https://github.com/gocodebox/lifterlms/issues/1959)
-+ The block editor is now enabled by default for certificates when using WordPress versions 5.8 and later.
- + Existing certificates are marked as "legacy" and will continue to use the classic editor until migrated.
- + To migrate a certificate, click the "Migrate Certificate" button. This will force the certificate's content into blocks.
-+ A number of new settings are available to certificates when using the block editor:
- + Set the certificate's display (and print) size using common paper sizes such as US Letter, US Legal, A3, A4, and more.
- + Set the certificate's display orientation: portrait of landscape.
- + Set the certificate's inner margins.
- + Set the certificate's background color.
-+ A new block, the Certificate Title Block, has been made available to certificates.
- + The block works like a WordPress core Heading Block with added options for selecting from a few display fonts (provided by Google Web Fonts).
- + The block controls the title of awarded certificates.
-+ + Added the ability to sync awarded certificates with the template used to generate them. [#1078](https://github.com/gocodebox/lifterlms/issues/1078)
-+ The `post_name` of earned certificate posts will be generated with a randomized 3+ character string in favor of relying on sequential numbers.
-+ Added the ability for administrators and LMS managers to edit earned certificates/achievements from the students reporting screen, as well as award new certificates/achievements to students.
-+ + Added certificate global options for the default size of new certificates and certificate templates.
-+ Certificate and email template merge code buttons now include `[llms-user]` information shortcodes.
-+ Added certificate sequential ID functionality merge code.
-
-##### Updates and Enhancements
-
-+ Added pagination to achievement and certificate reporting pages.
-+ Certificates no longer use the `header.php` and `footer.php` files from the site's theme, instead custom templates (`templates/certificates/header.php` and `templates/certificates/footer.php`) are used instead. These templates are minimal and exclude theme wrappers which reduces the visual conflicts encountered from theme wrappers, backgrounds, and more, especially when printing certificates. [#463](https://github.com/gocodebox/lifterlms/issues/463)
-+ The achievements and certificates dashboard endpoints are now paginated. [#669](https://github.com/gocodebox/lifterlms/issues/669)
-+ Added new default images for use with achievements and certificates.
- + The site-wide default images can be customized on the admin panel under Settings -> Engagements.
- + The old default images can be used by filtering `llms_use_legacy_engagement_images`. [#1081](https://github.com/gocodebox/lifterlms/issues/1081)
-+ The URL of earned user certificates has been changed from "my_certificate" to "certificate". Requests to the old url are automatically redirected to the new url, including instances where the URL slug has been translated.
-+ The URL of certificate template previews has been changed from "certificate" to "certificate-template".
-+ The certificate merge code, `{first_name}`, now outputs an empty string in favor of falling back to the user's nickname when there is no first name for the user. [#1640](https://github.com/gocodebox/lifterlms/issues/1640)
-+ Updates LifterLMS REST to [v1.0.0-beta.22](https://make.lifterlms.com/2021/12/15/lifterlms-rest-api-version-1-0-0-beta-22/).
-+ The look and behavior of the certificate {{MINI_CERTIFICATE}} pop-over notification merge code now displays a placeholder preview of the certificate in favor of attempting to render a tiny version of the actual certificate. [#1950](https://github.com/gocodebox/lifterlms/issues/1950)
-
-##### Bug Fixes
-
-+ + Fixed how the protected `LLMS_Notifications_Query::$found_results` property is accessed in `LLMS_Abstract_Notification_Controller::has_subscriber_received()`. + Fixed how the protected `LLMS_Notifications_Query::$max_pages` property is accessed in `lifterlms_template_student_dashboard_my_notifications()`.
-+ Delayed engagements are automatically unscheduled when the related post is deleted.
-+ Prior to sending a delayed engagement the recipient's enrollment in the related post is verified resulting the engagement not being triggered if the recipient's enrollment has been terminated. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ A disabled student dashboard endpoint will no longer display the endpoint's summary on the main dashboard page. [#535](https://github.com/gocodebox/lifterlms/issues/535)
-+ Post search filter boxes on various post tables will now longer display a link to the selected post.
-+ Basic notification code is no longer loaded on the admin panel.
-
-##### Deprecations
-
-+ Public access to properties of the abstract `LLMS_Database_Query` has been deprecated.
- + Public access to class property `LLMS_Database_Query::$found_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_found_results()`.
- + Public access to class property `LLMS_Database_Query::$max_pages`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_max_pages()`.
- + Public access to class property `LLMS_Database_Query::$number_results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_number_results()`.
- + Public access to class property `LLMS_Database_Query::$results`. The property is no longer publicly writable but can be read via `LLMS_Database_Query::get_results()`.
- + Public access to class property `LLMS_Database_Query::$query_vars`. The variable as a whole cannot be publicly accessed, instead use `LLMS_Database_Query::get()` and `LLMS_Database_Query::set()` to read and write to the array.
- + The above changes were made to the abstract class `LLMS_Database_Query` but the following concrete classes that utilize the abstract are also affected by this change: `LLMS_Query_User_Postmeta`, `LLMS_Student_Query`, `LLMS_Query_Quiz_Attempt`, `LLMS_Events_Query`, and `LLMS_Notifications_Query`.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Achievement::format_string()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_title()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Achievement::create()` is deprecated with no replacement.
-+ Method `LLMS_Achievements::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement()`.
-+ Class `LLMS_Certificate` is deprecated with no direct replacement.
- + Method `LLMS_Certificate::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Certificate::format_string()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
-+ Method `LLMS_Certificates::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate()`.
-+ Method `LLMS_Engagements::init()` is deprecated with no replacement.
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Method `LLMS_Database_Query::set_found_results()` is deprecated.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement_User::has_user_earned()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::init()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::get_content_html()` is deprecated with no replacement.
-+ Class `LLMS_Certificate_User` is deprecated with no direct replacement.
- + Method `LLMS_Certificate_User::init()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::set_shortcode_user()` is deprecated with no replacement.
-+ Engagement debug logging is removed. Use `llms_log()` directly instead.
-+ Filter `llms_db_query_get_default_args` is deprecated in favor of `llms_{$this->id}_query_get_default_args`.
-+ Filter `llms_certificate_has_user_earned` is deprecated in favor of `llms_earned_certificate_dupcheck`.
-+ Unused public class property `LLMS_Achievements::$content` is deprecated with no replacement.
-+ Method `LLMS_Admin_Post_Types::meta_metabox_init()` is deprecated with no replacement.
-+ The site options `lifterlms_certificate_bg_img_width`, `lifterlms_certificate_bg_img_height`, and `lifterlms_certificate_legacy_image_size` are now used only for certificates and certificate templates created using the classic editor.
- + The settings, found on the Engagements Settings screen, are hidden by default.
- + During the database upgrade from versions earlier than 6.x, an site option, `llms_has_legacy_certificates` is added when at least one certificate is found. This option will display the settings so they can continue to be used for legacy certificates.
- + After migrating all certificates on a site, the settings will still display. In order to remove them from the screen a developer can either delete the option `llms_has_legacy_certificates` or return `false` from the filter `llms_has_legacy_certificates`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`. [#290](https://github.com/gocodebox/lifterlms/issues/290)
-+ The constant `LLMS_ENGAGEMENT_DEBUG` is deprecated with no replacement.
-+ Engagement debugging via `LLMS_Engagements::log` is deprecated. Use `llms_log()` instead.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Filter `lifterlms_register_post_type_llms_my_certificate` is deprecated in favor of `lifterlms_register_post_type_my_certificate`.
-+ Deprecated the misspelled protected method `LLMS_Database_Query::preprare_query()` and replaced with `LLMS_Database_Query::prepare_query()`.
- + Class method `LLMS_Events_Query::preprare_query` replaced with `LLMS_Events_Query::prepare_query()`.
- + Class method `LLMS_Query_Quiz_Attempt::preprare_query` replaced with `LLMS_Query_Quiz_Attempt::prepare_query()`.
- + Class method `LLMS_Query_User_Postmeta::preprare_query` replaced with `LLMS_Query_User_Postmeta::prepare_query()`.
- + Class method `LLMS_Student_Query::preprare_query` replaced with `LLMS_Student_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`. [#859](https://github.com/gocodebox/lifterlms/issues/859)
-
-##### Breaking Changes
-
-+ Removed FSE template: `templates/block-templates/single-certificate.html`.
-+ Removed the deprecated `LLMS()` function in favor of the `llms()` function.
-+ Removed the deprecated the `LLMS_SendWP::do_remote_install()` method in favor of the `LLMS_Abstract_Email_Provider::do_remote_install()` method.
-+ Removed the deprecated `LLMS_Abstract_Email_Provider::output_css()` method.
-+ Removed the deprecated `LLMS_Abstract_Generator_Posts::increment()` method.
-+ Removed the deprecated `LLMS_Admin_Users_Table::load_dependencies()` method.
-+ Removed the deprecated `LLMS_Admin_Import::localize_stat()` method.
-+ Removed the deprecated `LLMS_Admin_Notices_Core::check_staging()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::generator_course_status()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::output_step_html()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::scripts()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::watch_course_generation()` method.
-+ Removed the deprecated `llms_format_decimal()` function.
-+ Removed the deprecated `llms_set_person_auth_cookie()` function.
-+ Removed the deprecated `LLMS_Course::sections` property.
-+ Removed the deprecated `LLMS_Course::sku` property.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_pw_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::is_inline_script_enqueued()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::format_date()` method.
-+ Removed the deprecated `LLMS_Generator::get_author_id_from_raw()` method.
-+ Removed the deprecated `LLMS_Generator::get_default_post_status()` method.
-+ Removed the deprecated `LLMS_Generator::get_generated_posts()` method.
-+ Removed the deprecated `LLMS_Generator::increment()` method.
-+ Removed the deprecated `llms__created` action hook from the `LLMS_Abstract_Database_Store::create()` method.
-+ Removed the deprecated `llms__deleted` action hook from the `LLMS_Abstract_Database_Store::delete()` method.
-+ Removed the deprecated `llms__updated` action hook from the `LLMS_Abstract_Database_Store::update()` method.
-+ Removed the deprecated `llms_user_removed_from_membership_level` action hook from the `LLMS_Student::unenroll()` method.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `lifterlms_template_pricing_table()` function.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `templates/product/pricing-table.php` file.
-+ Removed the deprecated `LLMS_Frontend_Password` class.
-+ Removed the deprecated `LLMS_Install::db_updates()` method.
-+ Removed the deprecated `LLMS_Install::update_notice()` method.
-+ Removed the deprecated `LLMS_Notifications::dispatch_processors()` method.
-+ Removed the deprecated `llms_processors_async_dispatching` filter hook from the `LLMS_Notifications::__construct()` method.
-+ Removed the deprecated `LLMS_Notifications::$_instance` property.
-+ Removed the deprecated `LLMS_Person_Handler::register()` method.
-+ Removed the deprecated `LLMS_Person_Handler::sanitize_field()` method.
-+ Removed the deprecated `LLMS_Person_Handler::update()` method.
-+ Removed the deprecated `LLMS_Person_Handler::validate_fields()` method.
-+ Removed the deprecated `LLMS_Person_Handler::voucher_toggle_script()` method.
-+ Removed the deprecated `templates/admin/notices/db-update.php` file.
-+ Removed the deprecated `templates/admin/notices/db-updating.php` file.
-+ Removed the deprecated `llms_usernames_blacklist` filter hook in the `llms_get_usernames_blocklist()` function.
-+ Removed the deprecated `includes/libraries/wp-background-processing/index.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-async-request.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-background-process.php` file.
-+ Removed the deprecated `LLMS_Section::get_next_available_lesson_order()` method.
-+ Removed the deprecated `LLMS_Section::get_order()` method.
-+ Removed the deprecated `LLMS_Section::get_parent_course()` method.
-+ Removed the deprecated `LLMS_Section::set_parent_course()` method.
-+ Removed the deprecated `LLMS_AJAX::check_voucher_duplicate()` method.
-+ Removed the deprecated `LLMS_AJAX::get_ajax_data()` method.
-+ Removed the deprecated `LLMS_AJAX::register_script()` method.
-+ Removed the deprecated `LLMS_Interface_Post_Audio` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Sales_Page` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Video` interface.
-+ Removed the deprecated `LLMS_Achievements::$_instance` property.
-+ Removed the deprecated `LLMS_Certificates::$_instance` property.
-+ Removed the deprecated `LLMS_Emails::$_instance` property.
-+ Removed the deprecated `LLMS_Engagements::$_instance` property.
-+ Removed the deprecated `LLMS_Events::$_instance` property.
-+ Removed the deprecated `LLMS_Grades::$_instance` property.
-+ Removed the deprecated `LLMS_Integrations::$_instance` property.
-+ Removed the deprecated `LLMS_Payment_Gateways::$_instance` property.
-+ Removed the deprecated `LLMS_Processors::$_instance` property.
-+ Removed the deprecated `LLMS_Sessions::$_instance` property.
-
-##### Developer Notes
-
-+ Added `LLMS_Awards_Query`, used for querying data about awarded certificates and achievements.
- + The method signature `LLMS_Student::get_achievements()` and `LLMS_Student::get_certificates()` now use this class under tho hood.
- + The previous method signature, which passed data into a direct SQL query, is now deprecated.
-+ Achievement and certificate data storage locations have been modified, primarily to reduce reliance on the `wp_postmeta` table which will result in a site-wide performance improvement, especially on large sites.
- + Meta properties `_llms_achievement_content` and `_llms_certificate_content` have been removed in favor of `WP_Post::$post_content`.
- + Meta properties `_llms_achievement_title` and `_llms_certificate_title` have been removed in favor of `WP_Post::$post_title`.
- + Meta properties `_llms_achievement_template` and `_llms_certificate_template` have been removed in favor of `WP_Post::$post_parent`.
- + Meta properties `_llms_achievement_image` and `_llms_certificate_image` have been moved the meta property `_thumbnail_id` in order to utilize the WordPress core's featured image functionality and internal APIs.
-+ Reliance on `lifterlms_user_postmeta` for achievement and certificate data will be removed in a future release.
- + User postmeta properties `_achievement_earned` and `_certificate_earned` will continue to be recorded but are no longer being used internally.
- + The `updated_date` is now accessible via `WP_Post::$post_date`.
- + The `user_id` is now accessible via `WP_Post::$post_author`.
-+ Added new Javascript UI components library, modeled after `@wordpress/components`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/components).
-+ Added a new SVG icon library, modeled after `@wordpress/icons`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/icons).
-+ The merge code button seen on certificate and email template editors is now an SVG image instead of a PNG.
-+ Added utility function for escaping and quoting strings. [#1027](https://github.com/gocodebox/lifterlms/issues/1027)
-+ Added new utility function for stripping prefixes from strings.
-
-##### Performance Improvements
-
-+ Increased the number of files that are autoloaded instead of manually loaded.
-
-##### Updated Templates
-
-+ [templates/achievements/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/achievements/loop.php)
-+ [templates/achievements/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/achievements/template.php)
-+ [templates/admin/notices/db-update.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/admin/notices/db-update.php)
-+ [templates/admin/notices/db-updating.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/admin/notices/db-updating.php)
-+ [templates/admin/reporting/reporting.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/admin/reporting/reporting.php)
-+ [templates/admin/reporting/tabs/courses/overview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/admin/reporting/tabs/courses/overview.php)
-+ [templates/admin/reporting/tabs/memberships/overview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/admin/reporting/tabs/memberships/overview.php)
-+ [templates/admin/reporting/tabs/quizzes/overview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/admin/reporting/tabs/quizzes/overview.php)
-+ [templates/admin/reporting/tabs/students/information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/admin/reporting/tabs/students/information.php)
-+ [templates/block-templates/archive-course.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/archive-course.html)
-+ [templates/block-templates/archive-llms_membership.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/archive-llms_membership.html)
-+ [templates/block-templates/single-no-access.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/single-no-access.html)
-+ [templates/block-templates/taxonomy-course_cat.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/taxonomy-course_cat.html)
-+ [templates/block-templates/taxonomy-course_difficulty.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/taxonomy-course_difficulty.html)
-+ [templates/block-templates/taxonomy-course_tag.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/taxonomy-course_tag.html)
-+ [templates/block-templates/taxonomy-course_track.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/taxonomy-course_track.html)
-+ [templates/block-templates/taxonomy-membership_cat.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/taxonomy-membership_cat.html)
-+ [templates/block-templates/taxonomy-membership_tag.html](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/block-templates/taxonomy-membership_tag.html)
-+ [templates/certificates/actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/actions.php)
-+ [templates/certificates/content-legacy.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/content-legacy.php)
-+ [templates/certificates/content.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/content.php)
-+ [templates/certificates/dynamic-styles.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/dynamic-styles.php)
-+ [templates/certificates/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/footer.php)
-+ [templates/certificates/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/header.php)
-+ [templates/certificates/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/loop.php)
-+ [templates/certificates/preview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/preview.php)
-+ [templates/certificates/template.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/certificates/template.php)
-+ [templates/checkout/form-confirm-payment.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/checkout/form-confirm-payment.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/checkout/form-switch-source.php)
-+ [templates/content-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/content-certificate.php)
-+ [templates/course/lesson-navigation.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/course/lesson-navigation.php)
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/course/lesson-preview.php)
-+ [templates/course/parent-course.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/course/parent-course.php)
-+ [templates/emails/footer.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/emails/footer.php)
-+ [templates/emails/header.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/emails/header.php)
-+ [templates/loop-main.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/loop-main.php)
-+ [templates/loop.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/loop.php)
-+ [templates/myaccount/my-grades-single-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/myaccount/my-grades-single-table.php)
-+ [templates/myaccount/view-order-actions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/myaccount/view-order-actions.php)
-+ [templates/myaccount/view-order-information.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/myaccount/view-order-information.php)
-+ [templates/myaccount/view-order-transactions.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/myaccount/view-order-transactions.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/myaccount/view-order.php)
-+ [templates/product/pricing-table.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/product/pricing-table.php)
-+ [templates/quiz/questions/content-picture_choice.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/quiz/questions/content-picture_choice.php)
-+ [templates/quiz/results.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/quiz/results.php)
-+ [templates/single-certificate.php](https://github.com/gocodebox/lifterlms/blob/6.0.0-beta.1/templates/single-certificate.php)
-
-
-v5.9.0 - 2022-02-15
--------------------
-
-##### Updates and Enhancements
-
-+ Picture choice questions are now organized using flexbox in favor of a float-powered column layout.
-+ Resolved PHP 8.1 deprecation warnings. [#1859](https://github.com/gocodebox/lifterlms/issues/1859)
-
-##### Bug Fixes
-
-+ Updated `llms_get_endpoint_url()` to better adhere to a site's permalink structure with regards to the presence of a trailing slash in the generated url. [#1983](https://github.com/gocodebox/lifterlms/issues/1983)
-+ Only allow users with `edit_post` capabilities to bypass content restrictions.
-+ Fixed stretched images in quiz description/questions when using the Twenty Twenty-Two theme. [#1976](https://github.com/gocodebox/lifterlms/issues/1976)
-
-##### Deprecations
-
-+ Method `LLMS_AJAX::check_voucher_duplicate()` is deprecated in favor of `LLMS_AJAX_HANDLER::check_voucher_duplicate()`.
-
-##### Updated Templates
-
-+ [templates/admin/reporting/tabs/courses/overview.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/admin/reporting/tabs/courses/overview.php)
-+ [templates/admin/reporting/tabs/memberships/overview.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/admin/reporting/tabs/memberships/overview.php)
-+ [templates/admin/reporting/tabs/quizzes/overview.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/admin/reporting/tabs/quizzes/overview.php)
-+ [templates/block-templates/archive-course.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/archive-course.html)
-+ [templates/block-templates/archive-llms_membership.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/archive-llms_membership.html)
-+ [templates/block-templates/single-certificate.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/single-certificate.html)
-+ [templates/block-templates/single-no-access.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/single-no-access.html)
-+ [templates/block-templates/taxonomy-course_cat.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/taxonomy-course_cat.html)
-+ [templates/block-templates/taxonomy-course_difficulty.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/taxonomy-course_difficulty.html)
-+ [templates/block-templates/taxonomy-course_tag.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/taxonomy-course_tag.html)
-+ [templates/block-templates/taxonomy-course_track.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/taxonomy-course_track.html)
-+ [templates/block-templates/taxonomy-membership_cat.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/taxonomy-membership_cat.html)
-+ [templates/block-templates/taxonomy-membership_tag.html](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/block-templates/taxonomy-membership_tag.html)
-+ [templates/checkout/form-confirm-payment.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/checkout/form-confirm-payment.php)
-+ [templates/course/lesson-navigation.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/course/lesson-navigation.php)
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/course/lesson-preview.php)
-+ [templates/course/parent-course.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/course/parent-course.php)
-+ [templates/loop-main.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/loop-main.php)
-+ [templates/loop.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/loop.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/myaccount/view-order.php)
-+ [templates/quiz/questions/content-picture_choice.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/quiz/questions/content-picture_choice.php)
-+ [templates/quiz/results.php](https://github.com/gocodebox/lifterlms/blob/5.9.0/templates/quiz/results.php)
-
-
-v6.0.0-alpha.4 - 2022-02-11
----------------------------
-
-##### Updates and Enhancements
-
-+ Removed usage of PHP features deprecated in PHP 8.1.
-+ Added a link to return to the student dashboard when viewing an awarded certificate.
-+ Allow block templates to be overridden from themes or plugins.
-+ Added a "Reset Certificate" button to restore certificates to the default template.
-+ Added links from achievement and certificate templates to view all awards generated from the template.
-+ Added the ability to sync achievements (sync all awards to the parent template and sync one award to it's parent).
-+ Improved class autoloading.
-
-##### Bug Fixes
-
-+ Fixed certificate print compatibility issues with the OceanWP and Genesis themes.
-+ Fixed custom font usage in the Certificate Title block to utilize WP Core functionality introduced in version 5.9.
-+ Fixed access to protected properties in the `LLMS_Notifications_Query` class.
-
-##### Breaking Changes
-
-+ Removed the Single Certificate block template.
-
-
-v5.8.0 - 2022-01-26
--------------------
-
-##### New Features
-
-+ Add theme support for the Twenty Twenty-Two theme. [#1824](https://github.com/gocodebox/lifterlms/issues/1824)
-+ Added WordPress Full Site Editing compatibility for various LifterLMS-powered templates.
-
-##### Updates and Enhancements
-
-+ The minimum required WordPress core version is now version 5.5.
-+ Tested against WordPress version 5.9.
-+ Updated LifterLMS Blocks: [v2.3.0](https://make.lifterlms.com/2022/01/25/lifterlms-blocks-version-2-3-0/), [v2.3.1](https://make.lifterlms.com/2022/01/26/lifterlms-blocks-version-2-3-1/).
-+ Remove the "description" registered with LifterLMS custom post types. [#710](https://github.com/gocodebox/lifterlms/issues/710)
-
-##### Updated Templates
-
-+ [templates/block-templates/archive-course.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/archive-course.html)
-+ [templates/block-templates/archive-llms_membership.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/archive-llms_membership.html)
-+ [templates/block-templates/single-certificate.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/single-certificate.html)
-+ [templates/block-templates/single-no-access.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/single-no-access.html)
-+ [templates/block-templates/taxonomy-course_cat.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/taxonomy-course_cat.html)
-+ [templates/block-templates/taxonomy-course_difficulty.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/taxonomy-course_difficulty.html)
-+ [templates/block-templates/taxonomy-course_tag.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/taxonomy-course_tag.html)
-+ [templates/block-templates/taxonomy-course_track.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/taxonomy-course_track.html)
-+ [templates/block-templates/taxonomy-membership_cat.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/taxonomy-membership_cat.html)
-+ [templates/block-templates/taxonomy-membership_tag.html](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/block-templates/taxonomy-membership_tag.html)
-+ [templates/course/lesson-navigation.php](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/course/lesson-navigation.php)
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/course/lesson-preview.php)
-+ [templates/course/parent-course.php](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/course/parent-course.php)
-+ [templates/loop-main.php](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/loop-main.php)
-+ [templates/loop.php](https://github.com/gocodebox/lifterlms/blob/5.8.0/templates/loop.php)
-
-
-v6.0.0-alpha.3 - 2022-01-14
----------------------------
-
-##### Updates and Enhancements
-
-+ Automatically dequeue print-only stylesheets to reduce theme and plugin conflicts when printing certificates.
-+ Only enable the Certificate Title block font-family selector for WordPress 5.9 and later.
-+ Only enable the Block Editor for certificates on WordPress 5.8 and later.
-+ Replaced welcome message placeholder text with a real welcome message.
-
-##### Bug Fixes
-
-+ Explicitly define a default font-family ("default") for the Certificate Title block.
-+ Fixed visual issues encountered on certificates when resizing the browser window.
-+ Fixed issue with the certificate block template on WordPress 5.8 (divider blocks aren't centered by default).
-
-##### Breaking Changes
-
-+ Removed the deprecated `LLMS()` function in favor of the `llms()` function.
-+ Removed the deprecated `LLMS_SendWP::do_remote_install()` method in favor of the `LLMS_Abstract_Email_Provider::do_remote_install()` method.
-+ Removed the deprecated `LLMS_Abstract_Email_Provider::output_css()` method.
-+ Removed the deprecated `LLMS_Abstract_Generator_Posts::increment()` method.
-+ Removed the deprecated `LLMS_Admin_Users_Table::load_dependencies()` method.
-+ Removed the deprecated `LLMS_Admin_Import::localize_stat()` method.
-+ Removed the deprecated `LLMS_Admin_Notices_Core::check_staging()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::generator_course_status()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::output_step_html()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::scripts()` method.
-+ Removed the deprecated `LLMS_Admin_Setup_Wizard::watch_course_generation()` method.
-+ Removed the deprecated `llms_format_decimal()` function.
-+ Removed the deprecated `llms_set_person_auth_cookie()` function.
-+ Removed the deprecated `LLMS_Course::sections` property.
-+ Removed the deprecated `LLMS_Course::sku` property.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_pw_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::enqueue_inline_script()` method.
-+ Removed the deprecated `LLMS_Frontend_Assets::is_inline_script_enqueued()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::add_custom_values()` method.
-+ Removed the deprecated `LLMS_Generator::format_date()` method.
-+ Removed the deprecated `LLMS_Generator::get_author_id_from_raw()` method.
-+ Removed the deprecated `LLMS_Generator::get_default_post_status()` method.
-+ Removed the deprecated `LLMS_Generator::get_generated_posts()` method.
-+ Removed the deprecated `LLMS_Generator::increment()` method.
-+ Removed the deprecated `llms__created` action hook from the `LLMS_Abstract_Database_Store::create()` method.
-+ Removed the deprecated `llms__deleted` action hook from the `LLMS_Abstract_Database_Store::delete()` method.
-+ Removed the deprecated `llms__updated` action hook from the `LLMS_Abstract_Database_Store::update()` method.
-+ Removed the deprecated `llms_user_removed_from_membership_level` action hook from the `LLMS_Student::unenroll()` method.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `lifterlms_template_pricing_table()` function.
-+ Removed the deprecated and misspelled `$purchaseable` global variable in the `templates/product/pricing-table.php` file.
-+ Removed the deprecated `LLMS_Frontend_Password` class.
-+ Removed the deprecated `LLMS_Install::db_updates()` method.
-+ Removed the deprecated `LLMS_Install::update_notice()` method.
-+ Removed the deprecated `LLMS_Notifications::dispatch_processors()` method.
-+ Removed the deprecated `llms_processors_async_dispatching` filter hook from the `LLMS_Notifications::__construct()` method.
-+ Removed the deprecated `LLMS_Notifications::$_instance` property.
-+ Removed the deprecated `LLMS_Person_Handler::register()` method.
-+ Removed the deprecated `LLMS_Person_Handler::sanitize_field()` method.
-+ Removed the deprecated `LLMS_Person_Handler::update()` method.
-+ Removed the deprecated `LLMS_Person_Handler::validate_fields()` method.
-+ Removed the deprecated `LLMS_Person_Handler::voucher_toggle_script()` method.
-+ Removed the deprecated `templates/admin/notices/db-update.php` file.
-+ Removed the deprecated `templates/admin/notices/db-updating.php` file.
-+ Removed the deprecated `llms_usernames_blacklist` filter hook in the `llms_get_usernames_blocklist()` function.
-+ Removed the deprecated `includes/libraries/wp-background-processing/index.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-async-request.php` file.
-+ Removed the deprecated `includes/libraries/wp-background-processing/wp-background-process.php` file.
-+ Removed the deprecated `LLMS_Section::get_next_available_lesson_order()` method.
-+ Removed the deprecated `LLMS_Section::get_order()` method.
-+ Removed the deprecated `LLMS_Section::get_parent_course()` method.
-+ Removed the deprecated `LLMS_Section::set_parent_course()` method.
-+ Removed the deprecated `LLMS_AJAX::get_ajax_data()` method.
-+ Removed the deprecated `LLMS_AJAX::register_script()` method.
-+ Removed the deprecated `LLMS_Interface_Post_Audio` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Sales_Page` interface.
-+ Removed the deprecated `LLMS_Interface_Post_Video` interface.
-+ Removed the deprecated `LLMS_Achievements::$_instance` property.
-+ Removed the deprecated `LLMS_Certificates::$_instance` property.
-+ Removed the deprecated `LLMS_Emails::$_instance` property.
-+ Removed the deprecated `LLMS_Engagements::$_instance` property.
-+ Removed the deprecated `LLMS_Events::$_instance` property.
-+ Removed the deprecated `LLMS_Grades::$_instance` property.
-+ Removed the deprecated `LLMS_Integrations::$_instance` property.
-+ Removed the deprecated `LLMS_Payment_Gateways::$_instance` property.
-+ Removed the deprecated `LLMS_Processors::$_instance` property.
-+ Removed the deprecated `LLMS_Sessions::$_instance` property.
-
-
-v5.7.0 - 2022-01-11
--------------------
-
-##### Updates and Enhancements
-
-+ Informed developers about the deprecated `LLMS_Section::get_next_available_lesson_order()` method.
-+ Informed developers about the deprecated `LLMS_Section::get_order()` method.
-+ Informed developers about the deprecated `LLMS_Section::get_parent_course()` method.
-+ Informed developers about the deprecated `LLMS_Section::set_parent_course()` method.
-
-##### Deprecations
-
-+ Deprecated `LLMS_Frontend_Assets::enqueue_inline_pw_script()` with no replacement.
-+ Deprecated the `LLMS_Lesson::get_order()` method in favor of the `LLMS_Lesson::get( 'order' )` method.
-+ Deprecated the `LLMS_Lesson::get_parent_course()` method in favor of the `LLMS_Lesson::get( 'parent_course' )` method.
-+ Deprecated the `LLMS_Lesson::set_parent_course()` method in favor of the `LLMS_Lesson::set( 'parent_course', $course_id )` method.
-+ Deprecated the `LLMS_AJAX_Handler::add_lesson_to_course()` method with no replacement.
-+ Deprecated the `LLMS_AJAX_Handler::create_lesson()` method with no replacement.
-+ Deprecated the `LLMS_AJAX_Handler::create_section()` method with no replacement.
-+ Deprecated the `LLMS_Lesson_Handler::assign_to_course()` method with no replacement.
-+ Deprecated the `LLMS_Post_Handler::create_section()` method with no replacement.
-
-##### Updated Templates
-
-+ [templates/course/lesson-navigation.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/course/lesson-navigation.php)
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/course/lesson-preview.php)
-+ [templates/course/parent-course.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/course/parent-course.php)
-
-
-v6.0.0-alpha.2 - 2022-01-04
----------------------------
-
-##### New Features
-
-+ Added certificate global options for the default size of new certificates and certificate templates.
-
-##### Updates and enhancements
-
-+ The site options `lifterlms_certificate_bg_img_width`,
-`lifterlms_certificate_bg_img_height`, and
-`lifterlms_certificate_legacy_image_size` are now used only for certificates
-and certificate templates created using the classic editor.
- + The settings, found on the Engagements Settings screen, are hidden by default.
- + During the database upgrade from versions earlier than 6.x, an site option, `llms_has_legacy_certificates` is added when at least one certificate is found. This option will display the settings so they can continue to be used for legacy certificates.
- + After migrating all certificates on a site, the settings will still display. In order to remove them from the screen a developer can either delete the option `llms_has_legacy_certificates` or return `false` from the filter `llms_has_legacy_certificates`.
-+ Restore certificate save hooks after executing callback updates to facilitate scenarios where more than one certificate is updated in a single request.
-
-##### Bug Fixes
-
-+ Only register the Certificate Title block for use on certificate post types.
-
-##### Updated Templates
-
-+ [templates/certificates/content-legacy.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/content-legacy.php)
-
-
-v6.0.0-alpha.1 - 2021-12-28
----------------------------
-
-**This version is an unstable pre-release! We strongly advise against installing this in a production environment.**
-
-##### New Features
-
-+ The block editor is now enabled by default for certificates.
- + Existing certificates are marked as "legacy" and will continue to use the classic editor until migrated.
- + To migrate a certificate, click the "Migrate Certificate" button. This will force the certificate's content into blocks.
-+ A number of new settings are available to certificates when using the block editor:
- + Set the certificate's display (and print) size using common paper sizes such as US Letter, US Legal, A3, A4, and more.
- + Set the certificate's display orientation: portrait of landscape.
- + Set the certificate's inner margins.
- + Set the certificate's background color.
-+ A new block, the Certificate Title Block, has been made available to certificates.
- + The block works like a WordPress core Heading Block with added options for selecting from a few display fonts (provided by Google Web Fonts).
- + The block controls the title of awarded certificates.
-+ + Added the ability to sync awarded certificates with the template used to generate them. [#1078](https://github.com/gocodebox/lifterlms#1078)
-+ The `post_name` of earned certificate posts will be generated with a randomized 3+ character string in favor of relying on sequential numbers.
-+ Added the ability for administrators and LMS managers to edit earned certificates/achievements from the students reporting screen, as well as award new certificates/achievements to students.
-+ Certificate and email template merge code buttons now include [llms-user] information shortcodes.
-+ Added certificate sequential ID functionality merge code. [Read more](@TODO).
-
-##### Updates and Enhancements
-
-+ Added pagination to achievement and certificate reporting pages.
-+ Certificates no longer use the `header.php` and `footer.php` files from the site's theme, instead custom templates (`templates/certificates/header.php` and `templates/certificates/footer.php`) are used instead. These templates are minimal and exclude theme wrappers which reduces the visual conflicts encountered from theme wrappers, backgrounds, and more, especially when printing certificates. [#463](https://github.com/gocodebox/lifterlms#463)
-+ The achievements and certificates dashboard endpoints are now paginated. [#669](https://github.com/gocodebox/lifterlms#669)
-+ Added new default images for use with achievements and certificates.
- + The site-wide default images can be customized on the admin panel under Settings -> Engagements.
- + The old default images can be used by filtering `llms_use_legacy_engagement_images`. [#1081](https://github.com/gocodebox/lifterlms#1081)
-+ The URL of earned user certificates has been changed from "my_certificate" to "certificate". Requests to the old url are automatically redirected to the new url, including instances where the URL slug has been translated.
-+ The URL of certificate template previews has been changed from "certificate" to "certificate-template".
-+ The certificate merge code, `{first_name}`, now outputs an empty string in favor of falling back to the user's nickname when there is no first name for the user. [#1640](https://github.com/gocodebox/lifterlms#1640)
-+ Updates LifterLMS REST to [v1.0.0-beta.22](https://make.lifterlms.com/2021/12/15/lifterlms-rest-api-version-1-0-0-beta-22/).
-
-##### Bug Fixes
-
-+ Delayed engagements are automatically unscheduled when the related post is deleted.
-+ Prior to sending a delayed engagement the recipient's enrollment in the related post is verified resulting the engagement not being triggered if the recipient's enrollment has been terminated. [#290](https://github.com/gocodebox/lifterlms#290)
-+ A disabled student dashboard endpoint will no longer display the endpoint's summary on the main dashboard page. [#535](https://github.com/gocodebox/lifterlms#535)
-+ Post search filter boxes on various post tables will now longer display a link to the selected post.
-+ Basic notification code is no longer loaded on the admin panel.
-
-##### Deprecations
-
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Achievement::format_string()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_title()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content()` is deprecated with no replacement.
- + Method `LLMS_Achievement::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Achievement::create()` is deprecated with no replacement.
-+ Method `LLMS_Achievments::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement()`.
-+ Class `LLMS_Certificate` is deprecated with no direct replacement.
- + Method `LLMS_Certificate::is_enabled()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_blogname()` is deprecated with no replacement.
- + Method `LLMS_Certificate::format_string()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate::get_title()` is deprecated with no replacement.
-+ Method `LLMS_Certificates::trigger_engagement()` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate()`.
-+ Method `LLMS_Engagements::init()` is deprecated with no replacement.
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Method `LLMS_Database_Query::set_found_results()` is deprecated.
-+ Class `LLMS_Achievement_User` is deprecated with no direct replacement.
- + Method `LLMS_Achievement_User::has_user_earned()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::init()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Achievement_User::get_content_html()` is deprecated with no replacement.
-+ Class `LLMS_Certificate_User` is deprecated with no direct replacement.
- + Method `LLMS_Certificate_User::init()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::trigger()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::get_content_html()` is deprecated with no replacement.
- + Method `LLMS_Certificate_User::set_shortcode_user()` is deprecated with no replacement.
-+ Engagement debug logging is removed. Use `llms_log()` directly instead.
-+ Filter `llms_db_query_get_default_args` is deprecated in favor of `llms_{$this->id}_query_get_default_args`.
-+ Filter `llms_certificate_has_user_earned` is deprecated in favor of `llms_earned_certificate_dupcheck`.
-+ Unused public class property `LLMS_Achievements::$content` is deprecated with no replacement.
-+ Method `LLMS_Engagements::handle_certificate` is deprecated in favor of `LLMS_Engagement_Handler::handle_certificate`. [#290](https://github.com/gocodebox/lifterlms#290)
-+ Method `LLMS_Engagements::handle_achievement` is deprecated in favor of `LLMS_Engagement_Handler::handle_achievement`. [#290](https://github.com/gocodebox/lifterlms#290)
-+ The constant `LLMS_ENGAGEMENT_DEBUG` is deprecated with no replacement.
-+ Engagement debugging via `LLMS_Engagements::log` is deprecated. Use `llms_log()` instead.
-+ Method `LLMS_Engagements::handle_email` is deprecated in favor of `LLMS_Engagement_Handler::handle_email`.
-+ Filter `lifterlms_register_post_type_llms_my_certificate` is deprecated in favor of `lifterlms_register_post_type_my_certificate`.
-+ Deprecated the misspelled protected method `LLMS_Database_Query::preprare_query()` and replaced with `LLMS_Database_Query::prepare_query()`.
- + Class method `LLMS_Events_Query::preprare_query` replaced with `LLMS_Events_Query::prepare_query()`.
- + Class method `LLMS_Query_Quiz_Attempt::preprare_query` replaced with `LLMS_Query_Quiz_Attempt::prepare_query()`.
- + Class method `LLMS_Query_User_Postmeta::preprare_query` replaced with `LLMS_Query_User_Postmeta::prepare_query()`.
- + Class method `LLMS_Student_Query::preprare_query` replaced with `LLMS_Student_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`.
- + Class method `LLMS_Notifications_Query::preprare_query` replaced with `LLMS_Notifications_Query::prepare_query()`. [#859](https://github.com/gocodebox/lifterlms#859)
-
-##### Developer Notes
-
-+ Added `LLMS_Awards_Query`, used for querying data about awarded certificates and achievements.
- + The method signature `LLMS_Student::get_achievements()` and `LLMS_Student::get_certificates()` now use this class under tho hood.
- + The previous method signature, which passed data into a direct SQL query, is now deprecated.
-+ Achievement and certificate data storage locations have been modified, primarily to reduce reliance on the `wp_postmeta` table which will result in a site-wide performance improvement, especially on large sites.
- + Meta properties `_llms_achievement_content` and `_llms_certificate_content` have been removed in favor of `WP_Post::$post_content`.
- + Meta properties `_llms_achievement_title` and `_llms_certificate_title` have been removed in favor of `WP_Post::$post_title`.
- + Meta properties `_llms_achievement_template` and `_llms_certificate_template` have been removed in favor of `WP_Post::$post_parent`.
- + Meta properties `_llms_achievement_image` and `_llms_certificate_image` have been moved the meta property `_thumbnail_id` in order to utilize the WordPress core's featured image functionality and internal APIs.
-+ Reliance on `lifterlms_user_postmeta` for achievement and certificate data will be removed in a future release.
- + User postmeta properties `_achievement_earned` and `_certificate_earned` will continue to be recorded but are no longer being used internally.
- + The `updated_date` is now accessible via `WP_Post::$post_date`.
- + The `user_id` is now accessible via `WP_Post::$post_author`.
-+ Added new Javascript UI components library, modeled after `@wordpress/components`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/components).
-+ Added a new SVG icon library, modeled after `@wordpress/icons`. [Read more](https://github.com/gocodebox/lifterlms/tree/dev-600/packages/icons).
-+ The merge code button seen on certificate and email template editors is now an SVG image instead of a PNG.
-+ Added utility function for escaping and quoting strings. [#1027](https://github.com/gocodebox/lifterlms#1027)
-+ Added new utility function for stripping prefixes from strings.
-
-##### Updated Templates
-
-+ [templates/achievements/loop.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/achievements/loop.php)
-+ [templates/achievements/template.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/achievements/template.php)
-+ [templates/admin/reporting/tabs/students/information.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/admin/reporting/tabs/students/information.php)
-+ [templates/certificates/actions.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/actions.php)
-+ [templates/certificates/content-legacy.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/content-legacy.php)
-+ [templates/certificates/content.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/content.php)
-+ [templates/certificates/dynamic-styles.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/dynamic-styles.php)
-+ [templates/certificates/footer.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/footer.php)
-+ [templates/certificates/header.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/header.php)
-+ [templates/certificates/loop.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/loop.php)
-+ [templates/certificates/preview.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/preview.php)
-+ [templates/certificates/template.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/certificates/template.php)
-+ [templates/content-certificate.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/content-certificate.php)
-+ [templates/single-certificate.php](https://github.com/gocodebox/lifterlms/blob/trunk/templates/single-certificate.php)
-
-
-v5.6.0 - 2021-12-07
--------------------
-
-##### New Features
-
-+ Added an option to prevent users (by role) from copying site content and saving local copies of images.
-+ Added new site setting to disallow concurrent user sessions for specified user roles.
-
-##### Updates and Enhancements
-
-+ Updates LifterLMS REST to [v1.0.0-beta.21](https://make.lifterlms.com/2021/12/07/lifterlms-rest-api-version-1-0-0-beta-21/).
-
-##### Developer Notes
-
-+ Database migration functions can now be namespaced, eliminating the need to prefix update function names with a version number.
-
-
-v5.5.0 - 2021-11-05
--------------------
-
-##### New Features
-
-+ Includes the LLMS-CLI beta, a set of WP-CLI commands for LifterLMS and LifterLMS add-ons, as part of the core plugin:
- + To get started, run `wp llms --help` in your terminal or read the [online command documentation](https://developer.lifterlms.com/cli/commands/llms/).
- + Please note that the LLMS-CLI is included as a public beta feature. The command API is in a pre-release state and, as such, is subject to change without warning.
- + If you encounter any issues or wish to provide feedback on the LLMS-CLI please get in touch at [https://github.com/gocodebox/lifterlms-cli](https://github.com/gocodebox/lifterlms-cli).
-
-##### Bug Fixes
-
-+ Fix AJAX post search when using search queries containing quotes.
-
-##### Deprecations
-
-+ The `lifterlms_register_post_type_llms_engagement` is deprecated in favor of `lifterlms_register_post_type_engagement`.
-+ The `lifterlms_register_post_type_llms_achievement` is deprecated in favor of `lifterlms_register_post_type_achievement`.
-+ The `lifterlms_register_post_type_llms_certificate` is deprecated in favor of `lifterlms_register_post_type_certificate`.
-+ The `lifterlms_register_post_type_llms_my_certificate` is deprecated in favor of `lifterlms_register_post_type_my_certificate`.
-+ The `lifterlms_register_post_type_llms_email` is deprecated in favor of `lifterlms_register_post_type_email`.
-+ The `lifterlms_register_post_type_llms_coupon` is deprecated in favor of `lifterlms_register_post_type_coupon`.
-+ The `lifterlms_register_post_type_llms_voucher` is deprecated in favor of `lifterlms_register_post_type_voucher`.
-
-##### Developer Notes
-
-+ The `llms-addons` style asset no longer ships an unminified version.
-+ The `llms-admin-add-ons` style asset no longer ships an unminified version and the filename of the distributed file has changed.
-+ All the LifterLMS post types are now registered using the static method `LLMS_Post_Types::register_post_type()`.
-+ Upgraded woocommerce/action-scheduler to [v3.4.0](https://github.com/woocommerce/action-scheduler/releases/tag/3.4.0).
-
-
-v5.4.1 - 2021-10-26
--------------------
-
-##### Bug fixes
-
-+ Exclude internal-use-only properties (related to reporting caches and student counts) when exporting or cloning courses. [#1532](https://github.com/gocodebox/lifterlms/issues/1532)
-+ Don't sanitize input from user forms until validation has succeeded. [#1829](https://github.com/gocodebox/lifterlms/issues/1829.)
-+ Fixed an issue encountered when fields are removed from reusable blocks, causing some user forms from functioning as expected. [#1832](https://github.com/gocodebox/lifterlms/issues/1832)
-
-
-v5.4.0 - 2021-10-14
--------------------
-
-##### Updates
-
-+ Added logic to prevent the permanent deletion of courses or memberships with active subscriptions.
-+ When a subscription attempts to charge a recurring payment against a deleted course or membership the transaction will be cancelled and the order marked as failed.
-+ Updates LifterLMS Blocks to [v2.2.1](https://make.lifterlms.com/2021/09/29/lifterlms-blocks-version-2-2-1/).
-+ Updates LifterLMS REST to [v1.0.0-beta.20](https://make.lifterlms.com/2021/10/11/lifterlms-rest-api-version-1-0-0-beta-20/).
-
-##### Bug fixes
-
-+ Fixed issue encountered when cloning lessons with attached assignments.
-+ Fixed an error encountered when viewing an order for a deleted course or membership on the student dashboard.
-
-##### Templates Updated
-
-+ templates/myaccount/view-order.php
-
-
-v5.3.3 - 2021-10-05
--------------------
-
-##### Updates
-
-+ Update woocommerce/actions-scheduler to version 3.3.0.
-
-##### Bug fixes
-
-+ Fixed an issue causing the latest earned achievement to not display on the "My Grades" tab in certain scenarios.
-+ Fix issue causing a `waiting...` message to display on the JS dev console.
-+ Fix improper usage of `apply_filters_deprecated()` encountered when using deprecated theme settings filters in the course builder.
-+ Fixed missing text domain, thanks [chetansatasiya](https://github.com/chetansatasiya)!
-
-##### Developer notes
-
-+ Improved the `LLMS.waitFor()` runtime JS dependency loader to output improved debugging information.
-
-
-v5.3.2 - 2021-09-21
--------------------
-
-##### Updates
-
-+ Updated the SendWP integration account management URL.
-
-##### Bug fixes
-
-+ Fixed issue encountered with TinyMCE editor instances in repeater metabox groups.
-+ Fixed issue causing the latest achievement to not display when reviewing grades on the student dashboard.
-
-
-v5.3.1 - 2021-09-13
--------------------
-
-##### Bug fixes
-
-+ Fixed quote slashing for non-admin roles when editing content in the course builder.
-+ The LifterLMS admin icon now uses an encoded SVG to improve admin color scheme compatibility.
-+ Fixed an issue with empty admin notices.
-
-##### Dev updates
-
-+ The creation date of `llms_orders` is now determined by `llms_current_time()`.
-
-
-v5.3.0 - 2021-08-31
--------------------
-
-##### Updates
-
-+ Improved logic used to determine when a limited length subscription has completed its payment schedule.
-+ Improved accessibility of various icon buttons on the admin orders view/edit screen.
-+ Improved display of quiz attempts containing questions which have been deleted from the database.
-+ POT files from included library plugins (like LifterLMS REST) are now excluded from LifterLMS distributions.
-
-##### Development updates
-
-+ Introduced `LLMS_Trait_Singleton` to replace redundant singleton pattern definitions across classes in the codebase.
-+ Moved the loading of the autoloader to the main `lifterlms.php` file.
-+ Updated the `LLMS_Payment_Gateway` abstract class to utilize `LLMS_Abstract_Options_Data` for accessing gateway options.
-+ Audio and video embed methods shared by `LLMS_Course` and `LLMS_Membership` have been relocated to `LLMS_Trait_Audio_Video_Embed`.
-+ Sales page methods shared by `LLMS_Course` and `LLMS_Membership` have been relocated to `LLMS_Trait_Sales_Page`.
-
-##### Bug Fixes
-
-+ Fixed a visual issue encountered on the payment confirmation screen on small screens / mobile devices.
-+ Fix untranslatable time period strings (day, week, month, and year) found on the admin orders view/edit screen.
-+ Fixed an error encountered when attempting to grade a quiz attempt containing deleted questions.
-
-##### Deprecations
-
-+ Removed usage and references to the `LLMS_Order` post meta property `date_billing_end`. To determine if a subscription has ended, use `LLMS_Order::get_remaining_payments()` instead.
-+ Removed private method `LLMS_Order::calculate_billing_end_date()`.
-+ Deprecated the class property `$_instance` from the following classes, use the public method `instance()` instead:
- + `LLMS_Achievements`
- + `LLMS_Certificates`
- + `LLMS_Emails`
- + `LLMS_Engagements`
- + `LLMS_Events`
- + `LLMS_Grades`
- + `LLMS_Integrations`
- + `LLMS_Notifications`
- + `LLMS_Payment_Gateways`
- + `LLMS_Processors`
- + `LLMS_Sessions`
-
-##### Templates Updated
-
-+ templates/checkout/form-confirm-payment.php
-+ templates/admin/reporting/tabs/quizzes/attempt.php
-+ templates/quiz/results-attempt-questions-list.php
-
-
-v5.2.1 - 2021-08-17
--------------------
-
-##### Updates
-
-+ [LifterLMS Helper Version 3.4.1](https://make.lifterlms.com/2021/08/17/lifterlms-helper-version-3-4-1/).
-+ Made minor development-related changes to the `LLMS_Order` class.
-
-##### Bug Fixes
-
-+ Fixed an issue encountered when a course or membership sales page redirect is enabled but no URL is saved.
-
-
-v5.2.0 - 2021-08-10
--------------------
-
-##### Upcoming Payment Reminder Notification
-
-+ A new notification, the "Upcoming Payment Reminder" notification has been added. This notification sends a reminder to students a configurable number of days before a payment is do for a recurring subscription.
-+ When upgrading to version 5.2.0, this notification will be automatically *disabled*, visit LifterLMS -> Settings -> Notifications and select the new notification to enable it after upgrading.
-+ Props to [@niluzok](https://github.com/niluzok) for doing the initial work required to build this notification!
-
-##### Updates
-
-+ Reworked the database upgrader script to allow for minor upgrades which don't require significant data migration to upgrade silently without requiring user consent to initiate.
-+ Improved internal methods used to generate tables in the body of email notifications.
-
-##### Bug Fixes
-
-+ Student registration date is now displayed in the site's timezone in favor of UTC time.
-+ Properly pass options `template_path` and `default_path` to the template handler when creating an admin notice using a template.
-+ Removed translation (and incorrect text domain) from a logging function encountered when a recurring payment errors as a result of the payment gateway having been deactivated.
-
-##### Deprecations
-
-+ `LLMS_Install::db_updates()` is deprecated, use ``LLMS_DB_Upgrader::enqueue_updates()` instead.
-+ `LLMS_Install::update_notice()` is deprecated with no replacement.
-+ Template `admin/notices/db-update.php` is deprecated in favor of `includes/admin/views/db-update.php`.
-+ Template `admin/notices/db-updating.php` is deprecated with no replacement.
-
-
-v5.1.3 - 2021-08-04
--------------------
-
-+ Bugfix: Fixed an issue where a white box would be output over the certificate background image.
-+ Bugfix: Fixed an issue in the course builder causing lessons to be orphaned from a course when moved into an unsaved section.
-+ [LifterLMS Helper Version 3.4.0](https://make.lifterlms.com/2021/08/04/lifterlms-helper-version-3-4-0/)
-
-
-v5.1.2 - 2021-07-28
--------------------
-
-+ Bugfix: Pass second parameter to the `get_the_excerpt` filter.
-+ Fix: Corrected typos in error messages encountered during password reset.
-
-
-v5.1.1 - 2021-07-26
--------------------
-
-+ Bugfix: Fixed a bug causing malformed character codes to be rendered in forms when installing forms with translated labels.
-+ [LifterLMS Helper version 3.3.1](https://make.lifterlms.com/2021/07/26/lifterlms-helper-version-3-3-1/)
-
-
-v5.1.0 - 2021-07-19
--------------------
-
-##### Updates
-
-+ **Raised the minimum required WordPress core version to 5.8!**
-+ Adds WordPress core 5.8 compatibility.
-+ Improved user information forms required field validation.
-+ Added functionality to ensure that user email and password fields are *always* displayed to logged out users on checkout and registration forms.
-+ Added functionality to ensure that user email and password fields are *always* displayed on the account edit form.
-+ [LifterLMS Blocks version 2.2.0](https://make.lifterlms.com/2021/07/19/lifterlms-blocks-version-2-2-0/)
-
-##### Bug fixes
-
-+ Fixed an issue preventing certain orphaned quizzes from being deleted.
-+ Prevent users from submitting a password change without submitting their current password.
-+ Allow logged in users to checkout when no form fields are set to display.
-
-
-v5.0.2 - 2021-07-08
--------------------
-
-##### LifterLMS Blocks
-
-+ Upgraded to [version 2.1.1](https://make.lifterlms.com/2021/07/08/lifterlms-blocks-version-2-1-1/).
-
-##### Bug Fixes
-
-+ Fixed issue with non-Latin characters in dashboard endpoint URL slugs.
-+ Fixed issue preventing address localization when using the [lifterlms_registration] shortcode.
-
-
-v5.0.1 - 2021-06-28
--------------------
-
-##### Updates
-
-+ Update to [LifterLMS Blocks v2.1.0](https://make.lifterlms.com/2021/06/28/lifterlms-blocks-version-2-1-0/).
-+ Added a new filter to allow programmatically alter required field validation results.
-
-##### Bugfixes
-
-+ Fixed an issue causing preventing form layout options from working when passed into shortcodes.
-+ Fixed an issue preventing custom radio, select, and dropdown fields from working during checkout.
-+ Fixed an accessibility issue encountered during password strength validation.
-
-
-v5.0.0 - 2021-06-22
--------------------
-
-##### User Information Form Builder
-
-+ Customize all user information collection forms using the block editor for drag and drop and WYSIWYG form building.
-+ Customize field labels, placeholders, descriptions and more with an easy point and click interface.
-+ Determine if fields are required or optional with a simple toggle switch.
-+ Update the form layout with the block editor. Reorder fields, add columns, and more with a simple drag and drop interface.
-+ Remove unwanted fields with the click of a button.
-
-##### User Location Information Form Fields
-
-+ During user account creation and updates the user location fields are now locale aware ensuring that the proper terminology is used and only locale-required fields are displayed for the selected locale.
-+ The "Country" field has been updated to be automatically populated with a list of countries. View the full list in the file at `languages/countries.php` and the filter `lifterlms_countries` can be used to modify the default list at runtime.
-+ The "State" field on user forms has been updated to be automatically populated with a list of states (provinces or regions) for the selected country. This list of states can be found in the file at `languages/states.php` and the filter `lifterlms_states` can be used to modify the default list at runtime.
-+ Both "Country" and "State" fields are now searchable dropdowns elements.
-+ The lists of countries and states will be automatically updated during future releases based on information provided by [GeoNames](https://www.geonames.org/) APIs.
-
-##### Mergecodes everywhere via new `[llms-user]` shortcode
-
-+ Allows merging most user information field data into any post or page, email, or notification (as well as widgets and more).
-
-##### Updates
-
-+ Email and password confirmation fields may now be made optional.
-+ "User Information Options" have been largely removed in favor of determining which fields are displayed via the forms UI
-+ The former "User Information Options" settings area has been renamed to "User Privacy Options".
-+ Removed email lookup logic since `wp_authenticate()` supports email addresses as `user_login` since WP 4.5.
-+ Custom user fields added via filters are now displayed on the admin panel at priority 11 instead of 10.
-+ Added shortcode processing in LifterLMS-generated emails.
-+ If a symbol cannot be found for the supplied currency code, return the code instead of an empty string.
-
-##### Bug Fixes
-
-+ Changed the filter on return of `LLMS_Person_Handler::get_password_reset_fields()` from `lifterlms_lost_password_fields` to `llms_password_reset_fields`.
-+ Fixed duplicate references to the `llms-select2` script.
-
-##### Development changes
-
-+ Added before and after actions hooks for admin tools.
-+ The filter `lifterlms_before_user_${action}` is now triggered by `do_action_ref_array()` instead of `do_action()` allowing modification of `$posted_data` and `$fields` via hooks.
-+ A number of action and filter hooks have been moved to new locations within the codebase. They will continue to function as expected (with some minor exceptions).
-+ Enqueue select2 on account and checkout pages for searchable dropdowns for country & state.
-+ Stop loading removed processor "table_to_csv".
-
-##### Library & Vendor Updates
-
-+ Updates LifterLMS Blocks to version 2.0.1.
-+ Updates woocommerce/actions-scheduler to version 3.2.1.
-+ Load core libraries from new location and load WP Background Processing lib.
-+ The vendor script dependency `topModal.js` has been removed.
-
-##### Templates Updated
-
-+ templates/checkout/form-checkout.php
-+ templates/checkout/form-confirm-payment.php
-+ templates/checkout/form-gateways.php
-+ templates/global/form-login.php
-+ templates/global/form-registration.php
-+ templates/myaccount/form-edit-account.php
-+ templates/product/free-enroll-form.php
-
-##### Deprecations
-
-The following have been deprecated and will be removed from LifterLMS in a major update following version 5.0.0.
-
-+ Class Method: `LLMS_Person_Handler::get_available_fields()` is deprecated in favor of `LLMS_Forms::get_form_fields()`.
-+ Class Method: `LLMS_Person_Handler::register()` is deprecated, in favor of `llms_register_user()`.
-+ Class Method: `LLMS_Person_Handler::sanitize_field()` (private method) is deprecated with no replacement.
-+ Class Method: `LLMS_Person_Handler::update()` is deprecated, in favor of `llms_update_user()`.
-+ Class Method: `LLMS_Person_Handler::validate_fields()` is deprecated with no replacement.
-+ Class Method: `LLMS_Person_Handler::voucher_toggle_script()` is deprecated with no replacement.
-+ Filter: `llms_usernames_blacklist` is deprecated, use `llms_usernames_blocklist` instead.
-+ Filter: `lifterlms_get_user_custom_fields` is deprecated with no replacement.
-+ Function: `llms_get_minimum_password_strength()` is deprecated with no replacement.
-+ Option: `lifterlms_registration_generate_username` is deprecated in favor of the new method `LLMS_Forms::are_usernames_enabled()`.
-
-##### Removed Items
-
-+ Private method `LLMS_Processors::includes()` has been removed.
-+ Private methods `LLMS_Person_Handler::fill_fields()` and `LLMS_Person_Handler::insert_data()` were removed.
-+ Previously deprecated class method `LLMS_Quiz::get_lessons()` has been removed.
-+ Previously deprecated class method `LLMS_Controller_Quizzes::take_quiz()` has been removed.
-+ Previously deprecated class `LLMS_Processor_Table_To_Csv` has been removed.
-
-
-v5.0.0-rc.2 - 2021-06-18
-------------------------
-
-+ Remove password description merge codes from reusable block schema.
-+ Explicitly define required field attributes on reusable block schema.
-+ Requires WP 5.7 or later to edit forms & show an upgrade nudge when requirements are not met.
-+ Add a link from the (now) legacy account settings area to help experienced users find the new form building area
-+ Add a (subtle) custom fields add-on upgrade nudge when viewing the forms list on the admin panel
-+ Update LifterLMS Blocks to 2.0.0-rc.2
-
-
-v5.0.0-rc.1 - 2021-06-15
-------------------------
-
-+ Updates Action Scheduler library to version 3.2.0
-+ Remove the {min_strength} and {min_length} merge codes from the User Password block description.
-+ Don't load removed files during OptimizePress compatibility.
-+ Add a 5.0.0 DB upgrade routine and welcome notice
-+ Add the LifterLMS Helper as an included library
-+ Add WordPress 5.8 compatibility on the Widgets and Customizer screens.
-+ Move form location definitions into a schema file
-+ Require WordPress 5.7+ to manage forms via the block editor
-+ Upgrades LifterLMS Blocks to 2.0.0-rc.1
-
-
-v5.0.0-beta.2 - 2021-06-01
----------------------------
-
-+ Updates LifterLMS Blocks to 2.0.0-beta.6.
-+ (Re-)introduces the user information shortcode as `[llms-user]`.
-+ Add Admins status tool to reinstall core forms & reusable blocks.
-+ Fixed issue causing data from conditionally disabled fields (like state) from being cleared during form submission
-+ Updated form post type labels and added missing labels
-+ Removed the previously deprecated class `LLMS_Frontend_Forms` and it's deprecated class methods `reset_password()` and `voucher_check()`.
-+ Removed the previously deprecated class `LLMS_Frontend_Password` and it's deprecated class methods: `retrieve_password()`, `check_password()`, and `reset_password()`.
-+ Updated country and state localization lists.
-
-
-v5.0.0-beta.1 - 2021-05-19
----------------------------
-
-+ LifterLMS Blocks 2.0.0-beta.5
-+ Added site-wide field name validation
-+ Reworked the output of user information fields on the admin panel to share a handler and APIs with frontend fields.
-+ Deprecated filter: `lifterlms_get_user_custom_fields` in favor of `llms_admin_profile_fields`
-+ Improved previewing of form posts using WP Core block editor UI elements
-+ Open Registration form can now always be previewed regardless of the open registration site setting
-
-
-v5.0.0-alpha.6 - 2021-05-07
----------------------------
-
-+ LifterLMS Blocks 2.0.0-beta.4
-+ Fix default reusable password field type from plain text to password
-+ Change the default reusable block post titles to reduce confusion when searching for blocks in the editor
-
-
-v5.0.0-alpha.5 - 2021-05-03
----------------------------
-
-+ Reorganized new files into subdirectories.
-+ Added serverside password minimum length validation.
-+ Fix duplicate password strength meter output.
-+ Fix the user password field type from text to password
-+ Fix the phone number field type from text to tel
-+ Fix user state select field
-+ Don't autoload field values from specified datastore when a "value" is explicitly passed to the field.
-+ Only load published reusable blocks on the frontend of the website
-+ Improved the UX for editing a users account by automatically "hiding" password and email fields and only requiring them to be submitted when users explicit request an update via the field's "change" toggle button.
-
-
-v5.0.0-alpha.4 - 2021-04-26
----------------------------
-
-+ Default form templates now use reusable blocks.
-+ Improved the user experience surrounding fields with a confirmation field (email address and password).
-+ Added the ability to define a field's column width instead of requiring the usage of WP column blocks.
-+ Added support for reusable blocks on form posts
-+ Upgraded LifterLMS Blocks to 2.0.0-beta.3.
-
-
-v5.0.0-alpha.3 - 2021-03-23
----------------------------
-
-+ Fixed issue preventing users from editing their email address and password on the dashboard account edit screens.
-+ Fixed issues with country names with the article "the" in their name, for example "The Netherlands" instead of "Netherlands The".
-+ Upgraded LifterLMS Blocks to version 2.0.0-beta.2.
-
-
-v5.0.0-alpha.2 - 2021-03-22
----------------------------
-
-##### Updates
-
-+ Updates LifterLMS Blocks to version 2.0.0-beta.1
-+ Adds functionality to force usage of the Block Editor for editing LifterLMS forms
-+ Updates localization functionality and methods to have more accurate information.
-+ Added a function for determining if open registration is enabled.
-+ Added a WP Admin Bar link below the "Edit Page" link to enable editing the form (if a form exists on the page).
-
-##### Bug Fixes
-
-+ Fixed an issue encountered when custom HTML fields exist on a form (backwards compatibility for pre 5.x fields API).
-
-
-v5.0.0-alpha.1 - 2021-01-07
----------------------------
-
-##### User Information Form Builder
-
-+ Customize all user information collection forms using the block editor for drag and drop and WYSIWYG form building.
-+ Customize field labels, placeholders, descriptions and more with an easy point and click interface.
-+ Determine if fields are required or optional with a simple toggle switch.
-+ Update the form layout with the block editor. Reorder fields, add columns, and more with a simple drag and drop interface.
-+ Remove unwanted fields with the click of a button.
-
-##### User Location Information Form Fields
-
-+ During user account creation and updates the user location fields are now locale aware ensuring that the proper terminology is used and only locale-required fields are displayed for the selected locale.
-+ The "Country" field has been updated to be automatically populated with a list of countries. View the full list in the file at `languages/countries.php` and the filter `lifterlms_countries` can be used to modify the default list at runtime.
-+ The "State" field on user forms has been updated to be automatically populated with a list of states (provinces or regions) for the selected country. This list of states can be found in the file at `languages/states.php` and the filter `lifterlms_states` can be used to modify the default list at runtime.
-+ Both "Country" and "State" fields are now searchable dropdowns elements.
-+ The lists of countries and states will be automatically updated during future releases based on information provided by [GeoNames](https://www.geonames.org/) APIs.
-
-##### Mergecodes everywhere via new `[user]` shortcode
-
-+ TODO.
-
-##### Updates
-
-+ Email and password confirmation fields may now be made optional.
-+ "User Information Options" have been largely removed in favor of determining which fields are displayed via the forms UI
-+ The former "User Information Options" settings area has been renamed to "User Privacy Options".
-
-##### Bug Fixes
-
-+ Changed the filter on return of `LLMS_Person_Handler::get_password_reset_fields()` from `lifterlms_lost_password_fields` to `llms_password_reset_fields`.
-
-##### Development changes
-
-+ The filter `lifterlms_before_user_${action}` is now triggered by `do_action_ref_array()` instead of `do_action()` allowing modification of `$posted_data` and `$fields` via hooks.
-+ A number of action and filter hooks have been moved to new locations within the codebase. They will continue to function as expected (with some minor exceptions).
-+ Enqueue select2 on account and checkout pages for searchable dropdowns for country & state.
-
-##### Library & Vendor Updates
-
-+ Load core libraries from new location and load WP Background Processing lib.
-+ The vendor script dependency `topModal.js` has been removed.
-
-##### Templates Updated
-
-+ templates/global/form-login.php
-+ templates/global/form-registration.php
-+ templates/product/free-enroll-form.php
-
-##### Deprecations
-
-The following have been deprecated and will be removed from LifterLMS in a major update following version 5.0.0.
-
-+ Class Method: `LLMS_Person_Handler::get_available_fields()` is deprecated in favor of `LLMS_Forms::get_form_fields()`.
-+ Class Method: `LLMS_Person_Handler::register()` is deprecated, in favor of `llms_register_user()`.
-+ Class Method: `LLMS_Person_Handler::sanitize_field()` (private method) is deprecated with no replacement.
-+ Class Method: `LLMS_Person_Handler::update()` is deprecated, in favor of `llms_update_user()`.
-+ Class Method: `LLMS_Person_Handler::validate_fields()` is deprecated with no replacement.
-+ Class Method: `LLMS_Person_Handler::voucher_toggle_script()` is deprecated with no replacement.
-+ Filter: `llms_usernames_blacklist` is deprecated, use `llms_usernames_blocklist` instead.
-+ Function: `llms_get_minimum_password_strength()` is deprecated with no replacement.
-+ Option: `lifterlms_registration_generate_username` is deprecated in favor of the new method `LLMS_Forms::are_usernames_enabled()`.
-
-##### Removed Items
-
-+ Private method `LLMS_Processors::includes()` has been removed.
-+ Private methods `LLMS_Person_Handler::fill_fields()` and `LLMS_Person_Handler::insert_data()` were removed.
-+ Previously deprecated class method `LLMS_Quiz::get_lessons()` has been removed.
-+ Previously deprecated class method `LLMS_Controller_Quizzes::take_quiz()` has been removed.
-+ Previously deprecated class `LLMS_Processor_Table_To_Csv` has been removed.
-
-
-v4.21.3 - 2021-05-31
---------------------
-
-##### Updates
-
-+ Increase 3rd party support for WP core hook `lostpassword_post` hook.
-
-##### Bug fixes
-
-+ Props to [Hemant Patidar](https://www.linkedin.com/in/hemantsolo/) for discovering an issue preventing rate limiting in various security plugins from working on the LifterLMS password recovery form.
-+ Fixed an issue encountered when updating LifterLMS premium add-ons via the LifterLMS Helper encountered when API errors are occur.
-+ Updated the failure error code from 'activation' to 'deactivation' in the `LLMS_Add_On` class.
-+ Updated the API connection error message returned when using the `LLMS_Abstract_API_Handler` class.
-
-##### Deprecations
-
-+ Class `LLMS_Frontend_Password` is deprecated, see deprecated methods and their replacements below:
-
- + `LLMS_Frontend_Password::retrieve_password()` is deprecated in favor of `LLMS_Controller_Account::lost_password()`.
- + `LLMS_Frontend_Password::check_password_reset_key()` is deprecated in favor of `check_password_reset_key()`.
- + `LLMS_Frontend_Password::reset_password()` is deprecated in favor of `reset_password()`.
-
-
-v4.21.2 - 2021-05-17
---------------------
-
-##### Security Update
-
-This releases fixes a security issue affecting LifterLMS versions 4.21.1 and earlier:
-
-+ Thank you to [Amirmohammad vakili](https://www.linkedin.com/in/amirmuhammad-vakili-65a7a11b3/) for reporting an insecure direct object reference issue.
-
-##### Updates
-
-+ Added the `view_grades` capability which is used to determine whether or not a user has the ability to view another user's grades on the website's frontend.
-
-##### Bug fixes
-
-+ Fixed an issue causing PHP errors when attempting to access a quiz attempt that doesn't exist.
-+ Fixed a localization issue encountered when entering transaction amounts on the admin panel.
-
-
-v4.21.1 - 2021-04-29
---------------------
-
-##### Security Update
-
-This releases fixes two security issues affecting LifterLMS versions 4.21.0 and earlier:
-
-+ Thank you to [Amirmohammad vakili](https://www.linkedin.com/in/amirmuhammad-vakili-65a7a11b3/) for reporting a way to store XSS.
-+ Thank you to Ashish Jha from [Bluefire Redteam](https://www.bluefire-redteam.com/) for reporting a reflected XSS issue on checkout screens.
-
-
-v4.21.0 - 2021-04-19
---------------------
-
-##### Updates
-
-+ Certificate exports will now automatically include (most) externally hosted images and stylesheets.
-+ Opt-in forward compatibility changes have been made to the `LLMS_Abstract_Options_Data` class.
-
-##### Bugfixes
-
-+ Fixed an issue causing one-time payment orders from being included in totals on some reporting screens.
-+ Fixed an issue causing student enrollment counts to be incorrect under some circumstances.
-+ Fixed issues resulting in unnecessary duplicated instances of course background data processing.
-+ Fixed an error encountered when a course is deleted prior to its background data being processed.
-+ Fixed an escaping issue causing passwords with a backslash character from being usable following a password reset.
-
-
-v4.20.0 - 2021-03-16
---------------------
-
-##### Bugfixes
-
-+ Fixed an issue causing a fatal error when attempting to access reports for deleted students. Thanks Thanks [@pondermatic](https://github.com/pondermatic)!
-+ Fixed an issue encountered on the builder causing the last section to be returned when retrieving the previous section for the first section.
-
-
-v4.19.0 - 2021-03-11
---------------------
-
-##### Supported Version Requirement Updates
-
-+ **The minimum supported PHP version has been raised to PHP 7.3. Please upgrade to a [supported PHP version](https://www.php.net/supported-versions).**
-+ **The minimum supported WordPress core version has been raised to version 5.3.**
-
-##### Bug fixes
-
-+ Fixed an issue causing TinyMCE editor instances to be unusable within metaboxes when using the block editor.
-
-
-v4.18.0 - 2021-03-04
---------------------
-
-**This is the last release of LifterLMS that will declare support for PHP 7.2. PHP 7.2 reached its official [end of life](https://www.php.net/eol.php) on November 30, 2020. With the next release of LifterLMS the minimum supported PHP version will be raised to 7.3. If you're currently using PHP 7.2 please contact your host and request an upgrade to a [supported PHP version](https://www.php.net/supported-versions) as soon as possible!**
-
-##### Updates
-
-+ Tested up to WordPress core version 5.7
-+ Updated several occurrences of `json_encode()` with preferred `wp_json_encode()`.
-
-##### Bug fixes
-
-+ Added a tie-breaker when there are multiple enrollment statuses with the same date & time. Thanks [@pondermatic](https://github.com/pondermatic)!
-+ On admin order pages and tables don't print links for deleted students.
-+ Fixed an issue on admin order pages when viewing an order for a deleted student.
-
-
-v4.17.0 - 2021-02-22
---------------------
-
-##### Updates
-
-+ The post type feature "llms-sales-page" has been added to course and membership post types, signifying they support custom sales pages.
-
-##### Bug fixes
-
-+ Fixed compatibility issues with Yoast SEO 15.8.
-+ Fixed duplicate action hook in `content-no-access-after.php` template.
-+ Added early returns to several templates to prevent undefined variables errors.
-+ Fixed an undefined variable encountered in course builder JS debug logging.
-
-##### Templates Updated
-
-+ content-no-access-after.php
-+ quiz/meta-information.php
-+ quiz/results.php
-+ quiz/start-button.php
-
-
-v4.16.0 - 2021-02-18
---------------------
-
-##### Updates
-
-+ Added preview management to the student dashboard to allow previewing of the dashboard as a site visitor.
-+ Added a new filter to allow customization of courses output by the [lifterlms_courses] shortcode. Thanks [@reedhewitt](https://github.com/reedhewitt)!
-+ Added compatibility code to reduce plugin conflicts encountered in the course builder. Resolves a conflict encountered when building quizzes with Yoast SEO installed.
-
-##### Bug fixes
-
-+ Fixed undefined variable error encountered when creating custom notification types. Thanks [@pondermatic](https://github.com/pondermatic)!
-+ Fixed incorrect variables passed to `sprintf()` in logging functions used by the course data background processor. Thanks [@pondermatic](https://github.com/pondermatic)!
-
-
-v4.15.0 - 2021-02-09
---------------------
-
-##### Updates
-
-+ Database migration: remove any "orphaned" access plans which were not properly cleaned up during deletion of parent course or membership.
-+ Improved performance of membership post association query methods.
-
-##### Bug fixes
-
-+ Access plans will now be automatically deleted when their parent course or membership is deleted.
-+ Fix an issue with donut charts/graphs on RTL sites.
-+ Fix an issue causing unpublished (draft/private) courses from being returned during queries for membership post associations.
-
-##### LifterLMS REST 1.0.0-beta.15
-
-###### Updates
-
-+ Added Access Plan resource and endpoint.
-+ Provide a more significant error message when trying to delete an item without permissions.
-+ Use `WP_Http` constants in favor of integers when referencing HTTP status codes.
-
-###### Bug fixes
-
-+ Fixes localization issues where a singular name was used in favor of the expected plural form.
-+ Fixed issues where an error object was not properly returned when expected
-+ Fixed call to undefined function `llms_bad_request_error()`, must be `llms_rest_bad_request_error()`.
-+ Fixed access plans resource link.
-+ Fixed wrong trigger retrieved when multiple trigger were present for the same user/post pair on Student Enrollment resources.
-
-
-v4.14.0 - 2021-02-04
---------------------
-
-##### Updates
-
-+ Added a user preference option allowing users to opt-out of the course builder's autosave functionality. [More information](https://lifterlms.com/docs/using-course-builder/#manual-saving).
-+ 5-star review request displayed at 30 enrollments instead of 50.
-
-##### Bug fixes
-
-+ Fixed an issue encountered when using shortcodes in the description of an access plan.
-+ Fixed an issue encountered when editing auto-draft courses on the course builder.
-
-##### Deprecations
-
-+ `LLMS_Controller_Quizzes::take_quiz()` is deprecated in favor of `LLMS_AJAX_Handler::quiz_start()`.
-+ Method `LLMS_Quiz::get_lessons()` is deprecated with no replacement.
-
-
-v4.13.0 - 2021-01-26
---------------------
-
-##### Updates
-
-+ **The minimum supported WordPress core version has been raised to 5.2.** For more information, please review the [LifterLMS Minimum System Requirements](https://lifterlms.com/docs/minimum-system-requirements-lifterlms/).
-+ When cloning courses and lessons the cloned post will be created as a draft.
-+ When cloning courses the suffix "(Clone)" will be appended to the title of the course to unify cloning behavior with lessons.
-+ Added information about LifterLMS specific constant values to the LifterLMS system report.
-+ Added a new constant `LLMS_IS_SITE_CLONE` which can be used to force the site's clone status.
-
-##### Bug fixes
-
-+ Reverts site clone detection check changes implemented in 4.12.0 to restore pre 4.12.0 functionality which only runs checks on the admin panel for logged in users with the `manage_lifterlms` capability.
-+ Restore reliance on `mb_convert_encoding()` when passing html strings into `DOMDocument` and use the alternate method introduced in version 4.8.0 as a fallback.
-+ Fixed an issue encountered when unexpected or malformed data is stored in the LifterLMS admin notices option.
-
-
-v4.12.0 - 2021-01-20
---------------------
-
-##### Updates
-
-+ Automatic site clone detection checks have been adjusted to always run in favor of only running on the admin panel.
-+ LifterLMS Site Features (like recurring payment status) can now be configured via constant values.
-+ Added `llms_load_admin_tools` action to allow 3rd parties to easily hook into our admin tools system.
-+ Made numerous performance improvements on the course data background processor.
-+ Course data background processing will now be automatically throttled for courses with 500 students or more as opposed to the old value of 2,500 or more.
-
-##### Bug fixes
-
-+ Fixed an incorrect HTML `for` attribute and added an `id` to the related input element on the student dashboard voucher redemption endpoint.
-+ Fixed a pagination error encountered when using course or membership list shortcodes on the static front page.
-+ Make sure `is_lifterlms()` exists before calling it in navigation menu-related classes.
-
-##### Deprecations
-
-+ `LLMS_Admin_Notices_Core::check_staging()` is deprecated in favor of `LLMS_Staging::notice()`.
-+ Unused property `LLMS_Course::$sections` is replaced by `LLMS_Course::get_sections()`.
-+ Unused property `LLMS_Course::$sku` is deprecated with no replacement.
-+ `LLMS_Frontend_Forms` is deprecated, functionality is available via `LLMS_Controller_Account`.
-+ `LLMS_Frontend_Forms::reset_password()` is deprecated in favor of `LLMS_Controller_Account::reset_password()`.
-
-##### Templates Updated
-
-+ templates/myaccount/form-redeem-voucher.php
-
-
-v4.11.0 - 2021-01-07
---------------------
-
-##### Updates
-
-+ Adds the ability to use the Instructors blocks on the membership post type. Thanks [@alaa-alshamy](https://github.com/alaa-alshamy)!
-+ Updated LifterLMS Blocks to [Version 1.11.1](https://make.lifterlms.com/2020/12/29/lifterlms-blocks-version-1-11-1/).
-
-##### Bug fixes
-
-+ Fixed a PHP Notice encountered when trying to retrieve next lesson from an empty section.
-
-##### Templates updated
-
-+ templates/course/author.php
-
-
-v4.10.2 - 2021-01-04
---------------------
-
-##### Updates
-
-+ Improved performance of `llms_get_enrolled_students()`.
-+ Refactored lesson navigation query functions.
-
-##### Bug fixes
-
-+ Fixed sorting error when sorting student reports by name.
-
-
-v4.10.1 - 2020-12-10
---------------------
-
-##### Bug fixes
-
-+ Fixed visual issues encountered on the admin Add-Ons screen.
-+ Use `hr.wp-header-end` in favor of a second (hidden) to "catch" admin notices on the Add-Ons screen.
-+ Replace incorrect usage of invalid ID `llms_shop` with `courses` during catalog template loader checks.
-+ Function `llms_get_post()` will now only allow instantiation of LifterLMS classes.
-+ Remove unneeded require autoloaded file `includes/class.llms.quiz.data.php`.
-
-
-v4.10.0 - 2020-12-01
---------------------
-
-##### Updates
-
-+ Adds native theme support for the WordPress default theme Twenty Twenty-One.
-+ Improved the `llms_archive_description()` function and related filter.
-
-##### Bug fixes
-
-+ Fix issue encountered when using multiple role plugins to add the Instructor role to an Administrator user account. Thanks [@daniel-shuy](https://github.com/daniel-shuy)!
-+ Fixed an issue encountered when using non-latin characters in a course post URL slug. Thanks [@alaa-alshamy](https://github.com/alaa-alshamy)!
-
-##### Templates Updated
-
-+ templates/loop/pagination.php
-
-
-v4.9.0 - 2020-11-24
--------------------
-
-+ Tested up to WordPress core 5.6 (RC.1).
-+ Raised the minimum required WordPress core version to 5.1.
-+ Add new localization utilities for developers.
-+ Fixed various issues found on PHP 8.
-+ Added script localization for block editor scripts.
-+ Updated LifterLMS Rest to [Version 1.0.0-beta.17](https://make.lifterlms.com/2020/11/24/lifterlms-rest-api-version-1-0-0-beta-17/).
-+ Updated LifterLMS Blocks to [Version 1.10.0](https://make.lifterlms.com/2020/11/24/lifterlms-blocks-version-1-10-0/).
-
-
-v4.8.0 - 2020-11-16
--------------------
-
-##### Updates
-
-+ Added additional course imports and templates at the end of the setup wizard
-+ Added a cloud importer enabling 1-click importing of courses and course templates via the importer at LifterLMS -> Import
-+ Added strict comparisons in several places.
-+ Course "extra" data is only added to course arrays during exports to improve performance on the course builder.
-+ Improved template override loading performance on sites with no child theme.
-
-##### Bug fixes
-
-+ Fixed issues related to reliance on methods provided by the `mb_string` PHP module.
-
-##### Deprecations
-
-+ `LLMS_Admin_Setup_Wizard::generator_course_status()` is deprecated with no replacement.
-+ `LLMS_Admin_Setup_Wizard::watch_course_generation()` is deprecated with no replacement.
-
-
-v4.7.1 - 2020-11-05
--------------------
-
-##### Bug fixes
-
-+ During import generation set the post excerpt during the initial post insert instead of during metadata updates after creation.
-
-##### LifterLMS REST API 1.0.0-beta.16
-
-+ Improved performance of various database queries.
-
-
-v4.7.0 - 2020-11-02
--------------------
-
-##### Updates
-
-+ Major refactor of the `LLMS_Generator` class.
-+ Course export structure improved to include images and reusable blocks found in post content.
-+ When importing courses images will be automatically sideloaded into the media library as new attachment posts
-+ When importing courses reusable blocks will be imported
-+ Improved the success message displayed following a course import
-+ The class `LLMS_Admin_Reporting` is now always loaded on the admin panel.
-+ Performance improvements have been made to the `LLMS_Events_Query` to support using the `no_found_rows` query argument.
-+ When an order's billing plan "completes", a new meta property will be added to the order, `plan_ended`, which can be used to query orders with completed plans.
-+ Made improvements to the admin payment rescheduler tool to have more accurate reporting information.
-
-##### Bug fixes
-
-+ Replaced an instance of the LifterLMS (old) 1.0 rocket logo with the current rocket logo. Thanks [@imknight](https://github.com/imknight)!
-+ Ensure builder `switch-number` fields are set with the `number` type attribute. Thanks [@imknight](https://github.com/imknight)!
-+ Don't display a "View Post" link when updating post types that aren't publicly queryable. Thanks [@imknight](https://github.com/imknight)!
-+ Fixed the incorrect output of an achievement's title in a popover notification when using the {{ACHIEVEMENT_TITLE}} merge code. Thanks [@CadenG150](https://github.com/@CadenG150)!
-+ Fixed an error encountered when plugins utilize the `WP_Users_List_Table` class outside of the `users.php` screen.
-
-##### Deprecations
-
-+ `LLMS_Admin_Import::localize_stat()` is deprecated with no replacement.
-+ `LLMS_Admin_Users_Table::load_dependencies()` is deprecated with no replacement. The included class, `LLMS_Admin_Reporting` is now always loaded.
-+ `LLMS_Generator::add_custom_values()` is deprecated in favor of `LLMS_Generator_Courses::add_custom_values`.
-+ `LLMS_Generator::get_author_id_from_raw()` is deprecated in favor of `LLMS_Generator_Courses::get_author_id_from_raw()`.
-+ `LLMS_Generator::get_default_post_status()` is deprecated in favor of `LLMS_Generator_Courses::get_default_post_status()`.
-+ `LLMS_Generator::get_generated_posts()` is deprecated in favor of `LLMS_Generator::get_generated_content()`.
-+ `LLMS_Generator::format_date()` is deprecated in favor of `LLMS_Generator_Courses::format_date()`.
-+ `LLMS_Generator::increment()` is deprecated with no replacement.
-
-
-v4.6.0 - 2020-10-19
--------------------
-
-+ Added an admin tool to help automatically identify and schedule missed recurring payments
-+ Use `llms_deprecated_function()` in favor of `llms_log()`.
-+ Removed logging and use `apply_filters_deprecated()` in favor of `apply_filters()`.
-
-
-v4.5.1 - 2020-10-14
--------------------
-
-##### Updates
-
-+ Added logic in `LLMS_Database_Query` to reduce unnecessary DB reads when total results are not required.
-
-##### Bug fixes
-
-+ Removed the course "Excerpt" area in favor of utilization of the course sales page content.
-+ Show sales reporting currency symbol based on LifterLMS site options in favor of the browser's locale settings.
-+ Fixed an issue causing achievement-related JS DOM events to be bound unnecessarily. Thanks to [@imknight](https://github.com/imknight)!
-+ Fixed an issue causing site administrator capabilities to be removed during LifterLMS data removal.
-+ Fixed an issue causing an instructors course post count to display 0 on the admin panel courses post table. Thanks to [nhandl3](https://github.com/nhandl3)!
-+ Only display the admin bar "View Manager" to users who can bypass content restrictions.
-+ Updated jQuery code to stop using deprecated events and methods in preparation for jQuery upgrades in the WordPress core.
-+ Fixed PHP notice encountered on the admin panel when using Yoast SEO.
-
-
-v4.5.0 - 2020-10-06
--------------------
-
-##### Updates
-
-+ Students can now choose to make their certificates publicly accessible. Huge thanks to [@alaa-alshamy](https://github.com/alaa-alshamy) for contributing this awesome new feature!
-+ When accessing a certificate that does not have sharing enabled, a 404 will be served in favor of an error message.
-+ Admin payment gateway notices will no longer redisplay a week after being dismissed.
-+ Log files will be automatically split when a file is 5MB or larger, ensuring that log files never grow too large.
-+ During student registration, `wp_signon()` is used to login the newly created user.
-+ Improved slow background process database queries run during the automatic "closing" of idle user sessions.
-
-##### Bug fixes
-
-+ `LLMS_User_Certificate::get_related_post_id()` and `LLMS_User_Certificate::get_user_id()` will now always return an integer.
-+ Fixes issues related to account sign on/out and session start/end events being recorded incorrectly.
-
-##### Deprecations
-
-+ `llms_set_person_auth_cookie()` is deprecated in favor of WP core methods such as `wp_signon()`, `wp_set_current_user()`, and/or `wp_set_auth_cookie()`.
-
-
-v4.4.4 - 2020-09-21
--------------------
-
-##### Bug fixes
-
-+ Don't pass unsupported parameter `$use_cache` to the `calculate_grade()` method, thanks [@pondermatic](https://github.com/pondermatic)!
-+ Add an HTML title attribute to the admin setup wizard page.
-+ Fix issue causing notices to be logged during quiz attempt deletion on the admin panel.
-
-##### Deprecations
-
-+ Method `LLMS_Admin_Setup_Wizard::scripts()` & `LLMS_Admin_Setup_Wizard::output_step_html()` are deprecated with no replacements.
-
-##### LifterLMS REST API version 1.0.0-beta.15
-
-+ Bugfix: Created lessons will now have the derivative `course_id` property set according to the ID of the lesson's parent section.
-+ Bugfix: The `course_id` property of lessons is now properly marked as read-only.
-
-
-v4.4.3 - 2020-09-16
--------------------
-
-+ Bugfix: Fix engagement email duplicate check issue.
-+ Bugfix: Fix transposition issue found in engagement email dupcheck debug log message.
-
-
-v4.4.2 - 2020-09-08
--------------------
-
-+ Bugfix: Fix lesson navigation regression introduced in 4.4.0.
-
-
-v4.4.1 - 2020-09-04
--------------------
-
-+ Bugfix: Delayed engagement emails will not be sent to students who's enrollment is not active in the related course or membership which triggered the email.
-+ Bugfix: Fixed regression introduced in 4.4.0 preventing the `certificates.css` stylesheet from loading on certificate screens.
-+ Update: Engagement email related logs will be logged to a separate logfile, `engagement-emails` in favor of the main `llms` log.
-
-
-v4.4.0 - 2020-09-02
--------------------
-
-##### Updates
-
-+ Improved LifterLMS static asset registration, queuing, definitions, and management.
-+ Added strict comparators in various areas of the codebase.
-
-##### Changes to deprecated function logs and warnings
-
-+ The `llms_deprecated_function()` method now uses `_deprecated_function()` (from the WP core) under the hood.
-+ LifterLMS deprecation warnings are logged to the WP core `debug.log` file in favor of the LifterLMS log file.
-+ LifterLMS deprecation warnings will now trigger a `E_USER_DEPRECATED` error when `WP_DEBUG` is enabled.
-
-##### Bugfixes
-
-+ Fixed a lesson navigation issue encountered when sections contain unpublished lessons.
-+ Fixed an undefined variable notice encountered on the student dashboard.
-+ Fixed an issue encountered when the `wp_login_url()` function returns an empty string.
-+ Fixed a double slash found in an asset URI.
-
-##### Deprecations
-
-+ `LLMS_Frontend_Assets::is_inline_script_enqueued()` is deprecated in favor of `LLMS_Frontend_Assets::is_inline_enqueued()`.
-+ `LLMS_Ajax::register_script()` is deprecated with no replacement.
-+ `LLMS_Ajax::get_ajax_data()` is deprecated with no replacement.
-+ Javascript AJAX nonce variable is moved from `wp_ajax_data.nonce` to `window.llms.ajax-nonce`.
-
-##### Templates Updated
-
-+ templates/checkout/form-gateways.php
-+ templates/course/lesson-preview.php
-+ templates/course/syllabus.php
-
-
-v4.3.3 - 2020-08-17
--------------------
-
-+ Fixed an issue causing legends of reporting charts to be truncated and only readable after a mouse hover.
-+ Fixed an issue caused by passing `null` values to `wp_insert_post()`.
-+ Fixed a javascript error encountered on LifterLMS settings screens.
-
-
-v4.3.2 - 2020-08-10
--------------------
-
-+ WP 5.5 compatibility: Automatically deregister "protected" post types from wp-sitemap.xml.
-
-
-v4.3.1 - 2020-08-06
--------------------
-
-+ When resetting tracking data cookies, set a "secure" cookie where possible.
-+ Catch an unhandled error encountered when generating certificate exports.
-+ When an error is encountered during certificate export generation, display an error notice instead of a general notice.
-
-
-v4.3.0 - 2020-07-28
--------------------
-
-##### Security Fix
-
-+ Fixed an XSS issue on account edit and registration forms. Thanks to [Morningstar](https://twitter.com/0xMstar) for reporting this issue!
-
-##### Bug fixes
-
-+ Fixed an error encountered during customizer live theme preview encountered when Twenty-twenty is the current theme.
-+ The `$type` property of the `LLMS_Abstract_Database_Store` is now set to a default placeholder value (`_db_record_`) in favor of an empty string.
-+ Set the `$type` property of the `LLMS_Event` class to `event`.
-+ Set the `$type` property of the `LLMS_Quiz_Attempt` class to `quiz_attempt`.
-+ Set the `$type` property of the `LLMS_User_Post_Meta` class to `user_postmeta`.
-
-##### Updates
-
-+ Added a filter `llms_form_field_args` to allow extending form fields prior to HTML rendering.
-
-##### Deprecations
-
-The following filter hooks have been deprecated. These hooks were being called as the result of a bug (noted above) and should no longer be used. They will be removed in the next *major* version of LifterLMS.
-
-+ `llms__created` has been deprecated, use `llms_{$type}_created` where `{$type}` is the database record type defined by the class property.
-+ `llms__deleted` has been deprecated, use `llms_{$type}_deleted` where `{$type}` is the database record type defined by the class property.
-+ `llms__updated` has been deprecated, use `llms_{$type}_updated` where `{$type}` is the database record type defined by the class property.
-
-
-v4.2.0 - 2020-07-21
--------------------
-
-##### Updates
-
-+ Admins can now preview the checkout screen as visitors or students using the "View As" function from the WP Admin bar
-+ Javascript cookies now set cookies with `sameSite` set to `strict` as recommended by Firefox/Mozilla.
-+ Added filters to allow 3rd parties to use LifterLMS completion tracking APIs to "complete" external or non-LMS content.
-+ Added "deep" orphan checks when checking the relationship between a quiz and a lesson.
-+ Normalized the return structure in `LLMS_Post_Instructors::get_instructors()` when no instructor set, thanks [@nicolas-jaussaud](https://github.com/nicolas-jaussaud)!
-+ Update LifterLMS rocket icon used in the WP Admin Bar in the "View As" area.
-
-##### Bug fixes
-
-+ When deleting a quiz attempt the related lesson will now be automatically marked as "Incomplete" when appropriate.
-+ `LLMS_Abstract_User_Data::get_id()` now always returns an integer.
-+ Fixed a 404 error resulting from settings tooltips referencing a missing icon asset.
-+ Added logic to set the order status to 'cancelled' when an enrollment linked to an order is deleted.
-
-
-
-v4.1.0 - 2020-07-06
--------------------
-
-##### LifterLMS REST 1.0.0-beta.14
-
-+ **Breaking**: `LLMS_REST_Controller::prepare_links()` now requires a second parameter, the `WP_REST_Request` for the current request. Any classes extending and overwriting this method must adjust their method signature to accommodate this change.
-+ Bugfix: Fixed issue causing response objects to unintentionally include keys of remapped fields. This error occurs only when extending core controllers and attempting to exclude core fields.
-
-
-v4.0.0 - 2020-06-25
--------------------
-
-This is a *major* release. Many backwards incompatible changes have been made that may affect your site if you have custom code which rely on previously deprecated functions or methods. If you're not sure about your custom code, test the upgrade in a [staging site](https://lifterlms.com/docs/staging/).
-
-##### Bug Fixes
-
-+ Fixed an issue encountered during quiz grading.
-+ Add RTL language support for popover interfaces found throughout the course builder.
-+ Fixed issue encountered in MySQL 8.0 when using the bbPress integration.
-
-##### LifterLMS REST API 1.0.0-beta.13
-
-+ Bugfix: Fixed error response messages on the instructors endpoint.
-+ Bugfix: Fixed student progress deletion endpoint issues preventing progress from being fully removed.
-
-##### Action Scheduler Library
-
-Switches from prospress/action-scheduler to woocommerce/action-scheduler. The repository has been moved but it's the same library & upgrades to latest version (3.1.6).
-
-While this is a semantically major upgrade of the library there are no backwards incompatible changes to the public API.
-
-There have been several deprecated functions/classes. The LifterLMS core does not directly use any of these deprecated functions but 3rd parties might and should review the changelog of the library to see if they are affected by any deprecations: https://github.com/woocommerce/action-scheduler/releases.
-
-##### Deprecations
-
-+ Function `LLMS()` is deprecated in favor of `llms()`.
-
-##### Templates Modified
-
-+ templates/global/form-login.php
-+ templates/global/form-registration.php
-
-##### Miscellaneous Breaking Changes
-
-**WP Session Manager Library**
-
-Removes the bundled WP Session Manager plugin dependency, all public methods included with this plugin have been removed without direct replacements.
-
-**Removed JS dependencies**
-
-Removes bundled JS bootstrap 3 dependencies: "collapse" and "transition"
-
-**Removed CSS Classes**
-
-Removes classnames from student dashboard login and registration form wrapper elements which conflict with bootstrap causing visual issues.
-
-These classes are not used by the LifterLMS core or add-ons and are a legacy class that hasn't been removed for fear of creating backwards compatibility issues with any custom css, 3rd party themes, etc...
-
-+ templates/global/form-login.php: Removes `col-1` class from the `div.llms-person-login-form-wrapper` element.
-+ templates/global/form-registration.php: : Removes `col-2` class from the `div.llms-new-person-form-wrapper` element.
-
-**Removed SVG assets and functionality**
-
-+ LifterLMS no longer utilizes SVGs powered by the `LLMS_Svg` class. The class has been deprecated and removed (see below).
-+ The `assets/svg` directory (and all SVG assets contained within) has been removed.
-+ The constant `LLMS_SVG_DIR` has been removed.
-
-##### Previously deprecated classes (and files) that have been removed
-
-+ `LLMS_Admin_Analytics`: `includes/admin/class.llms.admin.analytics.php`
-+ `LLMS_Analytics`: `includes/class.llms.analytics.php`
-+ `LLMS_Analytics_Courses`: `includes/admin/analytics/class.llms.analytics.courses.php`
-+ `LLMS_Analytics_Memberships`: `includes/admin/analytics/class.llms.analytics.memberships.php`
-+ `LLMS_Analytics_Page`: `includes/admin/analytics/class.llms.analytics.page.php`
-+ `LLMS_Analytics_Sales`: `includes/admin/analytics/class.llms.analytics.sales.php`
-+ `LLMS_Course_Basic`: `includes/class.llms.course.basic.php`
-+ `LLMS_Course_Handler`: `includes/class.llms.course.handler.php`
-+ `LLMS_Course_Factory`: `includes/class.llms.course.factory.php`
-+ `LLMS_Lesson_Basic`: `includes/class.llms.lesson.basic.php`
-+ `LLMS_Meta_Box_Expiration`: `includes/admin/post-types/meta-boxes/class.llms.meta.box.expiration.php`
-+ `LLMS_Meta_Box_Video`: `includes/admin/post-types/meta-boxes/class.llms.meta.box.video.php`
-+ `LLMS_Number`: `includes/class.llms.number.php`
-+ `LLMS_Person`: `includes/class.llms.person.php`
-+ `LLMS_Quiz_Legacy`: `includes/class.llms.quiz.legacy.php`
-+ `LLMS_Svg`: `includes/class.llms.svg.php`
-+ `LLMS_Table_Questions`: `includes/admin/reporting/tables/llms.table.questions.php`
-+ `LLMS\Users\User`: `includes/Users/User.php`
-
-##### Previously deprecated class properties that have been removed
-
-+ `LifterLMS->person` (generally accessed via `LLMS()->person`).
-+ `LLMS_Analytics_Widget->date_end`
-+ `LLMS_Analytics_Widget->date_start`
-+ `LLMS_Analytics_Widget->output`
-+ `LLMS_Certificate->enabled`
-+ `LLMS_Course_Data->$course`
-+ `LLMS_Course_Data->$course_id`
-
-##### Previously deprecated class methods that have been removed:
-
-+ `LLMS_Admin_Table::queue_export()`
-+ `LLMS_AJAX::get_achievements()`
-+ `LLMS_AJAX::get_all_posts()`
-+ `LLMS_AJAX::get_associated_lessons()`
-+ `LLMS_AJAX::get_certificates()`
-+ `LLMS_AJAX::get_courses()`
-+ `LLMS_AJAX::get_course_tracks()`
-+ `LLMS_AJAX::get_emails()`
-+ `LLMS_AJAX::get_enrolled_students()`
-+ `LLMS_AJAX::get_enrolled_students_ids()`
-+ `LLMS_AJAX::get_lesson()`
-+ `LLMS_AJAX::get_lessons()`
-+ `LLMS_AJAX::get_lessons_alt()`
-+ `LLMS_AJAX::get_memberships()`
-+ `LLMS_AJAX::get_question()`
-+ `LLMS_AJAX::get_sections()`
-+ `LLMS_AJAX::get_sections_alt()`
-+ `LLMS_AJAX::get_students()`
-+ `LLMS_AJAX::update_syllabus()`
-+ `LLMS_Course::get_children_sections()`
-+ `LLMS_Course::get_children_lessons()`
-+ `LLMS_Course::get_author()`
-+ `LLMS_Course::get_author_id()`
-+ `LLMS_Course::get_author_name()`
-+ `LLMS_Course::get_sku()`
-+ `LLMS_Course::get_id()`
-+ `LLMS_Course::get_title()`
-+ `LLMS_Course::get_permalink()`
-+ `LLMS_Course::get_user_postmeta_data()`
-+ `LLMS_Course::get_user_postmetas_by_key()`
-+ `LLMS_Course::get_checkout_url()`
-+ `LLMS_Course::get_start_date()`
-+ `LLMS_Course::get_end_date()`
-+ `LLMS_Course::get_next_uncompleted_lesson()`
-+ `LLMS_Course::get_lesson_ids()`
-+ `LLMS_Course::get_syllabus_sections()`
-+ `LLMS_Course::get_short_description()`
-+ `LLMS_Course::get_syllabus()`
-+ `LLMS_Course::get_user_enroll_date()`
-+ `LLMS_Course::get_user_post_data()`
-+ `LLMS_Course::check_enrollment()`
-+ `LLMS_Course::is_user_enrolled()`
-+ `LLMS_Course::get_student_progress()`
-+ `LLMS_Course::get_membership_link()`
-+ `LLMS_Lesson::get_assigned_quiz()`
-+ `LLMS_Lesson::get_drip_days()`
-+ `LLMS_Lesson::mark_complete()`
-+ `LLMS_PlayNice::divi_fb_wc_product_tabs_after()`
-+ `LLMS_PlayNice::divi_fb_wc_product_tabs_before()`
-+ `LLMS_PlayNice::wc_is_account_page()`
-+ `LLMS_Post_Instructors::get_defaults()`
-+ `LLMS_Query::set_dashboard_pagination()`
-+ `LLMS_Query::add_query_vars()`
-+ `LLMS_Question::get_correct_option()`
-+ `LLMS_Question::get_correct_option_key()`
-+ `LLMS_Question::get_options()`
-+ `LLMS_Quiz::get_assoc_lesson()`
-+ `LLMS_Quiz::get_passing_percent()`
-+ `LLMS_Quiz::get_remaining_attempts_by_user()`
-+ `LLMS_Quiz::get_time_limit()`
-+ `LLMS_Quiz::get_total_allowed_attempts()`
-+ `LLMS_Quiz::get_total_attempts_by_user()`
-+ `LLMS_Quiz_Attempt::get_status()`
-+ `LLMS_Shortcode_My_Account::lost_password()`
-+ `LLMS_Section::count_children_lessons()`
-+ `LLMS_Section::delete()`
-+ `LLMS_Section::get_children_lessons()`
-+ `LLMS_Section::remove_all_child_lessons()`
-+ `LLMS_Section::remove_child_lesson()`
-+ `LLMS_Section::set_order()`
-+ `LLMS_Section::set_title()`
-+ `LLMS_Section::update()`
-+ `LLMS_Session::init()`
-+ `LLMS_Session::maybe_start_session()`
-+ `LLMS_Session::set_expiration_variant_time()`
-+ `LLMS_Session::set_expiration_time()`
-+ `LLMS_Session::use_php_sessions()`
-+ `LLMS_Student::delete_quiz_attempt()`
-+ `LLMS_Student::get_best_quiz_attempt()`
-+ `LLMS_Student::get_quiz_data()`
-+ `LLMS_Student::has_access()`
-+ `LLMS_Student_Dashboard::output_courses_content()`
-+ `LLMS_Student_Dashboard::output_dashboard_content()`
-+ `LLMS_Student_Dashboard::output_notifications_content()`
-+ `LLMS_Widget_Course_Progress::widget_contents()`
-
-##### Previously deprecated functions that have been removed
-
-+ `is_filtered()`
-+ `lifterlms_template_loop_view_link()`
-+ `llms_add_user_table_columns()`
-+ `llms_add_user_table_rows()`
-+ `llms_create_new_person()`
-+ `llms_get_question()`
-+ `llms_get_quiz()`
-+ `llms_set_user_password_rest_key()`
-+ `llms_setup_product_data()`
-+ `llms_setup_question_data()`
-+ `llms_verify_password_reset_key()`
-
-##### Previously deprecated hooks that have been removed
-
-+ Action: `lifterlms_before_memberships_loop_item_title`
-+ Action: `lifterlms_after_memberships_loop_item_title`
-+ Action: `lifterlms_after_memberships_loop_item_title`
-+ Filter: `lifterlms_completed_transaction_message`
-+ Filter: `lifterlms_is_filtered`
-+ Filter: `lifterlms_get_analytics_pages`
-+ Filter: `lifterlms_analytics_tabs_array`
-
-##### Previously deprecated shortcodes that have been removed
-
-+ `[courses]`
-+ `[lifterlms_user_statistics]`
-
-##### Previously deprecated templates that have been removed
-
-+ `templates/loop/view-link.php`
-
-##### Previously deprecated global variables that have been removed
-
-+ `$product`
-+ `$question`
-
-
-v3.41.1 - 2020-06-23
---------------------
-
-+ Apply restrictions to post content and excerpts during WP REST requests.
-
-
-v4.0.0-rc.1 - 2020-06-18
-------------------------
-
-View release notes at [https://make.lifterlms.com/2020/06/18/lifterlms-version-4-0-0-rc-1/](https://make.lifterlms.com/2020/06/18/lifterlms-version-4-0-0-rc-1/).
-
-
-v3.41.0 - 2020-06-12
---------------------
-
-##### Bug Fixes
-
-+ Fix issues encountered when a user role with the `edit_users` capability has multiple LifterLMS roles (like Student).
-
-##### LifterLMS 4.0.0 Release Preparation
-
-LifterLMS 4.0.0, our first major release in several years, is nearing the end of it's beta testing cycle. Many unused legacy functions, classes, and files are being removed in version 4.0.0 and well as many functions, classes, and files that were previously deprecated.
-
-The following is a list of items that have not been previously deprecated but will be removed from LifterLMS 4.0.0.
-
-For full details on the release, information on beta testing, and more, see our [blog post on the release](https://make.lifterlms.com/2020/06/01/preparing-for-lifterlms-4-0-0/).
-
-##### Deprecations
-
-The WP Session Manager plugin / library that is bundled into the LifterLMS core code base is deprecated from our code base and is being fully removed in favor of an internal session manager.
-
-The bundled Javascript Boostrap 3 modules, "collapse" and "transition" are deprecated from our codebase and are being removed.
-
-The following CSS classes are deprecated and will be removed:
-
-+ `templates/global/form-login.php`: The `col-1` class from the `div.llms-person-login-form-wrapper` element will be removed.
-+ `templates/global/form-registration.php`: : The `col-2` class from the `div.llms-new-person-form-wrapper` element will be removed.
-
-The following classes are deprecated:
-
-+ `LLMS_Number`: `includes/class.llms.number.php`
-+ `LLMS_Person`: `includes/class.llms.person.php`
-+ `LLMS_Table_Questions`: `includes/admin/reporting/tables/llms.table.questions.php`
-
-The following class methods are deprecated:
-
-+ `LLMS_PlayNice::divi_fb_wc_product_tabs_after()`
-+ `LLMS_PlayNice::divi_fb_wc_product_tabs_before()`
-+ `LLMS_Question::get_correct_option()`
-+ `LLMS_Question::get_correct_option_key()`
-+ `LLMS_Quiz::get_passing_percent()`, use `LLMS_Quiz::get( 'passing_percent' )` instead.
-+ `LLMS_Quiz::get_assoc_lesson()`, use `LLMS_Quiz::get( 'lesson_id' )` instead.
-+ `LLMS_Session::init()`
-+ `LLMS_Session::maybe_start_session()`
-+ `LLMS_Session::set_expiration_variant_time()`
-+ `LLMS_Session::set_expiration_time()`
-+ `LLMS_Session::use_php_sessions()`
-
-The following class properties are deprecated:
-
-+ `LifterLMS->person` (generally accessed via `LLMS()->person`).
-
-The following functions are deprecated:
-
-+ `lifterlms_template_loop_view_link()`
-+ `llms_add_user_table_columns()`
-+ `llms_add_user_table_rows()`
-+ `llms_get_question()`
-+ `llms_get_quiz()`
-+ `llms_setup_product_data()`
-+ `llms_setup_question_data()`
-
-The following global variables are deprecated:
-
-+ `$product`
-+ `$question`
-
-The following action hooks are deprecated:
-
-+ `lifterlms_before_memberships_loop_item_title`
-+ `lifterlms_after_memberships_loop_item_title`
-+ `lifterlms_after_memberships_loop_item_title`
-
-The following template file is deprecated:
-
-+ `templates/loop/view-link.php`
-
-
-v4.0.0-beta.3 - 2020-06-10
---------------------------
-
-View beta release notes at [https://make.lifterlms.com/2020/06/10/lifterlms-version-4-0-0-beta-3/](https://make.lifterlms.com/2020/06/10/lifterlms-version-4-0-0-beta-3/).
-
-
-v3.40.0 - 2020-06-09
---------------------
-
-##### Updates
-
-+ Adds a 1-click installation connector for the MailHawk email delivery plugin.
-
-##### Bugfixes
-
-+ Fixed an issue encountered during checkout when using a coupon against an access plan with a free trial.
-
-##### Deprecations
-
-+ `LLMS_SendWP::do_remote_install()` will be converted to a protected method and should no longer be called directly.
-+ `LLMS_Abstract_Email_Provider::output_css()`
-
-##### Templates updated
-
-+ templates/checkout/form-gateways.php
-
-
-v4.0.0-beta.2 - 2020-06-04
---------------------------
-
-View beta release notes at [https://make.lifterlms.com/2020/06/04/lifterlms-version-4-0-0-beta-2/](https://make.lifterlms.com/2020/06/04/lifterlms-version-4-0-0-beta-2/).
-
-
-v4.0.0-beta.1 - 2020-06-01
---------------------------
-
-View beta release notes at [https://make.lifterlms.com/2020/06/01/lifterlms-version-4-0-0-beta-1/](https://make.lifterlms.com/2020/06/01/lifterlms-version-4-0-0-beta-1/).
-
-
-v3.39.0 - 2020-05-28
---------------------
-
-+ Student Welcome notifications and user registered engagements now fire when users are created via the REST POST requests to the `/students` endpoint.
-+ Bugfix: Error encountered when printing full-page certificates on certain themes.
-
-##### LifterLMS REST 1.0.0-beta.12
-
-+ Feature: Added the ability to filter student and instructor collection list requests by various user information fields.
-+ Fix: Prevent infinite loops encountered when invalid API keys are utilized.
-+ Fix: Add an action used to fire LifterLMS core engagement and notification emails
-
-
-v3.38.2 - 2020-05-19
---------------------
-
-+ Added a default question type ("choice") to prevent malformed questions from being inadvertently stored in the database.
-+ When retrieving question data from the database, automatically fall back to the default question type value if no question type is saved.
-
-
-v3.38.1 - 2020-05-11
---------------------
-
-+ Update: Added methods for retrieving a list of posts associated with a membership.
-+ Bug fix: Fixed an issue causing certificate backgrounds to be cropped or cut in certain circumstances.
-+ Bug fix: Fixed an issue generating certificate downloads on servers where `mime_content_type()` does not exist.
-+ Bug fix: Fixed an issue which caused bbPress course forum restrictions to stop working.
-
-
-v3.38.0 - 2020-04-29
---------------------
-
-##### Updates
-
-+ The output of course restriction errors which may prevent enrollment is now displayed in it's own template in favor of the logic being included in the `product/pricing-table.php` template.
-+ The course progress bar shortcode will now only display the progress bar to enrolled users. An additional option has been added to the shortcode to allow showing a 0% progress bar to non-enrolled users. [Read more](https://lifterlms.com/docs/shortcodes/#lifterlms_course_progress).
-+ The "Course Progress" widget now has an option to optionally display the progress bar to non-enrolled users. By default it will display only to enrolled students.
-+ Updates LifterLMS Blocks to version 1.9.0
-
-##### Bug fixes
-
-+ Fixed an issue causing free access plans to bypass course enrollment restrictions like capacity and enrollment time periods.
-+ Fixed an issue causing custom checkout success redirects to fail when using gateways that require a payment confirmation step. This fixes an issue in the LifterLMS PayPal payment gateway.
-+ Fixed an issue causing deprecation theme-compatibility related deprecation notices to be incorrectly thrown.
-+ Fixed spelling error in variable passed to the `product/pricing-table.php` template. The misspelled variable is still being passed to the variable for backwards compatibility.
-+ Updated the way notification background processors are dispatched. This fixes an issue in the LifterLMS Twilio add-on.
-
-##### Deprecations
-
-+ `LLMS_Notifications::dispatch_processors()` is deprecated in favor of async dispatching via `LLMS_Notifications::schedule_processors_dispatch()`.
-
-##### Templates Updated
-
-+ templates/product/pricing-table.php
-
-##### LifterLMS Blocks
-
-+ Update: Improved script dependencies definitions.
-+ Update: Updated asset paths for consistency with other LifterLMS projects.
-+ Update: Updated various WP Core references that have been deprecated (maintains backwards compatibility).
-+ Update: The Lesson Progression block is no longer rendered server-side in the block editor (minor performance improvement).
-+ Update: Converted the course progress block into a dynamic block. Fixes an issue allowing the progress block to be visible to non-enrolled students.
-+ Update: Added a filter on the output of the Pricing Table block: `llms_blocks_render_pricing_table_block`.
-+ Bug fix: Fixed an issue encountered when using the WP Core "Table" block.
-+ Bug fix: Fixed a few areas where `class` was being used instead of `className` to define CSS classes on elements in the block editor.
-+ Bug fix: Fixed a user-experience issues encountered on the Course Information block when all possible information is disabled.
-+ Bug fix: Fixed an issue causing visibility attributes to render on blocks that don't support them.
-+ Bug fix: Fixed an issue preventing 3rd party blocks from modifying default block visibility settings.
-+ Bug fix: Fixed a spelling error visible inside the block editor.
-+ Bug fix: Fixed an issue causing the "Course Progress" block to be shown to non-enrolled students and visitors.
-+ Bug fix: Removed redundant CSS from frontend.
-+ Bug fix: Stop outputting editor CSS on the frontend.
-+ Bug fix: Dynamic blocks with no content to render will now only output their empty render messages inside the block editor, not on the frontend.
-+ Changes to the Classic Editor Block:
- + The classic editor block will no longer show block visibility settings because it is impossible to use those settings to filter the block on the frontend.
- + In order to apply visibility settings to the classic editor block, place the Classic Editor within a "Group" block and apply visibility settings to the Group.
-
-
-v3.37.19 - 2020-04-20
----------------------
-
-##### Updates
-
-+ Added a new debugging tool to clear pending batches created by background processors.
-+ Added a new method `LLMS_Abstract_Notification_View::get_object()` which can be used by notification views to override the loading of the post (or object) which triggered the notification.
-
-##### Bug Fixes
-
-+ Added localization to strings on the coupon admin screen. Thanks [parfilov](https://github.com/parfilov)!
-+ Fixed issue encountered in metaboxes when the `$post` global variable is not set.
-
-
-v3.37.18 - 2020-04-14
----------------------
-
-+ Fix regression introduced in version 3.34.0 which prevented checkout success redirection to external domains.
-+ Resolved a conflict with LifterLMS, Divi, and WooCommerce encountered when using the Divi frontend pagebuilder on courses and memberships.
-+ Fixed issue causing localization issues when creating access plans, thanks [@mcguffin](https://github.com/mcguffin)!
-
-
-v3.37.17 - 2020-04-10
----------------------
-
-##### Updates
-
-+ Updated the lost password and password reset form handlers for improved error handling and extendability by other plugins.
-
-##### Bug Fixes
-
-+ Fixed a conflict with WooCommerce resulting in password reset issues on the WooCommerce account dashboard.
-+ Fixed an issue allowing voucher codes from deleted vouchers to still be redeemed.
-+ Fixed an issue with pagination on the courses tab of a users BuddyPress profile.
-+ Fixed a typo in the `post_status` query arg when retrieving access plans for a course or membership.
-
-##### Deprecations
-
-+ `LLMS_PlayNice::wc_is_account_page()` is no longer required and is deprecated with no replacement
-+ WP core `get_password_reset_key()` should be used in favor of `llms_set_user_password_rest_key()`.
-+ WP core `check_password_reset_key()` should be used in favor of `llms_verify_password_reset_key()`.
-
-
-v3.37.16 - 2020-03-31
----------------------
-
-+ Bugfix: Fix issue causing student dashboard notification view to work incorrectly.
-
-
-v3.37.15 - 2020-03-27
----------------------
-
-##### Security Notice
-
-**This releases fixes a security issue. Please upgrade immediately!**
-
-Props to [Omri Herscovici and Sagi Tzadik from Check Point Research](https://www.checkpoint.com/) who found and disclosed the vulnerability resolved in this release.
-
-##### Updates & Bug Fixes
-
-+ Excluded `page.*` events in order to keep the events table small.
-+ Fixed error encountered when errors encountered validating custom fields. Thanks to [@wenchen](https://github.com/wenchen)!
-+ Fixed issue causing course pagination issues in certain scenarios.
-
-##### LifterLMS REST API Version 1.0.0-beta.11
-
-+ Bugfix: Correctly store user `billing_postcode` meta data.
-+ Bugfix: Fixed issue preventing course.created (and other post.created) webhooks from firing.
-
-
-v3.37.14 - 2020-03-25
----------------------
-
-+ Update: Added the ability to view the PHP error log file (as defined by `ini_get( 'error_log' )` ) on the LifterLMS -> Status -> Logs page.
-+ Update: Added strict comparisons for various condition checks.
-+ Bugfix: Fixed an issue where users might be redirected to the wrong course following a course import at the conclusion of the setup wizard.
-+ Bugfix: Fixed issue with tracking event data being lost due to cookie size limitations.
-+ Bugfix: Fixed issue potentially encountered when checking user capabilities for certificates and achievements.
-+ Bugfix: Fixed an issue preventing additional instances of the JS `LLMS.Storage` class from being instantiated.
-
-
-v3.37.13 - 2020-03-10
----------------------
-
-+ Remove usage of internal functions marked as deprecated.
-
-
-v3.37.12 - 2020-03-10
----------------------
-
-##### Updates
-
-+ Tested up to WordPress Core version 5.4.
-+ Added support for post revisions for course, lesson, and membership post types.
-
-##### Developer updates
-
-+ Added strict comparisons for various condition checks.
-+ Added a new filter, `llms_builder_{$post_type}_force_delete` which allows control over whether a post is moved to the trash or immediately deleted when trashed via the course builder.
-
-##### Bugfixes
-
-+ Fixed the name of the "actions" column on the quiz reporting screen.
-+ Fixed PHP warnings resulting from functions used to exclude order notes from comment counts.
-+ Fixed issue causing order notes to be included in the count displayed on the admin comments list despite their exclusion from the table itself.
-+ Fixed PHP notice thrown on the WordPress menu editor interface encountered when student dashboard endpoints have been deleted or removed.
-+ Fixed issue causing quotes to be encoded in various email, achievement, and certificate fields.
-
-##### Deprecations
-
-The following have been deprecated with no replacements and will be removed in the next major update:
-
-+ `LLMS_Course_Factory::get_course()`
-+ `LLMS_Course_Factory::get_lesson()`
-+ `LLMS_Course_Factory::get_product()`
-+ `LLMS_Course_Factory::get_quiz()`
-+ `LLMS_Course_Factory::get_question()`
-+ `LLMS_Course_Handler::get_users_not_enrolled()`
-
-
-v3.37.11 - 2020-03-03
----------------------
-
-##### Updates
-
-+ Resolved a conflict with the "Starter Templates" plugin which made it impossible to edit quizzes while the plugin was enabled.
-
-##### Bugfixes
-
-+ Fixed an issue causing lesson post authors to be "lost" when adding an existing lesson to a course.
-+ Fixed an issue causing php notices to be generated during existing lesson addition on the course builder.
-+ Fixed an issue causing course bbPress forums to be lost when editing that course using the "Quick Edit" function from the courses table.
-
-##### LifterLMS REST v1.0.0-beta.10
-
-+ Added text domain to i18n functions that were missing the domain.
-+ Added a "trigger" parameter to enrollment-related endpoints.
-+ Added `llms_rest_enrollments_item_schema`, `llms_rest_prepare_enrollment_object_response`, `llms_rest_enrollment_links` filter hooks.
-+ Fixed setting roles instead of appending them when updating user, thanks [@pondermatic](https://github.com/pondermatic)!
-+ Fixed return when the enrollment to be deleted doesn't exist, returns `204` instead of `404`.
-+ Fixed 'context' query parameter schema, thanks [@pondermatic](https://github.com/pondermatic)!
-
-
-v3.37.10 - 2020-02-19
----------------------
-
-+ Update: Exclude the privacy policy page from the sitewide restriction.
-+ Update: Added filter `llms_enable_open_registration`.
-+ Fix: Notices are printed on pages configured as a membership restriction redirect page.
-+ Fix: Do not apply membership restrictions on the page set as membership's restriction redirect page.
-+ Fix: Added flag to print notices when landing on the redirected page.
-
-
-v3.37.9 - 2020-02-11
---------------------
-
-+ Updated CSS classes used in privacy policy text suggestions per changes in WordPress core 5.3. Thanks [@garretthyder](https://github.com/garretthyder)!
-+ Added privacy exported group descriptions. Thanks [@garretthyder](https://github.com/garretthyder)!
-+ Added filters `llms_user_enrollment_allowed_post_types` & `llms_user_enrollment_status_allowed_post_types` which allow 3rd parties to enroll users into additional post types via core enrollment methods.
-+ Added option for admin settings fields to show an asterisk for required fields.
-+ Added option for integration plugins can now add automatically generated "Settings" link to the plugins screen.
-+ Bugfix: Fixed an IE compatibility issue related to usage of `Object.assign()`.
-
-
-v3.37.8 - 2020-01-21
---------------------
-
-+ Fix: Student quiz attempts are now automatically deleted when a quiz is deleted.
-+ Fix: "Orphaned" quizzes (those with no parent course and/or lesson) can be deleted from the Quiz reporting table.
-+ Fix: Quiz IDs on the quiz reporting screen now link to the quiz within the course builder. If the quiz is an "orphan" there will be no link.
-
-
-v3.38.0-beta.2 - 2019-12-19
----------------------------
-
-+ Update LifterLMS Blocks to v1.7.3.
-
-
-v3.38.0-beta.1 - 2019-12-13
----------------------------
-
-##### Form Management Improvements
-
-+ Forms (registration, checkout, account) are now managed via a block editor interface.
-+ Customize field labels, description, and placeholders in a simple WYSIWYG interface.
-+ Mark fields as required with a toggle.
-+ Reorder fields with drag and drop.
-+ Customize layout using block editor columns.
-+ Use LifterLMS block-level visibility to conditionally display fields based on enrollment or logged in status.
-
-##### Form Localization
-
-+ Added default country and state/region lists (see the "languages" directory).
-+ Country and state forms are now searchable dropdowns that adjusted based on the currently selected country.
-+ Each country's locale information (such as what a "post code" is called and whether or not the country has states or post codes) will update automatically based on the selected country.
-+ Enqueue select2 on account and checkout pages for searchable dropdowns for country & state.
-
-##### Updates
-
-+ New shortcode `[user]` which is used to output user information in a merge code interface.
-+ Improved form field generation via `LLMS_Form_Field` class.
-+ LifterLMS Settings: renamed "User Information Options" to "User Privacy Options".
-+ Reorganized open registration setting.
-+ Use `LLMS.wait_for()` for dependency waiting.
-+ Moved checkout template variable declarations to the checkout shortcode controller.
-+ Removed field display settings in favor of form customization using the form editors.
-+ Organized function files. Some functions have been moved.
-+ Function `llms_get_minimum_password_strength_name()` now accepts a parameter to retrieve strength name by key.
-+ Use `LLMS.wait_for()` for dependency waiting.
-
-##### LifterLMS Blocks v1.6.0
-
-+ Feature: Added form field blocks for use on the Forms manager.
-+ Feature: Add logic for `logged_in` and `logged_out` block visibility options.
-+ Update: Added isDisabled property to Search component.
-+ Update: Adjusted priority of `render_block` filter to 20.
-+ Bug fix: Import `InspectorControls` from `wp.blockEditor` in favor of deprecated `wp.editor`
-+ Bug fix: Automatically store course/membership instructor with `post_author` data when the post is created.
-+ Bug fix: Pass style rules as camelCase.
-
-##### Removed unused Javascript assets
-
-+ Remove unused bootstrap transition and collapse scripts.
-+ Remove topModal vendor dependency.
-+ Remove password strength inline enqueues.
-
-##### Bug fixes
-
-+ Only attempt to add a nonce to the datastore when a nonce exists in the settings object.
-
-##### Deprecations
-
-+ Deprecated `LLMS_Person_Handler::register()` method, use `llms_register_user()` instead.
-+ Deprecated `llms_get_minimum_password_strength()` with no replacement.
-
-##### Template Updates
-
-+ templates/checkout/form-checkout.php
-+ templates/checkout/form-gateways.php
-+ templates/global/form-registration.php
-
-v3.37.7 - 2020-01-08
---------------------
-
-+ Fix error resulting from undefined default value.
-+ Fix PHP 7.4 deprecation notice.
-
-
-v3.37.6 - 2019-12-12
---------------------
-
-+ New transaction creation date is now specified using `llms_current_time()`.
-+ Use the last successful transaction time to calculate from when the previously stored next payment date is in the future.
-+ Fixed an issue causing transaction post titles to be recorded with missing data due to invalid `strftime()` placeholders.
-
-
-v3.37.5 - 2019-12-09
---------------------
-
-+ Update LifterLMS Blocks to v1.7.2: fixes a bug causing the block editor to encounter a fatal error when accessing custom post types that don't support custom fields.
-
-
-v3.37.4 - 2019-12-06
---------------------
-
-##### Bug Fixes
-
-+ Fixed a bug causing certificate _template_ exports to export the site's homepage instead of the certificate preview.
-+ When exporting a certificate template, use the `post_author` to determine what user to use for merge code data.
-+ Revert Accounts settings tab page id to "account".
-
-##### LifterLMS Blocks v1.7.1
-
-+ Feature: Add logic for `logged_in` and `logged_out` block visibility options.
-+ Update: Added `isDisabled` property to Search component.
-+ Update: Adjusted priority of `render_block` filter to 20.
-+ Update: Added filter, `llms_block_supports_visibility` to allow modification of the return of the check.
-+ Update: Disabled block visibility on registration & account forms to prevent a potentially confusing form creation experience.
-+ Update: Added block editor rendering for password type fields.
-+ Update: Perform post migrations on `current_screen` instead of `admin_enqueue_scripts`.
-+ Update: Update various dependencies to use updated gutenberg packages.
-+ Bug fix: Fixed a WordPress 5.3 issues with JSON data affecting the ability to save course/membership instructors.
-+ Bug fix: Import `InspectorControls` from `wp.blockEditor` in favor of deprecated `wp.editor`
-+ Bug fix: Automatically store course/membership instructor with `post_author` data when the post is created.
-+ Bug fix: Pass style rules as camelCase.
-+ Bug fix: Fixed an issue causing "No HTML Returned" to be displayed in place of the Lesson Progression block on free lessons when viewed by a logged-out user.
-
-
-v3.37.3 - 2019-12-03
---------------------
-
-+ Added an action `llms_certificate_generate_export` to allow modification of certificate exports before being stored on the server.
-+ Don't unslash uploaded file `tmp_name`, thanks [@pondermatic](https://github.com/pondermatic)!
-+ TwentyTwenty Theme Support: Hide site header and footer, and set a white body background in single certificates.
-+ Renamed setting field IDs to be unique for open/close wrapper fields on the engagements and account settings pages.
-+ Removed redundant functions defined in the `LLMS_Settings_Page` class to reduce code redundancy in account and engagement setting page classes.
-+ The `LLMS_Settings_Page` base class now automatically defines actions to save and output settings content.
-
-
-v3.37.2 - 2019-11-22
---------------------
-
-+ LifterLMS notices will now be displayed on pages defined as a Course or Membership sales page.
-+ TwentyTwenty Theme: Updated to use `background-color` property instead of `background` shorthand when adding custom elements to style.
-+ Added filter `llms_sessions_end_idle_cron_recurrence` to allow customization of the recurrence of the idle session cleanup cronjob.
-+ Added filter `llms_quiz_is_open` to allow customization of whether or not a quiz is available to a student.
-+ When adding an client-side tracking events to the always make sure the server-side verification nonce is always set on the storage object.
-+ The Course/Membership filter on the main students reporting screen now correctly limits post results based on instructor access.
-
-
-v3.37.1 - 2019-11-13
---------------------
-
-+ TwentyTwenty Theme: Fixed course information block misalignment.
-+ Fixed conflict with WooCommerce resulting from the movement of the deprecated LifterLMS function `is_filtered()`.
-
-
-v3.37.0 - 2019-11-11
---------------------
-
-##### Updates
-
-+ Tested and compatible with WordPress core 5.3.
-+ Add theme support for the TwentyTwenty core default theme.
-+ Improved security and data sanitization in with regards to the SendWP integration connector.
-
-##### LifterLMS Rest API 1.0.0-beta.8
-
-+ Added memberships controller, huge thanks to [@pondermatic](https://github.com/pondermatic)!
-+ Added new filters:
-
- + `llms_rest_lesson_filters_removed_for_response`
- + `llms_rest_course_item_schema`
- + `llms_rest_pre_insert_course`
- + `llms_rest_prepare_course_object_response`
- + `llms_rest_course_links`
-
-+ Improved validation when defining instructors for courses.
-+ Improved performance on post collection listing functions.
-+ Ensure that a course instructor is always set for courses.
-+ Fixed `sales_page_url` not returned in `edit` context.
-+ In `update_additional_object_fields()` method, use `WP_Error::$errors` in place of `WP_Error::has_errors()` to support WordPress version prior to 5.1.
-
-
-v3.36.5 - 2019-11-05
---------------------
-
-+ Add filter: `llms_user_caps_edit_others_posts_post_types` to allow 3rd parties to utilize core methods for determining if a user can manage another users LMS content on the admin panel.
-
-
-v3.36.4 - 2019-11-01
---------------------
-
-+ Fixes a conflict with CartFlows introduced by a Divi theme compatibility fix added in 3.36.3. Is WordPress complicated or what?
-
-
-v3.36.3 - 2019-10-24
---------------------
-
-##### Updates
-
-+ Added new `LLMS_Membership` class methods: `get_categories()`, `get_tags()` and `toArrayAfter()` methods. Thanks [@pondermatic](https://github.com/pondermatic)!
-
-##### Compatibility
-
-+ Fixed access plan description conflicts with the Classic Editor block. This also resolves compatibility issues with Elementor which uses a hidden TinyMCE instance.
-+ Changed `pre_get_posts` callback from `10` (default) to `15`. Fixes conflict with Divi (and possibly other themes) which prevented LifterLMS catalog settings from functioning properly.
-
-##### Bugfixes
-
-+ Added translation to error message encountered when non-members attempt to purchase a members-only access plan. Thanks [@mrosati84](https://github.com/mrosati84)!
-+ Fix return of `LLMS_Generator::set_generator()`.
-+ Fixed a typo causing invalid imports from returning the expected error. Thanks [@pondermatic](https://github.com/pondermatic)!
-+ Fixed issue preventing membership post type settings from saving properly due to incorrect sanitization filters.
-+ Fixed issue where `wp_list_pluck()` would run on non arrays.
-
-
-v3.36.2 - 2019-10-01
---------------------
-
-##### Updates
-
-+ Tested to WordPress 5.3.0-beta.2
-+ Upgrade UI on student course reporting screens.
-+ Added logic to physically remove from the membership level and remove enrollments data on related products, when deleting a membership enrollment.
-+ Lesson metabox "start" drip method made available only if the parent course has a start date set.
-
-##### Bugfixes
-
-+ Fixed JS error when client-side event tracking settings aren't loaded, thanks [@wenchen](https://github.com/wenchen)!
-+ Fixed PHP warning resulting from drip the "Course Start" lesson drip settings when no course start date exists.
-+ Fixed fatal error encountered when reviewing an order placed with a payment gateway that's been deactivated.
-
-##### Files Updated
-
-+ assets/js/app/llms-tracking.js
-+ includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php
-+ includes/models/model.llms.lesson.php
-+ includes/models/model.llms.student.php
-+ lifterlms.php
-
-##### Templates Updated
-
-+ templates/admin/post-types/order-details.php
-+ templates/admin/reporting/tabs/students/courses-course.php
-
-
-v3.36.1 - 2019-09-24
---------------------
-
-##### Updates
-
-+ Include SendWP Connector in LifterLMS Engagement Settings.
-+ Removed usage of `WP_Error::has_errors()` to support WordPress version prior to 5.1.
-+ Improve performances when checking if an event is valid in `LLMS_Events->is_event_valid()`.
-+ Remove redundant check on `is_singular()` and `is_post_type_archive()` in `LLMS_Events->should_track_client_events()`.
-
-##### Bugfixes
-
-+ Fixed a compatibility issue with FitVids.js causing excess white space displayed around videos when using the library, WP plugin, or themes that utilize the library.
-+ Fixed an issue allowing recurring charges to continue processing after the order or customer had been deleted from the site.
-+ Fixed issue causing Membership Restriction settings from properly saving.
-+ Fixed issue that allowed instructors to see all quizzes on a site when the instructor had either no courses or only empty courses (courses with no lessons).
-+ Fixed "Last Seen" column displaying wrong date when the student last login date was saved as timestamp.
-+ Fixed an issue causing popover notifications to be skipped (never displayed) as a result of redirects.
-
-
-v3.36.0 - 2019-09-16
---------------------
-
-##### User Interaction event and session Tracking
-
-+ Added user interaction tracking for the following events:
-
- + User sign in and out.
- + Page load and exit (for LMS content)
- + Page focus and blur (for LMS content)
- + And more to come
-
-+ Interaction events are grouped into sessions automatically. A session is "closed" after 30 minutes of inactivity or a log-out event.
-+ Added "Last Seen" student reporting column which reports the last recorded activity for the student.
-
-##### Enhancements
-
-+ Automatically hydrate when calling LLMS_Abstract_Database_Store::to_array().
-+ Added CSS to make course and lesson video embeds automatically responsive.
-
-##### Bug Fixes
-
-+ Correctly pass the `$remember` variable when using `llms_set_person_auth_cookie()`.
-+ Fixed undefined index error when retrieving an unset value from an unsaved database model.
-+ Fix issue causing quotes to be encoded in shortcodes used in course and membership restriction message settings fields.
-+ Fix issue preventing manual updates of order dates (next payment, trial expiration, and access expiration) from being saved properly.
-
-
-v3.35.2 - 2019-09-06
---------------------
-
-+ When sanitizing settings, don't strip tags on editor and textarea fields that allow HTML.
-+ Added JS filter `llms_lesson_rerender_change_events` to lesson editor view re-render change events.
-
-
-v3.35.1 - 2019-09-04
---------------------
-
-+ Fix instances of improper input sanitization and handling.
-+ Include scripts, styles, and images for reporting charts and datepickers
-
-
-v3.35.0 - 2019-09-04
---------------------
-
-##### Security Notice
-
-+ Fixed a security vulnerability disclosed by the WordPress plugin review team. Please upgrade immediately!
-
-##### Updates
-
-+ Explicitly setting css and js file versions for various static assets..
-+ Added data sanitization methods in various form handlers.
-+ Added nonce verification to various form handlers.
-
-##### Bug fixes
-
-+ Fixed some translation strings that had literal variables instead of placeholders.
-+ Fixed undefined index error encountered when attempting to email a voucher export.
-+ Fixed undefined index error when PHP file upload errors are encountered during a course import.
-
-##### Deprecations
-
-The following unused classes have been marked as deprecated and will be removed from LifterLMS in the next major release.
-
-+ LLMS_Analytics_Memberships
-+ LLMS_Analytics_Courses
-+ LLMS_Analytics_Sales
-+ LLMS_Meta_Box_Expiration
-+ LLMS_Meta_Box_Video
-
-##### Template Updates
-
-+ [admin/reporting/tabs/courses/overview.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/courses/overview.php)
-+ [admin/reporting/tabs/memberships/overview.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/memberships/overview.php)
-+ [admin/reporting/tabs/quizzes/attempts.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/quizzes/attempts.php)
-+ [admin/reporting/tabs/quizzes/overview.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/quizzes/overview.php)
-+ [admin/reporting/tabs/students/courses-course.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/students/courses-course.php)
-+ [admin/reporting/tabs/students/courses.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/students/courses.php)
-+ [loop/featured-image.php](https://github.com/gocodebox/lifterlms/blob/master/templates/loop/featured-image.php)
-+ [myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/view-order.php)
-+ [quiz/results.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/results.php)
-+ [single-certificate.php](https://github.com/gocodebox/lifterlms/blob/master/templates/single-certificate.php)
-+ [single-no-access.php](https://github.com/gocodebox/lifterlms/blob/master/templates/single-no-access.php)
-+ [taxonomy-course_cat.php](https://github.com/gocodebox/lifterlms/blob/master/templates/taxonomy-course_cat.php)
-+ [taxonomy-course_difficulty.php](https://github.com/gocodebox/lifterlms/blob/master/templates/taxonomy-course_difficulty.php)
-+ [taxonomy-course_tag.php](https://github.com/gocodebox/lifterlms/blob/master/templates/taxonomy-course_tag.php)
-+ [taxonomy-course_track.php](https://github.com/gocodebox/lifterlms/blob/master/templates/taxonomy-course_track.php)
-+ [taxonomy-membership_cat.php](https://github.com/gocodebox/lifterlms/blob/master/templates/taxonomy-membership_cat.php)
-+ [taxonomy-membership_tag.php](https://github.com/gocodebox/lifterlms/blob/master/templates/taxonomy-membership_tag.php)
-
-
-v3.34.5 - 2019-08-29
---------------------
-
-+ Fixed logic issues preventing pending orders from being completed.
-
-##### Templates Changed
-
-+ [checkout/form-confirm-payment.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-confirm-payment.php)
-
-v3.34.4 - 2019-08-27
---------------------
-
-+ Add a new admin settings field type, "keyval", used for displaying custom html alongside a setting.
-+ Added filter `llms_order_can_be_confirmed`.
-+ Always bind JS for the login form handler on checkout and registration screens.
-
-##### Templates Changed
-
-+ [checkout/form-confirm-payment.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-confirm-payment.php)
-
-##### LifterLMS REST API v1.0.0-beta.6
-
-+ Fix issue causing certain webhooks to not trigger as a result of action load order.
-+ Change "access_plans" to "Access Plans" for better human reading.
-
-
-v3.34.3 - 2019-08-22
---------------------
-
-+ During payment gateway order completion, use `llms_redirect_and_exit()` instead of `wp_redirect()` and `exit()`.
-
-##### LifterLMS REST API v1.0.0-beta.5
-
-+ Load all required files and functions when authentication is triggered.
-+ Access `$_SERVER` variables via `filter_var` instead of `llms_filter_input` to work around PHP bug https://bugs.php.net/bug.php?id=49184.
-
-
-v3.34.2 - 2019-08-21
---------------------
-
-##### LifterLMS REST API v1.0.0-beta.4
-
-+ Load authentication handlers as early as possible. Fixes conflicts with numerous plugins which load user information earlier than expected by the WordPress core.
-+ Harden permissions associated with viewing student enrollment information.
-+ Returns a 400 Bad Request when invalid dates are supplied.
-+ Student Enrollment objects return student and post id's as integers instead of strings.
-+ Fixed references to an undefined function.
-
-
-v3.34.1 - 2019-08-19
---------------------
-
-+ Update LifterLMS REST to v1.0.0-beta.3
-
-##### Interface and Experience improvements during API Key creation
-
-+ Better expose that API Keys are never shown again after the initial creation.
-+ Allow downloading of API Credentials as a `.txt` file.
-+ Add `required` properties to required fields.
-
-##### Updates
-
-+ Added the ability to CRUD webhooks via the REST API.
-+ Conditionally throw `_doing_it_wrong` on server controller stubs.
-+ Improve performance by returning early when errors are encountered for various methods.
-+ Utilizes a new custom property `show_in_llms_rest` to determine if taxonomies should be displayed in the LifterLMS REST API.
-+ On the webhooks table the "Delivery URL" is trimmed to 40 characters to improve table readability.
-
-##### Bug fixes
-
-+ Fixed a formatting error when creating webhooks with the default auto-generated webhook name.
-+ On the webhooks table a translatable string is output for the status instead of the database value.
-+ Fix an issue causing the "Last" page pagination link to display for lists with 0 possible results.
-+ Don't output the "Last" page pagination link on the last page.
-
-
-
-v3.34.0 - 2019-08-15
---------------------
-
-##### LifterLMS REST API v1.0.0-beta.1
-
-+ A robust REST API is now included in the LifterLMS core.
-+ Create API Keys to consume and manage LifterLMS resources and students from external applications.
-+ Create webhooks to pass LifterLMS resource data to external applications (like Zapier!).
-+ The full API specification can be found at [https://gocodebox.github.io/lifterlms-rest/](https://gocodebox.github.io/lifterlms-rest/).
-
-##### Student management capabilities
-
-+ Explicit capabilities have been added to determine which users can create, view, update, and delete students.
-+ Admins and LMS Managers have all student management capabilities.
-+ Instructors and instructors assistants are granted limited view capabilities allowing them to only view students enrolled in their own courses/memberships.
-+ Added the `list_users` capability to the "Instructor" role, allowing instructor's to better view and manage their assistant instructors.
-+ The new capabilities are: `create_students`, `view_students`, `view_others_students`, `edit_students`, `edit_others_students`, `delete_students`, & `delete_others_students`.
-
-##### Updates
-
-+ Added new actions to help differentiate enrollment creation and update events.
-+ Added methods and logic for managing user management of other users.
-+ Added a filter `llms_table_get_table_classes` to LifterLMS admin tables which allows customization of the CSS classes applied to the ` ` elements. Thanks [@pondermatic](https://github.com/pondermatic)!
-+ Added a filter `llms_install_get_schema` to the database schema to allow 3rd parties to run table installations alongside the core.
-+ Added the ability to pull "raw" (unfiltered) data from the database via classes extending the `LLMS_Post_Model` abstract.
-+ Added a `bulk_set()` method to the `LLMS_Post_Model` abstract allowing the updating of multiple properties in one command.
-+ Added `comment_status`, `ping_status`, `date_gmt`, `modified_gmt`, `menu_order`, `post_password` as gettable\settable post properties via the `LLMS_Post_Model` abstract.
-+ Links on reporting tables are now the proper color.
-+ The `editable_roles` filter which determines which roles can manage which other roles is now always loaded (instead of being loaded only on the admin panel).
-+ Updated LifterLMS Blocks to 1.5.2
-
-##### Bug Fixes
-
-+ Fixed an issue preventing the `user_url` property from being retrieved by the `get()` method of the `LLMS_Abstract_User_Data` class.
-+ Fixed an issue causing the `LLMS_Instructors::get_assistants()` method to return assistants for the currently logged in user instead of the instructor of the instantiated object.
-+ Fixed an issue which would allow LMS Managers to edit and delete site administrators.
-
-##### Deprecations
-
-**The following functions and methods have been marked as deprecated and will be removed from LifterLMS with the next major release.**
-
-+ LLMS_Course::get_children_sections() use LLMS_Course::get_sections( 'posts' )" instead
-+ LLMS_Course::get_children_lessons() use LLMS_Course::get_lessons( 'posts' )" instead
-+ LLMS_Course::get_author()
-+ LLMS_Course::get_author_id() use LLMS_Course::get( "author" ) instead
-+ LLMS_Course::get_author_name()
-+ LLMS_Course::get_sku() use LLMS_Course::get( "sku" ) instead
-+ LLMS_Course::get_id() use LLMS_Course::get( "id" ) instead
-+ LLMS_Course::get_title() use get_the_title() instead
-+ LLMS_Course::get_permalink() use get_permalink() instead
-+ LLMS_Course::get_user_postmeta_data()
-+ LLMS_Course::get_user_postmetas_by_key()
-+ LLMS_Course::get_checkout_url()
-+ LLMS_Course::get_start_date() use LLMS_Course::get_date( "start_date" ) instead
-+ LLMS_Course::get_end_date() use LLMS_Course::get_date( "end_date" ) instead
-+ LLMS_Course::get_next_uncompleted_lesson()
-+ LLMS_Course::get_lesson_ids() use LLMS_Course::get_lessons( "ids" ) instead
-+ LLMS_Course::get_syllabus_sections() use LLMS_Course::get_sections() instead
-+ LLMS_Course::get_short_description() use LLMS_Course::get( "excerpt" ) instead
-+ LLMS_Course::get_syllabus() use LLMS_Course::get_sections() instead
-+ LLMS_Course::get_user_enroll_date()
-+ LLMS_Course::get_user_post_data()
-+ LLMS_Course::check_enrollment()
-+ LLMS_Course::is_user_enrolled() use llms_is_user_enrolled() instead
-+ LLMS_Course::get_student_progress() use LLMS_Student::get_progress() instead
-+ LLMS_Course::get_membership_link()
-
-
-v3.33.2 - 2019-06-26
---------------------
-
-+ It is now possible to send test copies of the "Student Welcome" email to yourself.
-+ Improved information logged when an error is encountered during an email send.
-+ Add backwards compatibility for legacy add-on integrations priority loading method.
-+ Fixed undefined index notice when viewing log files on the admin status screen.
-
-
-v3.33.1 - 2019-06-25
---------------------
-
-##### Updates
-
-+ Added method to retrieve the load priority of integrations.
-+ The capabilities used to determine if uses can clone and export courses now check `edit_course` instead of `edit_post`.
-
-##### Bug Fixes
-
-+ Fixed an issue which would cause the "Net Sales" line to sometimes display as a bar on the sales revenue reporting chart.
-+ Fixed an issue causing a PHP notice to be logged when viewing the sales reporting screen.
-+ Fixed an issue causing backslashes to be added before quotation marks in access plan descriptions.
-+ Integration classes are now loaded in the order defined by the integration class.
-+ Fixed an issue causing a PHP error when viewing the admin logs screen when no logs exist.
-
-
-v3.33.0 - 2019-05-21
---------------------
-
-##### Updates
-
-+ Added the ability for site administrators to delete (completely remove) enrollment records from the database.
-+ Catalogs sorted by Order (`menu_order`) now have an additional sort (by post title) to improve ordering consistency for items with the same order, thanks [@pondermatic](https://github.com/pondermatic)!
-+ Hooks in the dashboard order review template now pass the `LLMS_Order`.
-
-##### LifterLMS Blocks
-
-+ Updated to version 1.5.1
-+ All blocks are now registered only for post types where they can actually be used.
-+ Only register block visibility settings on static blocks. Fixes an issue causing core (or 3rd party) dynamic blocks from being managed within the block editor.
-
-##### Bug Fixes
-
-+ If an enrolled student accesses checkout for a course/membership they're already enrolled in they will be shown a message stating as much.
-+ Removed a redundant check for the existence of an order on the dashboard order review template.
-+ When an order is deleted, student enrollment records for that order will be removed. This fixes an issue causing admins to not be able to manage the enrollment status of a student enrolled via a deleted order.
-+ Fix issue causing errors when using the `[lifterlms_lesson_mark_complete]` shortcode on course post types.
-+ Fixed an issue causing quiz questions to generate publicly accessible permalinks which could be indexed by search engines.
-
-##### Templates Changed
-
-+ [course/complete-lesson-link.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/complete-lesson-link.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/master/templates/templates/myaccount/view-order.php)
-
-
-v3.32.0 - 2019-05-13
---------------------
-
-##### Updates
-
-+ Added Membership reporting
-+ Added the ability to restrict coupons to courses and memberships which are in draft or scheduled status.
-+ When recurring payments are disabled, output a "Staging" bubble on the "Orders" menu item.
-+ Recurring recharges now add order notes and trigger actions when gateway or recurring payment status errors are encountered.
-+ When managing recurring payment status through the warning notice, stay on the same page and clear nonces instead of redirecting to the LifterLMS Settings screen.
-+ Updated the Action Scheduler library to the latest version (2.2.5)
-+ Exposed the Action Scheduler's scheduled actions interface as a tab on the LifterLMS Status page.
-
-##### LifterLMS Blocks
-
-+ Updated to version 1.4.1.
-+ Fixed issue causing asset paths to have invalid double slashes.
-+ Fixed issue causing frontend css assets to look for an unresolvable dependency.
-
-##### Bug Fixes
-
-+ Fixed an issue allowing instructors to view a list of students from courses and memberships they don't have access to.
-+ WooCommerce compatibility filters added in 3.31.0 are now scheduled at `init` instead of `plugins_loaded`, resolves conflicts with several WooCommerce add-ons which utilize core WC functions before LifterLMS functions are loaded.
-
-
-v3.31.0 - 2019-05-06
---------------------
-
-##### Updates
-
-+ Tested to WordPress 5.2
-+ Adds explicit support for the twentynineteen default theme.
-+ The main students reporting table can now be filtered to show only students enrolled in a specific course or membership.
-+ Resolve conflict with WooCommerce (3.6 and later) resulting in 404s on the dashboard endpoints "lost password", "order history", and "edit account".
-+ Adds a dynamic filter (`llms_notification_view{$trigger_id}_basic_options`) to basic (pop-over) notifications to allow configuration of their settings.
-+ The filter `llms_plan_get_checkout_url` now passes a 3rd parameter: `$check_availability`
-+ Improves `LLMS_Course_Data` and `LLMS_Quiz_Data` classes by adding shared functionality to a shared abstract, `LLMS_Abstract_Post_Data`
-+ Changed access on class methods in `LLMS_Shortcode_Courses` from private to protected, thanks [@andrewvaughan](https://github.com/andrewvaughan)!
-
-##### Bug fixes
-
-+ Treats `post_excerpt` data as HTML instead of plain text. Fixes an issue resulting in HTML tags being stripped from lesson excerpts when duplicating a lesson in the course builder or importing lessons via the course importer.
-+ Fix an issue allowing access plan sales prices to be set as negative values.
-
-##### LifterLMS Blocks
-
-+ Updated to LifterLMS Blocks 1.4.0.
-+ Adds an "unmigration" utility to LifterLMS -> Status -> Tools & Utilities which can be used to remove LifterLMS blocks from courses and lessons which were migrated to the block editor structure.
-+ This tool is only available when the Classic Editor plugin is installed and enabled and it will remove blocks from ALL courses and lessons regardless of whether or not the block editor is being utilized on that post.
-
-##### Deprecations
-
-+ `LLMS_Query::add_query_vars()` use `LLMS_Query::set_query_vars()` instead.
-
-
-v3.30.3 - 2019-04-22
---------------------
-
-##### Updates
-
-+ Fixed typos and spelling errors in various strings.
-+ Corrected a typo in the `content-disposition` header used when exporting voucher CSVs, thanks [@pondermatic](https://github.com/pondermatic)!
-+ Improved the quiz attempt grading experience by automatically focusing the remarks field and only toggling the first answer if it's not visible, thanks [@eri-trabiccolo](https://github.com/eri-trabiccolo)!
-+ Removed commented out code on the Student Dashboard Notifications Tab template, thanks [@tnorthcutt](https://github.com/tnorthcutt)!
-
-##### Bug Fixes
-
-+ Renamed "descrpition" key to "description" found in the return of `LLMS_Instructor()->toArray()`.
-+ Fixed an issue causing slashes to be stripped from course content when cloning a course.
-+ Fixed an issue causing JS warnings to be thrown in the Javascript console on Course and Membership edit pages on the admin panel due to variables being defined too late, thanks [@eri-trabiccolo](https://github.com/eri-trabiccolo)!
-+ Fixed an undefined variable notice encountered when filtering quiz attempts on the quiz attempts reporting screen, thanks [@eri-trabiccolo](https://github.com/eri-trabiccolo)!
-+ Fixed an issue causing slashes to appear before quotation marks when saving remarks on a quiz attempt, thanks [@eri-trabiccolo](https://github.com/eri-trabiccolo)!
-+ [@pondermatic](https://github.com/pondermatic) fixed typos and misspellings in comment and docs in over 200 files and while that doesn't concern most users it's worthy of a mention.
-
-##### Deprecations
-
-The following unused classes have been marked as deprecated and will be removed from LifterLMS in the next major release.
-
-+ `LLMS\Users\User`
-+ `LLMS_Analytics_Page`
-+ `LLMS_Course_Basic`
-+ `LLMS_Lesson_Basic`
-+ `LLMS_Quiz_Legacy`
-
-##### Template Updates
-
-+ [templates/myaccount/my-notifications.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/my-notifications.php)
-
-
-v3.30.2 - 2019-04-09
---------------------
-
-+ Added new filter to allow 3rd parties to determine if a `LLMS_Post_Model` field should be added to the `custom` array when converting the post to an array.
-+ Added hooks and filters to the `LLMS_Generator` class to allow 3rd parties to easily generate content during course clone and import operations.
-+ Fixed an issue causing all available courses to display when the [lifterlms_courses] shortcode is used with the "mine" parameter and the current user viewing the shortcode is not enrolled in any courses.
-+ Fixed a PHP undefined variable warning present on the payment confirmation screen.
-
-##### Template Updates
-
-+ [templates/checkout/form-confirm-payment.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-confirm-payment.php)
-
-
-v3.30.1 - 2019-04-04
---------------------
-
-##### Updates
-
-+ Added handler to automatically resume pending (incomplete or abandoned) orders.
-+ Classes extending the `LLMS_Abstract_API_Handler` can now prevent a request body from being sent.
-+ Added dynamic filter `'llms_' . $action . '_more'` to allow customization of the "More" button text and url for student dashboard sections. Thanks @[pondermatic](https://github.com/pondermatic).
-+ Remove unused CSS code on the admin panel.
-
-##### Bug Fixes
-
-+ Fixed a bug preventing course imports as a result of action priority ordering issues.
-+ Function `llms_get_order_by_key()` correctly returns `null` instead of false when no order is found and will return an `int` instead of a numeric string when an order is found.
-+ Changed the method used to sort question choices to accommodate numeric choice markers. This fixes an issue in the Advanced Quizzes add-on causing reorder questions with 10+ choices to sort display in the incorrect order.
-+ Increased the specificity of LifterLMS element tooltip hovers. Resolves a conflict causing issues on the WooCommerce tax rate management screen.
-+ Fixed an issue causing certain fields in the Customizer from displaying a blue background as a result of very unspecific CSS rules, thanks [@Swapnildhanrale](https://github.com/Swapnildhanrale)!
-+ Fixed builder deep links to quizzes freezing due to dependencies not being available during initialization.
-+ Fixed builder issue causing duplicate copies of questions to be added when adding existing questions multiple times.
-
-##### Template Updates
-
-+ [templates/myaccount/dashboard-section.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/dashboard-section.php)
-
-
-v3.30.0 - 2019-03-21
---------------------
-
-##### Updates
-
-+ **Create custom thank you pages with new access plan checkout redirect options.**
-+ Added the ability to sort items on the membership auto enrollment table (drag and drop to sort and reorder).
-+ Improved the interface and interactions with the membership auto enrollment table settings.
-
-##### LifterLMS Blocks
-
-+ Updated LifterLMS Blocks to 1.3.8.
-+ Fixed an issue causing some installations to be unable to use certain blocks due to jQuery dependencies being declared improperly.
-
-##### Bug Fixes
-
-+ Fixed issue preventing courses with the same title from properly displayed on the membership automatic enrollment courses table on the admin panel.
-+ Fixed an issue preventing builder custom fields from being able to specify a custom sanitization callback.
-+ Fixed an issue preventing builder custom fields from being able to properly save and render multi-select data.
-
-##### Template Updates
-
-+ [templates/product/access-plan-restrictions.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-restrictions.php)
-+ [templates/product/free-enroll-form.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/free-enroll-form.php)
-
-
-v3.29.4 - 2019-03-08
---------------------
-
-+ Fixed an issue preventing users with email addresses containing an apostrophe from being able to login.
-
-
-v3.29.3 - 2019-03-01
---------------------
-
-##### Bug Fixes
-
-+ Removed attempts to validate & save access plan data when the Classic Editor "post" form is submitted.
-+ Fix issue causing 1-click free-enrollment for logged in users to refresh the screen without actually performing an enrollment.
-
-##### Template Updates
-
-+ [product/free-enroll-form.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/free-enroll-form.php)
-
-
-v3.29.2 - 2019-02-28
---------------------
-
-+ Fix issue causing blank "period" values on access plans from being updated.
-+ Fix an issue preventing paid access plans from being switched to "Free".
-
-
-v3.29.1 - 2019-02-27
---------------------
-
-+ Automatically reorder access plans when a plan is deleted.
-+ Skip (don't create) empty plans passed to the access plan save method as a result of deleted access plans.
-
-
-v3.29.0 - 2019-02-27
---------------------
-
-##### Improved Access Plan Management
-
-+ Added a set of methods for creating access plans programmatically.
-+ Updated the Access Plan metabox on courses and lessons with improved data validation.
-+ When using the block editor, the "Pricing Table" block will automatically update when access plan changes are saved to the database (from LifterLMS Blocks 1.3.5).
-+ Access plans are now created and updated via AJAX requests, resolves a 5.0 editor issue causing duplicated access plans to be created.
-
-##### Student Management Improvements
-
-+ Added the ability for instructors and admins to mark lessons complete and incomplete for students via the student course reporting table.
-
-##### Admin Panel Settings and Reporting Design Changes
-
-+ Replaced LifterLMS logos and icons on the admin panel with our new logo LifterLMS Logo and Icons.
-+ Revamped the design and layout of settings and reporting screens.
-
-##### Checkout Improvements
-
-+ Updated checkout javascript to expose an error addition functions
-+ Abstracted the checkout form submission functionality into a callable function not directly tied to `$_POST` data
-+ Removed display order field from payment gateway settings in favor of using the gateway table sortable list
-
-##### Other Updates
-
-+ Removed code related to an incompatibility between Yoast SEO Premium and LifterLMS resulting from former access plan save methods.
-+ Reduced application logic in the `course/complete-lesson-link.php` template file by refactoring button display filters into functions.
-+ Added function for checking if request is a REST request
-+ Updated LifterLMS Blocks to version 1.3.7
-
-##### Bug Fixes
-
-+ Fixed an issue preventing "Pricing Table" blocks from displaying on the admin panel when the current user was enrolled in the course or no payment gateways were enabled on the site.
-+ Fixed the checkout nonce to have a unique ID & name
-+ Fixed an issue with deleted quizzes causing quiz notification's to throw fatal errors.
-+ Fixed an issue preventing notification timestamps from displaying on the notifications dashboard page.
-+ Fix an issue causing `GET` requests with no query string variables from causing issues via incorrect JSON encoding via the API Handler abstract.
-+ Fix an issue causing access plan sale end dates from using the default WordPress date format settings.
-+ `LLMS_Lesson::has_quiz()` will now properly return a boolean instead of the ID of the associated quiz (or 0 when none found)
-
-##### Template Updates
-
-+ [checkout/form-checkout.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-checkout.php)
-+ [course/complete-lesson-link.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/complete-lesson-link.php)
-+ [product/access-plan-pricing.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-pricing.php)
-+ [notifications/basic.php](https://github.com/gocodebox/lifterlms/blob/master/templates/notifications/basic.php)
-
-##### Templates Removed
-
-Admin panel templates replaced with view files which cannot be overridden from a theme or custom plugin.
-
-+ `admin/post-types/product-access-plan.php`
-+ `admin/post-types/product.php`
-
-
-v3.28.3 - 2019-02-14
---------------------
-
-+ ❤❤❤ Happy Valentines Day or whatever ❤❤❤
-+ Tested to WordPress 5.1
-+ Fixed an issue causing JSON data saved by 3rd party plugins in course or lesson postmeta fields to be not duplicate properly during course duplications and imports.
-
-
-v3.28.2 - 2019-02-11
---------------------
-
-##### Updates
-
-+ Updated default country list to remove non-existent countries and resolve capitalization issues, thanks [nrherron92](https://github.com/nrherron92)!
-
-##### Bug fixes
-
-+ Fixed an issue causing the email notification content getter to use the same filter as popover notifications.
-+ Fixed an issue preventing default blog date & time settings from being used when displaying an access plan's access expiration date on course and membership pricing tables.
-+ Fixed an issue causing 404s on paginated dashboard endpoints when the permalink structure is set to anything other than `%postname%`.
-
-##### Deprecations
-
-+ `LLMS_Query->set_dashboard_pagination()`
-
-
-v3.28.1 - 2019-02-01
---------------------
-
-+ Fixed an issues preventing exports to be accessible on Apache servers.
-+ Fixed an issue causing servers with certain nginx rules to open CSV exports directly instead of downloading them.
-
-
-v3.28.0 - 2019-01-29
---------------------
-
-##### Updates
-
-+ Updated reporting table export functions to provide immediate download prompts of the files. Exports are generated in real time and you *must* remain on the page while it generates. The good news is if your site had issues with email or cronjobs it'll no longer be an issue for you.
-+ Updated lesson metabox to use icons for attached quizzes
-+ Added an orange highlight to the admin "Add-Ons & More" menu item
-+ Removed unused cron event.
-
-##### LifterLMS Blocks
-
-+ Updated LifterLMS Blocks to 1.3.4
-+ Adds support for handling courses & lessons in "Classic Editor" mode as defined by the Divi page builder
-+ Skips course and lesson migration when "Classic" mode is enabled.
-+ Adds conditions to identify "Classic" mode when the Classic Editor plugin settings are configured to enforce classic (or block) mode for *all* posts.
-
-##### Database Updates
-
-+ Unschedules the aforementioned unused cron event.
-
-##### Bug fixes
-
-+ Fixed an issue preventing the temp directory old file cleanup cron from firing on schedule.
-+ During plugin uninstallation the tmp cleanup cron will now be properly unscheduled.
-+ Fixed an issue causing notifications on the student dashboard to appear on top of static headers or the WP Admin Bar when scrolling.
-+ Fixed an issue preventing manual updating of customer and source information on orders resulting from unfocusable hidden form fields.
-+ Fixed mismatched HTML tags on the Admin Add-Ons screen
-
-##### Deprecations
-
-+ Class method: `LLMS_Admin_Table::queue_export()`
-+ Class: `LLMS_Processor_Table_To_Csv`
-
-
-v3.27.0 - 2019-01-22
---------------------
-
-###### Updates
-
-+ Added the ability to add existing questions to a quiz in the course builder. This allows cloning of existing questions as well as attaching "orphaned" questions currently attached to no quizzes.
-+ Added the ability to detach questions from quizzes. Coupled with adding existing questions, questions can now be easily moved between quizzes.
-+ Added permalink capabilities to the builder to allow linking to specific items within the builder (a lesson, quiz, etc...).
-+ Quizzes with 0 possible points will no longer show a Pass/Fail chart with a 0% (failing) grade on quiz results screens.
-+ Replaced option `lifterlms_lock_down` which cannot be set via any setting with a filter to reduce database calls. This will have no effect on anyone unless you manually set this option to "no" via a database query. Having done this would allow the admin bar to be shown to students.
-
-##### Bug Fixes
-
-+ Fixed an issue causing the default "Redeem Voucher" and "My Orders" student dashboard endpoint slugs from not having the correct default values. Thanks [@tnorthcutt](https://github.com/tnorthcutt)!
-+ Fixed an issue causing quotation marks in quiz question answers to show escaping slashes on results screens.
-+ Fixed a bug preventing viewing quiz results for quizzes with questions that have been deleted.
-+ Fixed a bug causing a PHP Notice to be output when registering a new user with a valid voucher.
-
-##### Templates Changed
-
-+ [quiz/results-attempt.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/results-attempt.php)
-
-
-v3.26.4 - 2019-01-16
---------------------
-
-+ Update to [LifterLMS Blocks 1.3.2](https://make.lifterlms.com/2019/01/15/lifterlms-blocks-version-1-3-1/), fixing an issue preventing template actions from being removed from migrated courses & lessons.
-
-
-v3.26.3 - 2019-01-15
---------------------
-
-##### Updates
-
-+ Fix issue preventing course difficulty and course length from being edited when using the classic editor plugin.
-+ Improved pagination methods on Student Dashboard Endpoints
-+ "My Notifications" dashboard tab now consistently paginated like other dashboard endpoints
-+ Update to [LifterLMS Blocks 1.3.1](https://make.lifterlms.com/2019/01/15/lifterlms-blocks-version-1-3-1/).
-
-##### Bug Fixes
-
-+ Fixed an issue preventing course difficulty and course length from being edited when using various page builders.
-+ Fixed issues causing errors on quiz reporting screens for quiz attempts made by deleted users.
-
-##### Deprecated Functions
-
-+ `LLMS_Student_Dashboard::output_notifications_content()` replaced with `lifterlms_template_student_dashboard_my_notifications()`
-
-##### Templates Changed
-
-+ [myaccount/my-notifications.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/my-notifications.php)
-+ [admin/reporting/tabs/quizzes/attempt.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/quizzes/attempt.php)
-
-
-v3.26.2 - 2019-01-09
---------------------
-
-+ Fast follow to fix incorrect version number pushed to the readme files for 3.26.1 which prevents upgrading to 3.26.1
-
-
-v3.26.1 - 2019-01-09
---------------------
-
-##### Updates
-
-+ Tested to WordPress 5.0.3
-+ Student CSV reports will now bypass cached data during report generation.
-+ Add course and membership catalog visibility settings into the block editor.
-+ Includes LifterLMS Blocks 1.3.0.
-
-##### Bug Fixes
-
-+ Fixed issue preventing the course instructors metabox from displaying when using the classic editor plugin.
-+ Fixed an issue causing membership background enrollment from processing when the course background processor is disabled via filters.
-+ Fixed an issue causing errors when reviewing orders on the admin panel which were placed via a payment gateway which is no longer active.
-+ Fixed an issue preventing course difficulty and course length from being edited when using the classic editor plugin.
-+ Fixed a very convoluted conflict between LifterLMS, WooCommerce, and Elementor explained at https://github.com/gocodebox/lifterlms/issues/730.
-
-
-v3.26.0 - 2018-12-27
---------------------
-
-+ Adds conditional support for page builders: Beaver Builder, Divi Builder, and Elementor.
-+ Fixed issue causing LifterLMS core sales pages from outputting automatic content (like pricing tables) on migrated posts.
-+ Student unenrollment calls always bypass cache during enrollment precheck.
-+ Membership post type "name" label is now plural (as it is supposed to be).
-
-
-v3.25.4 - 2018-12-17
---------------------
-
-+ Adds a filter (`llms_blocks_is_post_migrated`) to allow determining if a course or lesson has been migrated to the WP 5.0 block editor.
-+ Added a filter (`llms_dashboard_courses_wp_query_args`) to the WP_Query used to display courses on the student dashboard.
-+ Fixed issue on course builder causing prerequisites to not be saved when the first lesson in a course was selected as the prereq.
-+ Fixed issue on course builder causing lesson settings to be inaccessible without first saving the lesson to the database.
-
-
-v3.25.3 - 2018-12-14
---------------------
-
-+ Fixed compatibility issue with the Classic Editor plugin when it was added after a post was migrated to the new editor structure.
-
-
-v3.25.2 - 2018-12-13
---------------------
-
-+ Added new filters to the `LLMS_Product` model.
-+ Fix issue with student dashboard login redirect causing a white screen on initial login.
-
-
-v3.25.1 - 2018-12-12
---------------------
-
-##### Updates
-
-+ Editor blocks now display a lock icon when hovering/selecting a block which corresponds to the enrollment visibility settings of the block.
-+ Removal of core actions is now handled by a general migrator function instead of by individual blocks.
-
-##### Bug fixes
-
-+ Fixed issue preventing strings from the lifterlms-blocks package from being translatable.
-+ Fix issue causing block visibility options to not be properly set when enrollment visibility is first enabled for a block.
-+ Fixed compatibility issue with Yoast SEO Premium redirect manager settings, thanks [@moorscode](https://github.com/moorscode)!
-+ Fixed typo preventing tag size options (or filters) of course information block from functioning properly. Thanks [@tnorthcutt](https://github.com/tnorthcutt)!
-
-##### Templates Changed
-
-+ [templates/course/meta-wrapper-start.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/meta-wrapper-start.php)
-
-
-v3.25.0 - 2018-12-05
---------------------
-
-##### WordPress 5.0 Ready!
-
-+ **Tested with WordPress core 5.0 (Gutenberg)!**
-+ Editor Blocks: Course and Lesson layouts are now (preferably) powered by various editor blocks.
-+ When a block is added to a course or lesson, the template hook that automatically outputs that element is removed automatically (preventing duplicates).
-+ If you use the LifterLMS Labs: Action Manager you may no longer need it!
-+ Course & Membership instructors are now managed through an editor "plugin". Check out the rocket icon near the "Publish/Update" button.
-+ Instructor metabox will load conditionally based on presence of the block editor
-+ New courses and lessons will automatically have a preloaded block editor template
-+ Courses and lessons will automatically be "migrated" to these templates when edited on the admin panel
-+ Various course settings conditionally load based on the presence of the block editor
-+ Added filter to the headline size in the `course/meta-wrapper-start.php` template. Allows customization of headline via the "Course Information" block settings.
-+ If you're not ready for WordPress 5.0 you can still upgrade LifterLMS. This release is fully functional without the block editor.
-
-##### Bug Fixes
-
-+ Fixed typo in `quiz/start-button.php` template.
-+ Fixed error occurring during activation of LaunchPad via the Add-Ons & More screen.
-+ Fixed issue causing quiz reporting screens to be blank for users without `view_others_lifterlms_reports` capabilities.
-
-##### Templates Changed
-
-+ [templates/course/author.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/author.php)
-+ [course/meta-wrapper-start.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/meta-wrapper-start.php)
-+ [quiz/start-button.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/start-button.php)
-
-
-v3.24.3 - 2018-11-13
---------------------
-
-##### Updates
-
-+ Added user email, login, url, nicename, display name, first name, and last name as fields searched when searching orders. Thanks Thanks [@yojance](https://github.com/yojance)!
-
-##### Bug Fixes
-
-+ Fixed issue causing fatal errors encountered during certificate downloading caused by CSS ` ` tags existing outside of the `` element.
-+ Certificates downloaded by users who can see the WP Admin Bar will no longer show the admin bar on the downloaded certificate
-+ Fixed issue on iOS Safari causing multiple choice quiz questions to require a "long press" to be properly selected
-+ Fixed issue causing access plan sales to end 36m and 1s prior to end of the day on the desired sale end date. Thanks [@eri-trabiccolo](https://github.com/eri-trabiccolo)!
-+ Ensure that fallback url slugs for course & membership archives are translatable.
-
-
-v3.24.2 - 2018-10-30
---------------------
-
-+ Fix issue causing newline characters to be malformed on course builder description fields, resulting in `n` characters being output in strange places.
-
-
-v3.24.1 - 2018-10-29
---------------------
-
-##### Updates
-
-+ The shortcode `[lifterlms_hide_content]` now accepts multiple IDs and can specify whether the user must belong to either *all* or *any one* of the specified memberships. Thanks [@yojance](https://github.com/yojance)!
-+ The action `llms_voucher_used`, called when a voucher code is used, will now pass the voucher code as a 3rd parameter. Thanks [@yojance](https://github.com/yojance)!
-
-##### Bug Fixes
-
-+ Fixed a typo in engagement drop creation dropdown. Thanks [README1ST](https://github.com/README1ST)!
-+ Fixed issue causing backslash characters (`\`) to be removed from course elements (sections, lessons, quizzes, and assignments) constructed in the course builder.
-+ Fixed an issue in the 3.16.0 database migration script that would cause migrations to get stuck as a result of malformed data saved in an invalid format.
-+ Added processing handlers to payment confirmation form. Fixes an issue which would allow multiple payment confirmation requests to be made (if the form was submitted multiple times before the page reloaded) resulting in duplicate charges.
-
-##### Templates Changed
-
-+ templates/checkout/form-confirm-payment.php
-
-
-v3.24.0 - 2018-10-23
---------------------
-
-##### "My Grades" Student Dashboard Endpoint
-
-+ A new student dashboard endpoint, "My Grades", has been added
-+ The main screen displays a paginated and sortable list of all courses a student is enrolled in and outputs their progress and grade in the courses
-+ Students can drill into individual reporting screens for each course where specific details for each course are available for review
-
-##### Grading Enhancements
-
-+ Each lesson can now be assigned an individual "points" value
-+ When a course is graded the points assigned to each lesson will be used to calculate the value of the lesson's grade within the overall course grade
-+ Lessons can also be assigned a value of "0" to allow a lesson to not count towards the overall grade of the course.
-+ Email notifications are now sent to a student when an instructor reviews, grades, or leaves remarks on a quiz attempt.
-
-##### Test Email Notifications
-
-+ An interface and API for sending test email notifications has been added, the following notifications can now be tested:
-
- + Purchase Receipt
- + Quizzes: Failed (Thanks [@philwp](https://github.com/philwp)!)
- + Quizzes: Graded
- + Quizzes: Passed (Thanks [@philwp](https://github.com/philwp)!)
-
-##### Updates and Enhancements
-
-+ Quiz Passed & Quiz Failed notifications have new names on the admin panel ("Quizzes: Quiz Passed" & "Quizzes: Quiz Failed")
-+ The default content for Quiz Passed and Quiz Failed notifications have been enhanced. If you've modified these you can delete your modified content to have your notifications "restored" to the improved defaults.
-+ Change the page title of the Student Dashboard page installed via the Setup Wizard to be "Dashboard" instead of "My Courses." Thanks [@philwp](https://github.com/philwp)!
-+ In the course builder when a lesson is duplicated, the attached quiz will be duplicated as well
-+ Minor increase to performance in the `LLMS_Course->get_lessons()` method
-+ Added `student_id` as a parameter passed to the `llms_student_get_progress` filter
-+ Updated all access plan templates added in 3.23.0 to ensure `ABSPATH` is defined to prevent direct template access
-+ Remove use of deprecated `LLMS_Lesson->get_children_lessons()` in the `LLMS_Course` and `LLMS_Lesson` models as well as in the `course/syllabus.php` template
-+ Refactored the `LLMS_Section->get_percent_complete()` method to utilize methods from the `LLMS_Student` model
-+ Added the ability for admin table classes to define `` element CSS classes
-+ Admin settings pages with no settings to save (like the Notifications list) no longer display a "Save" button
-+ Added actions when creating, updating, and deleting records managed by `LLMS_Abstract_Database_Store` classes
-+ Updated system report to include URLs to settings with URLs, adds a small speed boost to support request turn around time.
-
-##### Please Rate & Review LifterLMS on WordPress.org
-
-+ Added a WordPress.org review request link to the footer of LifterLMS admin pages.
-+ Added a WordPress.org review request notice which displays a week after installation if the site has 50+ active students.
-
-##### Bug fixes
-
-+ Fixed issue causing HTML entity codes to display in email subject lines. Thanks [@philwp](https://github.com/philwp)!
-+ Fixed issue causing post cleanup functions to run queries against unsupported post types.
-+ Fixed typos in a handful of i18n functions so that the proper textdomain is now being used
-+ Removed `get_option()` call to unused option `lifterlms_logout_endpoint` which ran on WordPress initialization unnecessarily.
-+ Removed 3.21.0 fixes for iOS touch issues that are now causing iOS touch issues on quizzes.
-+ When an order is deleted, all order transactions will also be deleted. This does not happen until the order is deleted (transactions will remain while the order is in the trash)
-+ Fixed an issue causing duplicated quizzes to initially show images for question images & image choices (reorder pictures & picture choice) but the image data would not be properly saved so when returning to the builder or viewing a quiz on the frontend the images would be lost
-
-##### Deprecated Functions & Methods
-
-+ Deprecated `LLMS_Section->get_children_lessons()`, use `LLMS_Section->get_lessons( 'posts' )` instead
-
-##### Template Updates
-
-+ [course/syllabus.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/syllabus.php)
-+ [product/access-plan-button.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-button.php)
-+ [product/access-plan-description.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-description.php)
-+ [product/access-plan-feature.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-feature.php)
-+ [product/access-plan-pricing.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-pricing.php)
-+ [product/access-plan-restrictions.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-restrictions.php)
-+ [product/access-plan-title.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-title.php)
-+ [product/access-plan-trial.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/access-plan-trial.php)
-+ [product/free-enroll-form.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/free-enroll-form.php)
-
-
-v3.23.0 - 2018-08-27
---------------------
-
-##### Access Plan & Pricing Table Template Improvements
-
-+ The pricing table template has been split into multiple templates which are now rendered via action hooks. No visual changes have been made but if you've customized the template using a template override you'll want to review the template changes before updating!
-+ New action hooks are available to modify the rendering of access plans in course / membership pricing tables.
-
- + `llms_access_plan`: Main hook for outputting an entire access plan within the pricing table
- + `llms_before_access_plan`: Called before main content of access plan. Outputs the "Featured" area of plans
- + `llms_acces_plan_content`: Main access plan content. Outputs title, pricing info, restrictions, and description
- + `llms_acces_plan_footer`: Called after main content. Outputs trial info and the checkout / enrollment button
-
-+ Added filters to the returns of many of the functions in the `LLMS_Acces_Plan` model.
-+ Minor improvements made to `LLMS_Access_Plan` model
-
-##### Updates and Enhancements
-
-+ Improved handling of empty blank / empty data when adding instructors to courses and memberships
-+ Added filters to the "Sales Page Content" type options & functions for courses and memberships to allow 3rd parties to define their own type of sales page functionality
-+ Added filters to the saving of access plan data
-+ Improved the HTML and added CSS classes to the access plan admin panel html view
-
-##### Bug Fixes
-
-+ Fixes issue causing the "Preview Changes" button on courses to lock the "Update" publishing button which prevents changes from being properly saved.gi
-+ Fixed issue causing PHP errors when viewing courses / memberships on the admin panel when an instructor user was deleted
-+ Fixed issue causing PHP notices when viewing course / membership post lists on the admin panel when an instructor user was deleted
-+ Fixed issue causing PHP warnings to be generated when viewing the user add / edit screen on the admin panel
-+ Fixed an issue which would cause access plans to never be available to users. *This bug didn't affect any existing installations except if you wrote custom code that called the `LLMS_Access_Plan::is_available_to_user()` method.*
-
-##### Template Updates
-
-+ [templates/admin/post-types/product-access-plan.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/post-types/product-access-plan.php)
-+ [templates/product/pricing-table.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/pricing-table.php)
-
-
-v3.22.2 - 2018-08-13
---------------------
-
-+ Fixed issue causing banners on general settings screen to cause a fatal error when api connection errors occurred
-+ Improved CSS on setup wizard
-
-
-v3.22.1 - 2018-08-06
---------------------
-
-+ Fix issue causing themes to appear as requiring updates when using the LifterLMS Helper
-
-
-v3.22.0 - 2018-07-31
---------------------
-
-+ Frontend notifications are no longer powered by AJAX requests. This change will significantly reduce the number of requests made but will remove the ability for students to receive asynchronous notifications. This means that notifications will only be displayed on page load as notification polling will no longer occur while a student is on a page (while reading the content a lesson, for example).
-+ Course and membership catalogs items in navigation menus will now have expected CSS classes to identify current item and current item parents
-+ The admin panel add-ons screen has been reworked to be powered by the lifterlms.com REST api
-+ Some visual changes have been made to the add-ons screen
-+ The colors on the voucher screen on the admin panel have been updated to match the rest of the interfaces in LifterLMS
-
-
-v3.21.1 - 2018-07-24
---------------------
-
-+ Fixed issue causing visual issues on checkout summary when using coupons which apply discounts to a plan trial
-+ Fixed issue causing `.mo` files stored in the `languages/lifterlms` safe directory from being loaded before files stored in the default location `languages/plugins`
-+ Added methods to integration abstract to allow integration developers to automatically describe missing integration dependencies
-+ Tested to WordPress 4.9.8
-
-##### Template Updates
-
-+ [templates/checkout/form-summary.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-summary.php)
-
-
-v3.21.0 - 2018-07-18
---------------------
-
-##### Updates and Enhancements
-
-+ Added new actions before and after global login form HTML: `llms_before_person_login_form` & `llms_after_person_login_form`
-+ Settings API can now create disabled fields
-+ Added new actions to the checkout form: `lifterlms_pre_checkout_form` && `lifterlms_post_checkout_form`
-+ Added CRUD functions for interacting with data located in the `wp_lifterlms_user_postmeta` table
-+ Replaced various database queries for CRUD user postmeta data with new CRUD functions
-+ Added new utility function to allow splicing data into associative arrays
-
-##### Bug Fixes
-
-+ If all user information fields are disabled, the "Student Information" are will now be hidden during checkout for logged in users instead of displaying an empty information box
-+ Fixed plugin compatibility issue with Advanced Custom Fields
-+ Fixed issue causing multiple choice quiz questions to require a double tap on some iOS devices
-+ Fixed incorrectly named filter causing section titles to not display on student course reporting screens
-+ We do not advocate using PHP 5.5 or lower but if you were using 5.5 or lower and encountered an error during bulk enrollment we've fixed that for. Please upgrade to 7.2 though. We all want faster more secure websites.
-
-##### Template Updates
-
-+ [templates/checkout/form-checkout.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-checkout.php)
-+ [templates/global/form-login.php](https://github.com/gocodebox/lifterlms/blob/master/templates/global/form-login.php)
-
-
-v3.20.0 - 2018-07-12
---------------------
-
-+ Updated user interfaces on admin panel for courses and memberships with relation to "Enrolled" and "Non-Enrolled" student descriptions
-+ "Enrolled Student Description" is now the default WordPress editor
-+ "Non-Enrolled Student Description" is now the "Sales Page"
-+ Additional options for sales pages (the content displayed to visitors and non-enrolled students) have been added:
- + Do nothing (show course description)
- + Show custom content (use a WYSIWYG editor to define content)
- + Redirect to a WordPress page (use custom templates and enhance page builder compatibility and capabilities)
- + Redirect to a custom URL (use a sales page hosted on another domain!)
-+ Tested to WordPress 4.9.7
-
-v3.19.6 - 2018-07-06
---------------------
-
-+ Fix file load paths in OptimizePress plugin compatibility function
-
-
-v3.19.5 - 2018-07-05
---------------------
-
-+ Fixed bug causing `select2` multi-selects from functioning as multi-selects
-+ Fixed visual issue with `select2` elements being set without a width causing them to be both too small and too large in various scenarios.
-+ Fixed duplicate action on dashboard section template
-
-##### Template Updates
-
-+ [templates/myaccount/dashboard-section.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/dashboard-section.php)
-
-
-v3.19.4 - 2018-07-02
---------------------
-
-##### Updates and enhancements
-
-+ Bulk enroll multiple users into a course or membership from the Users table on your admin panel. See how at [https://lifterlms.com/docs/student-bulk-enrollment/](https://lifterlms.com/docs/student-bulk-enrollment/)
-+ Added event on builder to allow integrations to run trigger events when course elements are saved
-+ Added general redirect method `llms_redirect_and_exit()` which is a wrapper for `wp_redirect()` and `wp_safe_redirect()` which can be plugged (and tested via phpunit)
-+ Added new action called before validation occurs for a user account update form submission: `llms_before_user_account_update_submit`
-+ Removed placeholders from form fields. Fixes a UX issue causing registration forms to appear cluttered due to having both placeholders and labels.
-
-##### Bug fixes
-
-+ Fixed issue allowing nonce checks to be bypassed on login and registration forms
-+ Fixed issue causing a PHP notice if the registration form is submitted without an email address and automatic username generation is enabled
-+ Fixed issue preventing email addresses with the "'" character from being able to register, login, or update account information
-+ Fixed typo in automatic username generation filter `lifterlms_generated_username` (previously was `lifterlms_gnerated_username`)
-+ Fixed issue causing admin panel static assets to have a double slash (//) in the asset URI path
-+ Fixed issue allowing users with `view_lifterlms_reports` capability (Instructors) to access sales & enrollment reporting screens. The `view_others_lifterlms_reports` capability (Admins & LMS Managers) is now required to view these reporting tabs.
-+ Updated IDs of login and registration nonces to be unique. Fixes an issue causing Chrome to throw non-unique ID warnings in the developer console. Also, IDs are supposed to be unique _anyway_ but thanks for helping us out Google.
-
-
-v3.19.3 - 2018-06-14
---------------------
-
-+ Fix issue causing new quizzes to be unable to load questions list without reloading the builder
-
-
-v3.19.2 - 2018-06-14
---------------------
-
-##### Updates and enhancements
-
-+ The course builder will now load quiz question data when the quiz is opened instead of loading all quizzes on builder page load. Improves builder load times and addresses an issue which could cause timeouts in certain environments when attempting to edit very large courses.
-+ The currently viewed lesson will now be bold in the lesson outline widget.
-+ Added a CSS class `.llms-widget-syllabus .llms-lesson.current-lesson` which can be used to customize the display of the current lesson in the widget.
-+ Added the ability to filter quiz attempt reports by quiz status
-+ Updated language for access plans on with a limited number of payments to reflect the total number of payments due as opposed to the length (for example in years) that the plan will run.
-
-##### Bug fixes
-
-+ Fixed issue preventing oEmbed media from being used in quiz question descriptions
-+ Fixed issue preventing `` from being used in quiz question descriptions
-+ Quiz results will now exclude questions with 0 points value when displaying the number of questions in the quiz.
-+ Fixed error occurring when sorting was applied to quiz attempt reports which would cause quiz attempts from other quizzes to be included in the new sorted report
-+ Fixed filter `lifterlms_reviews_section_title` which was unusable due to the incorrect usage of `_e()` within the filter. Now using `__()` as expected.
-+ Fixed issue causing course featured image to display in place of lesson feature images
-
-##### Template Updates
-
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/lesson-preview.php)
-+ [templates/course/outline-list-small.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/outline-list-small.php)
-+ [templates/quiz/results-attempt.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/results-attempt.php)
-
-
-v3.19.1 - 2018-06-07
---------------------
-
-+ Fixed CSS specificity issue on admin panel causing white text on white background on system status pages
-
-
-v3.19.0 - 2018-06-07
---------------------
-
-##### Updates and enhancements
-
-+ Added a "My Memberships" tab to the student dashboard
-+ "My Memberships" preview area
-+ Updated admin panel order status badges to match frontend order status badges
-+ Added a new recurring order status "Pending Cancel." Orders in this state will allow students to access course / membership content until the next payment is due, on this date, instead of a recurring charge being made the order will move to "Cancelled" and the student's enrollment status will change to "Cancelled" removing their access to the course or membership.
-+ When a student cancels an active recurring order from the student dashboard, the order will move to "Pending Cancellation" instead of "Cancelled"
-+ Students can re-activate an order that's Pending Cancellation moving the expiration date to the next payment due date
-+ Added the ability to edit the access expiration date for orders with limited access settings and for orders in the "pending-cancel" state
-+ Added a filter to allow customization of the URL used to generate certificate downloads from
-+ When viewing taxonomy archives for any course or membership taxonomy (categories, tags, and tracks), if a term description exists, it will be used instead of the default catalog description content defined on the catalog page.
-+ Added a filter (`llms_archive_description`) to allow filtering of the archive description
-+ When `WP_DEBUG` is disabled the scheduled-actions posttype interface is now available via direct link. Useful for debugging but don't want to expose a menu-item link to clients. Access via wp-admin/edit.php?post_type=scheduled-action. Be warned: you shouldn't be modifying scheduled actions manually and that's why we're not exposing this directly, this should be used for debugging only!
-+ Updated the function used to check if lessons have featured images to improve performance and resolve an incompatibility issue with WP Overlays plugin.
-
-##### Bug fixes
-
-+ Fixed issue causing "My Courses" title to be duplicated on the student dashboard when viewing the endpoint
-+ Fixed issue causing the trial price to be displayed with a strike-through during a sale
-+ Fixed coupon issue causing coupons to expire at the beginning of the day on the expiration date instead of at the end of the day
-+ Fixed issue causing CSS rules to lose their declared order during exports causing export rendering issues with certain themes and plugin combinations
-
-##### Template Updates
-
-+ [templates/checkout/form-summary.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-summary.php)
-+ [templates/checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-switch-source.php)
-+ [templates/course/lesson-preview.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/lesson-preview.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/view-order.php)
-
-
-v3.18.2 - 2018-05-24
---------------------
-
-+ Improved integrations settings screen to allow each integration to have it's own settings tab (page) with only its own settings
-+ Allow programmatic access to notification content when notification views are accessed via filters
-+ Fixed issue causing subscription cancellation notifications to be sent to admins when new orders were created
-+ Fixed warning message displayed prior to membership bulk enrollment
-+ Fixed multibyte character encoding issue encountered during certificate exports
-
-
-v3.18.1 - 2018-05-18
---------------------
-
-+ Attached `llms_privacy_policy_form_field()` and `llms_agree_to_terms_form_field()` to an action hook `llms_registration_privacy`
-+ Define minimum WordPress version requirement as 4.8.
-
-##### Template Updates
-
-+ [templates/checkout/form-checkout.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-checkout.php)
-+ [templates/global/form-registration.php](https://github.com/gocodebox/lifterlms/blob/master/templates/global/form-registration.php)
-
-
-v3.18.0 - 2018-05-16
---------------------
-
-##### Privacy & GDPR Compliance Tools
-
-+ Added privacy policy notice on checkout, enrollment, and registration that integrates with the WP Core 4.9.6 Privacy Policy Page setting
-+ Added settings to allow customization of the privacy policy and terms & conditions notices during checkout, enrollment, and registration
-+ Added suggested Privacy Policy language outlining information gathered by a default LifterLMS site
-
-+ During a WordPress Personal Data Export request the following LifterLMS information will be added to the export
-
- + All personal information gathered from registration, checkout, and enrollment forms
- + Course and membership enrollments, progress, and grades
- + Earned achievements and certificates
- + All order data
-
-+ During a WordPress Personal Data Erasure request the following LifterLMS information will be erased
-
- + All personal information gathered from registration, checkout, and enrollment forms
- + Earned achievements and certificates
- + All notifications for or about the user
- + If the "Remove Order Data" setting is enabled, the order will be anonymized by removing student personal information from the order and, if the order is a recurring order, it will be cancelled.
- + If the "Remove Student LMS Data" setting is enabled, all student data related to course and membership activity will be removed
-
-+ All of the above relies on features available in WordPress core 4.9.6
-
-##### Updates and Enhancements
-
-+ Tested up to WordPress 4.9.6
-+ Improved pricing table UX for members-only access plans. An access plan button for a plan belonging to only one membership will click directly to the membership as opposed to opening a popover. Plan's with access via multiple memberships will continue to open a popover listing all availability options.
-+ Added a "My Certificates" tab to the Student Dashboard
-+ Certificates can be downloaded as HTML files (available when viewing a certificate or from the certificate reporting screen on the admin panel)
-+ Admins can now delete certificates and achievements from reporting screens on the admin panel
-+ Added additional information to certificate and achievement reporting tables
-+ Expanded widths of admin settings page setting names to be a bit wider and more readable
-+ Now conditionally hiding some settings when they are no longer relevant
-+ Added daily cron automatically remove files from the `LLMS_TMP_DIR` which are more that 24 hours old
-+ Removed unused template `content-llms_membership.php`
-+ Added initialization actions for use by integration classes
-
-##### Bug Fixes
-
-+ Fixed issue causing coupon reports to always display "1" regardless of actual number of coupons used
-+ Fixed issue causing new posts created via the Course Builder to always be created for user_id #1
-+ Fixed issue causing "My Achievements" to display twice on the My Achievements student dashboard tab
-+ Fixed issue preventing lessons from being completed when a quiz in draft mode was attached to the lesson
-+ Fixed issue causing minified RTL stylesheets to 404
-
-##### Template Updates
-
-+ [templates/admin/post-types/order-details.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/post-types/order-details.php)
-+ [templates/checkout/form-checkout.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-checkout.php)
-+ [templates/content-certificate.php](https://github.com/gocodebox/lifterlms/blob/master/templates/content-certificate.php)
-+ [templates/global/form-registration.php](https://github.com/gocodebox/lifterlms/blob/master/templates/global/form-registration.php)
-+ [templates/myaccount/dashboard-section.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/dashboard-section.php)
-
-
-v3.17.8 - 2018-05-04
---------------------
-
-##### Updates and Enhancements
-
-+ Added admin email notification when student cancels a subscription
-+ Quiz results will now display the question's description when reviewing results as a student and on the admin panel during grading
-+ Add action hook fired when a student cancels a subscription (`llms_subscription_cancelled_by_student`)
-+ Reduce unnecessary DB queries for integrations by checking for dependencies and then calling querying the options table to see if the integration has been enabled.
-+ Updated the notifications settings table to be more friendly to the human eye
-
-##### Bug Fixes
-
-+ Fix admin scripts enqueue order. Fixes issue preventing manual student enrollment selection from functioning properly in certain scenarios.
-+ Shift + Enter when in a question choice field now adds a return as expected instead of exiting the field
-+ When pasting into question choice fields HTML from RTF documents will be automatically stripped
-+ Ensure certificates print with a white background regardless of theme CSS
-+ Fix issue causing themes with `overflow:hidden` on divs from cutting certificate background images
-+ Upon export completion unlock tables regardless of mail success / failure
-+ Resolve issue causing incorrect number of access plans to be returned on systems that have custom defaults set for `WP_Query` `post_per_page` parameter
-+ Fix error occurring when all 3rd party integrations are disabled by filter, credit to [@Mte90](https://github.com/Mte90)!
-+ Ensure `LLMS()->integrations()->integrations()` returns all integrations regardless of availability.
-+ Updated `LLMS_Abstract_Options_Data` to have an option set method
-
-##### Template Updates
-
-+ [templates/quiz/results-attempt-questions-list.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/results-attempt-questions-list.php)
-
-
-v3.17.7 - 2018-04-27
---------------------
-
-+ Fix issue preventing assignments passing grade requirement from saving properly
-+ Fix issue preventing builder toggle switches from properly saving some switch field data
-+ Fix with "Launch Builder" button causing it to extend outside the bounds of its container
-+ Fix issue with builder radio select fields during view rerenders
-+ Course Outline shortcode (and widget) now retrieve parent course of the current page more consistently with other shortcodes
-+ Added ability to filter which custom post types which can be children of a course (allows course shortcodes & widgets to be used in assignment sidebars of custom content areas)
-
-
-v3.17.6 - 2018-04-26
---------------------
-
-+ Updated language on recurring orders with no expiration settings. Orders no longer say "Lifetime Access" and instead output no expiration information
-+ Quiz editor on builder updated to be consistent visually and functionally to the lesson settings editor
-+ Improved the builder field API to allow for radio element fields
-+ Fix issue causing JS error on admin settings pages
-+ Updated CSS for Certificates to be more generally compatible with theme styles when printed
-+ Allow system print settings to control print layout for certificates by removing explicit landscape declarations
-+ Now passing additional data to filters used to create custom columns on reporting screens
-+ Remove unused JS files & Chosen JS library
-+ Added filter to allow opting into alternate student dashboard order layout. Use `add_filter( 'llms_sd_stacked_order_layout', '__return_true' )` to stack the payment update sidebar below the main order information. This is disabled by default.
-+ Achievement and Certificate basic notifications now auto-dismiss after 10 seconds like all other basic notifications
-+ Deprecated Filter `llms_get_quiz_theme_settings` and added backwards compatible methods to transition themes using this filter to the new custom field api. For more information see new methods at https://lifterlms.com/docs/course-builder-custom-fields-for-developers/
-+ Increased default z-index on notifications to prevent notifications from being hidden behind floating / static navigation menus
-
-
-##### Template Updates
-
-+ [templates/myaccount/my-orders.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/my-orders.php)
-+ [templates/myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/view-order.php)
-
-
-v3.17.5 - 2018-04-23
---------------------
-
-##### Admin Settings Interface Improvements
-
-+ Improved admin settings page interface to allow for section navigation
-+ Updated checkout setting pages to utilize a separate section (page) for each available payment gateway
-+ Added a table of payment gateways to see at a glance which gateways are enabled and allows drag and drop reordering of gateway display order
-+ Moved dashboard endpoints to a separate section on the accounts settings area
-+ Updated CSS on settings page to have more regular spacing between subtitles and settings fields
-+ Added a "View" button next to any admin setting post/page selection field to allow quick viewing of the selected post
-+ Purchase page setting field is now ajax powered like all other page selection settings
-+ Renamed dashboard settings section titles to be more consistent with language in other areas of LifterLMS
-+ All dashboard endpoints now automatically sanitized to be URL safe
-
-##### Updates and Enhancements
-
-+ Dashboard endpoints can now be deregistered by setting the endpoint slug to be blank on account settings
-
-##### Bug Fixes
-
-+ Fix issue causing 404s for various script files when SCRIPT_DEBUG is enabled
-+ Fix issue with audio & video embeds to prevent fallback to default post attachments
-+ Fix issue causing student selection boxes to malfunction due to missing dependencies when loaded over slow connections
-
-##### Template Updates
-
-+ [templates/myaccount/navigation.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/navigation.php)
-
-
-v3.17.4 - 2018-04-17
---------------------
-
-+ Added core RTL language support
-+ Fixed fatal error on student management tables resulting from deleted admin users who manually enrolled students
-+ Added filter to allow 3rd parties to disable achievement dupchecking (`llms_achievement_has_user_earned`)
-+ Added {student_id} merge code which can be utilized on certificates
-+ Added merge code insert button to certificates editor
-+ Added filter to allow 3rd parties to disable certificate dupchecking (`llms_certificate_has_user_earned`)
-+ Added filter to allow 3rd parties to add custom merge codes to certificates (`llms_certificate_merge_codes`)
-+ Fix restriction check issue for lessons with drip or prerequisites on course outline widget / shortcode
-+ Bumped WP tested to version to 4.9.5
-
-##### Template Updates
-
-+ [templates/course/complete-lesson-link.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/complete-lesson-link.php)
-+ [templates/course/outline-list-small.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/outline-list-small.php)
-
-
-v3.17.3 - 2018-04-11
---------------------
-
-+ Course and Membership instructor metabox search field now correctly states "Select an Instructor" instead of previous "Select a Student"
-+ Added missing translation for "Select a Student" on admin panel student selection search fields
-+ Fix issue causing reporting export CSVs to throw a SYLK interpretation error when opened in Excel
-+ Fix issue causing drafted courses and memberships to be published when the "Update" button is clicked to save changes
-+ Remove use of PHP 7.2 deprecated `create_function`
-+ Fix errors resulting from quiz questions which have been deleted
-+ Fix issue causing current date / time to display as the End Date for incomplete quiz attempts on quiz reporting screens
-
-##### Template Updates
-
-+ [templates/admin/reporting/tabs/quizzes/attempt.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/quizzes/attempt.php)
-+ [templates/quiz/results-attempt-questions-list.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/results-attempt-questions-list.php)
-
-
-v3.17.2 - 2018-04-09
---------------------
-
-+ Fixed issue preventing lesson video and audio embeds from being *removed* when using the course builder settings editor
-+ Fixed issue causing question images to lose the image source
-+ Updated student management table for courses and memberships to show the name (and a link to the user profile) of the site user who manually enrolled the student.
-+ Add "All Time" reporting to various reporting filters
-+ Added API for builder fields to enable multiple select fields
-+ Fix memory leak related to assignments rendering on course builder
-+ Fix issue causing course progress and enrollment checks to incorrectly display progress data cached for other users
-+ Lesson progression actions (Mark Complete & Take Quiz buttons) will now always display to users with edit capabilities regardless of enrollment status
-
-##### Template Updates
-
-+ [templates/course/complete-lesson-link.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/complete-lesson-link.php)
-+ [templates/course/outline-list-small.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/outline-list-small.php)
-
-
-v3.17.1 - 2018-03-30
---------------------
-
-+ Refactored lesson completion methods to allow 3rd party customization of lesson completion behavior via filters and hooks.
-+ Remove duplicate lesson completion notice implemented. Only popover notifications will display now instead of popovers and inline messages.
-+ Object completion will now automatically prevent multiple records of completion from being recorded for a single object.
-+ Lesson Mark Complete button and lessons completed by quiz now utilizes a generic trigger to mark lessons as complete: `llms_trigger_lesson_completion`.
-+ Removed several unused functions from frontend forms class
-+ Moved lesson completion form controllers to their own class
-
-##### Templates updates
-
-+ [templates/course/complete-lesson-link.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/complete-lesson-link.php)
-
-
-v3.17.0 - 2018-03-27
---------------------
-
-##### Builder Updates
-
-+ Moved action buttons for each lesson (for opening quiz and lesson editor) to be static below the lesson title as opposed to only being visible on hover
-+ Added new audio and video status indicator icons for each lesson
-+ Various status indicator icons will now have different icons in addition to different colors depending on their state
-+ Replaced "pencil" icons that open the WordPress post editor with a small "WP" icon
-+ Added several actions and filters to backend functions so that 3rd parties can hook into builder saves
-+ Added lesson settings editing to the builder. Lesson settings can now be updated from settings metaboxes on the lesson post edit screen AND on the builder.
-+ Added prerequisite validation for lessons to prevent accidental impossible prerequisite creating (eg: Lesson 5 can never be a prerequisite for Lesson 4)
-+ Added functions and filters to allow 3rd parties to add custom fields to the builder. For more details see [an example](https://lifterlms.com/docs/course-builder-custom-fields-for-developers/).
-+ Fixed issue causing changes made in "Text" mode on content editors wouldn't trigger save events
-+ Fixed issue causing lesson prerequisites to not properly display on the course builder
-+ Fixed CSS z-index issues related to builder field tooltip displays
-+ Removed unused Javascript dependencies
-
-##### Bug Fixes
-
-+ Fixed typo on filter on quiz question image getter function
-
-##### Updates
-
-+ Performance improvements made to database queries and functions related to student enrollment status and student course progress queries. Thanks to [@mte90](https://github.com/Mte90) for raising issues and testing solutions related to these updates and changes!
-+ Added PHP Requires plugin header (5.6 minimum)
-+ Added HTTP User Agent data to the system report
-+ [LifterLMS Assignments Beta](https://lifterlms.com/product/lifterlms-assignments?utm_source=LifterLMS%20Plugin&utm_medium=CHANGELOG&utm_campaign=assignments%20preorder) is imminent and this release adds functionality to the Builder which will be extended by Assignments upon when availability
-
-
-v3.16.16 - 2018-03-19
----------------------
-
-+ Fixed builder issue causing multiple question choices to be incorrectly selected
-+ Fixed builder issue with media library uploads causing an error message to prevent new uploads before the quiz or question has been persisted to the database
-+ Fixed builder issue preventing quizzes from being deleted before they were persisted to the database
-+ Fixed builder issue causing autosaves to interrupt typing and reset lesson and section titles
-+ Fixed JS console error related to LifterLMS JS dependency checks
-
-
-v3.16.15 - 2018-03-13
----------------------
-
-##### Quiz Results Improvements and fixes
-
-+ Improved quiz result user and correct answer handling functions for more consistent HTML output
-+ Result answers (correct and user) will display as lists
-+ image question types will display without bullets and will "float" next to each other
-+ Fixed issue causing quiz results with multiple answers from outputting all HTMLS with no spaces between them
-
-##### Quiz Grading
-
-+ Fixed issue causing advanced reorder and reorder question types from being graded incorrectly in some scenarios
-+ Advanced fill in the blank questions are now case insensitive. Case sensitivity can be enabled with a filter: `add_filter( 'llms_quiz_grading_case_sensitive', '__return_true' )`
-
-##### Fixes
-
-+ Updated spacing and returns found in the email header and footer templates to prevent line breaks from occurring in undesirable places on previews of HTML emails in mobile email clients
-+ Added options for themes to add layout support to quizzes where the custom field utilizes an underscore at the beginning of the field key
-+ Fixed CSS issue causing blanks of fill in the blanks to not be visible on the course builder when using Chrome on Windows
-+ Removed unnecessary `get_option()` call to unused option `lifterlms_permalinks`
-+ Updated permissions required to see various LifterLMS post types to rely on `manage_lifterlms` capabilities as opposed to `manage_options`
- + This will only affect the LMS Manager core role or any custom role which was provided with the `manage_options` capability. Manages will now be able to access all LMS content and custom roles would now not be able to access LMS content
- + Affected content types are: Orders, Coupons, Vouchers, Engagements, Achievements, Certificates, and Emails
-+ Several references to an option removed in LifterLMS 3.0 still existed in the codebase and have now been removed.
- + Option `lifterlms_course_display_banner` is no longer called or referenced
- + Template function `lifterlms_template_single_featured_image()` has been removed
- + Actions referencing `lifterlms_template_single_featured_image()` have been removed
- + Template function `lifterlms_get_featured_image_banner()` has been removed
- + Template `templates/course/featured-image.php` has been removed
-
-##### Templates updates
-
-+ [quiz/results-attempt-questions-list.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/results-attempt-questions-list.php)
-
-
-v3.16.14 - 2018-03-07
----------------------
-
-+ Courses reporting table now includes courses with the "Private" status
-+ Fixed issue causing some achievement notifications to be blank
-+ Added tooltips to question choice add / delete icon buttons
-+ Quiz results meta information elements now have unique CSS classes
-+ Removed reliance PHP 7.2 deprecated function `create_function()`
-+ Fixed invalid PHP 7.2 syntax creating a warning found on the setup wizard
-+ Fixed undefined index error related to admin notices
-+ Fixed untranslatable string on Users table ("No Memberships")
-+ Fixed discrepancy between membership restrictions as presented to logged out users and logged in users who cannot access membership
-+ Fixed FireFox and Edge issue causing changes to number inputs made via HTML5 input arrows from properly triggering save events
-
-
-v3.16.13 - 2018-02-28
----------------------
-
-+ Hotfix: Only create quizzes on the builder if quizzes exist on the lesson
-
-
-v3.16.12 - 2018-02-27
----------------------
-
-+ Quizzes can now be detached (removed from a lesson) or deleted (deleted from the lesson and the database) via the Course Builder
-+ Improved question choice randomization to ensure randomized choices never display in their original order.
-+ When a lesson is deleted, any quiz attached to the lesson will become an orphan
-+ When a lesson is deleted, any lesson with this lesson as a prerequisite will have it's prerequisite data removed
-+ When a quiz is deleted, all questions attached to the quiz will also be deleted
-+ When a quiz is deleted, the lesson associated with the quiz will have those associations removed
-+ Fixed grammar issue on restricted lesson tooltips when no custom message is stored on the course.
-+ Updated functions causing issues in PHP 5.4 to work on PHP 5.4. This has been done to reduce frustration for users still using PHP 5.4 and lower; [This does not mean we advocate using software past the end of its life or that we support PHP 5.4 and lower](https://lifterlms.com/docs/minimum-system-requirements-lifterlms/).
-
-
-v3.16.11 - 2018-02-22
----------------------
-
-+ Course import/exports and lesson duplication now carry custom meta data from 3rd party plugins and themes
-+ Added course completion date column to Course reporting students list
-+ Restriction checks made against a quiz will now properly cascade to the quiz's parent lesson
-+ Fixed issue preventing featured images from being exported with courses and lessons
-+ Fixed duplicate lesson issue causing quizzes to be double assigned to the old and new lesson
-+ Fixed issue allowing blog archive to be viewed by non-members when sitewide membership is enabled
-+ Fixed builder issue causing data to be lost during autosaves if data was edited during an autosave
-+ Fixed builder issue preventing lessons from moving between sections when clicking the "Prev" and "Next" section buttons
-+ Added actions to `LLMS_Generator` to allow 3rd parties to extend core generator functionality
-
-
-v3.16.10 - 2018-02-19
----------------------
-
-+ Content added to the editor of course & membership catalog pages will now be output *above* the catalog loop
-+ Fix issue preventing iframes and some shortcodes from working when added to a Quiz question description
-+ Added new columns to the Quizzes reporting table to display Course and Lesson relationships
-+ Improved the task handler of background updater to ensure upgrade functions that need to run multiple times can do so
-+ Fixed JS Backup confirmation dialog on the background updater.
-+ Add support for 32-bit systems in the `LLMS_Hasher` class
-+ Fix issue causing HTML template content to be added to lessons when duplicating an existing lesson within the course builder
-
-##### 3.16.0 migration improvements
-
-+ Accommodates questions imported by 3rd party Excel to LifterLMS Quiz plugin. Fixes an issue where choices would have no correct answer designated after migration.
-+ All migration functions now run on a loop. This improves progress reporting of the migration and prevents timeouts on mature databases with lots of quizzes, questions, and/or attempts.
-+ Fix an issue that caused duplicate quizzes or questions to be created when the "Taking too long?" link was clicked
-
-
-v3.16.9 - 2018-02-15
---------------------
-
-+ Fix issue causing error on student dashboard when reviewing an order with an access plan that was deleted.
-+ Fixed spelling error on course metabox
-+ Fixed spelling error on frontend quiz interface
-+ Fixed issues with 0 point questions:
- + Will no longer prevent quizzes from being automatically graded when a 0 point question is in an otherwise automatically gradable quiz
- + Point value not editable during review
- + Visual display on results displays with grey background not as an orange "pending" question
-+ Table schema uses default database charset. Fixes an issue with databases that don't support `utf8mb4` charsets.
-+ Updated `LLMS_Hasher` class for better compatibility with older versions of PHP
-
-
-v3.16.8 - 2018-02-13
---------------------
-
-##### Updates
-
-+ Added theme compatibility API so theme developers can add layout options to the quiz settings on the course builder. For details on adding theme compatibility see: [https://lifterlms.com/docs/quiz-theme-compatibility-developers/](https://lifterlms.com/docs/quiz-theme-compatibility-developers/).
-+ Quiz results "donut" chart had alternate styles for quizzes pending review (Dark grey text rather than red). You can target with the `.llms-donut.pending` CSS class to customize appearance.
-+ Allow filtering when retrieving student answer for a quiz attempt question via `llms_quiz_attempt_question_get_answer` filter
-
-##### Bug Fixes
-
-+ Fix issues causing conditionally gradable question types (fill in the blank and scale) from displaying without a status icon or possible points when awaiting admin review / grading.
-+ Fix issue preventing conditionally gradable question types (fill in the blank and scale) from being reviewable on the admin panel when the question is configured as requiring manual grading.
-+ Fix analytics widget undefined index warning during admin-ajax calls. Thanks [@Mte90](https://github.com/Mte90)!
-+ Fix issue causing `is_search()` to be called incorrectly. Thanks [@Mte90](https://github.com/Mte90)!
-+ Fix issue preventing text / html formatting from saving properly for access plan description fields
-+ Fix html character encoding issue on reporting widgets causing currency symbols to display as a character code instead of the symbol glyph.
-
-##### Templates changed
-
-+ templates/quiz/results-attempt-questions-list.php
-+ templates/quiz/results-attempt.php
-
-
-v3.16.7 - 2018-02-08
---------------------
-
-+ Added manual saving methods for the course builder that passes data via standard ajax calls. Allows users (hosts) to disable the Heartbeat API but still save builder data.
-+ Added an "Exit" button to the builder sidebar to allow exiting the builder back to the WP Edit Post screen for the current course
-+ Added dashboard links to the WP Admin Bar to allow existing the course builder to various areas of the dashboard
-+ Added data attribute to progress bars so JS (or CSS) can read the progress of a progress bar. Thanks [@dineshchouhan](https://github.com/dineshchouhan)!
-+ Fixed issue causing newly created lessons to lose their assigned quiz
-+ Fixed php `max_input_vars` issue causing a 400 Bad Request error when trying to save large courses in the course builder
-+ Removed reliance on PHP bcmath functions
-
-
-v3.16.6 - 2018-02-07
---------------------
-
-+ Removed reliance on PHP Hashids Library in favor of a simpler solution with no PHP module dependencies
-+ Added interfaces to allow customization of quiz url / slug
-+ Fixed [audio] shortcodes added to quiz question descriptions
-+ Fixed untranslatable strings on frontend of quizzes
-+ Fix issue causing certificate notifications to display as empty
-+ Fix issue preventing quiz pass/fail notifications from triggering properly for manually graded quizzes
-+ Fix undefined index warning on quiz pass/fail notifications
-
-
-v3.16.5 - 2018-02-06
---------------------
-
-+ Fix issue preventing manually graded quiz review points from saving properly
-+ Improved background updater to ensure scripts don't timeout during upgrades
-+ Admin builder JS now minified for increased performance
-+ Made frontend quiz and quiz-builder strings output via Javascript translatable
-
-
-v3.16.4 - 2018-02-05
---------------------
-
-+ Fix issue causing newly created quizzes to not be properly related to their parent lesson
-+ Fix issue preventing quiz time limits from starting unless an attempt limit is also set
-+ Fixes a WP Engine issue that prevented the builder from loading due to a blocked dependency
-
-
-v3.16.3 - 2018-02-02
---------------------
-
-+ When switching a quiz to "Published" it will now update the parent lesson to ensure it's recorded as having an enabled quiz.
-+ Declared the WordPress heartbeat API script as a dependency for the Course Builder JS. It seems that some servers and hosts dequeue the heartbeat when not explicitly required. This resolves a saving issue on those hosts.
-+ Added a Quiz Description content editor under quiz settings. This is the "Editor" from pre 3.16.0 quizzes and any content saved in these fields is now available in this description field
-+ Fixed issue causing points percentage calculation tooltip on quiz builder to show the incorrect percentage value
-+ Fix issue preventing lessons with no drip settings from being updated on the WP post editor
-+ Fix issue causing 500 error on lesson settings metabox for lessons not attached to sections
-+ Add a "Quiz Description" field to allow quiz post content to be edited on the quiz builder
-+ Added a database migration script to ensure quizzes migrated from 3.16 and lower that had quiz post content to automatically have the optional quiz description to be enabled
-
-
-v3.16.2 - 2018-02-02
---------------------
-
-+ Add an update notice to 3.16.0 migration scripts to provide more information about the major update.
-+ Removed quiz assignment fields on the lesson metabox to reduce confusion as quizzes are now managed exclusively on the quiz builder.
-+ Ensure questions migrated during 3.16 updates retain their initial points value from the quiz.
-
-
-v3.16.1 - 2018-02-01
---------------------
-
-+ Ensure quizzes in draft mode are only accessible by those with edit access (instructors, admins, etc...)
-+ Restore pre 3.16 actions and filters related to quiz start buttons
-+ Remove legacy error message for quiz accessibility issues by site admins
-+ Students who cannot access a quiz are redirected to the parent lesson if they attempt to access a quiz directly
-+ Fix undefined index warning on wp-login.php related to LifterLMS js assets. Thanks [Mte90](https://github.com/Mte90)!
-+ Update checkout error message to provide user with direction when they already have access to a course. Thanks [@andreasblumberg](https://github.com/andreasblumberg)!
-
-
-v3.16.0 - 2018-02-01
---------------------
-
-##### Quizzes
-
-+ New question types: True/False, Picture Choice, and Non-question content
-+ Picture & Multiple choice have options for multiple correct answers (checkbox-like questions)
-+ You can now create questions with NO POINTS (maybe for surveys?)
-+ Upgraded student quiz review interface
-+ Upgraded instructor quiz attempt review interface
-+ Admins may now leave remarks on questions directly
-+ Improved data available related to quizzes and quiz attempts on reporting screens
-+ Improved quiz user interface
-+ Added a progress bar to the quiz interface
-+ Shrunk the quiz timer
-+ Added a question # counter on the quiz interface
-+ Fixed issue causing randomized questions to get "lost" when navigating back through a quiz attempt
-+ Improved error handling on quizzes
-+ Overhauled quiz data structure for improved performance and scalability
-+ Requires database migration and update: [3.16.0](https://lifterlms.com/docs/lifterlms-database-updates/#3160)
-
-##### Course Builder Improvements
-
-+ Quiz-building is now available on the course builder
-+ Quiz and Question WordPress editors no longer available. Quizzes and Questions HAVE NOT DISAPPEARED, they've been improved and relocated
-+ All hooks & filters attached to `the_content` and `the_title` are now being removed when loading the course builder. This should prevent infinite spinners on builder loading and builder AJAX calls due to third-parties accidentally outputting html during these events.
-
-##### Updates
-
-+ Added space between arrows and "Next" and "Previous" text on pagination lists. Thanks [sujaypawar](https://github.com/sujaypawar)!
-+ Updated Quiz post type slug from "llms_quiz" to "quiz".
-+ Updated default return of `llms_get_post()` to be `false` rather than a `WP_Post` object when a LifterLMS post cannot be located
-
-##### Bug Fixes
-
-+ Fixed a potential database read error related to database store abstract
-+ Now passing Post ID as second parameter to the `the_title` filter called on post model getters
-
-
-##### Removed templates
-
-The following quiz templates have been removed. Customization of these templates causes quiz application functionality to break and they should not have been available for customization but were due to oversights. This has been corrected.
-
-+ templates/content-single-question-after.php
-+ templates/content-single-question-before.php
-+ templates/quiz/next-question.php
-+ templates/quiz/previous-question.php
-+ templates/quiz/question-count.php
-+ templates/quiz/quiz-question.php
-+ templates/quiz/single-choice.php
-+ templates/quiz/single-choice_ajax.php
-+ templates/quiz/summary.php
-+ templates/quiz/timer.php
-+ templates/quiz/wrapper-end.php
-+ templates/quiz/wrapper-start.php
-
-##### Removed Functions
-
-Various template functions related to quizzes were removed due to the deprecation of their related templates
-
-+ `lifterlms_template_quiz_timer()`
-+ `lifterlms_template_single_next_question()`
-+ `lifterlms_template_single_prev_question()`
-+ `lifterlms_template_single_single_choice()`
-+ `lifterlms_template_single_single_choice_ajax()`
-+ `lifterlms_template_single_question_count()`
-
-
-v3.15.1 - 2017-12-05
---------------------
-
-+ Ensure course & membership titles with HTML characters are decoded during reporting exports
-+ Fix issue causing some courses to display in membership columns on reporting exports
-
-
-v3.15.0 - 2017-12-04
---------------------
-
-##### Reporting Updates (and CSV exports!)
-
-+ Added course-level reporting table (see "Courses" tab of Reporting screen)
-+ Updated the interface on reporting screen when reviewing a single student
-+ Added reporting exports: students list, courses list, and list of students per course
-
-##### Bug fixes
-
-+ Fix error when `[lifterlms_course_continue_button]` shortcode is displayed to logged out or students not enrolled in the chosen course
-
-##### Minor updates
-
-+ Tested up to WordPress 4.9.1
-+ Added background data processors to ensure reporting data stays up to date in close to real time
-+ Add nocache constants and headers on student dashboard & checkout page to increase compatibility with caching plugins
-+ Added filter to student dashboard courses query
-
-
-v3.14.9 - 2017-11-27
---------------------
-
-+ Tested up to WordPress 4.9
-+ Fix error during uninstall related to missing file
-+ Fix issue with rewinding quiz using "Previous Question" button
-+ On final question of a quiz the "Next Lesson" button now says "Complete Quiz"
-+ When completing a quiz, the loading message will now say "Grading Quiz" the entire time instead of "Loading Question" and then "Grading Quiz"
-+ Fix issue causing the `` element on course builder pages from being partially empty
-
-
-v3.14.8 - 2017-11-06
---------------------
-
-+ Lessons can be cloned via the "Clone" action from the lessons post table
-
-##### Builder Improvements & Fixes
-
-+ Add "Existing Lesson" functionality can now clone and attach the clone (when adding a lesson currently attached to a course) OR attach orphans
-+ Lessons created via Course builder will have their slugs renamed the first time the lesson title is updated via the builder
-+ No longer display notices on the course builder
-+ Add extra space to the scrollable area on course builder
-+ Removed logging and debugging functions from admin builder class
-+ JS-generated error messages on the course builder are now translatable
-
-##### Bug Fixes
-
-+ Fix: Show all memberships on dashboard
-
-
-v3.14.7 - 2017-10-25
---------------------
-
-##### Navigation Menu Items
-
-+ Add LifterLMS endpoints to your nav menu
-+ Add Sign In and Sign Out links which display conditionally based on whether or not the visitor is logged in
-+ Checkout the docs at [https://lifterlms.com/docs/lifterlms-navigation-menu-items/](https://lifterlms.com/docs/lifterlms-navigation-menu-items/)
-
-##### Bug Fixes
-
-+ Fix SQL query issue with orphaned lesson query on course builder
-+ Fix undefined index warning occurring during theme switches
-+ Fix issue causing duplicate error messages to display on certain servers
-
-
-v3.14.6 - 2017-10-21
---------------------
-
-+ Fix: `` are no longer stripped when exporting or duplicating courses (this applies to lessons within the courses as well)
-+ Fix: Achievements on student dashboard now output the correct achievement title
-+ Fix: Courses on student dashboard ordered by Order attributes will obey settings correctly
-
-
-v3.14.5 - 2017-10-14
---------------------
-
-+ Course builder will persist open/collapsed state of sections when they are re-ordered
-+ Course builder lessons in a section are draggable after reordering a section
-
-
-v3.14.4 - 2017-10-13
---------------------
-
-+ You were right and we were wrong & we are sorry. This update returns the ability to add existing lessons to a course via the course builder.
-+ Lessons added to a section will no longer visually disappear when editing a section title on the course builder
-+ BuddyPress integration BP template fixes
-
-
-v3.14.3 - 2017-10-12
---------------------
-
-+ Fix [lifterlms_my_account] shortcode issue affecting Divi theme users
-
-
-v3.14.2 - 2017-10-11
---------------------
-
-+ Instructor query utilizes correct `$wpdb->prefix` for filtering by role instead of `wp_` which will not work when the `$table_prefix` in wp-config.php is customized
-+ include the admin notices class when running database update functions
-
-
-v3.14.1 - 2017-10-10
---------------------
-
-+ Fix `[lifterlms_my_achievements]` shortcode
-+ Fix reference to deprecated core function related to checking the permissions of content restricted to a membership
-+ Builder titles will be saved on all field focusout/blur events, not just tab & enter key presses
-+ LifterLMS custom meta save metaboxes will not trigger actions during ajax requests
-+ Fix issue displaying certificates on admin panel reporting screens
-
-
-v3.14.0 - 2017-10-10
---------------------
-
-+ Updated JS for 3.13 course builder to address issues on PHP 5.6 servers with asp_tags enabled
-+ Normalized date returns with various dates related to enrollments, achievements, and certificates. These dates now utilize the WP Core `date_format` option.
-+ Fixed strict comparison issue related to database query abstract (affected checks for last page & first page on admin reporting screens)
-+ Added a new capability `llms_instructor` for admins, lms managers, instructors, and instructor's assistant to easily differentiate "instructors" from "students"
-+ Fix `$wpdb->prepare` issue related to notification queries. Fixes WP 4.9-beta issue.
-
-##### Student Dashboard Updates
-
-+ Achievements on student dashboard now viewable in popover modal.
-+ Achievements tab added to student dashboard
-+ Courses, Memberships, Achievements, and Certificates have been updated to have a unified style
-+ Courses & Memberships extend the default catalog tiles
-+ Courses shortcode has new parameters useful for displaying a list of a specific users courses only. [More info](https://lifterlms.com/docs/shortcodes/#lifterlms_courses)
-
-##### Deprecated functions
-
-+ `LLMS_Student_Dashboard::output_courses_content()` replaced with `lifterlms_template_student_dashboard_my_courses( false )`
-+ `LLMS_Student_Dashboard::output_dashboard_content` replaced with `lifterlms_template_student_dashboard_home()`
-
-##### Template Updates
-
-+ [achievements/loop.php](https://github.com/gocodebox/lifterlms/blob/master/templates/achievements/loop.php)
-+ [achievements/template.php](https://github.com/gocodebox/lifterlms/blob/master/templates/achievements/template.php)
-+ [certificates/loop.php](https://github.com/gocodebox/lifterlms/blob/master/templates/certificates/loop.php)
-+ [certificates/preview.php](https://github.com/gocodebox/lifterlms/blob/master/templates/certificates/preview.php)
-+ [loop.php](https://github.com/gocodebox/lifterlms/blob/master/templates/loop.php)
-+ [loop/content.php](https://github.com/gocodebox/lifterlms/blob/master/templates/loop/content.php)
-+ [loop/enroll-date.php](https://github.com/gocodebox/lifterlms/blob/master/templates/loop/enroll-date.php)
-+ [loop/enroll-status.php](https://github.com/gocodebox/lifterlms/blob/master/templates/loop/enroll-status.php)
-+ [loop/pagination.php](https://github.com/gocodebox/lifterlms/blob/master/templates/loop/pagination.php)
-+ [myaccount/dashboard-section.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/dashboard-section.php)
-+ [myaccount/dashboard.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/dashboard.php)
-+ [myaccount/header.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/header.php)
-
-##### Deleted Templates
-
-+ /myaccount/my-achievements.php
-+ /myaccount/my-courses.php
-+ /myaccount/my-memberships.php
-
-
-v3.13.1 - 2017-10-04
---------------------
-
-+ Fix caching issue preventing quiz pass & fail engagements from triggering.
-+ Fix issue causing the "Builder" link to display on the lesson post table screen.
-+ Fix issue preventing new courses & memberships from being moved from draft -> published.
-+ Fix `wpdb->prepare()` empty placeholder issue related to engagement queries. Fixes warning added in WP 4.9.
-+ Add better version numbering to static assets to prevent caching issues during plugin updates
-
-
-v3.13.0 - 2017-10-02
---------------------
-
-##### An All New Course Builder
-
-+ The "Course Outline" metabox found on the admin panel when editing any LifterLMS course has been savagely beaten. We stole its lunch money and we put it towards the construction of an all interface
-+ Asynchronous loading: fixes issues where very large courses would drastically slow and possibly even time out the loading of the course edit screen
-+ Course outline is now collapsible and expandable. This Fixes issues where it was very hard to move lessons and sections around on very large courses
-+ In addition to the familiar (and now improved) drag and drop functionality, you may now also move sections and lessons up and down with button clicks. You can also move lessons between sections with button clicks
-+ Add new lessons and sections with a click or drag a new lesson or section into the existing course
-+ Edit section and lesson titles faster with inline title editing. No more modals with a potentially slow ajax load to update a title. Click the title, change it, and exit the field to automatically save!
-+ Delete sections and lessons with the click of a button
-+ Quick links to view (frontend) and edit (backend) lessons
-+ Completely internationalized. Thanks for you patience translators!
-+ Want to know more? Check out the [docs](https://lifterlms.com/docs/using-course-builder/).
-
-##### New User Roles
-
-+ Added new roles to enable you to provide access to LifterLMS (settings, courses building, etc...) without having to make an admin or mess with complicated code snippets.
-+ New Roles:
-
- + LMS Manager: Do everything in LifterLMS and nothing with plugins, themes, core settings, and so on
- + Instructor: Create, update, and delete courses and memberships
- + Instructor's Assistant: Edit courses and memberships
-
-+ More details and a full list of new LifterLMS capabilities are available [here](https://lifterlms.com/docs/roles-and-capabilities/).
-
-##### Updates & Fixes
-
-+ Tested up to WordPress 4.8.2
-+ The "Lesson Tree" metabox has been replaced with a simplified version of the lesson tree and a link to the launch the Course Builder.
-+ Course and membership categories and tags will now display on their respective post tables for sorting and filtering. They can be disabled on a per-user basis via the screen options.
-+ Removed `var_dump()` from bbPress integration restriction check
-
-##### Uninstall Script
-
-+ Uninstall script now removes all the things LifterLMS creates in your database if a constant is defined. Read more [here](https://lifterlms.com/docs/remove-lifterlms-data-plugin-uninstallation/).
-
-##### Database Update
-
-+ Adds default Instructor data for all LifterLMS Courses & Memberships based off of the post author of the course or membership
-+ [More information](https://lifterlms.com/docs/lifterlms-database-updates/#3130)
-
-##### Template Updates
-
-+ [admin/post-types/students.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/post-types/students.php)
-+ [admin/reporting/tabs/students/courses.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/students/courses.php)
-
-##### Deprecated Functions
-
-+ The following AJAX functions are no longer utilized by LifterLMS core. If you are utilizing them find alternatives (they all exist). These will be remove in the next **major** release:
-
- + `LLMS_AJAX::get_achievements()`
- + `LLMS_AJAX::get_all_posts()`
- + `LLMS_AJAX::get_associated_lessons()`
- + `LLMS_AJAX::get_certificates()`
- + `LLMS_AJAX::get_courses()`
- + `LLMS_AJAX::get_course_tracks()`
- + `LLMS_AJAX::get_emails()`
- + `LLMS_AJAX::get_enrolled_students()`
- + `LLMS_AJAX::get_enrolled_students_ids()`
- + `LLMS_AJAX::get_lesson()`
- + `LLMS_AJAX::get_lessons()`
- + `LLMS_AJAX::get_lessons_alt()`
- + `LLMS_AJAX::get_memberships()`
- + `LLMS_AJAX::get_question()`
- + `LLMS_AJAX::get_sections()`
- + `LLMS_AJAX::get_sections_alt()`
- + `LLMS_AJAX::get_students()`
- + `LLMS_AJAX::update_syllabus()`
-
-##### Removed Filters
-
-+ The following filters have been removed and are no longer in use.
-
- + `lifterlms_admin_courses_access`: replaced with user capability `edit_courses`
- + `lifterlms_admin_membership_access`: replaced with user capability `edit_memberships`
- + `lifterlms_admin_reporting_access`: replaced with user capability `manage_lifterlms`
- + `lifterlms_admin_settings_access`: replaced with user capability `manage_lifterlms`
- + `lifterlms_admin_import_access`: replaced with user capability `manage_lifterlms`
- + `lifterlms_admin_system_report_access`: replaced with user capability `manage_lifterlms`
-
-
-v3.12.2 - 2017-09-18
---------------------
-
-##### Bug fixes
-
-+ Fix issue with LifterLMS bbPress integration preventing course-restricted topics from being accessible by enrolled students
-+ Fix an issue preventing students expired from courses via access expiration settings from being manually re-enrolled by admins
-
-##### Deprecations
-
-+ `LLMS_Student` class function `has_access` is scheduled for deprecation in next major release. Developers should switch to `LLMS_Student->is_enrolled()`
-
-
-v3.12.1 - 2017-08-25
---------------------
-
-+ Prevent duplicate loading of repeater metabox fields
-+ Fix undefined warning related to quiz completion
-+ Ensure that the bbPress course forums shortcode & widget properly cascade up when used on a lesson or quiz
-
-
-v3.12.0 - 2017-08-17
---------------------
-
-+ New quiz feature: randomize the order of quiz questions each attempt! Props to [Larry Groebe](https://github.com/larrygroebe)
-+ Fixed logic error related to access checks when bubbling from quiz->lesson->course
-+ Fixed JS loader check for tinyMCE editors in repeater fields
-+ Fixed CSS issue related to tinyMCE editors in repeater fields
-+ Fixed issue causing tinyMCE editors in repeater fields to stop working after reordering rows
-+ LifterLMS alert box notices are now cleared during shutdown instead of immediately after rendering. Fixes some plugin compatibility issues.
-+ Fix reference to invalid meta key on order notes admin screen.
-+ Record order note when orders with a defined length complete
-+ When a payment is scheduled for an order with a defined length, calculate end date if no end date is saved
-+ Minor updates to the `LLMS_Abstract_Integration` class
-+ Fix undefined reference error on 404 pages resulting from the preview manager.
-
-##### bbPress Integration Updates
-
-+ Add "Private" Course Forums which allows forums to be made available only to students enrolled in the associated course
-+ Adds a shortcode and widget for outputting a list of forums associated with a course
-+ Adds the ability to restrict the page set as the bbPress forum index (via bbPress settings) to be restricted to LifterLMS memberships
-+ Adds engagement triggers to allow engagements to be fired when a student posts a reply or creates a new topic
-+ Improves integration membership restriction check performance
-+ Migrated to the `LLMS_Abstract_Integration` class. Visually changes the settings display but has no other impact
-+ [More information](https://lifterlms.com/docs/lifterlms-and-bbpress/)
-
-##### BuddyPress Integration Updates
-
-+ Add the ability to restrict activity, group, and member directory pages to LifterLMS memberships.
-+ Migrated to the `LLMS_Abstract_Integration` class. Visually changes the settings display but has no other impact
-+ [More information](https://lifterlms.com/docs/lifterlms-and-bbpress/)
-
-##### Database update
-
-+ calculate and store end dates for orders created prior to version 3.11.0 which have a defined length and do not have a stored end date.
-+ migrate bbPress and BuddyPress options to `LLMS_Abstract_Integration` naming convention
-+ [More information](https://lifterlms.com/docs/lifterlms-database-updates/#3120)
-
-##### Admin Post Table Upgrades
-
-+ Lessons
- + Fix section titles which formerly were a dead link. Now they're just text
- + Add filtering the table by associated course
-+ Quizzes
- + Display associated course and lesson columns with links
- + Add filtering by associated course and/or lesson
-+ Quiz Questions
- + Display associated Quizzes with links
- + Add filtering by associated quiz
-
-##### Template Updates
-
-+ [admin/post-types/order-details.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/post-types/order-details.php)
-
-
-v3.11.2 - 2017-08-14
---------------------
-
-+ Tested up to WP Core 3.8.1
-
-##### System Status and Reporting updates
-
-+ System Report renamed to "Status"
-+ Added information of template overrides to the system report
-+ Added "Get Help" button linking to LifterLMS Ticketing submission page
-+ Added "Logs" tab which allows for easy viewing & management of LifterLMS logs
-+ Added "Tools and Utilities" tab and moved tools from the General Settings screen to this tab
-+ Improved Session Reset tool
-
-
-v3.11.1 - 2017-08-03
---------------------
-
-+ New shortcode: `[lifterlms_course_continue_button]`. See [shortcode docs](https://lifterlms.com/docs/shortcodes/#lifterlms_course_continue_button) for more information.
-+ New shortcode: `[lifterlms_lesson_mark_complete]`. See [shortcode docs](https://lifterlms.com/docs/shortcodes/#lifterlms_lesson_mark_complete) for more information.
-+ Added filter `llms_product_pricing_table_enrollment_status` to allow forceful display of course/membership pricing tables regardless of user enrollment status.
-+ Fix course author shortcode to allow usage outside of a course via the `course_id` parameter.
-
-##### Template Updates
-
-+ [product/pricing-table.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/pricing-table.php)
-+ [product/course/progress.php](https://github.com/gocodebox/lifterlms/blob/master/templates/product/course/progress.php)
-
-
-v3.11.0 - 2017-07-31
---------------------
-
-+ New engagement trigger "Student purchases access plan" allows engagements to be triggered from a specific access plan!
-+ Minor performance improvements to notification-related database queries
-+ Fix issue causing payment gateways to always use test mode links from Orders on the admin panel
-+ Added default email notification merge code for outputting an HTML divider
-+ Added new actions to Dashboard template to allow adding custom content to course tiles on the dashboard
-
-##### Template Updates
-
-+ [myaccount/my-courses.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/my-courses.php)
-
-
-v3.10.2 - 2017-07-14
---------------------
-
-+ Fix fatal error related to purchase receipts for trashed or deleted orders
-+ l10n "Reviews" tab title on course settings
-+ Remove commented out sample preheader text from email header template which was displaying in some email clients.
-
-##### Template Updates
-
-+ [emails/header.php](https://github.com/gocodebox/lifterlms/blob/master/templates/emails/header.php)
-
-
-v3.10.1 - 2017-07-12
---------------------
-
-##### Bugfixes
-
-+ Prevent errors related to attempting to display notification data related to deleted students
-+ Fix errors related to displaying notifications for deleted post (courses, sections, lessons, quizzes, etc...)
-+ Fix error causing email notifications being sent after related user has been deleted
-+ Fix typo preventing `llms_form_field()` from outputting textareas
-
-##### Updates
-
-+ Add new filter `llms_allow_subscription_cancellation` useful for preventing students from self-cancelling their subscriptions on the student dashboard. [More info](https://lifterlms.com/docs/lifterlms-filters/#llms_allow_subscription_cancellation).
-+ Add new API for querying students via AJAX select2 elements
-+ Select2 Post Query elements can now query multiple post types simultaneously
-+ Select2 Post Query elements can now support ``
-
-###### i18n
-
-+ Course option metabox for reviews is not translatable
-
-
-v3.10.0 - 2017-07-05
---------------------
-
-##### Recurring Order Management (for Admins)
-
-+ Admins can now edit various pieces of data related to a recurring order from the order screen on the admin panel
- + Allow editing of the Next Payment Date
- + Allow editing of the Trial End Date (when a trial is active for the order)
- + Edit Payment Gateway and related gateway fields (Customer ID, Source ID, and Subscription ID)
-+ If you're using LifterLMS Stripe or LifterLMS PayPal please update to the latest version of these add-ons to take advantage of these new features!
-
-##### Recurring Order Management (for Students)
-
-+ Students can now switch the payment method (source) for their recurring subscriptions from the student dashboard
-+ Students can now cancel their recurring orders to prevent future payments on recurring orders
-+ If you're using LifterLMS Stripe or LifterLMS PayPal please update to the latest version of these add-ons to take advantage of these new features!
-
-##### Automatic Payment Retries (for supporting gateways)
-
-+ LifterLMS Stripe and LifterLMS PayPal can now automatically retry failed payments to help recover lost revenue as a result of temporary declines to payment sources. Please see our documentation on this new feature [here](https://lifterlms.com/docs/automatic-retry-failed-payments/).
-+ If you're using LifterLMS Stripe or LifterLMS PayPal please update to the latest version of these add-ons to take advantage of these new features!
-
-##### Manual Payment Gateway Enhancements
-
-+ The Manual Payment Gateway (bundled with LifterLMS Core) can now handle recurring payments. For more information on utilizing recurring payments with the Manual Gateway please see the [gateway documentation](https://lifterlms.com/docs/using-lifterlms-manual-payment-gateway/).
-
-##### Updates and Fixes
-
-+ Force SSL setting now applies to Student Dashboard screens. This is useful as Google now recommends any page where a password is submitted should be encrypted and allows gateway communication from student dashboard screen with APIs that require an SSL connection.
-+ Fixed spelling error related to quizzes
-
-##### Templates changed
-
-**NEW**
-
-+ [checkout/form-switch-source.php](https://github.com/gocodebox/lifterlms/blob/master/templates/checkout/form-switch-source.php)
-+ [myaccount/view-order-transactions.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/view-order-transactions.php)
-
-**UPDATED**
-
-+ [admin/post-types/order-details.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/post-types/order-details.php)
-+ [myaccount/my-orders.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/my-orders.php)
-+ [myaccount/navigation.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/navigation.php)
-+ [myaccount/view-order.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/view-order.php)
-+ [quiz/summary.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/summary.php)
-
-
-v3.9.5 - 2017-06-13
--------------------
-
-+ Increased css z-index of basic notifications to prevent issues with themes that have high z-index on menus and other elements
-+ Increased the frequency of basic notification heartbeat check from 10 to 20 seconds
-+ Added filter to allow for customization of the notifications heartbeat interval, example [here](https://lifterlms.com/docs/lifterlms-filters/#llms_notifications_settings).
-+ Fixed error related to password reset when the "Disable Usernames" account setting is disabled
-
-
-v3.9.4 - 2017-06-12
--------------------
-
-+ Fix hardcoded db reference to `wp_posts` table
-
-
-v3.9.3 - 2017-06-09
--------------------
-
-+ Fix typo in notifications query
-
-
-v3.9.2 - 2017-06-07
--------------------
-
-+ Tested up to WordPress 4.8
-+ Fixed issue with merge codes on WP Editors for notifications, emails, etc...
-+ Update notifications query to only return results related to posts which actually exist. Prevents errors occurring when reviewing achievements on the student dashboard for courses, lessons, etc which have been deleted/trashed.
-+ Only display quiz time limit meta information when a time limit exists
-+ Fix display of quiz question order (question x of x)
-+ Improved logic powering quiz attempt grading for increased consistency, especially with regards to floats and rounding
-
-##### Templates Changed
-
-+ [quiz/meta-information.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/meta-information.php)
-+ [quiz/question-count.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/question-count.php)
-
-
-v3.9.1 - 2017-06-02
--------------------
-
-+ Fix engagement triggers with relation to quizzes to properly receive 3.9 api updates
-+ Fix quiz attempt counting issue resulting in the total attempts by a student always being one more than the actual value
-+ Fix membership access plan restrictions tooltip
-
-
-v3.9.0 - 2017-06-02
--------------------
-
-##### Quizzes
-
-+ All new quiz results interface for students
- + Donut charts are now animated
- + Donuts will be green for passing attempt and red for failing
- + Students can now review previous quiz attempts and summaries
- + Removed the juxtaposition of the current and best attempts to reduce confusion on the interface
- + Improved the consistency of the quiz meta information markup
- + Adjusted various pieces of language for an improved student experience
-+ Improvements to the quiz taking experience
- + Added the LLMS_Spinner (seen on checkout screens and various places on the admin panel) and various loading messages when starting quiz, transitioning between questions, and completing a quiz
- + Better error handling and management should issues arise during a quiz
- + Better unload & beforeunload JS management to warn students when they attempt to leave a quiz in progress
-+ Improved quiz data handling and management
- + Improved API calls and handlers related to taking quizzes for increased performance and consistency
- + quiz data can now be programmatically queried via consistent apis and data classes, see `LLMS_Student->quizzes()` and `LLMS_Quiz_Attempt`
-+ Quizzes no longer rely on session and cookie data. All quiz data will always be saved directly to the database and related to the student. Fixes an issue on certain servers preventing student from starting quizzes.
-+ Deprecated `LLMS_Quiz::start_quiz()`, `LLMS_Quiz::answer_question()`, and, `LLMS_Quiz::complete_quiz()`
- + Ajax handler functions of the same names should be used instead.
- + To programmatically "take" quizzes use related functions of similar names from the `LLMS_Quiz_Attempt` class
-
-##### Templates changed
-
-+ New
- + [quiz/meta-information.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/meta-information.php)
-
-+ Updated
- + [admin/reporting/tabs/students/courses.php](https://github.com/gocodebox/lifterlms/blob/master/templates/admin/reporting/tabs/students/courses.php)
- + [content-certificate.php](https://github.com/gocodebox/lifterlms/blob/master/templates/content-certificate.php)
- + [course/complete-lesson-link.php](https://github.com/gocodebox/lifterlms/blob/master/templates/course/complete-lesson-link.php)
- + [myaccount/my-notifications.php](https://github.com/gocodebox/lifterlms/blob/master/templates/myaccount/my-notifications.php)
- + [quiz/next-question.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/next-question.php)
- + [quiz/previous-question.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/previous-question.php)
- + [quiz/question-count.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/question-count.php)
- + [quiz/quiz-question.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/quiz-question.php)
- + [quiz/quiz-wrapper-end.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/quiz-wrapper-end.php)
- + [quiz/quiz-wrapper-start.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/quiz-wrapper-start.php)
- + [quiz/results.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/results.php)
- + [quiz/return-to-lesson.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/return-to-lesson.php)
- + [quiz/single-choice_ajax.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/single-choice_ajax.php)
- + [quiz/start-button.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/start-button.php)
- + [quiz/summary.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/summary.php)
-
-+ Removed
- + quiz/attempts.php - replaced by [quiz/meta-information.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/meta-information.php)
- + quiz/passing-percent.php - replaced by [quiz/meta-information.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/meta-information.php)
- + quiz/time-limit.php - replaced by [quiz/meta-information.php](https://github.com/gocodebox/lifterlms/blob/master/templates/quiz/meta-information.php)
-
-##### Fixes
-
-+ Student Dashboard notifications page will not display pagination links unless there's results to page through
-+ Student Dashboard notifications page will now display a message when no notifications are found
-+ Certificate previewing now takes into consideration the preview setting roles to allow admins (or other roles) to preview certificates
-+ Made student name self fallback (you) i18n friendly
-
-
-v3.8.1 - 2017-05-21
--------------------
-
-+ Fix merge code issue related to course title on quiz notifications
-
-
-v3.8.0 - 2017-05-20
--------------------
-
-+ Automatic email and basic (on-screen) notifications for various events within LifterLMS
- + All notifications can be customized
- + Email notifications can be optionally sent to custom email address, course authors, and more
-+ Students will automatically receive email receipts when making purchases and when recurring access plans rebill
-+ Hidden Access Plans
-+ Add a "Purchase Link" view button to access plans so admins can quickly grab the direct URL to an access plan
-+ Notifications history screen on Student Dashboard to review past notifications that have been received
-+ Updated LLMS_Email class and functionality
-+ Email templates have been completely rewritten and styled
-+ Updated and rewritten password reset flow
-+ Earned certificates are only accessible by the student who earned the certificate
-+ Added the functionality for image upload via options & settings api
-+ Removed a handful of unused templates related to LifterLMS certificates that were replaced a long time ago but still existed in the codebase for unknown reasons.
-+ Fixed filter on engagements settings page
-+ Minor adjustments to language and settings order on Engagements settings screen for email settings
-+ Email Header Image field is now an upload field as opposed to a "paste a url here" setting
-+ Phone number recorded to order and displayed on order for admin panel during purchases
-+ Order details now display full country name as opposed to the country code
-+ Fix installation script to ensure admin can preview by default
-
-
-v3.7.7 - 2017-05-16
--------------------
-
-+ Updated a few strings on the admin panel to be translatable
-+ Fix PHP warning output during plugin activation
-+ Fix reporting issue related to outputting quiz question answers where the correct answer is the first available answer
-+ Fix PHP 7.1 issue on the checkout screen
-+ Removed some unnecessary files from vendor libraries
-
-
-v3.7.6 - 2017-05-05
--------------------
-
-+ New translations for new categories on Add-ons screen
-+ Update to general settings which utilizes featured items from the general settings screen
-+ Update readme & related meta files
-+ Removed advert image files
-
-
-v3.7.5 - 2017-05-02
--------------------
-
-+ Upgrade WP Session Manager to latest version
-+ Code style updates across most files in codebase to bring to most recent styling guidelines put forth by [WP Coding Standards](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards)
-
-
-v3.7.4 - 2017-04-26
--------------------
-
-+ When cloned site detected automatically disable recurring_payments feature & trigger an action 3rd parties can hook into for custom 3rd party features
-+ Add better JS dependency management to prevent issues where assets loaded in the wrong order
-+ Fix issue where dismiss icon on LifterLMS admin notices was positioned poorly on non-LifterLMS admin screens
-+ Fix issue preventing edit account form submission on student dashboard when password strength meter is disabled
-
-
-v3.7.3 - 2017-04-21
--------------------
-
-+ Fixed issues where Course Track checks were not functioning properly with relation to prerequisite associations
-+ `LLMS_Generator` can now be used to generate course(s) from a raw array of course data using the SingleCourseGenerator and BulkCourseGenerator
-+ `LLMS_Generator` default post status can be set at runtime using `set_default_post_status()`
-+ Fixed an issue causing JS errors on the `wp-login.php` screen
-+ Tested up to WordPress 4.7.4
-
-### Template Updates
-
-+ `course/prerequisites.php` - Prerequisite checks check for 'course_track' rather than 'track'
-
-
-v3.7.2 - 2017-04-17
--------------------
-
-+ Resolved a JS errors on admin panel resulting from overly strict asset loading added in 3.7.0
-
-
-v3.7.1 - 2017-04-14
--------------------
-
-+ Fix php notice when no roles are selected for preview management feature
-
-
-v3.7.0 - 2017-04-13
--------------------
-
-**Preview Management**
-
-+ All new view management for users to make editing content easier for course builders
-+ Admins may customize the roles of users who can access view management
-+ Qualifying users can view content as an enrolled student or a non-enrolled visitor
-+ Default view allows users to bypass all restrictions (drip, membership, enrollment, and so on) for easy course navigation and management
-+ Thanks to [@fabianmarz](https://github.com/fabianmarz) and the team at and the team at [netzstrategen](https://github.com/netzstrategen) for their assistance with this feature!
-
-**Improvements**
-
-+ Edit Account Screen now utilizes updated APIs for better customization management
-+ Improve intelligence of enqueued admin js & css files
-
-**Fixes**
-
-+ Fixed coupon calculation issue related to currencies using commas as the decimal separator
-+ Properly display track related information when reviewing engagements on the admin panel
-+ fixed issue preventing course tracks from being recorded as completed
-
-
-v3.6.2 - 2017-04-10
--------------------
-
-+ Fix issue preventing export of vouchers via email
-+ added action `after_llms_mark_complete` to allow custom actions to happen after a course, lesson, etc... is marked complete
-
-
-v3.6.1 - 2017-03-28
--------------------
-
-+ Fix issue related to taking a quiz for the first time when no quiz data is available for a user
-+ Fix issue when course outline shortcode is displayed on non LifterLMS post types
-
-
-v3.6.0 - 2017-03-27
--------------------
-
-+ Courses and Memberships now have settings to control their visibility in catalogs and search results. For more information visit the [knowledge base](https://lifterlms.com/docs/course-membership-visibility-settings/).
-+ Courses are now a searchable post type. All existing courses will automatically remain excluded from search via new catalog visibility settings. New courses added after this date will be searchable unless the visibility is updated prior to publishing the course.
-+ Added options (and filters) to allow customization of the order of courses displayed on the Student Dashboard
- + Existing behavior (ordered by enrollment date, most recent to least recent) will be preserved
- + New installations will default (by popular demand) to Order (Low to High) which will obey the "Order" settings of courses
- + Customize or update the order for your site by visiting LifterLMS -> Settings -> Accounts and changing the setting for "Courses Sorting" under "Account Dashboard"
-+ New Shortcodes:
- + `[lifterlms_course_author]` - Display the Course Author's name, avatar, and (optionally) biography. [Info & Usage](https://lifterlms.com/docs/shortcodes/#lifterlms_course_author)
- + `[lifterlms_course_continue]` - Display a progress bar and continue button for enrolled students only. [Info & Usage](https://lifterlms.com/docs/shortcodes/#lifterlms_course_continue)
- + `[lifterlms_course_meta_info]` - Display all meta information for a course. [Info & Usage](https://lifterlms.com/docs/shortcodes/#lifterlms_course_meta_info)
- + `[lifterlms_course_prerequisites]` - Display a notice describing unfulfilled prerequisites for a course. [Info & Usage](https://lifterlms.com/docs/shortcodes/#lifterlms_course_prerequisites)
- + `[lifterlms_course_reviews]` - Display reviews and review form for a LifterLMS Course. [Info & Usage](https://lifterlms.com/docs/shortcodes/#lifterlms_course_reviews)
- + `[lifterlms_course_syllabus]` - Display the course syllabus. [Info & Usage](https://lifterlms.com/docs/shortcodes/#lifterlms_course_syllabus)
-+ "Back" & "Next" pagination links on Student Dashboard View Courses are now buttons instead of text links
-+ Fixed an issue preventing pagination links from displaying on the "View Courses" page of the student dashboard when the endpoint slug was customized
-+ Course and Membership taxonomy archive pages will now properly match the heights of tiles
-+ Fixed typo in `lifterlms_get_enrollment_status_name` filter
-+ Fixed typo in `lifterlms_get_order_status_name` filter
-+ Reduced complexity and redundancy of `llms_get_enrolled_students()`
-
-
-v3.5.3 - 2017-03-21
--------------------
-
-+ Ensure that access plan subscription schedule details are fully translatable
-+ Ensure "Services" title on admin add-ons screen can be translated
-+ Fix "View All My Courses" link on Student Dashboard to obey endpoint slug customizations
-+ Membership restriction checks only run on singular posts (not on archives)
-+ Ensure `[lifterlms_course_outline]` and Course Syllabus widget can be used on Quizzes.
-+ Fix reporting widgets for course & lesson completions to report the correct completion types only
-
-
-v3.5.2 - 2017-03-16
--------------------
-
-+ Fix course outline shortcode when used on a lesson
-+ Fix custom html form fields produced by `llms_form_field()`
-
-
-v3.5.1 - 2017-03-15
--------------------
-
-+ Lessons marked as incomplete will now display as incomplete in the course outline generated by the above Course Syllabus Widget and the course outline shortcode
-+ Updated course outline shortcode / course syllabus widget to utilize new APIs
-+ The template at `templates/course/outline-list-small.php` updated to reflect above changes. If you're overriding this template please review the changes and update accordingly
-+ Fix issue preventing course auto advance on lesson completion
-+ Shortcodes added within `[lifterlms_hide_content]` will now be processed
-
-
-v3.5.0 - 2017-03-13
--------------------
-
-+ New course setting **Retake Lessons** allows students to mark lessons as "incomplete" after completing lessons. Admins may enable this site-wide setting under Settings -> Courses.
-+ Course and Membership catalog per page settings will now only accept numbers
-+ "Catalogs" settings tab has been split into "Course" and "Membership" settings
-+ Settings added via filter `lifterlms_catalogs_settings` will be added to the "Course" settings tab and deprecated in the next major release
-+ Default course and membership catalog courses per page changed to 9. Previous default was 10 which results in a 4th row on catalogs with only one item.
-+ Tweaked size of LifterLMS admin tab menu items
-+ Pass API Mode Context to links generated by LifterLMS payment gateways
-+ Fixed typo on general settings screen
-+ Moved LifterLMS Add-on Banners from General Settings to an Add-Ons menu
-+ If required fields exist on checkout and are empty during free quick enrollment users will be redirected to the normal checkout page where they can enter required fields
-+ Updated action scheduler lib to latest version. Minor changes, fixes compatibility with WooMemberships.
-+ Recent activity stats widgets on general settings screen updated to be more reliable and accurate (and performant!)
-+ Added 3 new widgets to enrollments reporting tab: courses completed, lessons completed, and user registrations
-
-
-v3.4.8 - 2017-03-07
--------------------
-
-+ Tested to WordPress Version 4.7.3
-+ Fixed undefined index notice on admin panel
-+ Added a real description to new `_nx()` functions
-+ Access plan trial periods now allow proper translations
-
-
-v3.4.7 - 2017-03-03
--------------------
-
-+ Ensure run when the `lifterlms_db_version` option doesn't exist in the database
-
-
-v3.4.6 - 2017-03-03
--------------------
-
-+ Fixed a text domain typo preventing translation of "Correct Answer" on quiz results screen
-+ Ensure access plan "periods" are translatable
-+ Now using `date_i18n()` for certificate dates so that dates are properly localized
-+ Load plugin textdomain during `init` rather than `plugins_loaded`
-
-
-v3.4.5 - 2017-02-23
--------------------
-
-+ Ensure free access plans are available to logged out users
-
-
-v3.4.4 - 2017-02-22
--------------------
-
-+ Added a popup to warn students when leaving a quiz they've already started
-+ Enable removal of student quiz attempts by admins from student reporting screens
-+ Fix an undefined error on quiz reporting screens for incomplete quizzes
-+ Display incomplete (abandoned) quizzes as incomplete (instead of as still running) on the quiz reporting screen
-+ Prevent logged in users from bypassing membership restrictions for free members-only access plans
-
-
-v3.4.3 - 2017-02-20
--------------------
-
-+ Fix issue with bbPress integration so that forums restricted to multiple memberships allow users of at least one membership that the forum is restricted to access topics within that forum
-+ Ensure that the correct ajax url is used for quizzes, resolves issue for sites utilizing `FORCE_SSL_ADMIN`
-+ Refactored database background update scripts for increased reliability & performance
-+ Database update 3.3.0 moved to 3.4.3 in order to accommodate users who were unable to run the 3.3.0 update, please read the [3.4.3 database update notes](https://lifterlms.com/docs/lifterlms-database-updates/#343) for more information.
-+ WIP: refactoring shortcodes to a more sane set of functions and classes
-
-
-v3.4.2 - 2017-02-14
--------------------
-
-+ Backwards compatible css for tooltips
-
-
-v3.4.1 - 2017-02-14
--------------------
-
-+ Password strength meter now functions correctly when using the [lifterlms_registration] shortcode
-+ Ensure open registration with required voucher prevents registration with invalid vouchers
-+ Lesson completion via quiz completion only recorded the first time the quiz is completed
-+ Fix issue preventing membership catalog from obeying the catalog's ordering settings
-+ Prevent duplicate engagements from being triggered
-+ Admin tables can display percentages as a progress bar!
-+ Students reporting table displays overall progress as a progress bar
-+ Refactored frontend assets class to allow better management of inline scripts
-
-
-v3.4.0 - 2017-02-10
--------------------
-
-+ Enrollment for free access plans has improved based on your feedback. For more information see [https://lifterlms.com/docs/checkout-free-access-plans/](https://lifterlms.com/docs/checkout-free-access-plans/)
-+ Upgraded Student Management Table for courses and memberships:
- + Allow searching students by name / email
- + Allow filtering of students by current status
- + Allow sorting of students by name, user id, status, and enrollment updated date
- + Added student's grade to the table (courses only)
- + Table pagination allows skipping to the first and last pages
- + Student names link to full student reporting screen
- + Student IDs added to the table. ID links to the WP User Edit screen which was previously accessible by clicking the student's name
- + Utilizing improved database queries for displaying data on the table
-+ One-click bulk enrollment of all current members of a membership into an auto-enrollment course. More info [here](https://lifterlms.com/docs/membership-auto-enrollment/#bulk-enrollment)
-+ Students reporting table pagination can now jump to first and last page
-+ Students reporting table pagination now displays current page and total number of pages
-+ Added new class `LLMS_Student_Query` which is modeled, loosely, off of the `WP_User_Query` and allows for querying student data in relation to courses
-+ `LLMS_Admin_Table` abstract now supports filtering and jump to first and last page pagination options
-+ `llms_get_enrolled_students` now utilizes `LLMS_Student_Query` and resolves a bug where some users returned by this query would be returned with the incorrect status.
-+ Ensure `LLMS_Course::has_prerequisite( 'course' )` & `LLMS_Course::has_prerequisite( 'track' )` always return booleans
-+ Made a small performance tweak for courses without audio / video embeds
-+ Fix coupon expiration dates check to be more i18n friendly
-+ Update `LLMS_Coupon` class to utilize 3.3.0 class property enhancements
-+ added `llms_current_time`, a pluggable wrapper for `current_time()` to enable easier unit testing of date-related functions
-+ Shortcodes within course restriction messages are now handled properly to output their intend content rather than the raw shortcode
-+ Ensure the Page Attributes area is available on lessons so WordPress 4.7 custom post type page templates can be utilized
-
-
-v3.3.1 - 2017-01-31
--------------------
-
-+ Tested up to WordPress core 4.7.2
-+ Added new engagement triggers for Quiz completion, quiz failure, and quiz passed.
-+ Refactored Lesson Completion for sanity
-+ Added function `llms_mark_complete()` for simple programmatic completion of courses, sections, lessons, and tracks. See [usage docs](https://github.com/gocodebox/lifterlms/blob/master/includes/functions/llms.functions.person.php#L146-L162) for more information.
-+ Class function `LLMS_Lesson::mark_complete()` has been staged for deprecation. It will still function but developers should update code to use above function.
-+ LifterLMS background updaters will now display a progress report on the admin panel to add some transparency to how the update is doing.
-+ Added `author` support to `llms_membership` post type
-+ Added a way to remove all LifterLMS-generated data during plugin uninstallation.
-+ `llms_get_post()` will now work with any LifterLMS Post Model post types
-+ Removed references to `LLMS_Activate` class which was removed back in 2.0.
-+ Changed include method to session related classes for better handling via phpunit
-+ Refactored some of the `LLMS_Install` class for reliability and test coverage
- + Changed order of table and option creation during installation. Prevents a database warning from being thrown during installation.
- + Added function for retrieving default difficulty categories added during installation
- + Added function for removing default categories added during installation
-+ `llms_are_terms_and_conditions_required()` ensure the page id used in this function is an absint
-+ Removed redundant function `LLMS_Lesson::single_mark_complete_text()`
-+ Add css classes for buttons to be auto-width rather than the width of their containers
-+ Fix ID of engagement email class. Allows some filters and actions to actually be used.
-+ Properly display quiz failures as failures on the quiz results screen
-+ `loop/feature-image.php` now works for unsupported PHP 5.5 and down
-+ Fix issue with modifying section titles from within the course builder
-+ Fix undefined warning resulting from admin notice "flash" being undefined on pre-existing saved notices
-+ Updated template at `templates/course/complete-lesson-link.php` to include a few new CSS classes and utilize `llms_form_field()` to standardize buttons
-
-
-v3.3.0 - 2017-01-23
--------------------
-
-+ New course option allows displaying the video embed in place of the featured image on course tiles displayed on the course catalog screen
-+ Courses can now be exported individually or in bulk. Export of a course includes all course content, sections, lessons, and quizzes.
-+ Courses can now be duplicated. Duplication duplicates all course content, sections, lessons, and quizzes.
-+ Upon completion of the Setup Wizard a sample course can be automatically installed.
-+ Postmeta keys for Lessons and Sections which denote their relationship to their parents have been renamed for consistency, database upgrade 330 included in this release will rename the keys automatically. [Read more here](https://lifterlms.com/docs/lifterlms-database-updates/#330)
-+ Update to `LLMS_Post_Model` to allow easier programmatic definition and handling of extending class properties
-+ classes extending `LLMS_Post_Model` can now be serialized to json and converted to arrays programmatically
-+ new function `llms_get_post()` allows easier instantiation of an `LLMS_Post_Model` instance
-+ Added LifterLMS Database Version to the system report
-
-
-v3.2.7 - 2017-01-16
--------------------
-
-+ Fix float conversion of large numbers with relation to coupon price adjustments
-
-
-v3.2.6 - 2017-01-16
--------------------
-
-+ Tested up to WordPress Core 4.7.1
-+ Fix the display of track-related engagements on the engagement admin screen
-+ Fix float conversion of large numbers with relation to prices
-
-
-v3.2.5 - 2017-01-10
--------------------
-
-+ New shortcode: `[lifterlms_pricing_table]` allows pricing table display outside of a course or membership. See [https://lifterlms.com/docs/shortcodes/#lifterlms_pricing_table](https://lifterlms.com/docs/shortcodes/#lifterlms_pricing_table) for usage information.
-+ New shortcode: `[lifterlms_access_plan_button]` allows custom buttons for individual access plans to be created outside of a pricing table. See [https://lifterlms.com/docs/shortcodes/#lifterlms_access_plan_button](https://lifterlms.com/docs/shortcodes/#lifterlms_access_plan_button) for usage information.
-+ ensure every return from `llms_page_restricted` is filtered. Thanks to @matthalliday
-+ Ensure purchase page can only load for valid access plans
-+ Course / Membership taxonomy archives now obey orders defined by their respective catalog settings
-+ Fix language of automatic validation error message for numeric field types
-+ Fix translation function error causing course syllabus to display incorrect "x of x" text
-+ Added correct text domain to an i18n string displayed on the checkout confirmation screen, thanks @ymashev
-+ Ensure search result pages are viewable by members and non members regardless of result membership restrictions (unless site is restricted to sitewide membership)
-
-
-v3.2.4 - 2017-01-03
--------------------
-
-+ Fixed tooltips on lesson preview tiles (in course syllabus and on next/prev tiles inside lessons) to show the actual reason the lesson is inaccessible rather than always showing a generic enrollment message
-+ Removed the language "You must enroll in this course to unlock this lesson" in favor of "You do not have permission to access to this content" as a restriction message fallback when no better message is available
-+ "Quiz Results" title is now translatable
-+ Removed deprecated JS file "llms-metabox-data.js" which controlled UI/X for 2.x subscription data on courses and memberships
-+ Non LMS Content (pages, posts, forums, etc...) restricted to multiple memberships will now correctly allow users access to the content as long as they have access to at least one of the memberships
-+ Fixed a redirect loop encountered if direct access to a lesson with an incomplete prerequisite was attempted
-
-
-v3.2.3 - 2016-12-29
--------------------
-
-+ Progress and Grade are now sortable columns on the student reporting table
-+ Make enrollment statuses translatable for courses and memberships on the Student Dashboard
-+ "Sign Out" text on student dashboard is now translatable, thanks @yumashev
-+ Fixed prerequisite lesson display on lesson post tables
-+ Ensure post archive (blog) is visible regardless of post membership restrictions
-+ Moved lesson post table management functions to their own class
-+ Unused section post table management functions removed
-
-
-v3.2.2 - 2016-12-21
--------------------
-
-+ Adds filter `llms_student_dashboard_login_redirect` allowing customization of the redirect upon login via the Student Dashboard
-+ Adds a shortcode parameter, `login_redirect` to `[lifterlms_my_account]` allowing customization of the redirect upon login via the Student Dashboard
-+ Adds a new tool under "Tools and Utilities" on the LifterLMS Settings screen which allows users to clear the cached student overall progress and overall grade data
-+ Fixes a compatibility issue with the OptimizePress live editor
-+ Adds a text domain to a translation function where none was present, rendering the string untranslatable
-
-
-v3.2.1 - 2016-12-14
--------------------
-
-+ Fix operator position on `is_complete` check
-
-
-v3.2.0 - 2016-12-13
--------------------
-
-##### LifterLMS Reporting Beta
-
-+ Students overview displays broad information about your students in a searchable and sortable table
-+ Review data about individual students including:
- + Membership enrollments and statuses
- + Course enrollments, status, and progress
- + Quiz attempts and and their submitted answers
- + Earned achievements and certificates
-+ Sales and Enrollments analytics are now found under the "Reporting" screen
-+ Feedback on the beta? Let us know at [https://lifterlms.com/docs/lifterlms-reporting-beta/](https://lifterlms.com/docs/lifterlms-reporting-beta/)
-
-##### Other Updates & Fixes
-
-+ Lesson completion checks now look for at least one record of the completed lesson as opposed to looking for exactly one
-+ Fix positioning of teacher avatar on course/membership tiles
-+ Remove explicit color definition from Student Dashboard navigation links for greater theme compatibility
-
-
-v3.1.7 - 2016-12-06
--------------------
-
-+ Added support for WordPress Twenty Seventeen theme
-+ Improved the messaging and functions related to LifterLMS Sidebar support
-+ Add alternate language for a quiz requiring 100% grade to pass
-+ Added CSS class `.llms-button-primaray` to lesson "Mark as Complete" buttons
-+ Add box-sizing css rule to LifterLMS form field elements. Fixes layout issues on themes that don't border-box everything.
-+ Fix an issue that prevented the admin notice to enable/disable recurring payments from clearing when a button was pressed from screens other than the LLMS Settings screen
-+ Fix next payment date error when viewing a cancelled recurring order on the student dashboard
-+ Recurring payments now scheduled based on UTC time in accordance with the action scheduler which executes based on UTC rather than site timezone
-+ Add existing lesson to course modal now relies on async search. Improves performance and prevents timeouts on sites with a 500+ lessons
-+ Removed 2.x -> 3.x update notification message
-+ Fix an issue with comment counting on PHP7
-+ Updated action scheduler library to latest version
-
-
-v3.1.6 - 2016-11-11
--------------------
-
-+ Handle empty responses on analytics more responsibly
-+ Fix typo preventing completed orders from displaying in analytics when using course / membership filters
-+ Quiz builder now leverages llmsSelect2 rather than select2 directly. Resolves a number of theme and plugin compatibility issues.
-+ Prevent bullets and weird margins on LifterLMS notices with slightly more specific CSS
-+ Login error messages will now display regardless of whether or not open registration is enabled
-+ Attempts to access quizzes are redirected or error messages are output when student is not enrolled.
-
-
-v3.1.5 - 2016-11-10
--------------------
-
-+ Fix Month display on Analytics Screen
-
-
-v3.1.4 - 2016-11-10
--------------------
-
-+ Progress bars are slightly more intelligent to prevent a widowed "%" on themes with larger base font sizes
-+ LifterLMS Merge code button only displays where it's supposed to now
-+ Fix issue where users removed from a membership were not properly removed from courses they were auto-enrolled into because of that membership
-+ Fix analytics screen JS parsing error
-
-
-v3.1.3 - 2016-11-04
--------------------
-
-+ Added new action hooks to the course syllabus widget/shortcode template
-+ Added a small text link on the student dashboard which links to the full courses list of the dashboard
-+ Display order revenue for legacy orders instead of 0
-+ Make the Order History table on the Student Dashboard responsive
-+ Only display _published_ courses on the student dashboard
-+ Fixes a conflict with WP Seo Premium's redirect manager which was creating access plan redirects
-+ Reenable course review options on the admin panel
-+ Updates review output method so reviews are now output via a removeable action
-
-
-v3.1.2 - 2016-10-31
--------------------
-
-+ Update all course and lesson templates to rely only on `global $post` rather than on `$course` and `$lesson` globals which are working inconsistently across environments
-+ Fix typo related to the line-height of LifterLMS order notes on the admin panel. Thanks [@edent](https://github.com/edent)!
-
-
-v3.1.1 - 2016-10-28
--------------------
-
-+ Shortcode `[lifterlms_hide_content]` has some new functionality. See [documentation](https://lifterlms.com/docs/shortcodes/#lifterlms_hide_content) for usage and more information!
-+ Fix logic when determining if terms and condition checkboxes should be displayed on checkout & open registration.
-+ Define a placeholder on the Terms & Conditions page selection so it can be removed
-+ Explicitly declare `LLMS_Lesson` on lesson audio/video embed templates instead of relying the global `$lesson`. Some environments appear to be losing the global.
-+ Removed unused lesson template "full-description"
-
-
-v3.1.0 - 2016-10-27
--------------------
-
-+ New engagement triggers available to allow engagements to be fired when a student enrolls into a course or membership!
-+ Add custom email addresses for to, cc, and bcc when sending email engagements
-+ New Merge Code button for easy merging of custom merge codes when creating emails
-+ Added post table data for LifterLMS Engagements
-+ Added new filter `llms_email_engagement_date_format` which allows customization of the format of the `{current_date}` merge code available in LifterLMS Emails
-+ Added explicit max width declaration to images within LLMS Catalogs to prevent image overflow. Fixes some theme compatibility issues.
-+ Optimize course and lesson audio video templates for faster loads
-+ Fix course & lesson video to load videos instead of duplicating audio embeds
-+ Fix coupon usage query so that coupons cannot be used more than the maximum number of times. Also now displays the correct number of coupons used on the coupons post table.
-+ Fix LLMS Engagement Email merge codes to work in subject line
-
-
-v3.0.4 - 2016-10-20
--------------------
-
-+ Added shortcode `[lifterlms_login]` so the login form can be displayed. Information usage at [https://lifterlms.com/docs/shortcodes/#lifterlms_login](https://lifterlms.com/docs/shortcodes/#lifterlms_login)
-+ Added internal function `LLMS_Student->get_name()`
-+ Three basic course difficulties will be automatically created on installation and upgrades
-+ Updated course difficulty save methods to rely only on the taxonomy rather than the taxonomy and postmeta table
-+ Updated admin settings screens to only flush rewrite rules on screens where it is necessary to update rewrites
-+ Fix issue with customization of LifterLMS account endpoint URLs
-+ Fix a conflict with [Redirection](https://wordpress.org/plugins/redirection/) url monitoring that was causing redirects to be created from Courses and Memberships to the site home page automatically whenever updating the post
-+ Fix an undefined index warning on courses / memberships when updating post data
-+ Remove confusing and invalid warning message from Membership post screen on admin panel
-
-
-v3.0.3 - 2016-10-17
--------------------
-
-+ Added filter `llms_show_preview_excerpt` which can be used to hide the excerpt on course syllabus or next/back preview tiles in lesson navigation
-+ Fix logic so that only free lessons are marked as free lessons post 3.0 upgrade
-+ Fix incorrect display of the "restricted" and "non-restricted" content areas for memberships
-+ Fix undefined index warning output by membership metaboxes
-+ Fix dead like under "Force SSL" checkout setting
-+ Course & Membership tiles output by course or membership shortcodes now automatically match column heights like the default catalogs do.
-+ Correctly register students as the "Student" Role
-+ Database Upgrade script converts users with the role "studnet" to "student"
-
-
-v3.0.2 - 2016-10-14
--------------------
-
-+ Added action `lifterlms_before_student_dashboard_tab`
-+ Added action `lifterlms_after_student_dashboard_greeting`
-+ Added action `lifterlms_after_student_dashboard_tab`
-+ Added action `lifterlms_sd_before_membership`
-+ Added action `lifterlms_sd_after_membership`
-+ Fix membership shortcode
-+ Fix issue that prevented "Student Dashboard" from rendering if the page was set as the child of another page
-+ Fix undefined function error in admin notices
-+ Fix nonce errors resulting from admin notice html being served from the database rather than being dynamically generated
-+ Fix db upgrade script which was enabling course time period for restrictions for all courses regardless of their pre 3.0 restriction settings
-+ Fix db upgrade script that was causing empty sale dates to show start of unix epoch b/c they were empty strings
-+ Fix Javascript parse error preventing section & lesson editing from within the course outline on the admin panel
-+ Fix lesson icons from highlighting lesson settings like drip delay & quiz association
-+ Updated course outline color scheme to match the 3.0 admin color scheme overhaul
-+ `LLMS_Lesson::get_assigned_quiz()` will output deprecation warnings for those using debug mode. LLMS core no longer uses this function and will be deprecated in the next major release.
-+ Handle enrollment status of legacy orders based on enrollment rather than enrollment AND order status
-
-
-v3.0.1 - 2016-10-13
--------------------
-
-+ Properly prefix `llms_is_ajax()` to prevent 500 errors when leaving HTTPS forced checkout screen
-+ Fix student unenrollment from memberships which was leaving a trace of enrollment in the user_meta table
-+ Update student dashboard nav list items to have more specific no styles to prevent "double discs" on various themes
-+ Return course progress bar and "continue" button which was accidentally removed
-+ Added core support for "Divi" theme sidebars
-
-
-v3.0.0 - 2016-10-10
--------------------
-
-**This is a massive update which _breaks_ backwards compatibility for many LifterLMS features. A database migration is also necessary for upgrading users to reformat certain pieces of information which are being accessed differently in 3.0.0**
-
-**We strongly recommend that you backup your website before upgrading and, if possible, test LifterLMS 3.0.0 in a non-public-facing testing environment to ensure compatibility with your theme and other plugins and to ensure that 3.0.0 changes do not adversely affect your existing website.**
-
-**Please thoroughly read the following changelog and, if necessary, submit support tickets or post in the forums with any questions _prior_ to upgrading. LifterLMS Support _cannot_ and _will not_ manually resolve migration issues which may arise from upgrading to 3.0.0.**
-
-+ New shortcodes to be documented later, checkout "includes/class.llms.shortcodes.php" if you're feeling anxious
-+ All kinds of CSS changes to make LifterLMS, in general, be a little less old looking
-+ Added a number of CSS classes to various areas in the Checkout template at "templates/checkout/form-checkout.php"
-+ Added a "Cancel" button that allows you to hide the coupon form if the user decides not to add a coupon
-+ Removed jQuery animations from the coupon form toggle in favor of a CSS class toggle. If you decide you want some animations on the form add some CSS transitions to the `.llms-coupon-entry` element (and children) to change when the class `.active` is added or removed from the element.
-+ Refactored JavaScript related to LifterLMS Checkout. Improvements are minimal (if any) but the file is now smaller and more readable! Yay code stuff.
-+ Fixed some redundant text on single payment confirmation screen. ("Single payment of single payment of")
-+ Added a link to memberships listed under "My Memberships" on the LifterLMS Account Screen
-+ LifterLMS Order posts have been renamed in the database from "order" to "llms_order" to prevent any potential conflicts with other plugins. Automated database migration will handle the renaming of old orders.
-+ Fixed undefined variable notice generated by Sections without any lessons inside of them
-+ renamed function `add_query_var_product_id()` to `llms_add_query_var_product_id()`
-+ added a class for interacting with a course TRACK, instantiated by a track term or term_id (`LLMS_Track`)
-+ password strength meter and related settings / options via utilizing WordPress password strength functions available
-+ cleaned up the lesson locked tooltips to be a bit more sane and also utilized in course navigation on individual lessons.
-+ Updated admin menus for LifterLMS content to be more sane and organized and intuitive and so on and so forth
-
-##### Payment Gateways
-
-**NOTE: at this release, LifterLMS PayPal is the only payment gateway that will work with this release. We haven't started working on Stripe 4.0.0 which will work with LifterLMS 3.0.0**
-
-+ Payment gateways powered by a new abstract gateway class
-+ PayPal has been removed from LifterLMS and is available as premium extension
-
-##### Frontend Notices
-
-+ LifterLMS "Notices" have been rewritten, slightly.
-+ Most templates have been updated
-+ associated CSS has been updated
-+ Some sanity has been added to the related functions
-
-##### Post "Model" Concept / Overhaul
-
-Updated classes for programmatically accessing all sorts of data related to custom post types registered by LifterLMS.
-
-These post types currently include:
-
-+ Access Plans -- a non-public post type associated with courses and memberships which store payment related information
-+ Coupons (replaces includes/class.llms.coupon.php)
-+ Courses (replaces includes/class.llms.course.php)
-+ Lessons (replaces includes/class.llms.lesson.php)
-+ Memberships
-+ Orders (replaces includes/class.llms.order.php
-+ Products -- can be instantiated from courses or memberships (replaces includes/class.llms.product.php)
-+ Transaction -- a non-public post type associated with orders which store completed/attempted transaction data
-
-##### Improved admin metabox methods (and related)
-
-+ Updated custom LifterLMS Admin Metaboxes to have a more sane programmatic interface. This affects nearly all admin metabox classes in the plugin.
-+ A set of methods and classes have been added to improve the programmatic interface around custom post type post tables. These can be found in "includes/admin/post-types/post-tables"
-
-##### Coupons
-
-+ New class `LLMS_Coupon` allows for easy getting & setting of coupon data.
-+ Updated coupon post table to include relevant coupon information for all coupons at a glance
-+ Refactored admin panel coupon metabox generation to utilize new model for saving data
-+ Added translation functions to all strings in coupon settings screen
-+ Added new coupon settings
- + _Expiration Date_ -- coupons cannot be applied to a purchase after the expiration date
- + _Payment Type_ -- coupons can only be applied to either single or recurring payment plans. Existing coupons will be treated as single payment coupons until updated by the Admin.
- + _First Payment Discount_ -- Applies only to recurring payment coupons. Determines the discount applied to the first payment of a recurring payment transaction.
- + _Recurring Payments Discount_ -- Applies only to recurring payment coupons. Determines the discount applied all payments (other than the first) of a recurring payment transaction.
- + _Description_ -- Record internal notes for a coupon visible only by admins on the admin panel
-+ The "Coupon Code" field has been removed in favor of the WordPress Coupon Post Title being utilized as the code. After upgrading, an automated database migration will move all coupon code fields to the title. The title previously functioned as the coupon description. During the migration the existing title will be moved to the new description field.
-
-##### Orders
-
-+ Added Order Statuses
- + Completed - Single payment only. Denotes a successful transaction
- + Active - Recurring only. Denotes the subscription is active with no issues
- + Expired - Recurring only. Denotes the subscription has ended and is no longer active
- + Refunded - Denotes the order has been refunded.
- + Cancelled - Denotes the order has been cancelled manually by an admin.
- + Failed - Denotes payment has failed. For subscriptions a failed payment will switch from "active" to "failed"
- + Pending - Denotes that the order has been created but payment has not been completed yet
-+ Admin panel order table new features:
- + The following columns are now sortable in ascending and descending orders: Order, Product, and Date
- + Added totals based on order type (single or recurring) to the "Total" column
- + Added an order status column for quick status review
-+ Order notes available for internal and system notes. powered by WP comments. lots of inspiration (and code) from WooCommerce, thank you <3
-+ Added a bunch of currency settings (as well as right-side currency and decimal-less currency support!)
-
-##### New Templates
-
-+ __Pricing Table__ at "templates/product/pricing-table.php" utilized by courses and memberships for displaying access plan information. Replaces "templates/membership/purchase-link.php" and "templates/course/purchase-link.php"
-+ __Course Taxonomy Templates__ at "templates/course/categories.php", "templates/course/tags.php", and "templates/course/tracks.php" display comma separated lists for course custom taxonomy terms
-+ __Course Prerequisite Template__ at "templates/course/prerequisites.php" displays prerequisite information (course and tracks) for a given course.
-+ __Meta Wrapper__ templates at "templates/course/meta-wrapper-end.php" and "templates/course/meta-wrapper-start.php" wrap some HTML around various meta data output about a course
-+ Significantly updated checkout process with all kinds of new templates including:
- + templates/checkout/form-gateways.php
- + templates/checkout/form-summary.php
-+ __Unified "Lesson Preview"__ at "templates/course/lesson-preview.php" displays "buttons" in course syllabus (on course page) and in course navigation (on lesson pages)
-+ Various template hook priority changes in order to make adding content between default LifterLMS areas easier
-
-##### Deleted Templates
-
-+ templates/checkout/form-checkout-cc.php
-+ templates/checkout/form-pricing.php
-
-##### New & Updated Admin Interfaces & Templates
-
-+ Significantly improved, changed, or brand new templates for metaboxes for various post types:
- + templates/admin/post-types/order-details.php
- + templates/admin/post-types/order-transactions.php
- + templates/admin/post-types/product-access-plan.php
- + templates/admin/post-types/product.php
-
-##### New Functions
-
-+ `llms_confirm_payment_url()` - Retrieve the URL used for confirming LifterLMS Payments
-+ `llms_cancel_payment_url()` - Retrieve the URL users are directed to when cancelling a payment
-
-##### Install Script
-
-+ Removed some legacy default options that were being created and are no longer required for new installations.
-+ Removed unused `update_courses_archive()` function & related hook
-
-##### Select2
-
-Now utilizing a forked version of Select2 to prevent 3.5.x conflicts we've been plagued with
-
-##### Deprecated
-
-+ Removed filter `lifterlms_get_price_html`, use `lifterlms_get_single_price_html` instead
-+ Removed unused `LLMS_Product->get_price_suffix_html()` function
-+ Removed `LLMS_Product->set_price_html_as_value()` because we didn't like it anymore, don't use anything instead.
-+ Removed `add_query_var_course_id()` function
-+ Removed `displaying_sidebar_in_post_types()` function with the `LLMS_Sidebars::replace_default_sidebars()` function
-+ Filter `lifterlms_order_process_pending_redirect` has been replaced with `lifterlms_order_process_payment_redirect`
-+ Action `lifterlms_order_process_begin` has been deprecated
-+ Removed `lifterlms_order_process_complete` action
-+ Replaced `LLMS_Course::check_enrollment()` with various new utilities. See `llms_is_user_enrolled()` for fastest use.
-+ Officially removed the `LLMS_Language` class
-+ Officially removed the `PluginUpdateChecker` class stubs we created to prevent updating issues with LifterLMS extensions during our transition to 2.0.0. This library has caused nothing but pain for everyone on our team and many of our users. It's gone now, forever.
-+ Removed function `lifterlms_template_single_price()` and replaced with `lifterlms_template_pricing_table()`
-+ Removed templates at "includes/course/price.php" and "includes/membership/price.php" in favor of "includes/product/pricing-table.php"
-+ Removed `LLMS_Person::create_new_person()` in favor of `LLMS_Person_Handler::register()` or `llms_create_new_person()`
-+ Removed `LLMS_Person->set_user_login_timestamp_on_register()` and are simply adding the metadata during registration
-+ Removed `lifterlms_register_post` action hook which fired after new user registration validation, this has been replaced with `lifterlms_user_registration_after_validation`
-+ Removed `lifterlms_new_person_data` and `lifterlms_new_person_address` filters, replaced with `lifterlms_user_registration_data`
-+ Removed `LLMS_Person::login_user()` in favor of `LLMS_Person_Handler::login()`
-+ background updater
-+ system report facelift + inclusion of all new settings via `LLMS_Data` class
-+ Fix setup wizard styles to follow update admin panel styles
-+ add links to last step of setup wizard for documentation and demo
-+ removed a bunch of deprecated coupon-related functions
-+ added a "force ssl" option to ensure checkout is secured
-+ added settings and options around recurring payments and staging sites to prevent duplicate charges when testing on a cloned site
-+ Check course restrictions automatically when checking lesson
-+ Added user_id to all access function checks to allow for checks for non current user
-+ course restriction messages display regardless of enrollment status
-+ check memberships and lock purchase of members only access plans
-+ Fixed titles of course closed and open messages on the course restrictions options
-+ record a start date for access plans based off when order moves to complete or active for the first time
-+ automatically expire limited access plans
-+ gave a quick facelift & unification to a lot of admin panel elements
-+ Color consistency updated according to LLMS brand guide
-+ Unified front and backend button classes
-+ Updated all frontend buttons to have consistent classes
-+ Removed the "FREE" lesson SVG in favor of simple text which allows translating
-+ Install & activation overhauls. Resolves [#179](https://github.com/gocodebox/lifterlms/issues/179)
-+ jQuery MatchHeight lib unignored
-+ A bunch of settings pages updated and a bunch of settings deprecated
-+ Gateways setting page removed
-+ Memberships & Courses page combined into "Catalogs" settings
-+ Added a data getting class used by the tracker class
-+ added a new page creation function with better intelligence that (hopefully) prevents duplicate pages from being created during core page installation
-+ new default country setting
-+ all order status changes recorded as order notes
-+ pending orders can be completed after failed payments
-+ better handling for gateways with fields
-+ JS spinners support multiples via start & stop!
-+ Updated (and semi-finished) analytics
-+ achievement metabox converted
-+ minor updates to voucher class
-+ Added a "post state" visible on the Pages posts table identifying if the page is saved as a LifterLMS page (EG: Checkout Page)
-+ Fixed copy/paste error of duplicate enrollment closed message on course restrictions tab
-+ Removed WC integration in favor of WC
-+ Upgrade "back to course" template to new lesson API
-+ Renamed `course/parent_course.php` to `course/parent-course.php` for template naming consistency
-+ use `strict` when auto generating usernames when creating from email addresses, resolves [#182](https://github.com/gocodebox/lifterlms/issues/182)
-
-##### 3.0.0 Auto Upgrader
-
-+ lots of postmeta data rekeyed
-+ intelligently generated defaults for various pieces of new meta data on courses, lessons, and memberships
-+ automatically generate access plans from existing course and membership data
-+ update existing orders to pull semi-accurate data into analytics based on new database structure
-+ cleans database of a ton of deprecated options and postmeta data
-
-##### Deprecated
-
-+ function `llms_is_user_member()`, use `llms_is_user_enrolled()` instead
-+ function `llms_check_course_date_restrictions()`
-+ function `quiz_restricted()`
-+ function `membership_page_restricted()`
-+ function `is_topic_restricted()`
-+ function `llms_get_post_memberships()`
-+ function `llms_get_parent_post_memberships()`
-+ function `parent_page_restricted_by_membership()`
-+ function `outstanding_prerequisite_exists()`
-+ function `find_prerequisite()`
-+ function `llms_get_course_enrolled_date()`
-+ function `llms_get_lesson_start_date()`
-+ function `lesson_start_date_in_future()`
-+ function `page_restricted_by_membership_alert()`
-+ function `llms_does_user_memberships_contain_course()`
-+ class `LLMS_Checkout`
-+ function `LLMS()->checkout()`
-
-##### Auto Enrollment
-
-+ Course auto enrollment for Memberships has been restored
-+ Works exactly the same as previously except auto-enrollment is not dependent on a course "belonging to" the membership via membership restrictions. This is because membership restrictions no longer apply to courses
-
-##### Analytics
-
-+ Charts! I'm really excited about this. I know we still need more data but please say nice things to me, I worked really hard on these little charts.
-+ Updated styles & interface
-
-##### bbPress
-
-+ Restrict individual forums (and their topics) to LifterLMS Membership levels
-
-##### BuddyPress
-
-+ Fixes broken course display on bp profile
-+ Adds memberships subpage to bp profile
-
-##### notices
-
-+ Admin notices class for managing admin notices, it's pretty neat!
-
-##### Student Management on Courses and Memberships
-
-+ All new and improved student management interface for managing student enrollments from courses and memberships
-
-##### Deprecated
-
-+ filter: `llms_meta_fields_course_main`, replace with `llms_metabox_fields_lifterlms_course_options`
-
-##### Manual Payments
-
-+ Manual Payment Gateway can now be enabled on the frontend!
-+ When a manual payment is recorded the user will be redirected to a view order page where they will be prompted to make a manual payment
-+ Define the payment instructions on the admin panel "Checkout Settings"
-+ Once you verify payment, head to the pending order and hit the "Record a Manual Payment" button to record the transaction
-+ Upon recording the order status will be upgraded to "Complete" and the user will be enrolled automatically
-
-##### Student Dashboard Upgrades
-
-+ More sane template hooks and functions
-+ Pagination on Courses endpoint (view only a preview on the main dashboard)
-+ Orders history & view orders screens!
-
-Deprecated options (and related functions where applicable) for the following course & membership options:
-
- + `lifterlms_button_purchase_membership_custom_text`
- + `lifterlms_course_display_outline_lesson_thumbnails`
- + `lifterlms_course_display_author`
- + `lifterlms_course_display_banner`
- + `lifterlms_course_display_difficulty`
- + `lifterlms_course_display_length`
- + `lifterlms_course_display_categories`
- + `lifterlms_course_display_tags`
- + `lifterlms_course_display_tracks`
- + `lifterlms_lesson_nav_display_excerpt`
- + `lifterlms_course_display_outline`
- + `lifterlms_course_display_outline_titles`
- + `lifterlms_course_display_outline_lesson_thumbnails`
- + `lifterlms_display_lesson_complete_placeholders`
- + `redirect_to_checkout`
-
-In all scenarios either a `add_filter` (returning false) or a `remove_action()` can be used to replicate the option.
-
-
-v3.0.0-beta.4 - 2016-09-01
---------------------------
-
-+ fix issue with course prereq checks
-+ next payment due date visible on order admin view
-+ trial end date visible on order admin view
-
-##### Free Access Plans
-
-+ "Free" access plans now defined as such based on a checkbox rather than by entering 0 into the price
-+ Only single payment access plans can be free (a free recurring payment makes no sense but we can certainly discuss this if you disagree with me)
-+ trials are disabled with free plans (because trials only apply to recurring plans)
-+ sales are disabled for free access plans
-
-##### Checkout Form JS API
-
-+ unified JS checkout handler
-+ allows extensions to enqueue validation or pre-submission JS functions that should run prior to checkout form submission
-
-##### Manual Payment Gateway
-
-+ handles purchase of access plans marked ar FREE & orders that are discounted to 100% via coupons
-
-##### Open Enrollment
-
-+ Open Enrollment allows users to register on the account dashboard without purchasing a course
-+ Voucher settings are available to customize whether vouchers should be optional or required during open registration
-+ Better error reporting around voucher usage during enrollment
-
-##### Deprecated Functions
-
-+ `llms_get_coupon()`
-+ `get_section_id()`
-+ `check_course_capacity()`
-
-##### Quizzes
-
-+ Updated admin metaboxes to use new metabox abstract class
-+ display 0 instead of negative attempts on quiz summary
-+ updated logic in start button template
-
-##### Emails (for engagements)
-
-+ Admin metabox updated to new API
-+ Postmeta data migration:
- + `_email_subject` renamed to `_llms_email_subject`
- + `_email_heading` renamed to `_llms_email_heading`
-
-
-v2.7.12 - 2016-09-22
---------------------
-
-+ Added a new filter on content returned after port permission checks
-+ Added additional information to plugin update message in preparation for major 3.0 release
-+ Updated plugin contributor metadata
-
-
-v2.7.11 - 2016-07-22
---------------------
-
-+ Removed a duplicate action hook on course archive loop.
-+ Switched registration template include to use a more sane function
-+ Added updated banner adds with prettier ones. Wooooooo.
-
-
-v2.7.10 - 2016-07-19
---------------------
-
-+ Fix undefined noticed related to LifterLMS custom post type archive filtering
-+ Fix filter which was supposed to allow custom engagement types to be queried & triggered by engagements automatically but was passing data incorrectly
-
-
-v2.7.9 - 2016-07-11
--------------------
-
-+ We are now properly storing delayed engagement trigger data.
-+ Fixed an issue with our engagement query functions that caused, in very rare circumstances, the extra engagements to be triggered during an engagement trigger due to a lack of specificity in our query
-+ Fixed an undefined property notice related to email engagements when the email had no subject or header
-+ Fixed a typo in the description of a translation function.
-+ Added an engagement debug logging function. You can log all sorts of data related to engagements by adding `define( 'LLMS_ENGAGEMENT_DEBUG', true );` to your `wp-config.php` file.
-+ Allow course title shortcode to be used on course pages (and quizzes too). Documentation incorrectly said it was available on courses so we've fixed the function to allow for use on courses.
-
-
-v2.7.8 - 2016-07-05
--------------------
-
-+ Bugfix: Restore access to quiz results on quiz completion
-
-
-v2.7.7 - 2016-07-01
--------------------
-
-##### Russian
-
-+ LifterLMS is now 100% Translated into Russian thanks to our new Russian Translation Editor [@kellerpt](https://profiles.wordpress.org/kellerpt/)
-
-##### l18n
-
-+ All transition messages between questions during a Quiz are now translatable.
-+ LifterLMS subpages below the LifterLMS icon on the admin panel will now always display regardless of how you've chosen to translate the menu items. Hopefully puts to rest a long-standing i18n issue.
-
-###### Bug fixes
-
-+ Attempting to access a quiz when not enrolled in the associated course and having not properly started the quiz now results in a useful error message rather than a PHP warning.
-+ We've adjusted the way we're adding a admin panel "separator" to reduce conflicts with other plugins that have menu items with the same position as our separator (51).
-+ Added new logic to display an error message (instead of nothing) if there's an error during question loading.
-+ Resolve issue with course progress bar when added to a quiz sidebar (assuming your theme has sidebar support on your quizzes).
-+ Updated version number in the changelog for last version (it was supposed to be 2.7.6)
-
-
-v2.7.6 - 2016-06-28
--------------------
-
-+ Students manually removed by Memberships by using the "Students" tab of a LifterLMS Membership will now be fully removed from the membership.
-+ Updated a few time-related strings to be l18n friendly. These items were all around Quiz time reporting and quiz time limits.
-+ Updated testing information, tested up to WP 4.5.3
-+ Fixed date of last release on changelog. It had the wrong date. Does that really matter?
-+ Updated readme.txt description area, we have a new youtube video! Yassss.
-
-
-v2.7.5 - 2016-06-13
--------------------
-
-##### New features
-+ Added an "id" parameter to both LifterLMS Courses and LifterLMS Memberships shortcodes
-
-##### i18n
-+ Allow date translation on quiz results screen by using `date_i18n()` instead of `date()`
-+ Allow date translation on my courses screen by using `date_i18n()` instead of `date()`
-+ Ensure course status "Enrolled" is translatable on my courses screen
-
-##### Fixes
-+ Thanks to [@kjohnson](https://github.com/kjohnson) who fixed undefined index warnings & errors which occurred when viewing the last lesson in a section when the next section contained no lessons.
-+ Resolved an issue where formatting for "Restricted Access Description" course content would not display proper formatting.
-+ Fixed an issue with the "FREE" stamp for a free lesson caused layout issues.
-+ Removed the "is-complete" css class from incorrectly being added to lesson preview tiles for free lessons
-+ Fix an escaping issue when rendering Course titles inside LifterLMS notices. Prevents "\'s" from displaying when "'s" should be displaying (and similar issues).
-
-
-v2.7.4 - 2016-05-26
--------------------
-
-+ Fixed a bug with the new localization methods from 2.7.3
-+ Removed bundled it_IT translation files in favor of official language pack available at [https://translate.wordpress.org/projects/wp-plugins/lifterlms/language-packs](https://translate.wordpress.org/projects/wp-plugins/lifterlms/language-packs).
-+ Removed bundled en_US translation files because LifterLMS is in English so the files are unnecessary.
-+ Fixed a few mis-labeled filters applied when registering LifterLMS Custom Post Types
-+ Adjusted the default supported features of LifterLMS Quizzes and Questions
- + Quizzes now support custom fields as per user request
- + Commenting, thumbnails, and excerpts are no longer "supported" as they were never intended to be and were never correctly implemented.
- + If you are relying on any of these features for your quizzes or questions please use the following filters to re-implement these features: `lifterlms_register_post_type_quiz` or `lifterlms_register_post_type_question`. These will allow you filter the default arguments LifterLMS passes to the WordPress function `register_post_type()`
-
-
-v2.7.3 - 2016-05-23
--------------------
-
-+ Added a separate filter for login redirects `lifterlms_login_redirect` and added the user_id as a second parameter available to the filter.
-+ Added second parameter to `lifterlms_registration_redirect` to allow access to the registered user's user_id.
-+ Fixed a timestamp conversion issue on Course sale price checks that caused indefinite sales (those with no date restrictions) to appear not on sale during certain periods of time. The period would differ depending on the server's timezone settings and the time of visit.
-+ Added a "Pointer" when hovering quiz summary accordion to allow for a slightly more obvious user experience that the elements are expandable.
-+ Added some new localization methods to ensure strings that only appear in Javascript files will be translator friendly. This initially fixes a few issues on the Quiz Summary page and during quiz taking where strings only appeared in Javascript and were, therefore, completely inaccessible to translators.
-
-
-v2.7.2 - 2016-05-19
--------------------
-
-+ In course syllabus widget & shortcodes free lessons will now be clickable links.
-+ Record `llms_last_login` timestamp in usermeta when a user registers.
-
-
-v2.7.1 - 2016-05-09
--------------------
-
-##### Enrollment & Voucher Checks
-
-+ Enrollment functions will now automatically check to ensure that users are not already enrolled in a course or membership before enrolling. This addresses an issue which would create double enrollment for user redeeming a voucher for a product they were already enrolled in.
-+ Vouchers will now automatically check to see if the user has already redeemed this voucher before allowing the user to redeem it. This would have caused multiple enrollments and would allow one user to eat up an entire voucher by using it over and over again for funsies. A voucher can now *only* be redeemed once by a user as intended.
-+ `llms_is_user_enrolled()` now allows developers to check membership enrollment. Previously this function would only check enrollment of Courses despite what the documentation stated.
-
-##### Translation
-
-+ 3 strings have had translation functions added to them. This makes LifterLMS voucher redemptions translatable!
-
-##### Bugs & Fixes
-
-+ Fix javascript dependency & enqueueing issue on admin panel which prevented LifterLMS settings from saving correctly in various places
-+ Removed inline CSS from "next lesson button" on quiz completion / summary screen. This was overriding some default styles and making the button very thin and gross.
-
-
-v2.7.0 - 2016-05-05
--------------------
-
-##### LifterLMS Custom User Fields Exposed
-
-+ Custom fields added during registration via LifterLMS account settings are now exposed on the admin panel via the student's WordPress user profile
-+ All custom fields that are available (billing and phone) are editable on the WordPress user profile by anyone with profile edit access regardless of LifterLMS settings. If the settings are disabled (eg not required for registration) you can still add this information manually to a user's profile. This is useful if you require the information and then disable it later, you would still be able to access the information on the admin panel but would no longer be required for user's during registration.
-+ A few new filters added to help developers customize the experience here. Check out the documentation at [https://lifterlms.com/docs/lifterlms-filters/#admin-user-custom-fields](https://lifterlms.com/docs/lifterlms-filters/#admin-user-custom-fields)
-
-##### Membership Manual Add & Remove Student Functions
-
-+ Duplicated "Students" tab from the Course admin screen to Memberships
- + Students can be manually added to a membership by an admin
- + Students can be removed manually from a membership by an admin
-
-##### Updates
-
-+ Added the ability for students to edit their phone number via their account settings page if the phone number registration option is enabled on the site.
-
-##### Fixes
-
-+ Fixed a few spelling errors on LifterLMS admin panel order screens
-+ Fixed a typo on meta data for LifterLMS admin created (manual) orders
-
-
-v2.6.3 - 2016-05-02
--------------------
-
-+ Removed redirecting action from WooCommerce integration that was causing issues on multiple product purchase checkouts with larger databases.
-+ Added a new payment action `lifterlms_order_complete` which runs at the same time as some previous actions during payment processing but servers a different purpose. This is mostly in preparation for a forthcoming AffiliateWP integration.
-+ Fixed an issue with LifterLMS certificate background image that caused the wrong dimensions to be returned when outputting a LifterLMS certificate background image
-
-
-v2.6.2 - 2016-04-27
--------------------
-
-+ Fix class conflict in collapsible course outline widget template which caused some UX issues.
-+ Added new filters run during course & lesson sidebar registration to allow customization of LifterLMS sidebars
- + `lifterlms_register_course_sidebar`
- + `lifterlms_register_lesson_sidebar`
-+ Removed a stray logging function.
-+ Cleaned up some undefined variable warnings & notices on the quiz summary template
-+ Fixed an issue appearing when registering users did not submit the optional phone number which caused a PHP notice
-+ LifterLMS Orders generated by WooCommerce will now have a payment method of "WooCommerce". This also addresses an undefined notice produced during WooCommerce order completion because a LifterLMS Payment Method wasn't being defined.
-
-
-v2.6.1 - 2016-04-26
--------------------
-
-+ Fix class conflict in collapsible course outline widget template which caused some UX issues.
-
-
-v2.6.0 - 2016-04-25
--------------------
-
-##### Collapsible Course Outline Widget
-
-+ By request we've added an option to make your course outline widgets collapsible!
-+ View feature [Documentation](https://lifterlms.com/docs/course-syllabus-widget/)
-+ New translations available related to feature. I think it's 4 strings.
-
-##### Bug Fixes
-
-+ Removed an unused CSS selector that caused some issues on the admin panel. This resolves an issue identified with the Page Builder by SiteOrigin plugin. The selector was very generic (`.title`) and may have caused issues with other themes or plugins using that class.
-+ Resolved an issue that prevented post update, save, and publishing messages for core post types (posts, pages) from displaying properly.
-
-
-v2.5.1 - 2016-04-22
--------------------
-
-+ Fixed session handler initialization as it was being initialized prior to user data availability.
-+ Staged `LLMS_Language` class for deprecation in favor of WordPress translation functions `__()`, `_e()`, etc... **If you're a developer you'll start seeing warning's on screen or in your logs if you're using this function, it will be completely removed in the next MAJOR release (3.0.0)**
-+ Added a new function to handle the deprecation warning above (`llms_deprecated_function`) and now that we have this function we'll start deprecating all the things. Just kidding, or am I?
-+ This gives translators access to 69 new strings that were previously untranslatable! However, this number might be inaccurate +/- 5 strings. I only counted it once and I don't feel like the exact number is important enough for a recount to ensure accuracy. /shrug
-
-
-v2.5.0 - 2016-04-15
--------------------
-
-**Admin Panel Order Table Updates**
-
-+ Several visual improvements to the table
-+ Exposed the following fields on the table
- + Order number
- + Customer name (with a link to their WP profile)
- + Customer email (mailto link)
- + Payment gateway used (this is filterable per gateway as well so gateways can improve the functionality here in the future)
-+ Added a link to the product edit page from the product column
-+ Free orders will now display as "Free" as opposed to {currency}0.00
-+ Removed the not-so-useful "Order" column which was a long ugly string of data that was displayed in other columns already
-+ Removed the "Password Protected" flag since *all* orders are always automatically password protected for added security. This flag distracts from the interface so we've removed it. Orders _are_ still password protected though.
-+ Numerous strings that were previously not translatable have been made translatable on this screen
-+ A few new strings that previously didn't exist are now available for translation
-
-**Fixes and other small changes**
-
-+ Fixed a translation issue on the LifterLMS menu that we thought we fixed in the last release but have now really fixed (probably).
-+ Fixed a few small issues with engagements as they related to external engagements triggered by other plugins and LifterLMS extensions.
-+ Tired of seeing a banner for a plugin you've already installed? We have your back! The general settings area will now only display banners for plugins that aren't installed.
-+ Fixed various javascript issues, mostly removed `console.log()` statements.
-+ Fixed a spelling error on the membership admin panel settings screen
-
-
-v2.4.1 - 2016-04-07
--------------------
-
-+ Tested and compatible with WordPress 4.5 Release Candidate.
-+ Fixed a pagination issue related to updates to the quiz builder from 2.4.0 which would cause results to return incorrect results on the last page of paginated results in the "Add Question" dropdown.
-+ Added translation functions to LifterLMS Menu Items. Resolves an issue where translated LifterLMS installations might not see all the menu items under the LifterLMS Icon.
-+ Italian translation updates courtesy of [@AndreaBarghigiani](https://github.com/AndreaBarghigiani)
-+ On some themes the "Next Lesson" button was displayed while quizzes were being taken. We now *always* hide the next lesson button when a quiz is being taken.
-+ Adjusted some static functions to be non static in `class.llms.post-types.php`
-+ Added a function to ensure support for post thumbnails on LifterLMS custom post types
-+ If a user views a course that is available to them because it belongs to a membership level they are a member of, course pricing information will no longer be visible. This addresses a confusing user experience issue. Previously it _appeared_ like payment for a course was still required even though it really wasn't.
-+ Fixed undefined variable warning on quiz summary screen
-+ Resolve an issue with quiz timer that caused issues on time display if the time limit was set to a fraction of a minute (eg 1.5 minutes)
-+ resolved an undefined variable warning resulting from courses still holding a reference to a membership after the membership has been deleted or trashed
-
-
-v2.4.0 - 2016-03-29
--------------------
-
-##### Performance Improvements on the LifterLMS Quiz Builder
-
-+ Completely rewrote Javascript associated with building a LifterLMS Quiz. Our users have been identifying some performance issues and slowness when working with larger databases. We've refactored the Javascript and our related database queries to allow faster quiz building and fewer timeouts when working in the quiz builder.
-+ Fixed a bunch of undefined variables that would produce PHP warnings in various quiz templates
-+ Added validation to quiz questions on the admin panel to prevent the same question from being added to a quiz multiple times.
-+ Fixed an issue that prevented quizzes from correctly marking the lesson as completed when the quiz was passed.
-+ Added three new actions now available for developers to hook into.
- + `lifterlms_quiz_completed` called upon completion of a quiz (regardless of grade)
- + `lifterlms_quiz_passed` called when a quiz is completed with a passing grade
- + `lifterlms_quiz_failed` called when a quiz is completed with a failing grade
-+ Course Progress and Course Syllabus shortcodes (and widgets) now work on Quiz pages
-+ Completed Metabox refactor for the LifterLMS Quiz post type and removed `LLMS_Meta_Box_Quiz_General` class. All functions now exist in `LLMS_Meta_Box_Quiz`
-+ Added validation to the Quiz general settings
- + Cannot only enter numbers in attempts, percentage, and time limit fields
- + Cannot enter a negative number or a number greater than 100 in the percentage field
-+ Removed the membership restriction metabox from quiz admin and question admin screens
-
-##### Other fixes
-
-+ Fixed an issue that caused multiple certificates awarded for the same Course or Lesson to not properly display on the My Account page.
-+ Removed an event bound to the publishing of a LifterLMS Question that called a function that didn't exist and caused a Javascript error on the console (but didn't actually cause any problems)
-+ Removed a warning message that would display on sidebars when a shortcode was being displayed in a place that it couldn't function. We now simply don't display any content if the shortcode can't function.
-+ Resolved an issue that prevent users from "purchasing" products when using a 100% coupon and the Stripe payment gateway. Users experiencing this issue should also update to Stripe 3.0.1.
-+ Fixed an AJAX related issue that was incompatible with PHP7
-+ Added the ability to have a "max" value on LifterLMS Admin Metabox number fields
-
-
-v2.3.0 - 2016-03-24
--------------------
-
-##### Engagements Refactoring (lots of bugfixes, performance improvements, more hook & filter friendly)
-
-+ We've completely rewritten the LifterLMS Engagement Handler methods (`class LLMS_Engagements`) and added some new engagement actions.
-+ The rewrite unifies engagement handling into one function that can be easily hooked into by plugin and theme developers.
-+ We've moved any engagement related data out of the main `LifterLMS` class
-+ Fixed the broken engagement delay functionality which now runs of `wp_schedule_single_event`. This makes the function more reliable and also keeps it within the traditional WordPress architecture.
-+ Added an additional check before sending emails or triggering any engagements that will prevent the achievement from being awarded or the email from being sent if the post is in not published. This fixes an issue that caused emails in the trash from still being emailed.
-+ Removed the unused `LLMS_Engagements` class and file
-+ Added two new engagement trigger events "Membership Purchased" and "Course Purchased"
-+ Deprecated actions -- Removes some redundancy because the triggering actions (`lifterlms_course_completed` triggered the notification action, instead `lifterlms_course_completed` simply triggers the engagement now).
- + `lifterlms_lesson_completed_notification`
- + `lifterlms_section_completed_notification`
- + `lifterlms_course_completed_notification`
- + `lifterlms_course_track_completed_notification`
- + `lifterlms_course_completed_notification`
- + `lifterlms_user_purchased_product_notification`
- + `lifterlms_created_person_notification`
-
-##### Bug and Issue fixes
-
-+ Adjusted the size of the LifterLMS Admin Menu Icon. It was super big because of, perhaps, some overcompensation. It caused an issue on Gravity Forms admin pages for some reason (we didn't ever determine why) but we've resolved it by using an appropriately sized icon.
-+ Fixed a CSS issue that caused some weirdness on the course archive page on mobile devices
-+ Fixed an issue with automated membership expirations
-+ Fixed a function that should have been called statically in `LLMS_Ajax` class
-+ Fixed a ton of issues related to the triggering of engagements and cleaned up a lot of classes and functions associated with them.
-+ Properly instantiate `LifterLMS` singleton via LLMS() function and prevent direct instantiation of the class via `new LifterLMS()`.
-+ Removed the deprecated 'class.llms.email.person.new.php' file as it was rendered useless a long time ago and caused some duplicate emails.
-
-
-v2.2.3 - 2016-03-15
--------------------
-
-##### Translations
-
-+ Added translation functions around quite a few untranslated strings. Thanks to the team at [Netzstrategen](http://netzstrategen.com)
-+ Added German translation .mo and .po files again thanks to the team at [Netzstrategen](http://netzstrategen.com)
-
-##### Student Enrollment Functions
-
-We've refactored a bit of our code related to how to programmatically enroll a student in a course or membership during registration and purchase.
-
-A new class `LLMS_Student` makes working with a LifterLMS student (user) a bit easier. We'll begin exposing user meta data through this class as we continue to improve the usability of the codebase for other developers.
-
-We've also created a simple enrollment function `llms_enroll_student()` which enables programmatic enrollment to LifterLMS courses or memberships. This was previously handled in a pretty schizophrenic manner and this unifies various ways of enrollment into one clean function. All enrollment moving forward will use this functions.
-
-The enrollment function calls a new action as well as calling existing enrollment-related actions:
-
-+ `before_llms_user_enrollment` - called immediately prior to beginning the user enrollment function
-+ `llms_user_enrolled_in_course` (previously existing)
-+ `llms_user_added_to_membership_level` (previously existing)
-
-This also addresses an issue that prevented the `llms_user_enrolled_in_course` action from being called when a user was auto-enrolled in a course because they joined a membership level that included auto-enrollment in one or more courses.
-
-##### Bug and Issue fixes
-
-+ Fixed an inconsistency in the way membership IDs were being saved to the postmeta table that would cause courses to not *appear* restricted on the Membership Enrollment tab, even though they were actually restricted and functioning correctly.
-+ New lines are now preserved in the quiz question clarification text areas, thanks to @atimmer
-+ Escape HTML in the quiz question description fields on the admin panel to allow outputting html without rendering it, thanks @atimmer
-+ Fixed an issue related to the outputting of restricted course and membership content which caused errors on certain themes
-+ added a clearfix to the `.llms-lesson-preview` element on the course syllabus template
-+ Removed the `class.llms.person.handler.php` file as it wasn't actually being used by anything anywhere and contained no functions
-+ Removed some unused and deprecated class functions from the LLMS Student Metabox class
-+ Fixed an undefined javascript error resulting from code cleanup in 2.2.2. This issue prevented Vouchers from being published. The code has been further cleaned.
-
-
-v2.2.2 - 2016-03-15
--------------------
-
-##### One step closer to a public GitHub repository
-
-We've made a massive syntactical update to almost every file in the codebase for a (finally) unified and clearly defined coding standard. This puts us one step closer to beginning to open our GitHub repo publicly and accepting pull requests and contributions from developers everywhere.
-
-Okay, we haven't exactly _clearly_ defined it yet. We're working off a modified version of the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/best-practices/coding-standards/php/).
-
-Notable exceptions are related to file names because Thomas Levy didn't have the energy to rename a bunch of files as well as ignoring the Yoda Conditions standards. We'll be fixing these deviations in the future.
-
-##### Quizzes
-
-+ Created new time calculation and humanizing functions related to the display of quiz time on quiz results pages
-+ Quizzes will now display hours, minutes, and seconds depending on the time it took to take the quiz
-+ Timing calculations are more accurate and quizzes that are completed in less than 60 seconds will not bug out and display incredibly long lengths
-+ Resolved an issue that occasionally prevented quiz data from saving during the last question causing the quiz to hang in an uncompletable state
-+ Quiz questions now have a default point value of 1, thanks @atimmer
-+ Quiz question answers now accept valid HTML as per `wp_kses_post`, thanks again to @atimmer
-
-##### Translations
-
-+ Thanks to @AndreaBarghigiani and the team at [codeat](http://codeat.co/) LifterLMS now ships with Italian language files!
-
-##### Issue and bug resolutions
-
-+ Fixed a restriction issue that would happen when individual lessons were restricted to a membership level
-+ Fixed an issue with the `[lifterlms_my_account]` shortcode that was preventing the shortcode from working on the Divi theme.
-+ Engagements will now only be triggered if they are "Published". Resolves an issue where draft or trashed engagements were still firing.
-+ Fixed CSS overflow on LifterLMS Meta boxes. Fixes an issue where select boxes would be hidden inside a metabox.
-+ Changed the ConvertKit extension banner image on the LifterLMS general settings page and replaced added a link to the extension now that it's available.
-+ Added a link to the new ConvertKit extension to the .org readme
-+ When restricting an entire site to a membership level the page selected as the "Terms and Conditions" page in LifterLMS settings will automatically bypass Membership restriction settings. This will allow your unregistered users to actually read the T&C that they're confirming during registration.
-+ CSS fix for `has-icon` class on course syllabus
-+ Fixed a PHP warning that displayed when purchasing a membership with no auto-enrollment courses
-+ Fixed an undefined variable warning in the WooCommerce integration class
-+ Fixed a few templating issues related to certificates
-+ Added a few new CSS rules that should make certificates more compatible across various themes
-+ Added a css class to LifterLMS Next Lesson buttons, `llms-next-lesson`
-+ Updated the scheduled event name for cleaning up LifterLMS session data from the WP database. It had a conflicting name with the scheduled event for expiring LifterLMS memberships.
-
-
-v2.2.1 - 2016-03-07
--------------------
-
-+ Added a few actions to the `class.llms.voucher.php` class.
-
-
-v2.2.0 - 2016-03-04
--------------------
-
-##### Translations
-
-+ We've updated our .pot file for the first time in quite a while. We're really sorry for de-emphasizing translation. An updated .pot file will now accompany each version of LifterLMS whenever a translatable string is adjusted or when a new string is added.
-+ We've also made it easier to include custom translations. Read our [Translation Guide](https://lifterlms.readme.io/docs/getting-started-with-translation).
-
-##### Certificate Background Images
-
-_We've completely rewritten the certificates template (but it's all backwards compatible)._
-
-+ New filters are available to make customizing the certificate template easier for developers. All new filters are documented at [https://lifterlms.readme.io/docs/functions-certificates](https://lifterlms.readme.io/docs/functions-certificates).
-+ A new WordPress Image Size is now available and will be used for generating the image used by default when uploading certificates to the media library. Fore more information on these new settings visit [https://lifterlms.com/docs/certificate-background-image-sizes/](https://lifterlms.com/docs/certificate-background-image-sizes/).
-
-##### Course and Membership Pricing & Sales
-
-+ Sale price start and end date are now completely optional.
- + Provide neither a start date nor an end date to have a sale run indefinitely
- + Provide a start date with no end date to have a sale start at a pre-determined time with no pre-determined ending
- + Provide an end date with no start date to have a sale end a a pre-determined date but start immediately
- + Provide a start date and an end date to have a sale run for a pre-determined period of time
-+ Optimized the `LLMS_Product` class to provide more reliable and extendable use of the class
-+ The templates related to pricing functions have been refactored. Affected templates include: "templates/course/price.php", "templates/loop/price.php", "templates/membership/price.php"
-+ Many people complained about the size of the `.llms-price` element on course and membership tiles on loop pages. We removed the inflated size and will now default to your theme for sizing. You selector remains the same if you wish to customize the size of the price text.
-
-##### Coupon Updates
-
-+ Coupons can (finally) be removed after being applied!
-+ Coupons can now be restricted to specific courses and/or memberships
-+ Percentage based coupons can no longer be created with a value larger than 100%
-+ Added numeric restrictions to usage and coupon amount fields on the admin panel
-+ Fixed a programmatic error that prevented product restrictions from being entirely removed
-+ Fixed a few instances where hardcoded a US Dollar symbol ($) where a dynamic currency symbol should have been displayed.
-
-##### Wow Bad Syntax, Very Typo, Such Grammar, So Undefined
-
-+ Fixed a typo in filter associated with modifying the registration of the lesson post type (`lifterlms_register_post_type_lesson`)
-+ Fixed a grammatical error in a Membership restriction message
-+ Fixed a syntax error in "/templates/course/outline-list-small.php" that prevented the `done` CSS class from being properly applied to completed lessons
-+ Fixed a few typos and grammatical errors on the Course and Membership settings metaboxes
-+ Fixed an undefined variable in "templates/course/syllabus.php"
-+ Fixed an issue on the system report that prevented the "Courses Page" from being reported properly
-+ Fixed an issue that caused PHP warnings on the admin panel for students or WP users with no LifterLMS menu permissions
-+ Fixed an installation warning caused by a reference to an undefined class variable
-+ Fixed an HTML character encoding issue that caused `–` to display on the admin panel when viewing LifterLMS Orders
-+ Fixed an undefined variable found during engagement triggering for non-email engagements.
-
-##### Additional, less exciting updates
-
-+ Added input type restrictions to course & membership price fields.
-+ The "Emails" LifterLMS Settings Tab has been renamed "Engagements." All Email settings are found under this tab as well as some new settings related to other kinds of LifterLMS engagements.
-+ Added `the_content` filter to the content of emails sent by LifterLMS
-+ Fixed some CSS issues on Voucher screens
-+ Updated Courses settings retrieval function to retrieve the correct "shop" page id
-+ Added translation functions to voucher export meta box class
-+ Vouchers Export metabox will only allow export after a voucher has been published. This prevents an issue caused by attempting to export voucher codes before they were saved in the database via the publish / save action.
-+ Vouchers can no longer be saved with a use of "0"
-+ added a CSS class for various syllabus outputs that notes that the lesson has an icon. Previously CSS relied on "is-complete" to output styles for having an icon but with the addition of placeholders the "is-complete" is used only to note that the lesson is completed and "has-icon" is a more semantic class that applies to both complete and incomplete lessons with an icon.
-+ Removed the membership restriction metabox from some post types where it shouldn't have been displaying.
-+ admin select fields now have an option `allow_null` (default to "true") which can be set to `false` in order to prevent the output of the default "None" option
-
-
-v2.1.1 - 2016-02-15
--------------------
-
-##### System Report
-
-+ A new LifterLMS Admin Page is available which reports information about various server, WordPress, and LifterLMS settings that will help expedite support requests.
-+ More information about the system report is available at [https://lifterlms.com/docs/how-to-use-the-lifterlms-system-report/](https://lifterlms.com/docs/how-to-use-the-lifterlms-system-report/)
-
-##### Additional Updates
-
-+ Fixed a javascript issue which prevented users from saving vouchers
-+ Cleaned up formatting in a large number of included PHP files
-
-
-v2.0.5 - 2016-02-15
--------------------
-
-+ PayPal requests now using HTTP Version 1.1 in preparation for June 2016 [TLS 1.2 and HTTP/1.1 Updates](https://www.paypal-knowledge.com/infocenter/index?page=content&widgetview=true&id=FAQ1914&viewlocale=en_US). This resolves user's inability to begin PayPal checkout when using Sandbox mode.
-+ Updated deprecated function opt out to run off a constant that can be defined in `wp-config.php` instead of using a filter that is hard to use in the way that it is intended.
-
-
-v2.0.4 - 2016-02-15
--------------------
-
-+ Fixed a typo on the `class_exists` check in the deprecated functions file
-+ added a filter so that progressive users can opt out of loading the deprecated functions file
-
-
-v2.0.3 - 2016-02-12
--------------------
-
-+ Removed an unused quiz stub
-
-
-v2.0.2 - 2016-02-11
--------------------
-
-+ Bugfix: removed a progressive syntax array that caused fatal errors on older versions of PHP
-
-
-v2.0.1 - 2016-02-11
--------------------
-
-##### Updated General Settings Screen
-
-+ Improved the general settings interface to be more visually appealing and to provide some ad space to alert customers to other LifterLMS products and information.
-+ Moved Currency options to the Checkout settings screen
-
-##### Bug Fixes
-
-+ Properly initialized jQuery on the vouchers metabox admin scripts
-+ removed some php shortcut echos (`= $var; ?>`)
-+ Resolve issue where courses that are available with a membership or on it's own outside of the membership would prevent users from accessing content if they were not a member.
-+ Fixed a few files where undefined variables were being referenced and generating php notices
-+ removed an call to a WordPress core function that has never existed. Not sure what we were thinking there...
-
-###### Enhancements
-
-+ Updated CSS to provide better course syllabus layout on smaller screens
-+ Added validation to prevent against duplicate voucher code creation
-
-
-v2.0.0 - 2016-02-04
--------------------
-
-##### Auto-advancing lessons
-
-+ We've heard your feedback and added a new global course option which will auto-advance a student to the next lesson upon lesson completion.
-
-##### Bug Fixes
-
-+ Added spaces between numbers and "of" on the counter for course syllabus templates
-+ Removed a template hook that was creating duplicate lesson thumbnails on quite a few themes
-
-##### Membership Admin Improvements
-
-Visit the "Enrollment" tab on any membership to see some new additions to make managing your memberships easier.
-
-+ You can now add courses to and remove courses from a Membership from the Membership itself
-+ You can now opt to automatically enroll students in a course (or multiple courses) when they sign up for a membership by checking "Auto Enroll" next to the course on the Membership enrollment tab
-
-##### Student Enrollment & Removal on Courses Admin Screen
-
-We've updated the Students tab interface for performance and usability!
-
-+ AJAX enabled searching by student name and or email
-+ Increased performance for course page load by only calling student information when needed. This resolves a bug identified by users with large user databases and/or low-powered servers.
-+ Allow for addition or removal of several students at a time.
-
-##### Syllabus Template
-
-+ Added a Course setting to optionally enable Lesson Thumbnails on the Course Syllabus
-+ Added a Course setting Display greyed out lesson completion checkmark icons on lessons not competed in the course syllabus
-+ Reworded CSS on the course syllabus to rely on floats rather than absolute positioning, should allow for more robust customization with less frustration
-+ Refactored the syllabus template at "templates/course/syllabus.php" for better performance and readability
-
-##### Updates and enhancements
-
-+ User email is now displayed on the "Students" table on student analytics screens
-+ Membership now has it's own admin menu
-+ Reordered the LifterLMS admin menu and submenu items
-+ Removed membership specific taxonomies from courses
-+ Removed course specific taxonomies from memberships
-+ Coupon code is now a required field when creating a coupon
-+ "Humbled" the metabox on all post types that restricts the post to a membership. The metabox would previously gain priority over the WordPress publishing actions metabox. The priority has been reduced to "default" and will to fall into line with all other metaboxes on the screen and appear based on registration priority. If you can't find the metabox, SCROLL DOWN! If you want to put it back up on the top, you can simply drag it up there and WordPress will save your preference.
-
-##### Deprecated Classes
-
-We've added a "deprecated" file which holds a few stubs for classes and functions deprecated below as to prevent fatal errors. The functions and classes in the deprecated class are classes which we know are being utilized by approved LifterLMS extensions and will allow users to upgrade LifterLMS without upgrade extensions without breaking their websites!
-
-+ `LLMS_Activate` which as previously used to activate the plugin for updates via the LifterLMS Update Server and is no longer required.
-+ PUC (plugin update checker) Library has been completely removed as it is no longer required for plugin updates.
-+ `LLMS_Analytics_Dashboard` was removed as it was a stub that was never used and shouldn't have ever been released as a part of the LifterLMS codebase. I can't believe no one reported this bug!
-
-##### Deprecated Functions
-
-+ `lifterlms_template_section_syllabus()`
-
-**The following are officially deprecated and removed to prevent WooCommerce compatibility conflicts**
-
-+ `is_shop()` replaced by `is_llms_shop()`
-+ `is_account_page()` replaced by `is_llms_account_page()`
-+ `is_checkout()` replaced by `is_llms_checkot()`
-
-##### Deprecated Templates
-
-+ templates/course/section_syllabus.php
-
-##### New Account Dashboard Filters
-
-*[View documentation for more information](https://lifterlms.readme.io/docs/filters-account)*
-
-+ `lifterlms_account_greeting`
-+ `lifterlms_my_courses_title`
-+ `lifterlms_my_courses_enrollment_status_html`
-+ `lifterlms_my_courses_start_date_html`
-+ `lifterlms_my_courses_course_button_text`
-+ `lifterlms_my_certificates_title`
-
-##### New Checkout Page Filters:
-
-*[View documentation for more information](https://lifterlms.readme.io/docs/filters-checkout)*
-
-+ `lifterlms_checkout_user_logged_in_output`
-+ `lifterlms_checkout_user_not_logged_in_output`
-
-##### New Course Filters:
-
-*[View documentation for more information](https://lifterlms.readme.io/docs/filters-course)*
-
-+ `lifterlms_product_purchase_account_redirect`
-+ `lifterlms_product_purchase_redirect_membership_required`
-+ `lifterlms_product_purchase_checkout_redirect`
-+ `lifterlms_product_purchase_membership_redirect`
-+ `lifterlms_lesson_complete_icon`
-
-
-v1.5.0 - 2016-01-22
--------------------
-
-##### WooCommerce Integration Enhancements
-
-__NOTE: The following enhancements only apply when the WooCommerce Integration is enabled__
-
-**Always redirect to the WooCommerce Cart when a SKU Matched Product can be found**
-
-+ LifterLMS Products (courses and memberships) which are SKU matched to a WooCommerce product will now automatically add the related WooCommerce product to the WooCommerce shopping cart and then automatically redirect the visitor to the WooCommerce cart when the visitor attempts to enroll in a course or membership from the LifterLMS course or membership page.
-+ If no WooCommerce product is found via a SKU match, the user will proceed to the LifterLMS checkout.
-+ This will enable you to determine which Cart you want a user to use on a product by product basis. You may sell certain courses via WooCommerce and others via LifterLMS (should you choose to do so).
-
-**Multiple Item Checkout**
-
-+ When a WooCommerce order is complete user's will now be automatically enrolled in **all** courses and/or memberships in the WooCommerce order. This improves upon a previously limitation that would only allow WooCommerce checkout with one LifterLMS product at a time.
-+ The products in the order will be intelligently SKU matched to LifterLMS Courses or Memberships.
-+ You may also mix and match between WooCommerce products matched to LifterLMS products and those which are not matched to LifterLMS products. For example, your customers may now buy a Course via SKU matching as well as a T-Shirt that is not matched to a LifterLMS course via a SKU.
-
-##### Other Fixes and improvements
-
-+ Fixed a bug that caused quiz results to display for users who had never taken the quiz.
-+ Added Wistia as an oEmbed provider to fix an issue related to default oembed handling in WordPress 4.4.
-+ added a `.cc_cvv` class that mimics the existing `#cc_cvv` styles to allow gateway extensions to change the ID of the field in their credit card forms
-+ Added support for new 1.4.5 capability fixes to be also be reflected under the "+New" menu item in the WP Admin Bar. There are no changes to the filters, the capability filters will simply also remove restricted post types from the admin bar now (as they should).
-+ Tested and compatible up to WordPress 4.4.1
-
-##### Deprecations
-
-**The following functions have been staged for deprecation in LifterLMS 2.0!**
-
-+ Setup the `is_account_page()` function to be replaced by `is_llms_account_page()` function. The original causes conflicts when WooCommerce is installed as WooCommerce includes a core function by the same name. All references to `is_account_page()` in LifterLMS have been removed and the original has been left to prevent issues with developers currently relying on the LifterLMS version of the function.
-+ Setup the `is_checkout()` function to be replaced by `is_llms_checkout()` function. The original causes conflicts when WooCommerce is installed as WooCommerce includes a core function by the same name. All references to `is_checkout()` in LifterLMS have been removed and the original has been left to prevent issues with developers currently relying on the LifterLMS version of the function.
-
-
-v1.4.5 - 2016-01-13
--------------------
-
-+ Significant improvements to LifterLMS admin permissions as well as a hardening of permissions. Previously LifterLMS admin screens and menus were available to any users with `edit_posts` capabilities. This has been changed to `manage_options`. Filters for all screens and menus have been added with this release. If you're site currently relies on users with `edit_posts` to be able to access LifterLMS settings and analytics screens you must utilize these new filters in order to maintain their access. Please see full documentation on the new filters at [https://lifterlms.readme.io/docs/filters-admin-menu-and-screen-permissions](https://lifterlms.readme.io/docs/filters-admin-menu-and-screen-permissions). **Please consider testing your changes outside of production before updating to LifterLMS 1.4.5 in production.**
-+ Allow "Payment Method" to be translated on the "Confirm Payment" screen
-+ Allow the name of the payment gateway to be filtered on the "Confirm Payment" screen
-+ Added pagination support to lifterlms membership archive pages
-+ Fixed a bug related to some required global variables for quizzes and lessons being incorrectly set on certain hosts
-+ updated readme file to remove incomplete documentation
-+ Added Chosen multi-select options to admin panel metaboxes (settings and posts)
-+ Added two new actions that developers can hook into:
- + `llms_user_enrolled_in_course`, called when users are enrolled in a course. Usage details available [here](https://lifterlms.readme.io/docs/actions-user#llms_user_enrolled_in_course).
- + `llms_user_added_to_membership_level`, called when users are added to a membership level. Usage details available [here](https://lifterlms.readme.io/docs/actions-user#llms_user_added_to_membership_level).
-
-
-v1.4.4 - 2015-12-21
--------------------
-
-##### Updates
-
-+ My account page can now (optionally) display a list of memberships a student is currently enrolled in
-+ Student analytics on the admin panel display student's Memberships
-+ Student analytics on the admin panel will now display student's progress through courses in addition to their current enrollment status.
-+ Custom taxonomy archive templates for Course tags, categories, tracks, and difficulties now exist and properly function.
-+ Custom taxonomy archive templates for Membership categories and tags now exist and properly function.
-+ Added the `[lifterlms_memberships]` shortcode which was documented but never implemented. Details on usage available at [https://lifterlms.readme.io/docs/short-codes#memberships-lifterlms_memberships](https://lifterlms.readme.io/docs/short-codes#memberships-lifterlms_memberships)
-+ Added basic styles to LifterLMS pagination HTML elements (elements with class `.llms-pagination`) which formerly had no associated CSS.
-
-##### Deprecations
-
-+ Setup the `is_shop()` function to be replaced by `is_llms_shop()` function. The original causes conflicts when WooCommerce is installed as WooCommerce includes a core function by the same name. All references to `is_shop()` in LifterLMS have been removed and the original has been left to prevent issues with developers currently relying on the LifterLMS version of the function. It *will* be removed in the next major update (2.0) and will be noted as an officially deprecated feature at that time.
-
-##### Bug fixes
-
-+ Fixed pagination issues when using the `[lifterlms_courses]` shortcode
-+ Fixed an issue with the `is_shop()` function that prevented courses per page option from functioning properly on the default course archive page
-+ Student analytics profile on admin panel will display the correct number of memberships the student is enrolled in.
-+ Fixed a small CSS issue that caused extra white space to be displayed above Course or Membership tiles on archive pages when using the WordPress Twentyfifteen default theme
-
-##### Miscellaneous
-
-+ Account settings screen displays the correct title ("Account Settings" it previously said "Archive Settings")
-+ Made language changes to the LifterLMS settings intro screen copy
-+ Added link to CourseClinic on settings intro screen
-+ Added link to LifterLMS documentation on the settings intro screen
-
-
-v1.4.3 - 2015-12-11
--------------------
-
-+ Fixed an issue that could prevent some older servers from being able to run LifterLMS
-
-
-v1.4.2 - 2015-12-10
--------------------
-
-+ Tested and compatible with WordPress version 4.4
-+ BugFixes: fixed issue in `llms_featured_img()` that was preventing the `$size` variable from being passed to the WP core function being utilized.
-+ BugFixes: correctly handling conflicts with Plugin Update library
-
-
-v1.4.1 - 2015-12-02
--------------------
-+ Feature: Custom single price text - Display custom text for the single price on the courses and course page. Custom field does not require a single payment price be set. IE: Free!
-+ Feature: Custom Purchase Course Button Text Option. Change the text of the Take This Course button in Settings->Courses.
-+ Feature: New Become A Member button on courses that are restricted to memberships.
-+ Feature: Custom Become A Member Text Option. Change the text of the become a member button in Settings->Courses.
-+ Feature: Paypal Debug Mode. Enable debug mode in Settings->Gateways to view responses from Paypal API when errors occur.
-+ Updates: Updated support links in Settings->General.
-+ Updates: added minor styling to course page to increase margin and padding for some themes.
-+ Updates: Achievement content now available to pull into custom templates. The Achievement content is not by default displayed but can now be used in custom templates.
-+ BugFixes: Resolved issue with no default price selected at checkout when only recurring option existed.
-+ BugFixes: Lesson prerequisite now alert the user and provide a link to redirect the user to the next required lesson in the course.
-+ BugFixes: Paypal errors now return error message instead of white screen when Paypal API fails.
-+ BugFixes: Corrected JavaScript error with modals on course edit page in Internet Explorer 11.
-
-
-v1.4.0 - 2015-10-29
--------------------
-+ Feature: Free lessons - demo lessons that can be taken at any time by any user
-+ Feature: Guest lessons - demo lessons that can be taken by a non-logged in user
-+ Feature: Random quiz question - quiz questions can now be set to be in user set order or random order
-+ Updates: Automatically registers appropriate sidebars for Genesis theme
-+ Updates: Backend file cleanup
-+ Updates: Text cleanup
-+ Updates: Adds greater localization support (more strings to translate! yay!)
-+ Updates: Cleans up some unnecessary console.log() calls
-+ Updates: Removes mass of commented out code (cleaner reading)
-+ Updates: 'Next Lesson' button added after successful completion of quiz
-+ Updates: 'Next Lesson' button at bottom of lesson properly gets starting lesson of next section at the end of the previous section
-+ Updates: 'Previous Lesson' button at bottom of lesson will now properly get last lesson of previous section (if applicable)
-+ Updates: Move Registration Form to global templates to allow users to disable registration on login page but use registration form on custom page.
-+ BugFixes: WordPress pages are now properly restricted by memberships
-+ BugFixes: Fixes bug that caused order screen to act up if user was deleted
-+ BugFixes: Resolves nasty little bug that caused syllabus numbers to be out of whack
-+ BugFixes: Resolved error with WooCommerce integration where courses would not always register the user
-+ BugFixes: Corrected CSS conflict with Bridge theme settings page
-
-
-v1.3.10 - 2015-10-15
---------------------
-+ Updates: Clarifies some prerequisite text
-+ Updates: Quiz questions are now randomized!
-+ Updates: Fixes small CSS issue
-+ BugFixes: Resolves fatal errors with a small subset of premium themes
-
-
-v1.3.9 - 2015-10-5
-------------------
-+ BugFixes: Removes conflict with Yoast SEO
-+ BugFixes: Fixes CSS issues with box-sizing takeover
-+ Feature: New Settings Tile: Session Management. Found at LifterLMS->Settings->General.
-+ Feature: Clear User Session Tool. You can now clear all LifterLMS user session data from your site in LifterLMS->Settings->General
-+ Updates: Backend code cleanup
-
-
-v1.3.8 - 2015-10-02
--------------------
-+ BugFixes: Fixes Random error notices
-+ Updates: Updates email template handler
-
-
-v1.3.7 - 2015-09-25
--------------------
-+ Updates: Adds Spanish translation
-+ Updates: Adds new filter 'lifterlms_single_payment_text' to customize single payment string on checkout
-+ Updates: Student analytics now indicate which courses a student has completed
-+ BugFixes: Resolved security issue with WordPress searches and lessons
-+ BugFixes: Fixes analytics bug that potentially arises after a course is deleted
-
-
-v1.3.6 - 2015-09-18
--------------------
-+ BugFixes: Fixes pesky Zend Error that plagued some unfortunate victims
-+ BugFixes: Students can now be properly deleted from the course
-+ BugFixes: Fixes random class redeclaration error messages
-+ Updates: Adds new filter 'lifterlms_quiz_passed' to customize 'Passed' text after quiz
-+ Updates: Adds new filter 'lifterlms_quiz_failed' to customize 'Failed' text after quiz
-
-
-v1.3.5 - 2015-09-11
--------------------
-+ Revisions: Fixes typos
-+ Updates: Adds sidebar functionality to various themes
-
-
-v1.3.4 - 2015-09-04
--------------------
-+ BugFixes: Fixes bug with featured image on course page
-+ BugFixes: Fixes issue with lesson completed percentage on analytics page
-
-
-v1.3.3 - 2015-09-01
--------------------
-+ Updates: Removes deprecated plugin updater
-+ Updates: Adds Course Track prerequisite
-+ Updates: Various text fixes
-+ BugFixes: Fixes lesson name on prerequisite notification
-+ BugFixes: Fixes critical error with WordPress customizer
-
-
-v1.3.2 - 2015-08-30
--------------------
-+ Hotfix: resolves issues with sidebar shortcodes
-+ Updates: Text clarifications
-
-
-v1.3.1 - 2015-08-28
--------------------
-+ Hotfix: resolves issue with ajax url
-
-
-v1.3.0 - 2015-08-28
--------------------
-+ Improved popover behavior in course creation.
-+ BugFixing. Prevent multiple lesson and section form submission
-+ Fixed typos at backend quiz page
-+ Fixed check for update bug when plugin isn't properly activated.
-+ BugFixing, quiz post type should show author metabox
-+ Added course category filter to lifter_lms shortcode
-+ BugFixing, typo in [lifterlms_course_progress shortcode]
-+ BugFixing, Analytics shouldn't fetch students meta info from users were deleted.
-+ Adds in basic review functionality
-+ Updates plugin-updater to remedy PHP conflicts
-+ Fixes date bug in Analytics
-+ Cleans up jQuery console messages
-+ Adds in course tracks
-
-
-v1.2.8 - 2015-07-17
--------------------
-+ Updated Portuguese translation file
-+ Fixed issue where quiz score could not be equal to required grade.
-+ New Feature: Quiz Results Summary. Display the quiz results to the user on quiz completion.
-+ New feature: Clarification. Display information about correct and incorrect answers to users
-+ New Feature: Display correct answers to user on quiz completion
-+ Removed ability to add negative time limit to quiz
-+ New Membership feature: Make membership archive links go directly to checkout. Setting allows you to skip membership sales page and send users directly to registration and checkout.
-+ Sidebar support for prototype theme
-+ Sidebar support for X theme
-+ Sidebar support for WooCanvas
-+ New Shortcode: [lifterlms_hide_content]: Use to restrict content on a page, course or lesson to a specific membership. Pass the post id of the membership you want to restrict the content to. Example: [lifterlms_hide_content membership="5"]
-+ New updates to gulp build process
-+ Class autoloading and LLMS namespace introduced for more efficient coding.
-
-
-v1.2.7 - 2015-06-05
--------------------
-+ Minor bug fix with lesson redirect to quiz
-+ Minor change to global Course object instantiation.
-+ Bug Fix: Remove student from course
-+ Bug Fix: Appearance Menus missing select field (THANKS ANDREA!)
-+ New Course Setting: Hide Course Outline on course page
-+ New Shortcode: [lifterlms_course_outline] - displays course outline with settings (see documentation)
-+ Membership metabox design update
-+ Certificate metabox design update
-+ Achievement metabox design update
-+ Lesson metabox design update
-+ Emails metabox design update
-+ Coupons metabox design update
-+ Update to certificate design (better alignment and theme functionality)
-+ Better theme sidebar support
-+ More awesome control for developers building new settings for LifterLMS
-+ Advanced filter system for metabox fields with finite control for 3rd party developers.
-+ Woocommerce conflict correction to archive templates
-+ Style updates to allow themes better control on design
-
-
-v1.2.6 - 2015-04-28
--------------------
-+ Corrected issue with lesson re-order on save
-+ corrected html formatting issue on purchase page
-+ corrected html formatting issue on course page
-
-
-v1.2.5 - 2015-04-23
--------------------
-+ Corrected excerpt to not pull in lesson navigation
-+ Modified metabox api for better extension integration
-+ Corrected issue with order not displaying all information if coupon was not applied to order
-
-
-v1.2.4 - 2015-04-22
--------------------
-+ Moved All Course metaboxes to global Course Options Metabox
-+ Move Enrolled and Non-Enrolled user wysiwyg post editors to Options Metabox
-+ Removed Course Syllabus metabox, Added Course Outline Metabox
-+ Set priority of Course Outline and Course Options Metabox to top
-+ Added ability to Create new section to Course Outline
-+ Added ability to Create new lesson to Course Outline
-+ Added ability to add existing Lesson to Course Outline
-+ Added Lesson duplicate functionality when adding lesson previously assigned to another course.
-+ Added ability to drag lessons between sections in Course Outline
-+ Added ability to edit Section Title in Course Outline
-+ Added ability to edit lesson title and excerpt in Course Outline
-+ Added New Style and Design for better usability to Course Outline
-+ Added Lesson Icon with tooltip to Course Outline: Prerequisite - shows if prerequisite exists and displays name of prerequisite
-+ Added Lesson Icon with tooltip to Course Outline: Quiz - shows if quiz is assigned to course and displays name of quiz
-+ Added Lesson Icon with tooltip to Course Outline: Drip Content - shows if drip days are set and # of days
-+ Added Lesson Icon with tooltip to Course Outline: Content - displays if lesson has content added.
-+ Added Course Outline Metabox to Lesson Post Editor: Allows you to assign lesson to section and view entire course tree. Links to Course and all other lessons in course.
-+ Style Update: backgrounds on frontend. Removed all references to white background on front end elements
-+ Corrected Restriction for course in past. Updated course in past message to display as Course ended instead of Course not available until.
-+ Added restriction message when user attempts to visit a restricted lesson.
-+ Updated course syllabus sidebar widget to not display lessons as links if user is not enrolled in course.
-+ Added ability to use Attribute Order for sorting Courses and Memberships on Archive pages.
-+ Added support for selling memberships with Woocommerce. LifterLMS now checks memberships for SKU matches in addition to Courses when products are purchased using WooCommerce.
-+ Added gulp for scss, js and svg management
-+ Added svg sprite and svg class for managing svg elements on front and backend.
-+ Added better language translation support for strings
-+ Refactored Ajax Classes for cleaner, faster development
-+ Refactored metabox build class for cleaner, faster development
-+ Refactored Course syllabus to reduce query size for larger, complex courses
-+ Added Handler classes for Lessons, Sections, Courses and Posts
-+ Refactored Course get / set methods to reduce database queries
-
-
-v1.2.3 - 2015-03-12
--------------------
-+ Achievement design and functionality updates
-+ Achievement shortcode added
-+ Better searching added to engagement screen
-+ Achievement bug fixes
-+ On screen error reporting added to activation for trouble shooting
-+ Custom engagement methods added to certificate, achievement and sections
-+ Corrected new user registration engagement bug
-+ LifterLMS access reduced from manage_options to edit_posts
-+ Filters added to analytics to allow custom development
-+ Engagement bug fix: Section and Lesson bug select
-+ Syllabus bug corrected: No longer displays lessons in section box if no sections exist.
-+ Removed depreciated achievement template
-+ Membership Bug fix: Membership restriction will now only display on single posts.
-
-
-v1.2.2 - 2015-02-23
--------------------
-+ Corrected drip content bug
-+ Added Ajax functionality to quiz
-+ rounded quiz grades
-+ Added quiz time limit setting to Quiz
-+ Added quiz timer to quiz, front end
-+ Quiz allowed attempts field now allows unlimited attempts
-+ Set Ajax lesson delete method to not return empty lesson value
-+ Set next and previous questions to display below quiz question
-+ Decoupled Single option select question type from quiz to allow for more question types
-+ Added Quiz time limit to display on Quiz page
-+ Added functionality to automatically complete quiz when quiz timer reaches 0
-+ Moved Quiz functionality methods from front end forms class to Quiz class
-
-v1.2.1 - 2015-02-19
--------------------
-+ Updated settings page theming
-+ Added Set up Quick Start Guide
-+ Added Plugin Deactivation Option
-+ Updated language POT file
-+ Added Portuguese language support. Thank you Fernando Cassino for the translation :)
-
-
-v1.2.0 - 2015-02-17
--------------------
-+ Admin Course Analytics Dashboard Page. View at LifterLMS->Analytics->Course
-+ Admin Sales Analytics Dashboard Page. View at LifterLMS->Analytics->Sales
-+ Admin Memberships Analytics Dashboard Page. View at LifterLMS->Analytics->Memberships
-+ Admin Students Search Page. View at LifterLMS->Students
-+ Admin Student Profile Page ( View user information related to courses and memberships )
-+ Lesson and Course Sidebar Widgets ( Syllabus, Course Progress )
-+ Course Syllabus: Lesson blocks greyed out. Clicking lesson displays message to take course.
-+ Misc. Front end bug fixes
-+ Misc. Admin bug fixes
-+ Course and Lesson prerequisites: Can no longer select a prerequisite without marking "Has Prerequisite"
-+ Admin CSS updates
-+ Better Session Management
-+ Number and Date formatting handled by separate classes to provide consistent date formats across system
-+ Zero dollar coupon management: Coupons that set total to 0 will bypass payment gateway, generate order and enroll users.
-+ Better coupon verification.
-+ Better third party payment gateway support. Third party gateway plugins are now easier to develop and integrate.
-+ User Registration: Phone Number Registration field option now available in Accounts settings page.
-
-
-v1.1.2 - 2014-12-18
--------------------
-+ Moved Sidebar registration from plugin install to init
-
-
-v1.1.1 - 2014-12-16
--------------------
-+ Added user registration settings to require users to agree to Terms and Conditions on user registration
-+ Added comments to all classes methods and functions
-+ Removed unused and depreciated methods
-+ Added Lesson and Course Sidebar Widget Areas
-+ Fixed bug with course capacity option
-+ Fixed bug with endpoint rewrite
-+ Added localization POT file and us_EN.po translation file
-
-
-v1.1.0 - 2014-12-08
--------------------
-+ Updated HTML / CSS on Registration form
-+ Added Coupon Creation
-+ Added Coupon support for checkout processing
-+ Added Credit Card Support processing support
-+ Added Form filters for external integration
-+ Added Form templates for external integration
-+ Added Account Setting: Require First and Last Name on registration
-+ Added Account Setting: Require Billing Address on registration
-+ Added Account Setting: Require users to validate email address (double entry)
-+ Added password validation (double entry) on user registration / account creation
-+ Added Quiz Question post type and associated metaboxes
-+ Added Quiz post type and associated metaboxes
-+ Added ability to assign a quiz to a lesson
-+ Added front end quiz functionality
-+ Added Course capacity (limit # of students)
-
-### User Admin Table
-+ Added Membership Custom Column that displays user's membership information
-+ Added "Last Login" custom column that displays user's last login date/time
-
-### User Roles
-+ Updated user role from "person" to "student"
-+ Added temporary migration function to transition any register users with "person" role to "student" role
-+ Added "Student" role install function
-
-
-### BUDDYPRESS
-+ BuddyPress Screen Permission Fix
-+ Added two additional screens to BuddyPress: Certificates and Achievements
-
-### MISC
-+ Added llms options for course archive pagination and added course archive page pagination template
-+ Added user statistics shortcode
-
-
-v1.0.5 - 2014-11-12
--------------------
-
-+ Fixed a mis-placed parenthesis in templates/course/lesson-navigation.php related to outputting excerpt in navigation option
-+ Changed theme override template directory from /llms to /lifterlms
-+ Update the position & name of the "My Courses" Menu in BuddyPress Compatibility file
-+ New meta_key _parent_section added for easier connection and quicker queries.
-+ Section sorting on course syllabus
-+ Edit links added to course syllabus
-+ Assign section to course and view associated lessons metabox added to sections
-+ Assign lesson to section and view associated lessons metabox added to lessons
-+ Assigned Course, Assigned Section, Prerequisite and Membership Required added to lesson edit grid
-+ Assigned Course added to section edit grid'
-+ New membership setting: Restrict Entire Site by Membership Level (allows site restriction to everything but membership purchase and account).
-+ Updated template overriding to check child & parent themes
-+ Updated template overriding to apply filters to directories to check for overrides to allow themes and plugins to add their own directories
-
-
-v1.0.4 - 2014-11-04
--------------------
-
-+ Templating bug fix
-+ Added shortcode and autop support to course and lesson content / excerpt
-
-
-v1.0.3 - 2014-11-04
--------------------
-
-+ Major Templating Update!
-+ Removed Course, Lesson and Membership single lesson templates.
-+ Course and Section content templates now filter through WP content
-
-
-v1.0.2 - 2014-10-31
--------------------
-
-+ Added lesson short description to previous lesson preview links -- it was rendering on "Next" but not "Previous"
-+ Added a class to course shop links wrapper to signify the course has been completed
-+ Removed an unnecessary CSS rule related to the progress bar
-
-
-v1.0.2 - 2014-10-30
--------------------
-
-+ Fixed SSL certificate issues when retrieving data from https://lifterlms.com
-+ Added rocket settings icon back into repo
-
-
-v1.0.1 - 2014-10-30
--------------------
-
-+ Updated activation endpoint url to point towards live server rather than dev
-
-
-v1.0.0 - 2014-10-30
--------------------
-
-+ Initial public release.
diff --git a/README.md b/README.md
deleted file mode 100644
index e89a5db1f6..0000000000
--- a/README.md
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
-
-LifterLMS is a powerful WordPress learning management system plugin that makes it easy to create, sell, and protect engaging online courses and training based membership websites.
-
-
-
-
-
-[![WordPress Plugin Version][img-wp-plugin]][link-wp-repo]
-[![WordPress Plugin Tested WP Version][img-wp-tested]][link-wp-repo]
-[![PHP Supported Version][img-php]][link-php]
-
-[![WordPress Plugin Rating][img-wp-rating]][link-wp-reviews]
-[![WordPress Plugin Downloads][img-wp-downloads]][link-wp-advanced]
-[![WordPress Plugin Active Installs][img-wp-installs]][link-wp-advanced]
-
-[![PHPUnit Tests][img-phpunit-tests]][link-phpunit-tests]
-[![PHPCS Coding Standards][img-phpcs-checks]][link-phpcs-checks]
-[![Code Climate maintainability][img-cc-maintainability]][link-cc]
-[![Code Climate test coverage][img-cc-coverage]][link-cc-coverage]
-
-[![Contributions Welcome][img-contributions-welcome]](.github/CONTRIBUTING.md)
-[![Contributors][img-contributors]](#contributors)
-[![Slack community][img-slack]][link-slack]
-
-
-
-
-
-Welcome to the LifterLMS GitHub repository. This repository serves as the core project's central location for issue tracking and feature development.
-
-If you're not a developer or contributor, please use [LifterLMS plugin page][link-wp-repo] at WordPress.org.
-
-
-### Getting Help and Support
-
-GitHub is for bug reports and contributions only! If you have a support question or a request for a customization this is not the right place to post it. Please refer to [LifterLMS Support][link-support] or the [community forums][link-support-forums]. If you're looking for help customizing LifterLMS, please consider hiring a [LifterLMS Expert][link-experts].
-
-
-### Resources and Documentation
-
-+ [Changelog](./CHANGELOG.md)
-+ User documentation and knowledge base: https://lifterlms.com/docs/
-+ Contributor's blog: https://make.lifterlms.com/
-+ Developer portal: https://developer.lifterlms.com/
-
-
-### Included Core Packages
-
-The LifterLMS core includes several additional packages which are included in releases through composer. These core projects are installable as standalone plugins for development and testing purposes. The stable versions are automatically included in LifterLMS core releases.
-
-These packages have their own GitHub repositories:
-
-+ [LifterLMS Blocks](https://github.com/gocodebox/lifterlms-blocks)
-+ [LifterLMS REST API](https://github.com/gocodebox/lifterlms-rest)
-
-
-### Reporting a Bug
-
-Bugs can be reported at https://github.com/gocodebox/lifterlms/issues/new.
-
-Before reporting a bug, [search existing issues](https://github.com/gocodebox/lifterlms/issues) and ensure you're not creating a duplicate. If the issue already exists you can add your information to the existing report.
-
-Also check our [known issues and conflicts](https://lifterlms.com/doc-category/lifterlms/known-conflicts/) for possible resolutions.
-
-
-### Reporting a Security Vulnerability
-
-Security issues and vulnerabilities should be responsibly disclosed directly to the LifterLMS core developers via email. Please see our [Security Policy](.github/SECURITY.md) for details on disclosing a security vulnerability.
-
-
-### Installing
-
-If you clone or download this repo directly it will not run as a plugin inside WordPress!
-
-Installable production releases are available in on the [Releases tab](https://github.com/gocodebox/lifterlms/releases). You can get the latest stable release from [WordPress.org](https://downloads.wordpress.org/plugin/lifterlms.zip)
-
-If you're interested in installing development versions, see [Installing for Development](docs/installing.md)
-
-
-### Contributing
-
-[![Contributions Welcome][img-contributions-welcome]](.github/CONTRIBUTING.md)
-
-Interested in contributing to LifterLMS? We'd love to have your contributions. Read our contributor's guidelines [here](.github/CONTRIBUTING.md).
-
-
-### Contributors
-
-[![Contributors][img-contributors]](#contributors)
-
-Endless thanks to all our incredible contributors!
-
-[//]: contributor-faces
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-[//]: contributor-faces
-
-
-### Partners and Sponsors
-
-[ ](https://www.browserstack.com/)
-
-[BrowserStack](https://www.browserstack.com/) helps us ensure LifterLMS looks great and works on every imaginable browser and device.
-
-
-[link-cc]: https://codeclimate.com/github/gocodebox/lifterlms "LifterLMS on Code Climate"
-[link-cc-coverage]: https://codeclimate.com/github/gocodebox/lifterlms/coverage "Code coverage reports on Code Climate"
-[link-experts]: https://lifterlms.com/docs/do-you-have-any-recommended-developers-who-can-modifycustomize-lifterlms/ "Hire a LifterLMS Expert"
-[link-php]: https://www.php.net/supported-versions "PHP Support Versions"
-[link-phpunit-tests]: https://github.com/gocodebox/lifterlms/actions/workflows/test-phpunit.yml "PHPUnit Tests Status"
-[link-phpcs-checks]: https://github.com/gocodebox/lifterlms/actions/workflows/coding-standards.yml "PHPCS Coding Standards Checks"
-[link-slack]: https://lifterlms.com/slack "Chat with the community on Slack"
-[link-support]: https://lifterlms.com/my-account/my-tickets "LifterLMS customer support"
-[link-support-forums]: https://wordpress.org/support/plugin/lifterlms "LifterLMS user support forums"
-[link-wp-advanced]:https://wordpress.org/plugins/lifterlms/advanced/ "Advanced plugin details on the WordPress plugin repository"
-[link-wp-repo]:https://wordpress.org/plugins/lifterlms/ "LifterLMS on the WordPress plugin repository"
-[link-wp-reviews]:https://wordpress.org/support/plugin/lifterlms/reviews/ "Leave a review on the WordPress plugin repository"
-
-[img-cc-coverage]:https://img.shields.io/codeclimate/coverage/gocodebox/lifterlms?style=for-the-badge&logo=code-climate
-[img-cc-maintainability]:https://img.shields.io/codeclimate/maintainability/gocodebox/lifterlms?logo=code-climate&style=for-the-badge
-[img-contributors]: https://img.shields.io/github/contributors/gocodebox/lifterlms?color=blue&style=for-the-badge&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJzdmcyIiB3aWR0aD0iNjQ1IiBoZWlnaHQ9IjU4NSIgdmVyc2lvbj0iMS4wIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPiA8ZyBpZD0ibGF5ZXIxIj4gIDxwYXRoIGlkPSJwYXRoMjQxNyIgZD0ibTI5Ny4zIDU1MC44N2MtMTMuNzc1LTE1LjQzNi00OC4xNzEtNDUuNTMtNzYuNDM1LTY2Ljg3NC04My43NDQtNjMuMjQyLTk1LjE0Mi03Mi4zOTQtMTI5LjE0LTEwMy43LTYyLjY4NS01Ny43Mi04OS4zMDYtMTE1LjcxLTg5LjIxNC0xOTQuMzQgMC4wNDQ1MTItMzguMzg0IDIuNjYwOC01My4xNzIgMTMuNDEtNzUuNzk3IDE4LjIzNy0zOC4zODYgNDUuMS02Ni45MDkgNzkuNDQ1LTg0LjM1NSAyNC4zMjUtMTIuMzU2IDM2LjMyMy0xNy44NDUgNzYuOTQ0LTE4LjA3IDQyLjQ5My0wLjIzNDgzIDUxLjQzOSA0LjcxOTcgNzYuNDM1IDE4LjQ1MiAzMC40MjUgMTYuNzE0IDYxLjc0IDUyLjQzNiA2OC4yMTMgNzcuODExbDMuOTk4MSAxNS42NzIgOS44NTk2LTIxLjU4NWM1NS43MTYtMTIxLjk3IDIzMy42LTEyMC4xNSAyOTUuNSAzLjAzMTYgMTkuNjM4IDM5LjA3NiAyMS43OTQgMTIyLjUxIDQuMzgwMSAxNjkuNTEtMjIuNzE1IDYxLjMwOS02NS4zOCAxMDguMDUtMTY0LjAxIDE3OS42OC02NC42ODEgNDYuOTc0LTEzNy44OCAxMTguMDUtMTQyLjk4IDEyOC4wMy01LjkxNTUgMTEuNTg4LTAuMjgyMTYgMS44MTU5LTI2LjQwOC0yNy40NjF6IiBmaWxsPSIjZGQ1MDRmIi8%2BIDwvZz48L3N2Zz4%3D
-[img-contributions-welcome]: https://img.shields.io/badge/contributions-welcome-blue.svg?style=for-the-badge&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB3aWR0aD0iMTc5MiIgaGVpZ2h0PSIxNzkyIiB2aWV3Qm94PSIwIDAgMTc5MiAxNzkyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Ik02NzIgMTQ3MnEwLTQwLTI4LTY4dC02OC0yOC02OCAyOC0yOCA2OCAyOCA2OCA2OCAyOCA2OC0yOCAyOC02OHptMC0xMTUycTAtNDAtMjgtNjh0LTY4LTI4LTY4IDI4LTI4IDY4IDI4IDY4IDY4IDI4IDY4LTI4IDI4LTY4em02NDAgMTI4cTAtNDAtMjgtNjh0LTY4LTI4LTY4IDI4LTI4IDY4IDI4IDY4IDY4IDI4IDY4LTI4IDI4LTY4em05NiAwcTAgNTItMjYgOTYuNXQtNzAgNjkuNXEtMiAyODctMjI2IDQxNC02NyAzOC0yMDMgODEtMTI4IDQwLTE2OS41IDcxdC00MS41IDEwMHYyNnE0NCAyNSA3MCA2OS41dDI2IDk2LjVxMCA4MC01NiAxMzZ0LTEzNiA1Ni0xMzYtNTYtNTYtMTM2cTAtNTIgMjYtOTYuNXQ3MC02OS41di04MjBxLTQ0LTI1LTcwLTY5LjV0LTI2LTk2LjVxMC04MCA1Ni0xMzZ0MTM2LTU2IDEzNiA1NiA1NiAxMzZxMCA1Mi0yNiA5Ni41dC03MCA2OS41djQ5N3E1NC0yNiAxNTQtNTcgNTUtMTcgODcuNS0yOS41dDcwLjUtMzEgNTktMzkuNSA0MC41LTUxIDI4LTY5LjUgOC41LTkxLjVxLTQ0LTI1LTcwLTY5LjV0LTI2LTk2LjVxMC04MCA1Ni0xMzZ0MTM2LTU2IDEzNiA1NiA1NiAxMzZ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+
-[img-php]: https://img.shields.io/badge/PHP-7.2%2B-brightgreen?style=for-the-badge&logoColor=white&logo=php
-[img-phpunit-tests]: https://img.shields.io/github/workflow/status/gocodebox/lifterlms/Test%20PHPUnit?label=PHPUnit&logo=github&style=for-the-badge
-[img-phpcs-checks]: https://img.shields.io/github/workflow/status/gocodebox/lifterlms/Coding%20Standards?label=PHPCS&logo=github&style=for-the-badge
-[img-slack]: https://img.shields.io/badge/chat-on%20slack-blueviolet?style=for-the-badge&logo=slack
-[img-wp-downloads]: https://img.shields.io/wordpress/plugin/dt/lifterlms.svg?style=for-the-badge&logo=wordpress
-[img-wp-installs]: https://img.shields.io/wordpress/plugin/installs/lifterlms.svg?style=for-the-badge&logo=wordpress
-[img-wp-plugin]:https://img.shields.io/wordpress/plugin/v/lifterlms.svg?style=for-the-badge&logo=wordpress
-[img-wp-rating]:https://img.shields.io/wordpress/plugin/r/lifterlms.svg?style=for-the-badge&logo=wordpress
-[img-wp-tested]:https://img.shields.io/wordpress/v/lifterlms.svg?style=for-the-badge&logo=wordpress
diff --git a/_private/svg/llms-icon-calendar.svg b/_private/svg/llms-icon-calendar.svg
deleted file mode 100644
index 4e02ca4d61..0000000000
--- a/_private/svg/llms-icon-calendar.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-checkmark.svg b/_private/svg/llms-icon-checkmark.svg
deleted file mode 100644
index c8bdd2eb0c..0000000000
--- a/_private/svg/llms-icon-checkmark.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-circle-empty.svg b/_private/svg/llms-icon-circle-empty.svg
deleted file mode 100644
index cff0485a3a..0000000000
--- a/_private/svg/llms-icon-circle-empty.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-circle.svg b/_private/svg/llms-icon-circle.svg
deleted file mode 100644
index 37eafb0f77..0000000000
--- a/_private/svg/llms-icon-circle.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-close.svg b/_private/svg/llms-icon-close.svg
deleted file mode 100644
index 08ede2e8ea..0000000000
--- a/_private/svg/llms-icon-close.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-course-section.svg b/_private/svg/llms-icon-course-section.svg
deleted file mode 100644
index c4fa2f9590..0000000000
--- a/_private/svg/llms-icon-course-section.svg
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/_private/svg/llms-icon-existing-lesson.svg b/_private/svg/llms-icon-existing-lesson.svg
deleted file mode 100644
index c81a2e1444..0000000000
--- a/_private/svg/llms-icon-existing-lesson.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-facebook.svg b/_private/svg/llms-icon-facebook.svg
deleted file mode 100644
index 052d7607ae..0000000000
--- a/_private/svg/llms-icon-facebook.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-folder.svg b/_private/svg/llms-icon-folder.svg
deleted file mode 100644
index 78a73d7c3a..0000000000
--- a/_private/svg/llms-icon-folder.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-free.svg b/_private/svg/llms-icon-free.svg
deleted file mode 100644
index 97f8e04613..0000000000
--- a/_private/svg/llms-icon-free.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-gear.svg b/_private/svg/llms-icon-gear.svg
deleted file mode 100644
index f5e213cc24..0000000000
--- a/_private/svg/llms-icon-gear.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-google.svg b/_private/svg/llms-icon-google.svg
deleted file mode 100644
index e5ae714900..0000000000
--- a/_private/svg/llms-icon-google.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-graph.svg b/_private/svg/llms-icon-graph.svg
deleted file mode 100644
index d44b4b3562..0000000000
--- a/_private/svg/llms-icon-graph.svg
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-instagram.svg b/_private/svg/llms-icon-instagram.svg
deleted file mode 100644
index 2ded6d4aca..0000000000
--- a/_private/svg/llms-icon-instagram.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-lightbulb.svg b/_private/svg/llms-icon-lightbulb.svg
deleted file mode 100644
index ac4d827f84..0000000000
--- a/_private/svg/llms-icon-lightbulb.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-linkedin.svg b/_private/svg/llms-icon-linkedin.svg
deleted file mode 100644
index eb2b4d370a..0000000000
--- a/_private/svg/llms-icon-linkedin.svg
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-lock.svg b/_private/svg/llms-icon-lock.svg
deleted file mode 100644
index 0e6e321254..0000000000
--- a/_private/svg/llms-icon-lock.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-media.svg b/_private/svg/llms-icon-media.svg
deleted file mode 100644
index 8642eaecbb..0000000000
--- a/_private/svg/llms-icon-media.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-member.svg b/_private/svg/llms-icon-member.svg
deleted file mode 100644
index 29296c5204..0000000000
--- a/_private/svg/llms-icon-member.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-new-lesson.svg b/_private/svg/llms-icon-new-lesson.svg
deleted file mode 100644
index 5c77d519fd..0000000000
--- a/_private/svg/llms-icon-new-lesson.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-paper.svg b/_private/svg/llms-icon-paper.svg
deleted file mode 100644
index 33d0ee01d8..0000000000
--- a/_private/svg/llms-icon-paper.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-papers.svg b/_private/svg/llms-icon-papers.svg
deleted file mode 100644
index 971fbb8a3f..0000000000
--- a/_private/svg/llms-icon-papers.svg
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-play.svg b/_private/svg/llms-icon-play.svg
deleted file mode 100644
index 84f24e06af..0000000000
--- a/_private/svg/llms-icon-play.svg
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-plus.svg b/_private/svg/llms-icon-plus.svg
deleted file mode 100644
index 68e72da1aa..0000000000
--- a/_private/svg/llms-icon-plus.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-question.svg b/_private/svg/llms-icon-question.svg
deleted file mode 100644
index 96a968e830..0000000000
--- a/_private/svg/llms-icon-question.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-rightarrow.svg b/_private/svg/llms-icon-rightarrow.svg
deleted file mode 100644
index b41f94050f..0000000000
--- a/_private/svg/llms-icon-rightarrow.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-search.svg b/_private/svg/llms-icon-search.svg
deleted file mode 100644
index b6c659b26a..0000000000
--- a/_private/svg/llms-icon-search.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-target.svg b/_private/svg/llms-icon-target.svg
deleted file mode 100644
index 0df9f3ea7c..0000000000
--- a/_private/svg/llms-icon-target.svg
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-twitter.svg b/_private/svg/llms-icon-twitter.svg
deleted file mode 100644
index c1d3cebb54..0000000000
--- a/_private/svg/llms-icon-twitter.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-users.svg b/_private/svg/llms-icon-users.svg
deleted file mode 100644
index e7abbd1aa6..0000000000
--- a/_private/svg/llms-icon-users.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-view.svg b/_private/svg/llms-icon-view.svg
deleted file mode 100644
index 730ef22729..0000000000
--- a/_private/svg/llms-icon-view.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/_private/svg/llms-icon-youtube.svg b/_private/svg/llms-icon-youtube.svg
deleted file mode 100644
index 543bc31e90..0000000000
--- a/_private/svg/llms-icon-youtube.svg
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/assets/maps/css/admin-wizard.css.map b/assets/maps/css/admin-wizard.css.map
new file mode 100644
index 0000000000..6bf0948d9a
--- /dev/null
+++ b/assets/maps/css/admin-wizard.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["admin-wizard.scss","admin-wizard.css","_includes/_vars.scss"],"names":[],"mappings":"AAIA;EACC,aAAA;ACHD;;ADMA;EAEC,yBAAA;EACA,YAAA;EACA,OAAA;EACA,gBAAA;EACA,eAAA;EACA,MAAA;EACA,WAAA;ACJD;;ADQA;EACC,iBAAA;EACA,gBAAA;ACLD;;ADQA;EACC,kBAAA;ACLD;ADOC;EACC,qBAAA;ACLF;ADQC;EACC,gBAAA;ACNF;;ADWA;EACC,sBAAA;EACA,iDAAA;UAAA,yCAAA;EACA,aAAA;ACRD;ADUC;EACC,cAAA;ACRF;ADWC;EACC,cE7CiB;ADoCnB;ADYC;EACC,cAAA;EACA,eAAA;ACVF;ADaC;EACC,cEpCU;EFqCV,kCAAA;EACA,8BAAA;EACA,YAAA;EACA,kBAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;ACXF;ADcC;EACC,gBAAA;ACZF;ADeC;EACC,gCAAA;EACA,yBAAA;EACA,WAAA;ACbF;ADgBC;EACC,6BAAA;ACdF;ADgBE;EACC,mBAAA;EACA,UAAA;ACdH;ADgBG;EACC,eAAA;EACA,gBAAA;ACdJ;ADiBG;EACC,cAAA;EACA,eAAA;EACA,gBAAA;ACfJ;ADiBI;EACC,eAAA;ACfL;ADuBC;EACC,gBAAA;ACrBF;ADwBC;EACC,oBAAA;EAAA,oBAAA;EAAA,aAAA;EACA,8BAAA;EAAA,8BAAA;MAAA,+BAAA;UAAA,2BAAA;EACA,qBAAA;MAAA,kBAAA;UAAA,yBAAA;EACA,yBAAA;MAAA,sBAAA;UAAA,mBAAA;EACA,0BAAA;MAAA,qBAAA;EACA,UAAA;EACA,mBAAA;EACA,eAAA;ACtBF;ADwBE;EACC,SAAA;ACtBH;AD0BC;EACC,eAAA;EACA,cAAA;EACA,oBAAA;ACxBF;AD2BC;EACC,eAAA;ACzBF;AD4BC;EACC,WAAA;EACA,WAAA;EACA,SAAA;EACA,oBAtIc;AC4GhB;AD6BC;EACC,eAAA;EACA,oBAAA;EAAA,oBAAA;EAAA,aAAA;EACA,oBAAA;EACA,mBAAA;MAAA,eAAA;EACA,4BAAA;EAAA,6BAAA;MAAA,0BAAA;UAAA,sBAAA;EACA,UAAA;AC3BF;AD8BC;EACC,oBAnJc;ACuHhB;AD+BC;EACC,aAAA;AC7BF;;ADiCA;EACC,oBAAA;EAAA,oBAAA;EAAA,aAAA;EACA,yBAAA;MAAA,sBAAA;UAAA,8BAAA;EACA,QAAA;EACA,gBAAA;AC9BD;;ADiCA;EACC,mBAAA;MAAA,WAAA;UAAA,OAAA;AC9BD;;ADiCA;EACC,eAAA;EACA,iBAAA;EACA,oBAAA;EAAA,oBAAA;EAAA,aAAA;EACA,yBAAA;MAAA,sBAAA;UAAA,8BAAA;EACA,yBAAA;MAAA,sBAAA;UAAA,mBAAA;EACA,QAAA;AC9BD;ADgCC;EACC,qBAAA;AC9BF;ADgCE;EACC,gBAAA;EACA,0BAAA;EACA,gBAAA;EACA,kBAAA;AC9BH;;ADoCA;EACC,yBAAA;EACA,kBAAA;ACjCD;ADmCC;;EAEC,iBAAA;ACjCF;;ADsCC;EACC,cEtMiB;ADmKnB;ADsCC;EACC,aAAA;EACA,eAAA;EACA,kBAAA;EACA,iBAAA;ACpCF;;ADwCA;EACC,gCAAA;EACA,cAAA;ACrCD;ADuCC;EACC,6BAAA;EACA,cAAA;EACA,SAAA;EACA,eAAA;EACA,yBAAA;ACrCF;ADuCE;EACC,SAAA;ACrCH;ADwCE;EACC,kBAAA;ACtCH;ADyCE;EACC,gBAAA;ACvCH;AD0CE;EACC,WAAA;EACA,kBAAA;EACA,UAAA;ACxCH;AD2CE;EACC,YAAA;EACA,YAAA;ACzCH;AD2CG;EACC,qBEnPe;EFoPf,yBEpPe;AD2MnB;;ADiDA;EACC,oBAAA;EAAA,oBAAA;EAAA,aAAA;EACA,cAAA;AC9CD;ADgDC;EACC,gCAAA;EACA,qBAAA;EACA,eAAA;EACA,oBAAA;EACA,kBAAA;EACA,kBAAA;EACA,mBAAA;MAAA,WAAA;UAAA,OAAA;AC9CF;ADgDE;EACC,cE1QgB;EF2QhB,qBAAA;AC9CH;ADiDE;EACC,mBE/QgB;EFgRhB,SAAA;EACA,WAAA;EACA,yBAAA;EACA,mBAAA;EACA,WAAA;EACA,kBAAA;EACA,SAAA;EACA,iBAAA;EACA,mBAAA;EACA,UAAA;AC/CH;ADkDE;EACC,gBAAA;AChDH;ADkDG;EACC,gBAAA;AChDJ;ADoDE;EACC,yBAAA;AClDH;ADoDG;EACC,gBAAA;EACA,kBAAA;AClDJ;ADqDG;EACC,WAAA;ACnDJ;;AD6DC;EACC,2ZAAA;EACA,4BAAA;EACA,qCAAA;EACA,0BAAA;EACA,mBAAA;AC1DF","file":"../../css/admin-wizard.css","sourcesContent":["@import \"_includes/vars\";\n\n$input_padding: 0.3em 0.6em;\n\n#wpadminbar, #adminmenumain, #wpfooter {\n\tdisplay: none;\n}\n\n#llms-setup-wizard {\n\n\tbackground-color: #F0F0F1;\n\theight: 100%;\n\tleft: 0;\n\toverflow: scroll;\n\tposition: fixed;\n\ttop: 0;\n\twidth: 100%;\n\n}\n\n.llms-setup-wrapper {\n\tmargin: 30px auto;\n\tmax-width: 640px;\n}\n\n#llms-logo {\n\ttext-align: center;\n\n\ta {\n\t\tdisplay: inline-block;\n\t}\n\n\timg {\n\t\tmax-width: 200px;\n\t}\n\n}\n\n.llms-setup-content {\n\tbackground-color: #FFF;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);\n\tpadding: 30px;\n\n\th1, h2, h3, h4, h5, h6 {\n\t\tcolor: #3C434A;\n\t}\n\n\ta:not( .llms-button-primary ):not( .llms-button-secondary ) {\n\t\tcolor: $color-brand-blue;\n\t}\n\n\tp, li {\n\t\tcolor: #3C434A;\n\t\tfont-size: 16px;\n\t}\n\n\tp.error {\n\t\tcolor: $color-red;\n\t\tbackground: rgba($color-red,0.1);\n\t\tborder: 1px solid currentColor;\n\t\tpadding: 1em;\n\t\tborder-radius: 4px;\n\t\tmargin: 1.5em 0 0;\n\t\tfont-size: 15px;\n\t\ttext-align: left;\n\t}\n\n\tlabel {\n\t\tfont-weight: 500;\n\t}\n\n\ttable {\n\t\tborder-bottom: 1px solid #f1f1f1;\n\t\tborder-collapse: collapse;\n\t\twidth: 100%;\n\t}\n\n\ttd {\n\t\tborder-top: 1px solid #f1f1f1;\n\n\t\t&:first-child {\n\t\t\tpadding-right: 10px;\n\t\t\twidth: 33%;\n\n\t\t\ta {\n\t\t\t\tfont-size: 16px;\n\t\t\t\tfont-weight: 500;\n\t\t\t}\n\n\t\t\ti {\n\t\t\t\tdisplay: block;\n\t\t\t\tfont-size: 13px;\n\t\t\t\tmargin-top: 10px;\n\n\t\t\t\ta {\n\t\t\t\t\tfont-size: 13px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\tsmall {\n\t\tfont-size: small;\n\t}\n\n\t.has-checkbox-field {\n\t\tdisplay: flex;\n\t\tflex-direction: row-reverse;\n\t\tjustify-content: flex-end;\n\t\talign-items: center;\n\t\talign-content: center;\n\t\tgap: 0.5em;\n\t\tfont-weight: normal;\n\t\tfont-size: 16px;\n\n\t\tinput {\n\t\t\tmargin: 0;\n\t\t}\n\t}\n\n\tlabel {\n\t\tfont-size: 16px;\n\t\tdisplay: block;\n\t\tmargin-bottom: 0.5em;\n\t}\n\n\tsmall + label {\n\t\tmargin-top: 1em;\n\t}\n\n\t[type=text] {\n\t\twidth: 100%;\n\t\tclear: both;\n\t\tmargin: 0;\n\t\tpadding: $input_padding;\n\t}\n\n\t.has-date-field {\n\t\tfont-size: 16px;\n\t\tdisplay: flex;\n\t\tmargin-bottom: 0.5em;\n\t\tflex-wrap: wrap;\n\t\tflex-direction: column;\n\t\tgap: 0.5em;\n\t}\n\n\t[type=date] {\n\t\tpadding: $input_padding;\n\t}\n\n\t.is-hidden {\n\t\tdisplay: none;\n\t}\n}\n\n.llms-setup-date-fields {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: 1em;\n\tpadding-top: 2em;\n}\n\n.llms-setup-date-field {\n\tflex: 1;\n}\n\n.llms-setup-actions {\n\tmargin-top: 2em;\n\ttext-align: right;\n\tdisplay: flex;\n\tjustify-content: space-between;\n\talign-items: center;\n\tgap: 1em;\n\n\t.llms-button-primary {\n\t\tdisplay: inline-block;\n\n\t\t&:after {\n\t\t\tcontent: \"\\f054\";\n\t\t\tfont-family: 'FontAwesome';\n\t\t\tfont-weight: 900;\n\t\t\tpadding-left: 10px;\n\t\t}\n\n\t}\n}\n\n.llms-exit-setup {\n\tcolor: inherit !important;\n\tmargin-right: 10px;\n\n\t+ .llms-button-primary,\n\t+ .llms-button-secondary {\n\t\tmargin-left: auto;\n\t}\n}\n\n.llms-importing-msgs {\n\ta {\n\t\tcolor: $color-brand-blue;\n\t}\n\n\t.llms-importing-msg {\n\t\tdisplay: none;\n\t\tfont-size: 14px;\n\t\tfont-style: italic;\n\t\ttext-align: right;\n\t}\n}\n\nul.llms-importable-courses {\n\tborder-bottom: 1px solid #f1f1f1;\n\tdisplay: block;\n\n\tli.llms-importable-course {\n\t\tborder-top: 1px solid #f1f1f1;\n\t\tdisplay: block;\n\t\tmargin: 0;\n\t\tmax-width: 100%;\n\t\tpadding: 20px 40px 20px 0;\n\n\t\th3 {\n\t\t\tmargin: 0;\n\t\t}\n\n\t\tp {\n\t\t\tmargin: 10px 0 0 0;\n\t\t}\n\n\t\tlabel {\n\t\t\tfont-weight: 400;\n\t\t}\n\n\t\timg {\n\t\t\tfloat: left;\n\t\t\tmargin-right: 15px;\n\t\t\twidth: 22%;\n\t\t}\n\n\t\t.llms-switch {\n\t\t\tfloat: right;\n\t\t\tright: -40px;\n\n\t\t\tinput.llms-toggle-round:checked + label {\n\t\t\t\tborder-color: $color-brand-blue;\n\t\t\t\tbackground-color: $color-brand-blue;\n\t\t\t}\n\n\t\t}\n\n\t}\n}\n\n.llms-setup-progress {\n\tdisplay: flex;\n\tmargin: 20px 0;\n\n\tli {\n\t\tborder-bottom: 4px solid $color-brand-blue;\n\t\tdisplay: inline-block;\n\t\tfont-size: 14px;\n\t\tpadding-bottom: 10px;\n\t\tposition: relative;\n\t\ttext-align: center;\n\t\tflex: 1;\n\n\t\ta {\n\t\t\tcolor: $color-brand-blue;\n\t\t\ttext-decoration: none;\n\t\t}\n\n\t\t&:after {\n\t\t\tbackground: $color-brand-blue;\n\t\t\tbottom: 0;\n\t\t\tcontent: '';\n\t\t\tborder: 4px solid $color-brand-blue;\n\t\t\tborder-radius: 100%;\n\t\t\theight: 4px;\n\t\t\tposition: absolute;\n\t\t\tleft: 50%;\n\t\t\tmargin-left: -6px;\n\t\t\tmargin-bottom: -8px;\n\t\t\twidth: 4px;\n\t\t}\n\n\t\t&.current {\n\t\t\tfont-weight: 700;\n\n\t\t\t&:after {\n\t\t\t\tbackground: #fff;\n\t\t\t}\n\t\t}\n\n\t\t&.current ~ li {\n\t\t\tborder-bottom-color: #ccc;\n\n\t\t\t&:after {\n\t\t\t\tbackground: #ccc;\n\t\t\t\tborder-color: #ccc;\n\t\t\t}\n\n\t\t\ta {\n\t\t\t\tcolor: #bbb;\n\t\t\t}\n\t\t}\n\n\t}\n}\n\n.llms-setup-wrapper {\n\n\t// Calendar icon for custom date fields.\n\t[type=\"text\"].llms-datepicker {\n\t\tbackground-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' fill='%23ccc'%3E%3Cpath d='M152 24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H64C28.7 64 0 92.7 0 128v320c0 35.3 28.7 64 64 64h320c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64h-40V24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H152V24zM48 192h352v256c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V192z'/%3E%3C/svg%3E\");\n\t\tbackground-repeat: no-repeat;\n\t\tbackground-position: right 8px center;\n\t\tbackground-size: 16px 16px;\n\t\tpadding-right: 40px;\n\t}\n}\n","#wpadminbar, #adminmenumain, #wpfooter {\n display: none;\n}\n\n#llms-setup-wizard {\n background-color: #F0F0F1;\n height: 100%;\n left: 0;\n overflow: scroll;\n position: fixed;\n top: 0;\n width: 100%;\n}\n\n.llms-setup-wrapper {\n margin: 30px auto;\n max-width: 640px;\n}\n\n#llms-logo {\n text-align: center;\n}\n#llms-logo a {\n display: inline-block;\n}\n#llms-logo img {\n max-width: 200px;\n}\n\n.llms-setup-content {\n background-color: #FFF;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);\n padding: 30px;\n}\n.llms-setup-content h1, .llms-setup-content h2, .llms-setup-content h3, .llms-setup-content h4, .llms-setup-content h5, .llms-setup-content h6 {\n color: #3C434A;\n}\n.llms-setup-content a:not(.llms-button-primary):not(.llms-button-secondary) {\n color: #2295ff;\n}\n.llms-setup-content p, .llms-setup-content li {\n color: #3C434A;\n font-size: 16px;\n}\n.llms-setup-content p.error {\n color: #e5554e;\n background: rgba(229, 85, 78, 0.1);\n border: 1px solid currentColor;\n padding: 1em;\n border-radius: 4px;\n margin: 1.5em 0 0;\n font-size: 15px;\n text-align: left;\n}\n.llms-setup-content label {\n font-weight: 500;\n}\n.llms-setup-content table {\n border-bottom: 1px solid #f1f1f1;\n border-collapse: collapse;\n width: 100%;\n}\n.llms-setup-content td {\n border-top: 1px solid #f1f1f1;\n}\n.llms-setup-content td:first-child {\n padding-right: 10px;\n width: 33%;\n}\n.llms-setup-content td:first-child a {\n font-size: 16px;\n font-weight: 500;\n}\n.llms-setup-content td:first-child i {\n display: block;\n font-size: 13px;\n margin-top: 10px;\n}\n.llms-setup-content td:first-child i a {\n font-size: 13px;\n}\n.llms-setup-content small {\n font-size: small;\n}\n.llms-setup-content .has-checkbox-field {\n display: flex;\n flex-direction: row-reverse;\n justify-content: flex-end;\n align-items: center;\n align-content: center;\n gap: 0.5em;\n font-weight: normal;\n font-size: 16px;\n}\n.llms-setup-content .has-checkbox-field input {\n margin: 0;\n}\n.llms-setup-content label {\n font-size: 16px;\n display: block;\n margin-bottom: 0.5em;\n}\n.llms-setup-content small + label {\n margin-top: 1em;\n}\n.llms-setup-content [type=text] {\n width: 100%;\n clear: both;\n margin: 0;\n padding: 0.3em 0.6em;\n}\n.llms-setup-content .has-date-field {\n font-size: 16px;\n display: flex;\n margin-bottom: 0.5em;\n flex-wrap: wrap;\n flex-direction: column;\n gap: 0.5em;\n}\n.llms-setup-content [type=date] {\n padding: 0.3em 0.6em;\n}\n.llms-setup-content .is-hidden {\n display: none;\n}\n\n.llms-setup-date-fields {\n display: flex;\n justify-content: space-between;\n gap: 1em;\n padding-top: 2em;\n}\n\n.llms-setup-date-field {\n flex: 1;\n}\n\n.llms-setup-actions {\n margin-top: 2em;\n text-align: right;\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 1em;\n}\n.llms-setup-actions .llms-button-primary {\n display: inline-block;\n}\n.llms-setup-actions .llms-button-primary:after {\n content: \"\\f054\";\n font-family: \"FontAwesome\";\n font-weight: 900;\n padding-left: 10px;\n}\n\n.llms-exit-setup {\n color: inherit !important;\n margin-right: 10px;\n}\n.llms-exit-setup + .llms-button-primary,\n.llms-exit-setup + .llms-button-secondary {\n margin-left: auto;\n}\n\n.llms-importing-msgs a {\n color: #2295ff;\n}\n.llms-importing-msgs .llms-importing-msg {\n display: none;\n font-size: 14px;\n font-style: italic;\n text-align: right;\n}\n\nul.llms-importable-courses {\n border-bottom: 1px solid #f1f1f1;\n display: block;\n}\nul.llms-importable-courses li.llms-importable-course {\n border-top: 1px solid #f1f1f1;\n display: block;\n margin: 0;\n max-width: 100%;\n padding: 20px 40px 20px 0;\n}\nul.llms-importable-courses li.llms-importable-course h3 {\n margin: 0;\n}\nul.llms-importable-courses li.llms-importable-course p {\n margin: 10px 0 0 0;\n}\nul.llms-importable-courses li.llms-importable-course label {\n font-weight: 400;\n}\nul.llms-importable-courses li.llms-importable-course img {\n float: left;\n margin-right: 15px;\n width: 22%;\n}\nul.llms-importable-courses li.llms-importable-course .llms-switch {\n float: right;\n right: -40px;\n}\nul.llms-importable-courses li.llms-importable-course .llms-switch input.llms-toggle-round:checked + label {\n border-color: #2295ff;\n background-color: #2295ff;\n}\n\n.llms-setup-progress {\n display: flex;\n margin: 20px 0;\n}\n.llms-setup-progress li {\n border-bottom: 4px solid #2295ff;\n display: inline-block;\n font-size: 14px;\n padding-bottom: 10px;\n position: relative;\n text-align: center;\n flex: 1;\n}\n.llms-setup-progress li a {\n color: #2295ff;\n text-decoration: none;\n}\n.llms-setup-progress li:after {\n background: #2295ff;\n bottom: 0;\n content: \"\";\n border: 4px solid #2295ff;\n border-radius: 100%;\n height: 4px;\n position: absolute;\n left: 50%;\n margin-left: -6px;\n margin-bottom: -8px;\n width: 4px;\n}\n.llms-setup-progress li.current {\n font-weight: 700;\n}\n.llms-setup-progress li.current:after {\n background: #fff;\n}\n.llms-setup-progress li.current ~ li {\n border-bottom-color: #ccc;\n}\n.llms-setup-progress li.current ~ li:after {\n background: #ccc;\n border-color: #ccc;\n}\n.llms-setup-progress li.current ~ li a {\n color: #bbb;\n}\n\n.llms-setup-wrapper [type=text].llms-datepicker {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' fill='%23ccc'%3E%3Cpath d='M152 24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H64C28.7 64 0 92.7 0 128v320c0 35.3 28.7 64 64 64h320c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64h-40V24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H152V24zM48 192h352v256c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V192z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n background-size: 16px 16px;\n padding-right: 40px;\n}","// ----- LifterLMS Brand Colors ----- \\\\\n$color-brand-dark-blue: #243c56;\n\n$color-brand-blue: #2295ff;\n$color-brand-blue-dark: darken( $color-brand-blue, 12 ); // #0077e4\n$color-brand-blue-light: lighten( $color-brand-blue, 8 );\n\n$color-brand-orange: #f8954f;\n$color-brand-orange-dark: #f67d28;\n$color-brand-orange-light: lighten( $color-brand-orange, 8 );\n\n$color-brand-aqua: #17bebb;\n\n$color-brand-pink: #ef476f;\n\n\n\n// ----- name our versions of common colors ----- \\\\\n$color-black: #010101;\n$color-green: #83c373;\n$color-blue: $color-brand-blue;\n$color-red: #e5554e;\n$color-white: #fefefe;\n$color-aqua: #35bbaa;\n$color-purple: #845ef7;\n$color-orange: #ff922b;\n\n$color-red-hover: darken($color-red,5);\n\n\n// ----- state / action names ----- \\\\\n$color-success: $color-green;\n$color-danger: $color-red;\n\n\n\n\n\n\n\n\n$color-lightgrey:\t\t#ccc;\n$color-grey: \t\t\t#999;\n$color-darkgrey:\t\t#666;\n$color-cinder:\t\t\t#444;\n$color-lightblue:\t\t#33b1cb;\n$color-darkblue:\t\t#0185a3;\n\n\n\n\n\n\n\n\n\n\n\n\n$color-border: #efefef;\n\n$el-box-shadow: 0 1px 2px 0 rgba($color-black,0.4);\n$el-background: #f1f1f1;\n$el-background-hover: #eaeaea;\n\n$break-xsmall: 320px;\n$break-small: 641px;\n$break-medium: 768px;\n$break-large: 1024px;\n"],"sourceRoot":"../../scss"}
\ No newline at end of file
diff --git a/assets/maps/css/admin-wizard.min.css.map b/assets/maps/css/admin-wizard.min.css.map
new file mode 100644
index 0000000000..d948e9c01a
--- /dev/null
+++ b/assets/maps/css/admin-wizard.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["admin-wizard.css"],"names":[],"mappings":"AAAA,qCAAA,YACE,CAAA,mBAGF,wBACE,CAAA,WACA,CAAA,MACA,CAAA,eACA,CAAA,cACA,CAAA,KACA,CAAA,UACA,CAAA,oBAGF,gBACE,CAAA,eACA,CAAA,WAGF,iBACE,CAAA,aAEF,oBACE,CAAA,eAEF,eACE,CAAA,oBAGF,qBACE,CAAA,4CACA,CAAA,oCACQ,CAAA,YACR,CAAA,0IAEF,aACE,CAAA,4EAEF,aACE,CAAA,6CAEF,aACE,CAAA,cACA,CAAA,4BAEF,aACE,CAAA,6BACA,CAAA,6BACA,CAAA,WACA,CAAA,iBACA,CAAA,gBACA,CAAA,cACA,CAAA,eACA,CAAA,0BAEF,eACE,CAAA,0BAEF,+BACE,CAAA,wBACA,CAAA,UACA,CAAA,uBAEF,4BACE,CAAA,mCAEF,kBACE,CAAA,SACA,CAAA,qCAEF,cACE,CAAA,eACA,CAAA,qCAEF,aACE,CAAA,cACA,CAAA,eACA,CAAA,uCAEF,cACE,CAAA,0BAEF,eACE,CAAA,wCAEF,mBACE,CAAA,mBACA,CAAA,YACA,CAAA,6BACA,CAAA,6BACA,CAAA,8BACI,CAAA,0BACI,CAAA,oBACR,CAAA,iBACI,CAAA,wBACI,CAAA,wBACR,CAAA,qBACI,CAAA,kBACI,CAAA,yBACR,CAAA,oBACI,CAAA,QACJ,CAAA,kBACA,CAAA,cACA,CAAA,8CAEF,QACE,CAAA,0BAEF,cACE,CAAA,aACA,CAAA,kBACA,CAAA,gCAEF,cACE,CAAA,gCAEF,UACE,CAAA,UACA,CAAA,QACA,CAAA,iBACA,CAAA,oCAEF,cACE,CAAA,mBACA,CAAA,mBACA,CAAA,YACA,CAAA,kBACA,CAAA,kBACA,CAAA,cACI,CAAA,2BACJ,CAAA,4BACA,CAAA,yBACI,CAAA,qBACI,CAAA,QACR,CAAA,gCAEF,iBACE,CAAA,+BAEF,YACE,CAAA,wBAGF,mBACE,CAAA,mBACA,CAAA,YACA,CAAA,wBACA,CAAA,qBACI,CAAA,6BACI,CAAA,OACR,CAAA,eACA,CAAA,uBAGF,kBACE,CAAA,UACI,CAAA,MACI,CAAA,oBAGV,cACE,CAAA,gBACA,CAAA,mBACA,CAAA,mBACA,CAAA,YACA,CAAA,wBACA,CAAA,qBACI,CAAA,6BACI,CAAA,wBACR,CAAA,qBACI,CAAA,kBACI,CAAA,OACR,CAAA,yCAEF,oBACE,CAAA,+CAEF,WACE,CAAA,yBACA,CAAA,eACA,CAAA,iBACA,CAAA,iBAGF,wBACE,CAAA,iBACA,CAAA,8EAEF,gBAEE,CAAA,uBAGF,aACE,CAAA,yCAEF,YACE,CAAA,cACA,CAAA,iBACA,CAAA,gBACA,CAAA,2BAGF,+BACE,CAAA,aACA,CAAA,qDAEF,4BACE,CAAA,aACA,CAAA,QACA,CAAA,cACA,CAAA,wBACA,CAAA,wDAEF,QACE,CAAA,uDAEF,iBACE,CAAA,2DAEF,eACE,CAAA,yDAEF,UACE,CAAA,iBACA,CAAA,SACA,CAAA,kEAEF,WACE,CAAA,WACA,CAAA,wGAEF,oBACE,CAAA,wBACA,CAAA,qBAGF,mBACE,CAAA,mBACA,CAAA,YACA,CAAA,aACA,CAAA,wBAEF,+BACE,CAAA,oBACA,CAAA,cACA,CAAA,mBACA,CAAA,iBACA,CAAA,iBACA,CAAA,kBACA,CAAA,UACI,CAAA,MACI,CAAA,0BAEV,aACE,CAAA,oBACA,CAAA,8BAEF,kBACE,CAAA,QACA,CAAA,UACA,CAAA,wBACA,CAAA,kBACA,CAAA,UACA,CAAA,iBACA,CAAA,QACA,CAAA,gBACA,CAAA,kBACA,CAAA,SACA,CAAA,gCAEF,eACE,CAAA,sCAEF,eACE,CAAA,mCAEF,wBACE,CAAA,yCAEF,eACE,CAAA,iBACA,CAAA,qCAEF,UACE,CAAA,gDAGF,0ZACE,CAAA,2BACA,CAAA,oCACA,CAAA,yBACA,CAAA,kBACA","file":"../../css/admin-wizard.min.css","sourcesContent":["#wpadminbar, #adminmenumain, #wpfooter {\n display: none;\n}\n\n#llms-setup-wizard {\n background-color: #F0F0F1;\n height: 100%;\n left: 0;\n overflow: scroll;\n position: fixed;\n top: 0;\n width: 100%;\n}\n\n.llms-setup-wrapper {\n margin: 30px auto;\n max-width: 640px;\n}\n\n#llms-logo {\n text-align: center;\n}\n#llms-logo a {\n display: inline-block;\n}\n#llms-logo img {\n max-width: 200px;\n}\n\n.llms-setup-content {\n background-color: #FFF;\n -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);\n padding: 30px;\n}\n.llms-setup-content h1, .llms-setup-content h2, .llms-setup-content h3, .llms-setup-content h4, .llms-setup-content h5, .llms-setup-content h6 {\n color: #3C434A;\n}\n.llms-setup-content a:not(.llms-button-primary):not(.llms-button-secondary) {\n color: #2295ff;\n}\n.llms-setup-content p, .llms-setup-content li {\n color: #3C434A;\n font-size: 16px;\n}\n.llms-setup-content p.error {\n color: #e5554e;\n background: rgba(229, 85, 78, 0.1);\n border: 1px solid currentColor;\n padding: 1em;\n border-radius: 4px;\n margin: 1.5em 0 0;\n font-size: 15px;\n text-align: left;\n}\n.llms-setup-content label {\n font-weight: 500;\n}\n.llms-setup-content table {\n border-bottom: 1px solid #f1f1f1;\n border-collapse: collapse;\n width: 100%;\n}\n.llms-setup-content td {\n border-top: 1px solid #f1f1f1;\n}\n.llms-setup-content td:first-child {\n padding-right: 10px;\n width: 33%;\n}\n.llms-setup-content td:first-child a {\n font-size: 16px;\n font-weight: 500;\n}\n.llms-setup-content td:first-child i {\n display: block;\n font-size: 13px;\n margin-top: 10px;\n}\n.llms-setup-content td:first-child i a {\n font-size: 13px;\n}\n.llms-setup-content small {\n font-size: small;\n}\n.llms-setup-content .has-checkbox-field {\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-orient: horizontal;\n -webkit-box-direction: reverse;\n -ms-flex-direction: row-reverse;\n flex-direction: row-reverse;\n -webkit-box-pack: end;\n -ms-flex-pack: end;\n justify-content: flex-end;\n -webkit-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-line-pack: center;\n align-content: center;\n gap: 0.5em;\n font-weight: normal;\n font-size: 16px;\n}\n.llms-setup-content .has-checkbox-field input {\n margin: 0;\n}\n.llms-setup-content label {\n font-size: 16px;\n display: block;\n margin-bottom: 0.5em;\n}\n.llms-setup-content small + label {\n margin-top: 1em;\n}\n.llms-setup-content [type=text] {\n width: 100%;\n clear: both;\n margin: 0;\n padding: 0.3em 0.6em;\n}\n.llms-setup-content .has-date-field {\n font-size: 16px;\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n margin-bottom: 0.5em;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n -ms-flex-direction: column;\n flex-direction: column;\n gap: 0.5em;\n}\n.llms-setup-content [type=date] {\n padding: 0.3em 0.6em;\n}\n.llms-setup-content .is-hidden {\n display: none;\n}\n\n.llms-setup-date-fields {\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-pack: justify;\n -ms-flex-pack: justify;\n justify-content: space-between;\n gap: 1em;\n padding-top: 2em;\n}\n\n.llms-setup-date-field {\n -webkit-box-flex: 1;\n -ms-flex: 1;\n flex: 1;\n}\n\n.llms-setup-actions {\n margin-top: 2em;\n text-align: right;\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n -webkit-box-pack: justify;\n -ms-flex-pack: justify;\n justify-content: space-between;\n -webkit-box-align: center;\n -ms-flex-align: center;\n align-items: center;\n gap: 1em;\n}\n.llms-setup-actions .llms-button-primary {\n display: inline-block;\n}\n.llms-setup-actions .llms-button-primary:after {\n content: \"\\f054\";\n font-family: \"FontAwesome\";\n font-weight: 900;\n padding-left: 10px;\n}\n\n.llms-exit-setup {\n color: inherit !important;\n margin-right: 10px;\n}\n.llms-exit-setup + .llms-button-primary,\n.llms-exit-setup + .llms-button-secondary {\n margin-left: auto;\n}\n\n.llms-importing-msgs a {\n color: #2295ff;\n}\n.llms-importing-msgs .llms-importing-msg {\n display: none;\n font-size: 14px;\n font-style: italic;\n text-align: right;\n}\n\nul.llms-importable-courses {\n border-bottom: 1px solid #f1f1f1;\n display: block;\n}\nul.llms-importable-courses li.llms-importable-course {\n border-top: 1px solid #f1f1f1;\n display: block;\n margin: 0;\n max-width: 100%;\n padding: 20px 40px 20px 0;\n}\nul.llms-importable-courses li.llms-importable-course h3 {\n margin: 0;\n}\nul.llms-importable-courses li.llms-importable-course p {\n margin: 10px 0 0 0;\n}\nul.llms-importable-courses li.llms-importable-course label {\n font-weight: 400;\n}\nul.llms-importable-courses li.llms-importable-course img {\n float: left;\n margin-right: 15px;\n width: 22%;\n}\nul.llms-importable-courses li.llms-importable-course .llms-switch {\n float: right;\n right: -40px;\n}\nul.llms-importable-courses li.llms-importable-course .llms-switch input.llms-toggle-round:checked + label {\n border-color: #2295ff;\n background-color: #2295ff;\n}\n\n.llms-setup-progress {\n display: -webkit-box;\n display: -ms-flexbox;\n display: flex;\n margin: 20px 0;\n}\n.llms-setup-progress li {\n border-bottom: 4px solid #2295ff;\n display: inline-block;\n font-size: 14px;\n padding-bottom: 10px;\n position: relative;\n text-align: center;\n -webkit-box-flex: 1;\n -ms-flex: 1;\n flex: 1;\n}\n.llms-setup-progress li a {\n color: #2295ff;\n text-decoration: none;\n}\n.llms-setup-progress li:after {\n background: #2295ff;\n bottom: 0;\n content: \"\";\n border: 4px solid #2295ff;\n border-radius: 100%;\n height: 4px;\n position: absolute;\n left: 50%;\n margin-left: -6px;\n margin-bottom: -8px;\n width: 4px;\n}\n.llms-setup-progress li.current {\n font-weight: 700;\n}\n.llms-setup-progress li.current:after {\n background: #fff;\n}\n.llms-setup-progress li.current ~ li {\n border-bottom-color: #ccc;\n}\n.llms-setup-progress li.current ~ li:after {\n background: #ccc;\n border-color: #ccc;\n}\n.llms-setup-progress li.current ~ li a {\n color: #bbb;\n}\n\n.llms-setup-wrapper [type=text].llms-datepicker {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' fill='%23ccc'%3E%3Cpath d='M152 24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H64C28.7 64 0 92.7 0 128v320c0 35.3 28.7 64 64 64h320c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64h-40V24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H152V24zM48 192h352v256c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V192z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n background-size: 16px 16px;\n padding-right: 40px;\n}\n/*# sourceMappingURL=../maps/css/admin-wizard.css.map */\n"],"sourceRoot":"../../css"}
\ No newline at end of file
diff --git a/assets/maps/css/editor.css.map b/assets/maps/css/editor.css.map
new file mode 100644
index 0000000000..3cf670fadf
--- /dev/null
+++ b/assets/maps/css/editor.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["editor.scss","editor.css"],"names":[],"mappings":"AAAA;;;;EAIC,WAAA;ACCD;;ADEA;;EAEC,YAAA;EACA,yBAAA;EACA,eAAA;ACCD;;ADEA;EACC,kBAAA;ACCD;;ADEA;EACC,YAAA;ACCD;;ADEA;EACC,qBAAA;ACCD;;ADEA;EACC,WAAA;ACCD;;ADEA;EACC,8BAAA;UAAA,sBAAA;ACCD;;ADGA;EACC,WAAA;ACAD;;ADIC;EACC,kBAAA;EACA,kBAAA;ACDF;ADEE;EACC,WAAA;EACA,YAAA;EACA,kBAAA;EACA,OAAA;EACA,MAAA;EACA,oBAAA;KAAA,iBAAA;ACAH","file":"../../css/editor.css","sourcesContent":[".components-panel__row > .components-base-control,\n.components-form-token-field,\n.components-number-control,\n.components-range-control {\n\twidth: 100%;\n}\n\n.llms-block-empty,\n.llms-block-error {\n\tpadding: 1em;\n\tborder: 1px solid #e0e0e0;\n\tfont-size: 16px;\n}\n\n.llms-block-empty {\n\tfont-style: italic;\n}\n\n.wp-block .components-button {\n\theight: auto;\n}\n\n.wp-block .llms-button-primary {\n\ttext-decoration: none;\n}\n\n.llms-navigation-link-settings .components-panel__row > .components-base-control {\n\twidth: 100%;\n}\n\n.llms-block-icon {\n\ttransform: scale(0.75);\n}\n\n// Fixes core range control component flex issue.\n.components-range-control__root {\n\theight: 3em;\n}\n\n.llms-loop-item {\n\t.llms-video-wrapper {\n\t\taspect-ratio: 16/9;\n\t\tposition: relative;\n\t\tiframe {\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tposition: absolute;\n\t\t\tleft: 0;\n\t\t\ttop: 0;\n\t\t\tobject-fit: cover;\n\t\t}\n\t}\n}\n",".components-panel__row > .components-base-control,\n.components-form-token-field,\n.components-number-control,\n.components-range-control {\n width: 100%;\n}\n\n.llms-block-empty,\n.llms-block-error {\n padding: 1em;\n border: 1px solid #e0e0e0;\n font-size: 16px;\n}\n\n.llms-block-empty {\n font-style: italic;\n}\n\n.wp-block .components-button {\n height: auto;\n}\n\n.wp-block .llms-button-primary {\n text-decoration: none;\n}\n\n.llms-navigation-link-settings .components-panel__row > .components-base-control {\n width: 100%;\n}\n\n.llms-block-icon {\n transform: scale(0.75);\n}\n\n.components-range-control__root {\n height: 3em;\n}\n\n.llms-loop-item .llms-video-wrapper {\n aspect-ratio: 16/9;\n position: relative;\n}\n.llms-loop-item .llms-video-wrapper iframe {\n width: 100%;\n height: 100%;\n position: absolute;\n left: 0;\n top: 0;\n object-fit: cover;\n}"],"sourceRoot":"../../scss"}
\ No newline at end of file
diff --git a/assets/maps/css/editor.min.css.map b/assets/maps/css/editor.min.css.map
new file mode 100644
index 0000000000..f0691a91c4
--- /dev/null
+++ b/assets/maps/css/editor.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["editor.css"],"names":[],"mappings":"AAAA,kIAIE,UAAA,CAGF,oCAEE,WAAA,CACA,wBAAA,CACA,cAAA,CAGF,kBACE,iBAAA,CAGF,6BACE,WAAA,CAGF,+BACE,oBAAA,CAGF,+EACE,UAAA,CAGF,iBACE,6BAAA,CACQ,qBAAA,CAGV,gCACE,UAAA,CAGF,oCACE,iBAAA,CACA,iBAAA,CAEF,2CACE,UAAA,CACA,WAAA,CACA,iBAAA,CACA,MAAA,CACA,KAAA,CACA,mBAAA,CACG,gBAAA","file":"../../css/editor.min.css","sourcesContent":[".components-panel__row > .components-base-control,\n.components-form-token-field,\n.components-number-control,\n.components-range-control {\n width: 100%;\n}\n\n.llms-block-empty,\n.llms-block-error {\n padding: 1em;\n border: 1px solid #e0e0e0;\n font-size: 16px;\n}\n\n.llms-block-empty {\n font-style: italic;\n}\n\n.wp-block .components-button {\n height: auto;\n}\n\n.wp-block .llms-button-primary {\n text-decoration: none;\n}\n\n.llms-navigation-link-settings .components-panel__row > .components-base-control {\n width: 100%;\n}\n\n.llms-block-icon {\n -webkit-transform: scale(0.75);\n transform: scale(0.75);\n}\n\n.components-range-control__root {\n height: 3em;\n}\n\n.llms-loop-item .llms-video-wrapper {\n aspect-ratio: 16/9;\n position: relative;\n}\n.llms-loop-item .llms-video-wrapper iframe {\n width: 100%;\n height: 100%;\n position: absolute;\n left: 0;\n top: 0;\n -o-object-fit: cover;\n object-fit: cover;\n}\n/*# sourceMappingURL=../maps/css/editor.css.map */\n"],"sourceRoot":"../../css"}
\ No newline at end of file
diff --git a/assets/maps/js/llms-admin-wizard.min.js.map b/assets/maps/js/llms-admin-wizard.min.js.map
new file mode 100644
index 0000000000..bf18abd5a7
--- /dev/null
+++ b/assets/maps/js/llms-admin-wizard.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"../../js/llms-admin-wizard.min.js","sources":["llms-admin-wizard.js"],"sourcesContent":["/**\n * JS from the admin setup wizard\n *\n * @since 4.8.0\n * @version 4.8.0\n */\n\n( function() {\n\tconst\n\t\tcurrStep = document.getElementById( 'llms-setup-current-step' ),\n\t\texitLink = document.querySelector( '.llms-exit-setup' ),\n\t\timports = document.querySelectorAll( 'input[name=\"llms_setup_course_import_ids[]\"]' ),\n\t\tcheckboxToggle = document.getElementsByClassName( 'llms-checkbox-toggle' )[ 0 ] ?? null;\n\n\tif ( imports.length ) {\n\t\tconst\n\t\t\tsubmit = document.getElementById( 'llms-setup-submit' ),\n\t\t\tmsgs = document.querySelectorAll( '.llms-importing-msgs .llms-importing-msg' );\n\n\t\t/**\n\t\t * Retrieve the number of courses to be imported\n\t\t *\n\t\t * @since 4.8.0\n\t\t *\n\t\t * @return {Number} The number of courses to be imported.\n\t\t */\n\t\tfunction getSelectedImportCount() {\n\t\t\tlet count = 0;\n\n\t\t\timports.forEach( function( el ) {\n\t\t\t\tif ( el.checked ) {\n\t\t\t\t\t++count;\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\treturn count;\n\t\t}\n\n\t\t/**\n\t\t * Update UI when a user toggles an import on or off.\n\t\t *\n\t\t * @since 4.8.0\n\t\t */\n\t\timports.forEach( function( el ) {\n\t\t\tel.addEventListener( 'change', function() {\n\t\t\t\t// Hide all messages.\n\t\t\t\tmsgs.forEach( function( msg ) {\n\t\t\t\t\tmsg.style.display = 'none';\n\t\t\t\t} );\n\n\t\t\t\tconst selectedCount = getSelectedImportCount();\n\n\t\t\t\t// If there's no courses to be imported, disable the submit button.\n\t\t\t\tsubmit.disabled = 0 === getSelectedImportCount() ? 'disabled' : null;\n\n\t\t\t\t// Show messages where applicable.\n\t\t\t\tif ( 1 === selectedCount ) {\n\t\t\t\t\tmsgs[0].style.display = 'block';\n\t\t\t\t} else if ( selectedCount >= 2 ) {\n\t\t\t\t\tmsgs[1].style.display = 'block';\n\t\t\t\t\tdocument.getElementById( 'llms-importing-number' ).textContent = selectedCount;\n\t\t\t\t}\n\t\t\t} );\n\t\t} );\n\n\t\t// Trigger a change event so the UI displays properly on page load.\n\t\timports[0].dispatchEvent( new Event( 'change' ) );\n\n\t\t/**\n\t\t * Start a spinner when the \"Import Courses\" button is clicked.\n\t\t *\n\t\t * @since 4.8.0\n\t\t */\n\t\tsubmit.addEventListener( 'click', function() {\n\t\t\tLLMS.Spinner.start( jQuery( submit ), 'small' );\n\t\t} );\n\t}\n\n\tif ( exitLink && 'finish' !== currStep.value ) {\n\t\t/**\n\t\t * When users click \"Exit Setup\" prior to setup completion, open a confirmation dialog\n\t\t *\n\t\t * @since 4.8.0\n\t\t */\n\t\texitLink.addEventListener( 'click', function( e ) {\n\t\t\tif ( ! window.confirm( exitLink.dataset.confirm ) ) {\n\t\t\t\te.preventDefault();\n\t\t\t}\n\t\t} );\n\t}\n\n\tif ( checkboxToggle ) {\n\t\tcheckboxToggle.addEventListener( 'click', function() {\n\t\t\tconst hiddenFields = this.parentNode.parentNode.querySelectorAll( '.is-hidden,.is-visible' );\n\n\t\t\tfor ( let i = 0; i < hiddenFields.length; i++ ) {\n\t\t\t\thiddenFields[ i ].classList.toggle( 'is-visible' );\n\t\t\t\thiddenFields[ i ].classList.toggle( 'is-hidden' );\n\t\t\t}\n\t\t} );\n\t}\n}() );\n"],"names":["currStep","document","getElementById","exitLink","querySelector","imports","querySelectorAll","checkboxToggle","getElementsByClassName","length","submit","msgs","getSelectedImportCount","let","count","forEach","el","checked","addEventListener","msg","style","display","selectedCount","disabled","textContent","dispatchEvent","Event","LLMS","Spinner","start","jQuery","value","e","window","confirm","dataset","preventDefault","hiddenFields","this","parentNode","i","classList","toggle"],"mappings":"AAOA,CAAE,WACD,MACCA,EAAiBC,SAASC,eAAgB,yBAA0B,EACpEC,EAAiBF,SAASG,cAAe,kBAAmB,EAC5DC,EAAiBJ,SAASK,iBAAkB,8CAA+C,EAC3FC,EAAiBN,SAASO,uBAAwB,sBAAuB,EAAG,IAAO,KAEpF,GAAKH,EAAQI,OAAS,CACrB,MACCC,EAAST,SAASC,eAAgB,mBAAoB,EACtDS,EAASV,SAASK,iBAAkB,0CAA2C,EAShF,SAASM,IACRC,IAAIC,EAAQ,EAQZ,OANAT,EAAQU,QAAS,SAAUC,GACrBA,EAAGC,SACP,EAAEH,CAEJ,CAAE,EAEKA,CACR,CAOAT,EAAQU,QAAS,SAAUC,GAC1BA,EAAGE,iBAAkB,SAAU,WAE9BP,EAAKI,QAAS,SAAUI,GACvBA,EAAIC,MAAMC,QAAU,MACrB,CAAE,EAEF,IAAMC,EAAgBV,EAAuB,EAG7CF,EAAOa,SAAW,IAAMX,EAAuB,EAAI,WAAa,KAG3D,IAAMU,EACVX,EAAK,GAAGS,MAAMC,QAAU,QACI,GAAjBC,IACXX,EAAK,GAAGS,MAAMC,QAAU,QACxBpB,SAASC,eAAgB,uBAAwB,EAAEsB,YAAcF,EAEnE,CAAE,CACH,CAAE,EAGFjB,EAAQ,GAAGoB,cAAe,IAAIC,MAAO,QAAS,CAAE,EAOhDhB,EAAOQ,iBAAkB,QAAS,WACjCS,KAAKC,QAAQC,MAAOC,OAAQpB,CAAO,EAAG,OAAQ,CAC/C,CAAE,CACH,CAEKP,GAAY,WAAaH,EAAS+B,OAMtC5B,EAASe,iBAAkB,QAAS,SAAUc,GACtCC,OAAOC,QAAS/B,EAASgC,QAAQD,OAAQ,GAC/CF,EAAEI,eAAe,CAEnB,CAAE,EAGE7B,GACJA,EAAeW,iBAAkB,QAAS,WACzC,IAAMmB,EAAeC,KAAKC,WAAWA,WAAWjC,iBAAkB,wBAAyB,EAE3F,IAAMO,IAAI2B,EAAI,EAAGA,EAAIH,EAAa5B,OAAQ+B,CAAC,GAC1CH,EAAcG,GAAIC,UAAUC,OAAQ,YAAa,EACjDL,EAAcG,GAAIC,UAAUC,OAAQ,WAAY,CAElD,CAAE,CAEJ,EAAI","sourceRoot":"../../js"}
\ No newline at end of file
diff --git a/assets/maps/js/llms-favorites.min.js.map b/assets/maps/js/llms-favorites.min.js.map
new file mode 100644
index 0000000000..e20d5bed3a
--- /dev/null
+++ b/assets/maps/js/llms-favorites.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"../../js/llms-favorites.min.js","sources":["llms-favorites.js"],"sourcesContent":["/* global LLMS, $ */\n/* jshint strict: true */\n\n/**\n * Front End Favorite Class.\n *\n * @type {Object}\n *\n * @since 7.5.0\n * @version 7.5.0\n */\n( function( $ ) {\n\n\tvar favorite = {\n\n\t\t/**\n\t\t * Bind DOM events.\n\t\t *\n\t\t * @since 7.5.0\n\t\t *\n\t\t * @return {Void}\n\t\t */\n\t\tbind: function() {\n\n\t\t\tvar self = this;\n\n\t\t\t// Favorite clicked.\n\t\t\t$( '.llms-favorite-wrapper' ).on( 'click', function( e ) {\n\t\t\t\te.preventDefault();\n\t\t\t\tvar $btn = $( this ).find( '.llms-heart-btn' );\n\t\t\t\t$btn && self.favorite( $btn );\n\t\t\t} );\n\n\t\t\t// Adding class in Favorite's parent.\n\t\t\t$( '.llms-favorite-wrapper' ).parent().addClass( 'llms-has-favorite' );\n\n\t\t},\n\n\t\t/**\n\t\t * Favorite / Unfavorite an object.\n\t\t *\n\t\t * @since 7.5.0\n\t\t *\n\t\t * @param {Object} $btn jQuery object for the \"Favorite / Unfavorite\" button.\n\t\t * @return {Void}\n\t\t */\n\t\tfavorite: function( $btn ) {\n\n\t\t\tvar object_id \t= $btn.attr( 'data-id' ),\n\t\t\t\tobject_type = $btn.attr( 'data-type' ),\n\t\t\t\tuser_action\t= $btn.attr( 'data-action' );\n\n\t\t\tLLMS.Ajax.call( {\n\t\t\t\tdata: {\n\t\t\t\t\taction: 'favorite_object',\n\t\t\t\t\tobject_id: object_id,\n\t\t\t\t\tobject_type: object_type,\n\t\t\t\t\tuser_action: user_action\n\t\t\t\t},\n\t\t\t\tbeforeSend: function() {},\n\t\t\t\tsuccess: function( r ) {\n\t\t\t\t\t/**\n\t\t\t\t\t * Get all the favorite buttons on the page related to the same lesson, e.g. when the syllabus\n\t\t\t\t\t * is shown on the sidebar of a lesson or a course, in that case you will have the same favorite\n\t\t\t\t\t * button twice. The code below makes sure both the buttons are updated.\n\t\t\t\t\t */\n\t\t\t\t\tvar $fav_btns = $( '[data-id='+object_id+'][data-type='+object_type+'][data-action='+user_action+']' );\n\t\t\t\t\tif( r.success ) {\n\t\t\t\t\t\t$fav_btns.each(\n\t\t\t\t\t\t\tfunction() {\n\t\t\t\t\t\t\t\tif( 'favorite' === user_action ) {\n\t\t\t\t\t\t\t\t\t$(this).removeClass( 'fa-heart-o' ).addClass( 'fa-heart' );\n\t\t\t\t\t\t\t\t\t$(this).attr( 'data-action', 'unfavorite' );\n\t\t\t\t\t\t\t\t} else if ( 'unfavorite' === user_action ) {\n\t\t\t\t\t\t\t\t\t$(this).removeClass( 'fa-heart' ).addClass( 'fa-heart-o' );\n\t\t\t\t\t\t\t\t\t$(this).attr( 'data-action', 'favorite' );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Updating count.\n\t\t\t\t\t\t\t\t$(this).closest( '.llms-favorite-wrapper' ).find( '.llms-favorites-count' ).text( r.total_favorites );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\t};\n\n\tfavorite.bind();\n\n\twindow.llms = window.llms || {};\n\twindow.llms.favorites = favorite;\n\n} )( jQuery );\n"],"names":["$","favorite","bind","self","this","on","e","preventDefault","$btn","find","parent","addClass","object_id","attr","object_type","user_action","LLMS","Ajax","call","data","action","beforeSend","success","r","$fav_btns","each","removeClass","closest","text","total_favorites","window","llms","favorites","jQuery"],"mappings":"AAWA,CAAA,SAAYA,GAEX,IAAIC,EAAW,CASdC,KAAM,WAEL,IAAIC,EAAOC,KAGXJ,EAAG,wBAAyB,EAAEK,GAAI,QAAS,SAAUC,GACpDA,EAAEC,eAAe,EACbC,EAAOR,EAAGI,IAAK,EAAEK,KAAM,iBAAkB,EAC7CD,GAAQL,EAAKF,SAAUO,CAAK,CAC7B,CAAE,EAGFR,EAAG,wBAAyB,EAAEU,OAAO,EAAEC,SAAU,mBAAoB,CAEtE,EAUAV,SAAU,SAAUO,GAEnB,IAAII,EAAaJ,EAAKK,KAAM,SAAU,EACrCC,EAAcN,EAAKK,KAAM,WAAY,EACrCE,EAAcP,EAAKK,KAAM,aAAc,EAExCG,KAAKC,KAAKC,KAAM,CACfC,KAAM,CACLC,OAAQ,kBACRR,UAAWA,EACXE,YAAaA,EACbC,YAAaA,CACd,EACAM,WAAY,aACZC,QAAS,SAAUC,GAMlB,IAAIC,EAAYxB,EAAG,YAAYY,EAAU,eAAeE,EAAY,iBAAiBC,EAAY,GAAI,EACjGQ,EAAED,SACLE,EAAUC,KACT,WACK,aAAeV,GAClBf,EAAEI,IAAI,EAAEsB,YAAa,YAAa,EAAEf,SAAU,UAAW,EACzDX,EAAEI,IAAI,EAAES,KAAM,cAAe,YAAa,GAC/B,eAAiBE,IAC5Bf,EAAEI,IAAI,EAAEsB,YAAa,UAAW,EAAEf,SAAU,YAAa,EACzDX,EAAEI,IAAI,EAAES,KAAM,cAAe,UAAW,GAGzCb,EAAEI,IAAI,EAAEuB,QAAS,wBAAyB,EAAElB,KAAM,uBAAwB,EAAEmB,KAAML,EAAEM,eAAgB,CACrG,CACD,CAEF,CACD,CAAE,CACH,CACD,EAEA5B,EAASC,KAAK,EAEd4B,OAAOC,KAAmBD,OAAOC,MAAQ,GACzCD,OAAOC,KAAKC,UAAc/B,CAEzB,EAAGgC,MAAO","sourceRoot":"../../js"}
\ No newline at end of file
diff --git a/assets/scss/_includes/_buttons.scss b/assets/scss/_includes/_buttons.scss
deleted file mode 100644
index ce53e5b818..0000000000
--- a/assets/scss/_includes/_buttons.scss
+++ /dev/null
@@ -1,153 +0,0 @@
-.llms-button-action,
-.llms-button-danger,
-.llms-button-primary,
-.llms-button-secondary {
- border:none;
- border-radius: 8px;
- color: $color-white;
- cursor: pointer;
- font-size: 16px;
- font-weight: 700;
- text-decoration: none;
- text-shadow: none;
- line-height: 1;
- margin: 0;
- max-width: 100%;
- padding: 12px 24px;
- position: relative;
- transition: all .5s ease;
-
- &:disabled {
- opacity: 0.5;
- }
- &:hover, &:active {
- color: $color-white;
- }
- &:focus {
- color: $color-white;
- }
-
- &.auto {
- width: auto;
- }
-
- &.full {
- display: block;
- text-align: center;
- width: 100%;
- }
-
- &.square {
- padding: 12px;
- }
-
- &.small {
- font-size: 13px;
- padding: 8px 14px;
- &.square { padding: 8px; }
- }
-
- &.large {
- font-size: 18px;
- line-height: 1.2;
- padding: 16px 32px;
- &.square { padding: 16px; }
- .fa {
- left: -7px;
- position: relative;
- }
- }
-
-}
-
-.llms-button-primary {
- background: $color-brand-blue;
- &:hover,
- &.clicked {
- background: $color-brand-blue-dark;
- }
- &:focus,
- &:active {
- background: $color-brand-blue-light;
- }
-}
-
-.llms-button-secondary {
- background: #e1e1e1;
- color: #414141;
- &:hover {
- color: #414141;
- background: darken( #e1e1e1, 8 );
- }
- &:focus,
- &:active {
- color: #414141;
- background: lighten( #e1e1e1, 4 );
- }
-}
-
-.llms-button-action {
- background: $color-brand-orange;
- &:hover,
- &.clicked {
- background: $color-brand-orange-dark;
- }
- &:focus,
- &:active {
- background: $color-brand-orange-light;
- }
-}
-
-.llms-button-danger {
- background: $color-danger;
- &:hover {
- background: darken( $color-danger, 8 );
- }
- &:focus,
- &:active {
- background: lighten( $color-danger, 4 );
- }
-}
-
-.llms-button-outline {
- background: transparent;
- border: 3px solid #1D2327;
- border-radius: 8px;
- color: #1D2327;
- cursor: pointer;
- font-size: 16px;
- font-weight: 700;
- text-decoration: none;
- text-shadow: none;
- line-height: 1;
- margin: 0;
- max-width: 100%;
- padding: 12px 24px;
- position: relative;
- transition: all .5s ease;
-
- &:disabled {
- opacity: 0.5;
- }
- &:hover, &:active {
- color: #1D2327;
- }
- &:focus {
- color: #1D2327;
- }
-
- &.auto {
- width: auto;
- }
-
- &.full {
- display: block;
- text-align: center;
- width: 100%;
- }
-
- &.square {
- padding: 12px;
- }
-
-}
\ No newline at end of file
diff --git a/assets/scss/_includes/_extends.scss b/assets/scss/_includes/_extends.scss
deleted file mode 100644
index 3ff4e48156..0000000000
--- a/assets/scss/_includes/_extends.scss
+++ /dev/null
@@ -1,30 +0,0 @@
-%cf,
-%clearfix {
- &:before,
- &:after {
- content: " ";
- display: table;
- }
-
- &:after {
- clear: both;
- }
-}
-
-
-
-%llms-element {
- background: $el-background;
- box-shadow: $el-box-shadow;
- display: block;
- color: #212121;
- min-height: 85px;
- padding: 15px;
- text-decoration: none;
- position: relative;
- transition: background .4s ease;
-
- &:hover {
- background: $el-background-hover;
- }
-}
diff --git a/assets/scss/_includes/_grid.scss b/assets/scss/_includes/_grid.scss
deleted file mode 100644
index 4306e6099d..0000000000
--- a/assets/scss/_includes/_grid.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// Floated columns.
-//
-// Utilized prior to the introduction of `.llms-flex-cols`. Prefer
-// usage of flex cols for new code where possible.
-//
-.llms-cols {
- @extend %clearfix;
-
- .llms-col { width: 100%; }
-
- @media all and (min-width: 600px) {
- [class*="llms-col-"] {
- float: left;
- }
- }
-
-}
-
-//
-// Flex-box columns.
-//
-// Preferred over floated `.llms-cols` wherever possible.
-//
-.llms-flex-cols {
- display: flex;
- flex-flow: row wrap;
-
- [class*="llms-col"] {
- flex: 0 1 auto;
- width: 100%;
- }
-}
-
-@media all and (min-width: 600px) {
- .llms-cols, .llms-flex-cols {
- $cols: 1;
- @while $cols <= 12 {
- .llms-col-#{$cols} {
- width: calc( 100% / $cols );
- }
- $cols: $cols + 1;
- }
- }
-}
-
diff --git a/assets/scss/_includes/_llms-donut.scss b/assets/scss/_includes/_llms-donut.scss
deleted file mode 100644
index f01a645b49..0000000000
--- a/assets/scss/_includes/_llms-donut.scss
+++ /dev/null
@@ -1,82 +0,0 @@
-.llms-donut {
-
- @include clearfix;
-
- background-color: #f1f1f1;
- background-image: none;
- border-radius: 50%;
- color: $color-brand-pink;
- height: 200px;
- overflow: hidden;
- position: relative;
- width: 200px;
-
- svg {
- overflow: visible !important;
- pointer-events: none;
- width: 100%;
- }
-
- svg path {
- fill: none;
- stroke-width: 35px;
- stroke: $color-brand-pink;
- }
-
- &.mini {
- height: 36px;
- width: 36px;
- .percentage {
- font-size: 10px;
- }
- }
- &.small {
- height: 100px;
- width: 100px;
- .percentage {
- font-size: 18px;
- }
- }
- &.medium {
- height: 130px;
- width: 130px;
- .percentage {
- font-size: 26px;
- }
- }
- &.large {
- height: 260px;
- width: 260px;
- .percentage {
- font-size: 48px;
- }
- }
-
- .inside {
- align-items: center;
- background: #fff;
- border-radius: 50%;
- box-sizing: border-box;
- display: flex;
- flex-wrap: wrap;
- height: 80%;
- justify-content: center;
- left: 50%;
- position: absolute;
- text-align: center;
- transform: translate(-50%, -50%);
- width: 80%;
- top: 50%;
- z-index: 3;
- }
-
- .percentage {
- line-height: 1.2;
- font-size: 34px;
- }
-
- .caption {
- font-size: 50%;
- }
-
-}
diff --git a/assets/scss/_includes/_llms-form-field.scss b/assets/scss/_includes/_llms-form-field.scss
deleted file mode 100644
index bc421dd116..0000000000
--- a/assets/scss/_includes/_llms-form-field.scss
+++ /dev/null
@@ -1,213 +0,0 @@
-.llms-form-fields {
- @extend %clearfix;
- box-sizing: border-box;
- & * {
- box-sizing: border-box;
- }
- &.flush {
- .llms-form-field {
- padding: 0 0 10px;
- }
- }
-
- .wp-block-columns, .wp-block-column {
- margin-bottom: 0;
- }
-}
-
- .llms-form-heading {
- padding: 0 10px 10px;
- }
-
- .llms-form-field {
- float: left;
- padding: 0 10px 10px;
- position: relative;
- width: 100%;
-
- // Ensure "empty" labels don't break the layout.
- // See the billing_address_2 field which has no label.
- label:empty:after {
- content: '\00a0';
- }
-
- &.valid {
- input[type="date"], input[type="time"], input[type="datetime-local"], input[type="week"], input[type="month"], input[type="text"], input[type="email"], input[type="url"], input[type="password"], input[type="search"], input[type="tel"], input[type="number"], textarea, select {
- background: rgba( #83c373, .3 );
- border-color: #83c373;
- }
- }
-
- &.error,
- &.invalid {
- input[type="date"], input[type="time"], input[type="datetime-local"], input[type="week"], input[type="month"], input[type="text"], input[type="email"], input[type="url"], input[type="password"], input[type="search"], input[type="tel"], input[type="number"], textarea, select {
- background: rgba( $color-red, .3 );
- border-color: $color-red;
- }
- }
-
- &.llms-visually-hidden-field {
- display: none;
- }
-
- &.align-right {
- text-align: right;
- }
-
- @media screen and ( min-width: 600px ) {
- $i: 1;
- @while $i <= 12 {
- &.llms-cols-#{$i} {
- width: calc( $i / 12 ) * 100%;
- $i: $i + 1;
- }
- }
- }
-
- &.type-hidden { padding: 0; }
-
- &.type-radio,
- &.type-checkbox {
- input,
- label {
- display: inline-block;
- width: auto;
- }
- input {
- margin-right: 5px;
- }
- label + .llms-description {
- display: block;
- }
- }
-
- &.type-radio:not(.is-group) {
-
- input[type="radio"] {
- position: absolute;
- opacity: 0;
- visibility: none;
- }
-
- label:before {
- background: #fafafa;
- background-position: -24px 0;
- background-repeat: no-repeat;
- border-radius: 50%;
- box-shadow: hsla( 0,0%,100%,.15) 0 1px 1px, inset hsla(0,0%,0%,.35) 0 0 0 1px;
- content: '';
- cursor: pointer;
- display: inline-block;
- height: 22px;
- margin-right: 5px;
- position: relative;
- transition: background-position .15s cubic-bezier(.8, 0, 1, 1);
- top: -3px;
- vertical-align: middle;
- width: 22px;
- z-index: 2;
- }
-
- input[type="radio"]:checked + label:before {
- transition: background-position .2s .15s cubic-bezier(0, 0, .2, 1);
- background-position: 0 0;
- background-image: radial-gradient(ellipse at center, $color-brand-blue 0%,$color-brand-blue 40%, #fafafa 45%);
- }
-
- }
-
- .llms-input-group {
- margin-top: 5px;
- .llms-form-field {
- padding: 0 0 5px 5px;
- }
- }
-
- &.type-reset,
- &.type-button,
- &.type-submit {
- button:not(.auto) { width: 100%; }
- }
-
- .llms-description {
- font-size: 14px;
- font-style: italic;
- }
-
- .llms-required {
- color: $color-red;
- margin-left: 4px;
- }
-
- input, textarea, select {
- width: 100%;
- margin-bottom: 5px;
- }
-
- .select2-container .select2-selection--single {
- height: auto;
- padding: 4px 6px;
- }
- .select2-container--default .select2-selection--single .select2-selection__arrow {
- height: 100%;
- }
-
- }
-
-
- .llms-password-strength-meter {
- border: 1px solid #dadada;
- display: none;
- font-size: 10px;
- margin-top: -10px;
- padding: 1px;
- position: relative;
- text-align: center;
-
- &:before {
- bottom: 0;
- content: '';
- left: 0;
- position: absolute;
- top: 0;
- transition: width .4s ease;
- }
-
- &.mismatch,
- &.too-short,
- &.very-weak {
- border-color: #e35b5b;
- &:before {
- background: rgba( #e35b5b, 0.25 );
- width: 25%;
- }
- }
-
- &.too-short:before {
- width: 0;
- }
-
- &.weak {
- border-color: #f78b53;
- &:before {
- background: rgba( #f78b53, 0.25 );
- width: 50%;
- }
- }
-
- &.medium {
- border-color: #ffc733;
- &:before {
- background: rgba( #ffc733, 0.25 );
- width: 75%;
- }
- }
-
- &.strong {
- border-color: #83c373;
- &:before {
- background: rgba( #83c373, 0.25 );
- width: 100%;
- }
- }
- }
diff --git a/assets/scss/_includes/_mixins.scss b/assets/scss/_includes/_mixins.scss
deleted file mode 100644
index 876bb299ff..0000000000
--- a/assets/scss/_includes/_mixins.scss
+++ /dev/null
@@ -1,120 +0,0 @@
-
-@mixin clearfix() {
- &:before,
- &:after {
- content: " ";
- display: table;
- }
- &:after {
- clear: both;
- }
-}
-
-//
-// Positioning mixin
-//
-// @param [string] $position: position
-// @param [list] $args (()): offsets list
-//
-// @source http://hugogiraudel.com/2013/08/05/offsets-sass-mixin/
-//
-@mixin position($position, $args: ()) {
- $offsets: top right bottom left;
- position: $position;
-
- @each $offset in $offsets {
- $index: index($args, $offset);
-
- @if $index {
- @if $index == length($args) {
- #{$offset}: 0;
- }
- @else {
- $next: nth($args, $index + 1);
- @if is-valid-length($next) {
- #{$offset}: $next;
- }
- @else if index($offsets, $next) {
- #{$offset}: 0;
- }
- @else {
- @warn "Invalid value `#{$next}` for offset `#{$offset}`.";
- }
- }
- }
- }
-}
-
-//
-// Function checking if $value is a valid length
-// @param [literal] $value: value to test
-// @return [bool]
-//
-@function is-valid-length($value) {
- $r: (type-of($value) == "number" and not unitless($value)) or (index(auto initial inherit 0, $value) != null);
- @return $r;
-}
-
-//
-// Shorthands
-//
-@mixin absolute($args: ()) {
- @include position(absolute, $args);
-}
-
-@mixin fixed($args: ()) {
- @include position(fixed, $args);
-}
-
-@mixin relative($args: ()) {
- @include position(relative, $args);
-}
-
-
-
-@mixin order_status_badges() {
-
- .llms-status {
- border-radius: 3px;
- border-bottom: 1px solid #fff;
- display: inline-block;
- font-size: 80%;
- padding: 3px 6px;
- vertical-align: middle;
-
- &.llms-size--large {
- font-size: 105%;
- padding: 6px 12px;
- }
-
- &.llms-active,
- &.llms-completed,
- &.llms-pass, // quiz
- &.llms-txn-succeeded {
- color: darken( $color-green, 45 );
- background-color: $color-green;
- }
-
- &.llms-fail, // quiz
- &.llms-failed,
- &.llms-expired,
- &.llms-cancelled,
- &.llms-txn-failed {
- color: darken( $color-red, 40 );
- background-color: $color-red;
- }
-
- &.llms-incomplete, // assignment
- &.llms-on-hold,
- &.llms-pending,
- &.llms-pending-cancel,
- &.llms-refunded,
- &.llms-txn-pending,
- &.llms-txn-refunded {
- color: darken( orange, 30 );
- background-color: orange;
- }
-
- }
-
-}
diff --git a/assets/scss/_includes/_quiz-result-question-list.scss b/assets/scss/_includes/_quiz-result-question-list.scss
deleted file mode 100644
index fecef04a9a..0000000000
--- a/assets/scss/_includes/_quiz-result-question-list.scss
+++ /dev/null
@@ -1,134 +0,0 @@
-.llms-quiz-attempt-results {
- margin: 0;
- padding: 0;
- list-style-type: none;
-
- .llms-quiz-attempt-question {
- background: #efefef;
- margin: 0 0 10px;
- position: relative;
- list-style-type: none;
- .toggle-answer {
- @include clearfix();
- color: inherit;
- display: block;
- padding: 10px 35px 10px 10px;
- text-decoration: none;
- }
-
- &.status--waiting.correct,
- &.status--waiting.incorrect {
- background: rgba( $color-orange, 0.2 );
- .llms-status-icon {
- background-color: $color-orange;
- }
- }
-
- &.status--graded.correct {
- background: rgba( $color-green, 0.2 );
- .llms-status-icon {
- background-color: $color-green;
- }
- }
- &.status--graded.incorrect {
- background: rgba( $color-red, 0.2 );
- .llms-status-icon {
- background-color: $color-red;
- }
- }
- pre {
- overflow: auto;
- }
- .llms-question-title {
- float: left;
- margin: 0;
- line-height: 1;
- }
-
- .llms-points {
- float: right;
- line-height: 1;
- }
-
- .llms-status-icon-tip {
- position: absolute;
- right: -12px;
- top: -2px;
- }
-
- .llms-status-icon {
- color: rgba( 255, 255, 255, 0.65 );
- border-radius: 50%;
- font-size: 30px;
- height: 40px;
- line-height: 40px;
- text-align: center;
- width: 40px;
- }
-
- .llms-quiz-attempt-question-main {
- display: none;
- padding: 0 10px 10px;
-
- .llms-quiz-results-label {
- font-weight: 700;
- margin-bottom: 10px;
- }
-
- ul.llms-quiz-attempt-answers {
- margin: 0;
- padding: 0;
- li.llms-quiz-attempt-answer {
- padding: 0;
- margin: 0 0 0 30px;
- &:only-child {
- list-style-type: none;
- margin-left: 0;
- }
- }
- }
-
- img {
- height: auto;
- max-width: 200px;
- }
-
- .llms-quiz-attempt-answer-section {
- border-top: 2px solid rgba( #fff, 0.5 );
- margin-top: 20px;
- padding-top: 20px;
- &:first-child {
- border-top: none;
- margin-top: 0;
- padding-top: 0;
- }
- }
-
- }
-
- &.type--picture_choice,
- &.type--picture_reorder {
- ul.llms-quiz-attempt-answers {
- list-style-type: none;
- margin: 0;
- padding: 0;
-
- li.llms-quiz-attempt-answer {
- display: inline-block;
- list-style-type: none;
- margin: 0;
- padding: 5px;
- }
- }
- }
-
- &.type--removed {
- .llms-question-title {
- font-style: italic;
- font-weight: normal;
- }
- opacity: .5;
- }
-
- }
-}
diff --git a/assets/scss/_includes/_tooltip.scss b/assets/scss/_includes/_tooltip.scss
deleted file mode 100644
index 4f7b39613e..0000000000
--- a/assets/scss/_includes/_tooltip.scss
+++ /dev/null
@@ -1,135 +0,0 @@
-.lifterlms, // Settings & Course Builder.
-.llms-metabox, // Some Metaboxes.
-.llms-mb-container, // Other Metaboxes.
-.llms-quiz-wrapper { // Quiz results.
-
- [data-tip],
- [data-title-default],
- [data-title-active] {
-
- $bgcolor: #444;
-
- position: relative;
-
- &.tip--top-right {
- &:before {
- bottom: 100%;
- left: -10px;
- }
- &:hover:before {
- bottom: calc( 100% + 6px );
- }
- &:after {
- border-top-color: $bgcolor;
- left: 6px;
- top: 0;
- }
- &:hover:after {
- top: -6px;
- }
- }
-
-
- &.tip--top-left {
- &:before {
- bottom: 100%;
- right: -10px;
- }
- &:hover:before {
- bottom: calc( 100% + 6px );
- }
- &:after {
- border-top-color: $bgcolor;
- right: 6px;
- top: 0;
- }
- &:hover:after {
- top: -6px;
- }
- }
-
-
-
- &.tip--bottom-left {
- &:before {
- top: 100%;
- right: -10px;
- }
- &:hover:before {
- top: calc( 100% + 6px );
- }
- &:after {
- border-bottom-color: $bgcolor;
- right: 6px;
- bottom: 0;
- }
- &:hover:after {
- bottom: -6px;
- }
- }
-
- &.tip--bottom-right {
- &:before {
- top: 100%;
- left: -10px;
- }
- &:hover:before {
- top: calc( 100% + 6px );
- }
- &:after {
- border-bottom-color: $bgcolor;
- left: 6px;
- bottom: 0;
- }
- &:hover:after {
- bottom: -6px;
- }
- }
-
- &:before {
- background: $bgcolor;
- border-radius: 4px;
- color: #fff;
- font-size: 13px;
- line-height: 1.2;
- padding: 8px;
- max-width: 300px;
- width: max-content;
- }
- &:after {
- content: '';
- border: 6px solid transparent;
- height: 0;
- width: 0;
- }
-
- &:before,
- &:after {
- opacity: 0;
- transition: all 0.2s 0.1s ease;
- position: absolute;
- pointer-events: none;
- visibility: hidden;
- }
- &:hover:before,
- &:hover:after {
- opacity: 1;
- transition: all 0 0.1s ease;
- visibility: visible;
- z-index: 99999999;
- }
-
- }
-
- [data-tip] {
- &:before {
- content: attr(data-tip);
- }
- }
- [data-tip].active {
- &:before {
- content: attr(data-tip-active);
- }
- }
-
-}
diff --git a/assets/scss/_includes/_vars-brand-colors.scss b/assets/scss/_includes/_vars-brand-colors.scss
deleted file mode 100644
index 9f4660b944..0000000000
--- a/assets/scss/_includes/_vars-brand-colors.scss
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// LifterLMS Brand Colors
-// Currently overrides brand colors on the admin panel
-//
-
-$color-brand-blue: #466dd8;
-$color-brand-blue-dark: darken( $color-brand-blue, 8 );
-$color-brand-dark-blue: darken( $color-brand-blue, 24 );
-$color-brand-blue-light: lighten( $color-brand-blue, 8 );
-
-$color-brand-orange: #f8954f;
-$color-brand-orange-dark: #f67d28;
-$color-brand-orange-light: lighten( $color-brand-orange, 8 );
-
-$color-brand-aqua: #17bebb;
-
-$color-brand-pink: #ef476f;
-
-$color-blue: $color-brand-blue;
diff --git a/assets/scss/_includes/_vars.scss b/assets/scss/_includes/_vars.scss
deleted file mode 100644
index 57eda78b9f..0000000000
--- a/assets/scss/_includes/_vars.scss
+++ /dev/null
@@ -1,69 +0,0 @@
-// ----- LifterLMS Brand Colors ----- \\
-$color-brand-dark-blue: #243c56;
-
-$color-brand-blue: #2295ff;
-$color-brand-blue-dark: darken( $color-brand-blue, 12 ); // #0077e4
-$color-brand-blue-light: lighten( $color-brand-blue, 8 );
-
-$color-brand-orange: #f8954f;
-$color-brand-orange-dark: #f67d28;
-$color-brand-orange-light: lighten( $color-brand-orange, 8 );
-
-$color-brand-aqua: #17bebb;
-
-$color-brand-pink: #ef476f;
-
-
-
-// ----- name our versions of common colors ----- \\
-$color-black: #010101;
-$color-green: #83c373;
-$color-blue: $color-brand-blue;
-$color-red: #e5554e;
-$color-white: #fefefe;
-$color-aqua: #35bbaa;
-$color-purple: #845ef7;
-$color-orange: #ff922b;
-
-$color-red-hover: darken($color-red,5);
-
-
-// ----- state / action names ----- \\
-$color-success: $color-green;
-$color-danger: $color-red;
-
-
-
-
-
-
-
-
-$color-lightgrey: #ccc;
-$color-grey: #999;
-$color-darkgrey: #666;
-$color-cinder: #444;
-$color-lightblue: #33b1cb;
-$color-darkblue: #0185a3;
-
-
-
-
-
-
-
-
-
-
-
-
-$color-border: #efefef;
-
-$el-box-shadow: 0 1px 2px 0 rgba($color-black,0.4);
-$el-background: #f1f1f1;
-$el-background-hover: #eaeaea;
-
-$break-xsmall: 320px;
-$break-small: 641px;
-$break-medium: 768px;
-$break-large: 1024px;
diff --git a/assets/scss/_includes/vendor/_font-awesome.scss b/assets/scss/_includes/vendor/_font-awesome.scss
deleted file mode 100644
index ee906a8196..0000000000
--- a/assets/scss/_includes/vendor/_font-awesome.scss
+++ /dev/null
@@ -1,2337 +0,0 @@
-/*!
- * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
- * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
- */
-/* FONT PATH
- * -------------------------- */
-@font-face {
- font-family: 'FontAwesome';
- src: url('../fonts/fontawesome-webfont.eot?v=4.7.0');
- src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');
- font-weight: normal;
- font-style: normal;
-}
-.fa {
- display: inline-block;
- font: normal normal normal 14px/1 FontAwesome;
- font-size: inherit;
- text-rendering: auto;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-/* makes the font 33% larger relative to the icon container */
-.fa-lg {
- font-size: 1.33333333em;
- line-height: 0.75em;
- vertical-align: -15%;
-}
-.fa-2x {
- font-size: 2em;
-}
-.fa-3x {
- font-size: 3em;
-}
-.fa-4x {
- font-size: 4em;
-}
-.fa-5x {
- font-size: 5em;
-}
-.fa-fw {
- width: 1.28571429em;
- text-align: center;
-}
-.fa-ul {
- padding-left: 0;
- margin-left: 2.14285714em;
- list-style-type: none;
-}
-.fa-ul > li {
- position: relative;
-}
-.fa-li {
- position: absolute;
- left: -2.14285714em;
- width: 2.14285714em;
- top: 0.14285714em;
- text-align: center;
-}
-.fa-li.fa-lg {
- left: -1.85714286em;
-}
-.fa-border {
- padding: .2em .25em .15em;
- border: solid 0.08em #eeeeee;
- border-radius: .1em;
-}
-.fa-pull-left {
- float: left;
-}
-.fa-pull-right {
- float: right;
-}
-.fa.fa-pull-left {
- margin-right: .3em;
-}
-.fa.fa-pull-right {
- margin-left: .3em;
-}
-/* Deprecated as of 4.4.0 */
-.pull-right {
- float: right;
-}
-.pull-left {
- float: left;
-}
-.fa.pull-left {
- margin-right: .3em;
-}
-.fa.pull-right {
- margin-left: .3em;
-}
-.fa-spin {
- -webkit-animation: fa-spin 2s infinite linear;
- animation: fa-spin 2s infinite linear;
-}
-.fa-pulse {
- -webkit-animation: fa-spin 1s infinite steps(8);
- animation: fa-spin 1s infinite steps(8);
-}
-@-webkit-keyframes fa-spin {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(359deg);
- transform: rotate(359deg);
- }
-}
-@keyframes fa-spin {
- 0% {
- -webkit-transform: rotate(0deg);
- transform: rotate(0deg);
- }
- 100% {
- -webkit-transform: rotate(359deg);
- transform: rotate(359deg);
- }
-}
-.fa-rotate-90 {
- -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
- -webkit-transform: rotate(90deg);
- -ms-transform: rotate(90deg);
- transform: rotate(90deg);
-}
-.fa-rotate-180 {
- -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
- -webkit-transform: rotate(180deg);
- -ms-transform: rotate(180deg);
- transform: rotate(180deg);
-}
-.fa-rotate-270 {
- -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
- -webkit-transform: rotate(270deg);
- -ms-transform: rotate(270deg);
- transform: rotate(270deg);
-}
-.fa-flip-horizontal {
- -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
- -webkit-transform: scale(-1, 1);
- -ms-transform: scale(-1, 1);
- transform: scale(-1, 1);
-}
-.fa-flip-vertical {
- -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
- -webkit-transform: scale(1, -1);
- -ms-transform: scale(1, -1);
- transform: scale(1, -1);
-}
-:root .fa-rotate-90,
-:root .fa-rotate-180,
-:root .fa-rotate-270,
-:root .fa-flip-horizontal,
-:root .fa-flip-vertical {
- filter: none;
-}
-.fa-stack {
- position: relative;
- display: inline-block;
- width: 2em;
- height: 2em;
- line-height: 2em;
- vertical-align: middle;
-}
-.fa-stack-1x,
-.fa-stack-2x {
- position: absolute;
- left: 0;
- width: 100%;
- text-align: center;
-}
-.fa-stack-1x {
- line-height: inherit;
-}
-.fa-stack-2x {
- font-size: 2em;
-}
-.fa-inverse {
- color: #ffffff;
-}
-/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
- readers do not read off random characters that represent icons */
-.fa-glass:before {
- content: "\f000";
-}
-.fa-music:before {
- content: "\f001";
-}
-.fa-search:before {
- content: "\f002";
-}
-.fa-envelope-o:before {
- content: "\f003";
-}
-.fa-heart:before {
- content: "\f004";
-}
-.fa-star:before {
- content: "\f005";
-}
-.fa-star-o:before {
- content: "\f006";
-}
-.fa-user:before {
- content: "\f007";
-}
-.fa-film:before {
- content: "\f008";
-}
-.fa-th-large:before {
- content: "\f009";
-}
-.fa-th:before {
- content: "\f00a";
-}
-.fa-th-list:before {
- content: "\f00b";
-}
-.fa-check:before {
- content: "\f00c";
-}
-.fa-remove:before,
-.fa-close:before,
-.fa-times:before {
- content: "\f00d";
-}
-.fa-search-plus:before {
- content: "\f00e";
-}
-.fa-search-minus:before {
- content: "\f010";
-}
-.fa-power-off:before {
- content: "\f011";
-}
-.fa-signal:before {
- content: "\f012";
-}
-.fa-gear:before,
-.fa-cog:before {
- content: "\f013";
-}
-.fa-trash-o:before {
- content: "\f014";
-}
-.fa-home:before {
- content: "\f015";
-}
-.fa-file-o:before {
- content: "\f016";
-}
-.fa-clock-o:before {
- content: "\f017";
-}
-.fa-road:before {
- content: "\f018";
-}
-.fa-download:before {
- content: "\f019";
-}
-.fa-arrow-circle-o-down:before {
- content: "\f01a";
-}
-.fa-arrow-circle-o-up:before {
- content: "\f01b";
-}
-.fa-inbox:before {
- content: "\f01c";
-}
-.fa-play-circle-o:before {
- content: "\f01d";
-}
-.fa-rotate-right:before,
-.fa-repeat:before {
- content: "\f01e";
-}
-.fa-refresh:before {
- content: "\f021";
-}
-.fa-list-alt:before {
- content: "\f022";
-}
-.fa-lock:before {
- content: "\f023";
-}
-.fa-flag:before {
- content: "\f024";
-}
-.fa-headphones:before {
- content: "\f025";
-}
-.fa-volume-off:before {
- content: "\f026";
-}
-.fa-volume-down:before {
- content: "\f027";
-}
-.fa-volume-up:before {
- content: "\f028";
-}
-.fa-qrcode:before {
- content: "\f029";
-}
-.fa-barcode:before {
- content: "\f02a";
-}
-.fa-tag:before {
- content: "\f02b";
-}
-.fa-tags:before {
- content: "\f02c";
-}
-.fa-book:before {
- content: "\f02d";
-}
-.fa-bookmark:before {
- content: "\f02e";
-}
-.fa-print:before {
- content: "\f02f";
-}
-.fa-camera:before {
- content: "\f030";
-}
-.fa-font:before {
- content: "\f031";
-}
-.fa-bold:before {
- content: "\f032";
-}
-.fa-italic:before {
- content: "\f033";
-}
-.fa-text-height:before {
- content: "\f034";
-}
-.fa-text-width:before {
- content: "\f035";
-}
-.fa-align-left:before {
- content: "\f036";
-}
-.fa-align-center:before {
- content: "\f037";
-}
-.fa-align-right:before {
- content: "\f038";
-}
-.fa-align-justify:before {
- content: "\f039";
-}
-.fa-list:before {
- content: "\f03a";
-}
-.fa-dedent:before,
-.fa-outdent:before {
- content: "\f03b";
-}
-.fa-indent:before {
- content: "\f03c";
-}
-.fa-video-camera:before {
- content: "\f03d";
-}
-.fa-photo:before,
-.fa-image:before,
-.fa-picture-o:before {
- content: "\f03e";
-}
-.fa-pencil:before {
- content: "\f040";
-}
-.fa-map-marker:before {
- content: "\f041";
-}
-.fa-adjust:before {
- content: "\f042";
-}
-.fa-tint:before {
- content: "\f043";
-}
-.fa-edit:before,
-.fa-pencil-square-o:before {
- content: "\f044";
-}
-.fa-share-square-o:before {
- content: "\f045";
-}
-.fa-check-square-o:before {
- content: "\f046";
-}
-.fa-arrows:before {
- content: "\f047";
-}
-.fa-step-backward:before {
- content: "\f048";
-}
-.fa-fast-backward:before {
- content: "\f049";
-}
-.fa-backward:before {
- content: "\f04a";
-}
-.fa-play:before {
- content: "\f04b";
-}
-.fa-pause:before {
- content: "\f04c";
-}
-.fa-stop:before {
- content: "\f04d";
-}
-.fa-forward:before {
- content: "\f04e";
-}
-.fa-fast-forward:before {
- content: "\f050";
-}
-.fa-step-forward:before {
- content: "\f051";
-}
-.fa-eject:before {
- content: "\f052";
-}
-.fa-chevron-left:before {
- content: "\f053";
-}
-.fa-chevron-right:before {
- content: "\f054";
-}
-.fa-plus-circle:before {
- content: "\f055";
-}
-.fa-minus-circle:before {
- content: "\f056";
-}
-.fa-times-circle:before {
- content: "\f057";
-}
-.fa-check-circle:before {
- content: "\f058";
-}
-.fa-question-circle:before {
- content: "\f059";
-}
-.fa-info-circle:before {
- content: "\f05a";
-}
-.fa-crosshairs:before {
- content: "\f05b";
-}
-.fa-times-circle-o:before {
- content: "\f05c";
-}
-.fa-check-circle-o:before {
- content: "\f05d";
-}
-.fa-ban:before {
- content: "\f05e";
-}
-.fa-arrow-left:before {
- content: "\f060";
-}
-.fa-arrow-right:before {
- content: "\f061";
-}
-.fa-arrow-up:before {
- content: "\f062";
-}
-.fa-arrow-down:before {
- content: "\f063";
-}
-.fa-mail-forward:before,
-.fa-share:before {
- content: "\f064";
-}
-.fa-expand:before {
- content: "\f065";
-}
-.fa-compress:before {
- content: "\f066";
-}
-.fa-plus:before {
- content: "\f067";
-}
-.fa-minus:before {
- content: "\f068";
-}
-.fa-asterisk:before {
- content: "\f069";
-}
-.fa-exclamation-circle:before {
- content: "\f06a";
-}
-.fa-gift:before {
- content: "\f06b";
-}
-.fa-leaf:before {
- content: "\f06c";
-}
-.fa-fire:before {
- content: "\f06d";
-}
-.fa-eye:before {
- content: "\f06e";
-}
-.fa-eye-slash:before {
- content: "\f070";
-}
-.fa-warning:before,
-.fa-exclamation-triangle:before {
- content: "\f071";
-}
-.fa-plane:before {
- content: "\f072";
-}
-.fa-calendar:before {
- content: "\f073";
-}
-.fa-random:before {
- content: "\f074";
-}
-.fa-comment:before {
- content: "\f075";
-}
-.fa-magnet:before {
- content: "\f076";
-}
-.fa-chevron-up:before {
- content: "\f077";
-}
-.fa-chevron-down:before {
- content: "\f078";
-}
-.fa-retweet:before {
- content: "\f079";
-}
-.fa-shopping-cart:before {
- content: "\f07a";
-}
-.fa-folder:before {
- content: "\f07b";
-}
-.fa-folder-open:before {
- content: "\f07c";
-}
-.fa-arrows-v:before {
- content: "\f07d";
-}
-.fa-arrows-h:before {
- content: "\f07e";
-}
-.fa-bar-chart-o:before,
-.fa-bar-chart:before {
- content: "\f080";
-}
-.fa-twitter-square:before {
- content: "\f081";
-}
-.fa-facebook-square:before {
- content: "\f082";
-}
-.fa-camera-retro:before {
- content: "\f083";
-}
-.fa-key:before {
- content: "\f084";
-}
-.fa-gears:before,
-.fa-cogs:before {
- content: "\f085";
-}
-.fa-comments:before {
- content: "\f086";
-}
-.fa-thumbs-o-up:before {
- content: "\f087";
-}
-.fa-thumbs-o-down:before {
- content: "\f088";
-}
-.fa-star-half:before {
- content: "\f089";
-}
-.fa-heart-o:before {
- content: "\f08a";
-}
-.fa-sign-out:before {
- content: "\f08b";
-}
-.fa-linkedin-square:before {
- content: "\f08c";
-}
-.fa-thumb-tack:before {
- content: "\f08d";
-}
-.fa-external-link:before {
- content: "\f08e";
-}
-.fa-sign-in:before {
- content: "\f090";
-}
-.fa-trophy:before {
- content: "\f091";
-}
-.fa-github-square:before {
- content: "\f092";
-}
-.fa-upload:before {
- content: "\f093";
-}
-.fa-lemon-o:before {
- content: "\f094";
-}
-.fa-phone:before {
- content: "\f095";
-}
-.fa-square-o:before {
- content: "\f096";
-}
-.fa-bookmark-o:before {
- content: "\f097";
-}
-.fa-phone-square:before {
- content: "\f098";
-}
-.fa-twitter:before {
- content: "\f099";
-}
-.fa-facebook-f:before,
-.fa-facebook:before {
- content: "\f09a";
-}
-.fa-github:before {
- content: "\f09b";
-}
-.fa-unlock:before {
- content: "\f09c";
-}
-.fa-credit-card:before {
- content: "\f09d";
-}
-.fa-feed:before,
-.fa-rss:before {
- content: "\f09e";
-}
-.fa-hdd-o:before {
- content: "\f0a0";
-}
-.fa-bullhorn:before {
- content: "\f0a1";
-}
-.fa-bell:before {
- content: "\f0f3";
-}
-.fa-certificate:before {
- content: "\f0a3";
-}
-.fa-hand-o-right:before {
- content: "\f0a4";
-}
-.fa-hand-o-left:before {
- content: "\f0a5";
-}
-.fa-hand-o-up:before {
- content: "\f0a6";
-}
-.fa-hand-o-down:before {
- content: "\f0a7";
-}
-.fa-arrow-circle-left:before {
- content: "\f0a8";
-}
-.fa-arrow-circle-right:before {
- content: "\f0a9";
-}
-.fa-arrow-circle-up:before {
- content: "\f0aa";
-}
-.fa-arrow-circle-down:before {
- content: "\f0ab";
-}
-.fa-globe:before {
- content: "\f0ac";
-}
-.fa-wrench:before {
- content: "\f0ad";
-}
-.fa-tasks:before {
- content: "\f0ae";
-}
-.fa-filter:before {
- content: "\f0b0";
-}
-.fa-briefcase:before {
- content: "\f0b1";
-}
-.fa-arrows-alt:before {
- content: "\f0b2";
-}
-.fa-group:before,
-.fa-users:before {
- content: "\f0c0";
-}
-.fa-chain:before,
-.fa-link:before {
- content: "\f0c1";
-}
-.fa-cloud:before {
- content: "\f0c2";
-}
-.fa-flask:before {
- content: "\f0c3";
-}
-.fa-cut:before,
-.fa-scissors:before {
- content: "\f0c4";
-}
-.fa-copy:before,
-.fa-files-o:before {
- content: "\f0c5";
-}
-.fa-paperclip:before {
- content: "\f0c6";
-}
-.fa-save:before,
-.fa-floppy-o:before {
- content: "\f0c7";
-}
-.fa-square:before {
- content: "\f0c8";
-}
-.fa-navicon:before,
-.fa-reorder:before,
-.fa-bars:before {
- content: "\f0c9";
-}
-.fa-list-ul:before {
- content: "\f0ca";
-}
-.fa-list-ol:before {
- content: "\f0cb";
-}
-.fa-strikethrough:before {
- content: "\f0cc";
-}
-.fa-underline:before {
- content: "\f0cd";
-}
-.fa-table:before {
- content: "\f0ce";
-}
-.fa-magic:before {
- content: "\f0d0";
-}
-.fa-truck:before {
- content: "\f0d1";
-}
-.fa-pinterest:before {
- content: "\f0d2";
-}
-.fa-pinterest-square:before {
- content: "\f0d3";
-}
-.fa-google-plus-square:before {
- content: "\f0d4";
-}
-.fa-google-plus:before {
- content: "\f0d5";
-}
-.fa-money:before {
- content: "\f0d6";
-}
-.fa-caret-down:before {
- content: "\f0d7";
-}
-.fa-caret-up:before {
- content: "\f0d8";
-}
-.fa-caret-left:before {
- content: "\f0d9";
-}
-.fa-caret-right:before {
- content: "\f0da";
-}
-.fa-columns:before {
- content: "\f0db";
-}
-.fa-unsorted:before,
-.fa-sort:before {
- content: "\f0dc";
-}
-.fa-sort-down:before,
-.fa-sort-desc:before {
- content: "\f0dd";
-}
-.fa-sort-up:before,
-.fa-sort-asc:before {
- content: "\f0de";
-}
-.fa-envelope:before {
- content: "\f0e0";
-}
-.fa-linkedin:before {
- content: "\f0e1";
-}
-.fa-rotate-left:before,
-.fa-undo:before {
- content: "\f0e2";
-}
-.fa-legal:before,
-.fa-gavel:before {
- content: "\f0e3";
-}
-.fa-dashboard:before,
-.fa-tachometer:before {
- content: "\f0e4";
-}
-.fa-comment-o:before {
- content: "\f0e5";
-}
-.fa-comments-o:before {
- content: "\f0e6";
-}
-.fa-flash:before,
-.fa-bolt:before {
- content: "\f0e7";
-}
-.fa-sitemap:before {
- content: "\f0e8";
-}
-.fa-umbrella:before {
- content: "\f0e9";
-}
-.fa-paste:before,
-.fa-clipboard:before {
- content: "\f0ea";
-}
-.fa-lightbulb-o:before {
- content: "\f0eb";
-}
-.fa-exchange:before {
- content: "\f0ec";
-}
-.fa-cloud-download:before {
- content: "\f0ed";
-}
-.fa-cloud-upload:before {
- content: "\f0ee";
-}
-.fa-user-md:before {
- content: "\f0f0";
-}
-.fa-stethoscope:before {
- content: "\f0f1";
-}
-.fa-suitcase:before {
- content: "\f0f2";
-}
-.fa-bell-o:before {
- content: "\f0a2";
-}
-.fa-coffee:before {
- content: "\f0f4";
-}
-.fa-cutlery:before {
- content: "\f0f5";
-}
-.fa-file-text-o:before {
- content: "\f0f6";
-}
-.fa-building-o:before {
- content: "\f0f7";
-}
-.fa-hospital-o:before {
- content: "\f0f8";
-}
-.fa-ambulance:before {
- content: "\f0f9";
-}
-.fa-medkit:before {
- content: "\f0fa";
-}
-.fa-fighter-jet:before {
- content: "\f0fb";
-}
-.fa-beer:before {
- content: "\f0fc";
-}
-.fa-h-square:before {
- content: "\f0fd";
-}
-.fa-plus-square:before {
- content: "\f0fe";
-}
-.fa-angle-double-left:before {
- content: "\f100";
-}
-.fa-angle-double-right:before {
- content: "\f101";
-}
-.fa-angle-double-up:before {
- content: "\f102";
-}
-.fa-angle-double-down:before {
- content: "\f103";
-}
-.fa-angle-left:before {
- content: "\f104";
-}
-.fa-angle-right:before {
- content: "\f105";
-}
-.fa-angle-up:before {
- content: "\f106";
-}
-.fa-angle-down:before {
- content: "\f107";
-}
-.fa-desktop:before {
- content: "\f108";
-}
-.fa-laptop:before {
- content: "\f109";
-}
-.fa-tablet:before {
- content: "\f10a";
-}
-.fa-mobile-phone:before,
-.fa-mobile:before {
- content: "\f10b";
-}
-.fa-circle-o:before {
- content: "\f10c";
-}
-.fa-quote-left:before {
- content: "\f10d";
-}
-.fa-quote-right:before {
- content: "\f10e";
-}
-.fa-spinner:before {
- content: "\f110";
-}
-.fa-circle:before {
- content: "\f111";
-}
-.fa-mail-reply:before,
-.fa-reply:before {
- content: "\f112";
-}
-.fa-github-alt:before {
- content: "\f113";
-}
-.fa-folder-o:before {
- content: "\f114";
-}
-.fa-folder-open-o:before {
- content: "\f115";
-}
-.fa-smile-o:before {
- content: "\f118";
-}
-.fa-frown-o:before {
- content: "\f119";
-}
-.fa-meh-o:before {
- content: "\f11a";
-}
-.fa-gamepad:before {
- content: "\f11b";
-}
-.fa-keyboard-o:before {
- content: "\f11c";
-}
-.fa-flag-o:before {
- content: "\f11d";
-}
-.fa-flag-checkered:before {
- content: "\f11e";
-}
-.fa-terminal:before {
- content: "\f120";
-}
-.fa-code:before {
- content: "\f121";
-}
-.fa-mail-reply-all:before,
-.fa-reply-all:before {
- content: "\f122";
-}
-.fa-star-half-empty:before,
-.fa-star-half-full:before,
-.fa-star-half-o:before {
- content: "\f123";
-}
-.fa-location-arrow:before {
- content: "\f124";
-}
-.fa-crop:before {
- content: "\f125";
-}
-.fa-code-fork:before {
- content: "\f126";
-}
-.fa-unlink:before,
-.fa-chain-broken:before {
- content: "\f127";
-}
-.fa-question:before {
- content: "\f128";
-}
-.fa-info:before {
- content: "\f129";
-}
-.fa-exclamation:before {
- content: "\f12a";
-}
-.fa-superscript:before {
- content: "\f12b";
-}
-.fa-subscript:before {
- content: "\f12c";
-}
-.fa-eraser:before {
- content: "\f12d";
-}
-.fa-puzzle-piece:before {
- content: "\f12e";
-}
-.fa-microphone:before {
- content: "\f130";
-}
-.fa-microphone-slash:before {
- content: "\f131";
-}
-.fa-shield:before {
- content: "\f132";
-}
-.fa-calendar-o:before {
- content: "\f133";
-}
-.fa-fire-extinguisher:before {
- content: "\f134";
-}
-.fa-rocket:before {
- content: "\f135";
-}
-.fa-maxcdn:before {
- content: "\f136";
-}
-.fa-chevron-circle-left:before {
- content: "\f137";
-}
-.fa-chevron-circle-right:before {
- content: "\f138";
-}
-.fa-chevron-circle-up:before {
- content: "\f139";
-}
-.fa-chevron-circle-down:before {
- content: "\f13a";
-}
-.fa-html5:before {
- content: "\f13b";
-}
-.fa-css3:before {
- content: "\f13c";
-}
-.fa-anchor:before {
- content: "\f13d";
-}
-.fa-unlock-alt:before {
- content: "\f13e";
-}
-.fa-bullseye:before {
- content: "\f140";
-}
-.fa-ellipsis-h:before {
- content: "\f141";
-}
-.fa-ellipsis-v:before {
- content: "\f142";
-}
-.fa-rss-square:before {
- content: "\f143";
-}
-.fa-play-circle:before {
- content: "\f144";
-}
-.fa-ticket:before {
- content: "\f145";
-}
-.fa-minus-square:before {
- content: "\f146";
-}
-.fa-minus-square-o:before {
- content: "\f147";
-}
-.fa-level-up:before {
- content: "\f148";
-}
-.fa-level-down:before {
- content: "\f149";
-}
-.fa-check-square:before {
- content: "\f14a";
-}
-.fa-pencil-square:before {
- content: "\f14b";
-}
-.fa-external-link-square:before {
- content: "\f14c";
-}
-.fa-share-square:before {
- content: "\f14d";
-}
-.fa-compass:before {
- content: "\f14e";
-}
-.fa-toggle-down:before,
-.fa-caret-square-o-down:before {
- content: "\f150";
-}
-.fa-toggle-up:before,
-.fa-caret-square-o-up:before {
- content: "\f151";
-}
-.fa-toggle-right:before,
-.fa-caret-square-o-right:before {
- content: "\f152";
-}
-.fa-euro:before,
-.fa-eur:before {
- content: "\f153";
-}
-.fa-gbp:before {
- content: "\f154";
-}
-.fa-dollar:before,
-.fa-usd:before {
- content: "\f155";
-}
-.fa-rupee:before,
-.fa-inr:before {
- content: "\f156";
-}
-.fa-cny:before,
-.fa-rmb:before,
-.fa-yen:before,
-.fa-jpy:before {
- content: "\f157";
-}
-.fa-ruble:before,
-.fa-rouble:before,
-.fa-rub:before {
- content: "\f158";
-}
-.fa-won:before,
-.fa-krw:before {
- content: "\f159";
-}
-.fa-bitcoin:before,
-.fa-btc:before {
- content: "\f15a";
-}
-.fa-file:before {
- content: "\f15b";
-}
-.fa-file-text:before {
- content: "\f15c";
-}
-.fa-sort-alpha-asc:before {
- content: "\f15d";
-}
-.fa-sort-alpha-desc:before {
- content: "\f15e";
-}
-.fa-sort-amount-asc:before {
- content: "\f160";
-}
-.fa-sort-amount-desc:before {
- content: "\f161";
-}
-.fa-sort-numeric-asc:before {
- content: "\f162";
-}
-.fa-sort-numeric-desc:before {
- content: "\f163";
-}
-.fa-thumbs-up:before {
- content: "\f164";
-}
-.fa-thumbs-down:before {
- content: "\f165";
-}
-.fa-youtube-square:before {
- content: "\f166";
-}
-.fa-youtube:before {
- content: "\f167";
-}
-.fa-xing:before {
- content: "\f168";
-}
-.fa-xing-square:before {
- content: "\f169";
-}
-.fa-youtube-play:before {
- content: "\f16a";
-}
-.fa-dropbox:before {
- content: "\f16b";
-}
-.fa-stack-overflow:before {
- content: "\f16c";
-}
-.fa-instagram:before {
- content: "\f16d";
-}
-.fa-flickr:before {
- content: "\f16e";
-}
-.fa-adn:before {
- content: "\f170";
-}
-.fa-bitbucket:before {
- content: "\f171";
-}
-.fa-bitbucket-square:before {
- content: "\f172";
-}
-.fa-tumblr:before {
- content: "\f173";
-}
-.fa-tumblr-square:before {
- content: "\f174";
-}
-.fa-long-arrow-down:before {
- content: "\f175";
-}
-.fa-long-arrow-up:before {
- content: "\f176";
-}
-.fa-long-arrow-left:before {
- content: "\f177";
-}
-.fa-long-arrow-right:before {
- content: "\f178";
-}
-.fa-apple:before {
- content: "\f179";
-}
-.fa-windows:before {
- content: "\f17a";
-}
-.fa-android:before {
- content: "\f17b";
-}
-.fa-linux:before {
- content: "\f17c";
-}
-.fa-dribbble:before {
- content: "\f17d";
-}
-.fa-skype:before {
- content: "\f17e";
-}
-.fa-foursquare:before {
- content: "\f180";
-}
-.fa-trello:before {
- content: "\f181";
-}
-.fa-female:before {
- content: "\f182";
-}
-.fa-male:before {
- content: "\f183";
-}
-.fa-gittip:before,
-.fa-gratipay:before {
- content: "\f184";
-}
-.fa-sun-o:before {
- content: "\f185";
-}
-.fa-moon-o:before {
- content: "\f186";
-}
-.fa-archive:before {
- content: "\f187";
-}
-.fa-bug:before {
- content: "\f188";
-}
-.fa-vk:before {
- content: "\f189";
-}
-.fa-weibo:before {
- content: "\f18a";
-}
-.fa-renren:before {
- content: "\f18b";
-}
-.fa-pagelines:before {
- content: "\f18c";
-}
-.fa-stack-exchange:before {
- content: "\f18d";
-}
-.fa-arrow-circle-o-right:before {
- content: "\f18e";
-}
-.fa-arrow-circle-o-left:before {
- content: "\f190";
-}
-.fa-toggle-left:before,
-.fa-caret-square-o-left:before {
- content: "\f191";
-}
-.fa-dot-circle-o:before {
- content: "\f192";
-}
-.fa-wheelchair:before {
- content: "\f193";
-}
-.fa-vimeo-square:before {
- content: "\f194";
-}
-.fa-turkish-lira:before,
-.fa-try:before {
- content: "\f195";
-}
-.fa-plus-square-o:before {
- content: "\f196";
-}
-.fa-space-shuttle:before {
- content: "\f197";
-}
-.fa-slack:before {
- content: "\f198";
-}
-.fa-envelope-square:before {
- content: "\f199";
-}
-.fa-wordpress:before {
- content: "\f19a";
-}
-.fa-openid:before {
- content: "\f19b";
-}
-.fa-institution:before,
-.fa-bank:before,
-.fa-university:before {
- content: "\f19c";
-}
-.fa-mortar-board:before,
-.fa-graduation-cap:before {
- content: "\f19d";
-}
-.fa-yahoo:before {
- content: "\f19e";
-}
-.fa-google:before {
- content: "\f1a0";
-}
-.fa-reddit:before {
- content: "\f1a1";
-}
-.fa-reddit-square:before {
- content: "\f1a2";
-}
-.fa-stumbleupon-circle:before {
- content: "\f1a3";
-}
-.fa-stumbleupon:before {
- content: "\f1a4";
-}
-.fa-delicious:before {
- content: "\f1a5";
-}
-.fa-digg:before {
- content: "\f1a6";
-}
-.fa-pied-piper-pp:before {
- content: "\f1a7";
-}
-.fa-pied-piper-alt:before {
- content: "\f1a8";
-}
-.fa-drupal:before {
- content: "\f1a9";
-}
-.fa-joomla:before {
- content: "\f1aa";
-}
-.fa-language:before {
- content: "\f1ab";
-}
-.fa-fax:before {
- content: "\f1ac";
-}
-.fa-building:before {
- content: "\f1ad";
-}
-.fa-child:before {
- content: "\f1ae";
-}
-.fa-paw:before {
- content: "\f1b0";
-}
-.fa-spoon:before {
- content: "\f1b1";
-}
-.fa-cube:before {
- content: "\f1b2";
-}
-.fa-cubes:before {
- content: "\f1b3";
-}
-.fa-behance:before {
- content: "\f1b4";
-}
-.fa-behance-square:before {
- content: "\f1b5";
-}
-.fa-steam:before {
- content: "\f1b6";
-}
-.fa-steam-square:before {
- content: "\f1b7";
-}
-.fa-recycle:before {
- content: "\f1b8";
-}
-.fa-automobile:before,
-.fa-car:before {
- content: "\f1b9";
-}
-.fa-cab:before,
-.fa-taxi:before {
- content: "\f1ba";
-}
-.fa-tree:before {
- content: "\f1bb";
-}
-.fa-spotify:before {
- content: "\f1bc";
-}
-.fa-deviantart:before {
- content: "\f1bd";
-}
-.fa-soundcloud:before {
- content: "\f1be";
-}
-.fa-database:before {
- content: "\f1c0";
-}
-.fa-file-pdf-o:before {
- content: "\f1c1";
-}
-.fa-file-word-o:before {
- content: "\f1c2";
-}
-.fa-file-excel-o:before {
- content: "\f1c3";
-}
-.fa-file-powerpoint-o:before {
- content: "\f1c4";
-}
-.fa-file-photo-o:before,
-.fa-file-picture-o:before,
-.fa-file-image-o:before {
- content: "\f1c5";
-}
-.fa-file-zip-o:before,
-.fa-file-archive-o:before {
- content: "\f1c6";
-}
-.fa-file-sound-o:before,
-.fa-file-audio-o:before {
- content: "\f1c7";
-}
-.fa-file-movie-o:before,
-.fa-file-video-o:before {
- content: "\f1c8";
-}
-.fa-file-code-o:before {
- content: "\f1c9";
-}
-.fa-vine:before {
- content: "\f1ca";
-}
-.fa-codepen:before {
- content: "\f1cb";
-}
-.fa-jsfiddle:before {
- content: "\f1cc";
-}
-.fa-life-bouy:before,
-.fa-life-buoy:before,
-.fa-life-saver:before,
-.fa-support:before,
-.fa-life-ring:before {
- content: "\f1cd";
-}
-.fa-circle-o-notch:before {
- content: "\f1ce";
-}
-.fa-ra:before,
-.fa-resistance:before,
-.fa-rebel:before {
- content: "\f1d0";
-}
-.fa-ge:before,
-.fa-empire:before {
- content: "\f1d1";
-}
-.fa-git-square:before {
- content: "\f1d2";
-}
-.fa-git:before {
- content: "\f1d3";
-}
-.fa-y-combinator-square:before,
-.fa-yc-square:before,
-.fa-hacker-news:before {
- content: "\f1d4";
-}
-.fa-tencent-weibo:before {
- content: "\f1d5";
-}
-.fa-qq:before {
- content: "\f1d6";
-}
-.fa-wechat:before,
-.fa-weixin:before {
- content: "\f1d7";
-}
-.fa-send:before,
-.fa-paper-plane:before {
- content: "\f1d8";
-}
-.fa-send-o:before,
-.fa-paper-plane-o:before {
- content: "\f1d9";
-}
-.fa-history:before {
- content: "\f1da";
-}
-.fa-circle-thin:before {
- content: "\f1db";
-}
-.fa-header:before {
- content: "\f1dc";
-}
-.fa-paragraph:before {
- content: "\f1dd";
-}
-.fa-sliders:before {
- content: "\f1de";
-}
-.fa-share-alt:before {
- content: "\f1e0";
-}
-.fa-share-alt-square:before {
- content: "\f1e1";
-}
-.fa-bomb:before {
- content: "\f1e2";
-}
-.fa-soccer-ball-o:before,
-.fa-futbol-o:before {
- content: "\f1e3";
-}
-.fa-tty:before {
- content: "\f1e4";
-}
-.fa-binoculars:before {
- content: "\f1e5";
-}
-.fa-plug:before {
- content: "\f1e6";
-}
-.fa-slideshare:before {
- content: "\f1e7";
-}
-.fa-twitch:before {
- content: "\f1e8";
-}
-.fa-yelp:before {
- content: "\f1e9";
-}
-.fa-newspaper-o:before {
- content: "\f1ea";
-}
-.fa-wifi:before {
- content: "\f1eb";
-}
-.fa-calculator:before {
- content: "\f1ec";
-}
-.fa-paypal:before {
- content: "\f1ed";
-}
-.fa-google-wallet:before {
- content: "\f1ee";
-}
-.fa-cc-visa:before {
- content: "\f1f0";
-}
-.fa-cc-mastercard:before {
- content: "\f1f1";
-}
-.fa-cc-discover:before {
- content: "\f1f2";
-}
-.fa-cc-amex:before {
- content: "\f1f3";
-}
-.fa-cc-paypal:before {
- content: "\f1f4";
-}
-.fa-cc-stripe:before {
- content: "\f1f5";
-}
-.fa-bell-slash:before {
- content: "\f1f6";
-}
-.fa-bell-slash-o:before {
- content: "\f1f7";
-}
-.fa-trash:before {
- content: "\f1f8";
-}
-.fa-copyright:before {
- content: "\f1f9";
-}
-.fa-at:before {
- content: "\f1fa";
-}
-.fa-eyedropper:before {
- content: "\f1fb";
-}
-.fa-paint-brush:before {
- content: "\f1fc";
-}
-.fa-birthday-cake:before {
- content: "\f1fd";
-}
-.fa-area-chart:before {
- content: "\f1fe";
-}
-.fa-pie-chart:before {
- content: "\f200";
-}
-.fa-line-chart:before {
- content: "\f201";
-}
-.fa-lastfm:before {
- content: "\f202";
-}
-.fa-lastfm-square:before {
- content: "\f203";
-}
-.fa-toggle-off:before {
- content: "\f204";
-}
-.fa-toggle-on:before {
- content: "\f205";
-}
-.fa-bicycle:before {
- content: "\f206";
-}
-.fa-bus:before {
- content: "\f207";
-}
-.fa-ioxhost:before {
- content: "\f208";
-}
-.fa-angellist:before {
- content: "\f209";
-}
-.fa-cc:before {
- content: "\f20a";
-}
-.fa-shekel:before,
-.fa-sheqel:before,
-.fa-ils:before {
- content: "\f20b";
-}
-.fa-meanpath:before {
- content: "\f20c";
-}
-.fa-buysellads:before {
- content: "\f20d";
-}
-.fa-connectdevelop:before {
- content: "\f20e";
-}
-.fa-dashcube:before {
- content: "\f210";
-}
-.fa-forumbee:before {
- content: "\f211";
-}
-.fa-leanpub:before {
- content: "\f212";
-}
-.fa-sellsy:before {
- content: "\f213";
-}
-.fa-shirtsinbulk:before {
- content: "\f214";
-}
-.fa-simplybuilt:before {
- content: "\f215";
-}
-.fa-skyatlas:before {
- content: "\f216";
-}
-.fa-cart-plus:before {
- content: "\f217";
-}
-.fa-cart-arrow-down:before {
- content: "\f218";
-}
-.fa-diamond:before {
- content: "\f219";
-}
-.fa-ship:before {
- content: "\f21a";
-}
-.fa-user-secret:before {
- content: "\f21b";
-}
-.fa-motorcycle:before {
- content: "\f21c";
-}
-.fa-street-view:before {
- content: "\f21d";
-}
-.fa-heartbeat:before {
- content: "\f21e";
-}
-.fa-venus:before {
- content: "\f221";
-}
-.fa-mars:before {
- content: "\f222";
-}
-.fa-mercury:before {
- content: "\f223";
-}
-.fa-intersex:before,
-.fa-transgender:before {
- content: "\f224";
-}
-.fa-transgender-alt:before {
- content: "\f225";
-}
-.fa-venus-double:before {
- content: "\f226";
-}
-.fa-mars-double:before {
- content: "\f227";
-}
-.fa-venus-mars:before {
- content: "\f228";
-}
-.fa-mars-stroke:before {
- content: "\f229";
-}
-.fa-mars-stroke-v:before {
- content: "\f22a";
-}
-.fa-mars-stroke-h:before {
- content: "\f22b";
-}
-.fa-neuter:before {
- content: "\f22c";
-}
-.fa-genderless:before {
- content: "\f22d";
-}
-.fa-facebook-official:before {
- content: "\f230";
-}
-.fa-pinterest-p:before {
- content: "\f231";
-}
-.fa-whatsapp:before {
- content: "\f232";
-}
-.fa-server:before {
- content: "\f233";
-}
-.fa-user-plus:before {
- content: "\f234";
-}
-.fa-user-times:before {
- content: "\f235";
-}
-.fa-hotel:before,
-.fa-bed:before {
- content: "\f236";
-}
-.fa-viacoin:before {
- content: "\f237";
-}
-.fa-train:before {
- content: "\f238";
-}
-.fa-subway:before {
- content: "\f239";
-}
-.fa-medium:before {
- content: "\f23a";
-}
-.fa-yc:before,
-.fa-y-combinator:before {
- content: "\f23b";
-}
-.fa-optin-monster:before {
- content: "\f23c";
-}
-.fa-opencart:before {
- content: "\f23d";
-}
-.fa-expeditedssl:before {
- content: "\f23e";
-}
-.fa-battery-4:before,
-.fa-battery:before,
-.fa-battery-full:before {
- content: "\f240";
-}
-.fa-battery-3:before,
-.fa-battery-three-quarters:before {
- content: "\f241";
-}
-.fa-battery-2:before,
-.fa-battery-half:before {
- content: "\f242";
-}
-.fa-battery-1:before,
-.fa-battery-quarter:before {
- content: "\f243";
-}
-.fa-battery-0:before,
-.fa-battery-empty:before {
- content: "\f244";
-}
-.fa-mouse-pointer:before {
- content: "\f245";
-}
-.fa-i-cursor:before {
- content: "\f246";
-}
-.fa-object-group:before {
- content: "\f247";
-}
-.fa-object-ungroup:before {
- content: "\f248";
-}
-.fa-sticky-note:before {
- content: "\f249";
-}
-.fa-sticky-note-o:before {
- content: "\f24a";
-}
-.fa-cc-jcb:before {
- content: "\f24b";
-}
-.fa-cc-diners-club:before {
- content: "\f24c";
-}
-.fa-clone:before {
- content: "\f24d";
-}
-.fa-balance-scale:before {
- content: "\f24e";
-}
-.fa-hourglass-o:before {
- content: "\f250";
-}
-.fa-hourglass-1:before,
-.fa-hourglass-start:before {
- content: "\f251";
-}
-.fa-hourglass-2:before,
-.fa-hourglass-half:before {
- content: "\f252";
-}
-.fa-hourglass-3:before,
-.fa-hourglass-end:before {
- content: "\f253";
-}
-.fa-hourglass:before {
- content: "\f254";
-}
-.fa-hand-grab-o:before,
-.fa-hand-rock-o:before {
- content: "\f255";
-}
-.fa-hand-stop-o:before,
-.fa-hand-paper-o:before {
- content: "\f256";
-}
-.fa-hand-scissors-o:before {
- content: "\f257";
-}
-.fa-hand-lizard-o:before {
- content: "\f258";
-}
-.fa-hand-spock-o:before {
- content: "\f259";
-}
-.fa-hand-pointer-o:before {
- content: "\f25a";
-}
-.fa-hand-peace-o:before {
- content: "\f25b";
-}
-.fa-trademark:before {
- content: "\f25c";
-}
-.fa-registered:before {
- content: "\f25d";
-}
-.fa-creative-commons:before {
- content: "\f25e";
-}
-.fa-gg:before {
- content: "\f260";
-}
-.fa-gg-circle:before {
- content: "\f261";
-}
-.fa-tripadvisor:before {
- content: "\f262";
-}
-.fa-odnoklassniki:before {
- content: "\f263";
-}
-.fa-odnoklassniki-square:before {
- content: "\f264";
-}
-.fa-get-pocket:before {
- content: "\f265";
-}
-.fa-wikipedia-w:before {
- content: "\f266";
-}
-.fa-safari:before {
- content: "\f267";
-}
-.fa-chrome:before {
- content: "\f268";
-}
-.fa-firefox:before {
- content: "\f269";
-}
-.fa-opera:before {
- content: "\f26a";
-}
-.fa-internet-explorer:before {
- content: "\f26b";
-}
-.fa-tv:before,
-.fa-television:before {
- content: "\f26c";
-}
-.fa-contao:before {
- content: "\f26d";
-}
-.fa-500px:before {
- content: "\f26e";
-}
-.fa-amazon:before {
- content: "\f270";
-}
-.fa-calendar-plus-o:before {
- content: "\f271";
-}
-.fa-calendar-minus-o:before {
- content: "\f272";
-}
-.fa-calendar-times-o:before {
- content: "\f273";
-}
-.fa-calendar-check-o:before {
- content: "\f274";
-}
-.fa-industry:before {
- content: "\f275";
-}
-.fa-map-pin:before {
- content: "\f276";
-}
-.fa-map-signs:before {
- content: "\f277";
-}
-.fa-map-o:before {
- content: "\f278";
-}
-.fa-map:before {
- content: "\f279";
-}
-.fa-commenting:before {
- content: "\f27a";
-}
-.fa-commenting-o:before {
- content: "\f27b";
-}
-.fa-houzz:before {
- content: "\f27c";
-}
-.fa-vimeo:before {
- content: "\f27d";
-}
-.fa-black-tie:before {
- content: "\f27e";
-}
-.fa-fonticons:before {
- content: "\f280";
-}
-.fa-reddit-alien:before {
- content: "\f281";
-}
-.fa-edge:before {
- content: "\f282";
-}
-.fa-credit-card-alt:before {
- content: "\f283";
-}
-.fa-codiepie:before {
- content: "\f284";
-}
-.fa-modx:before {
- content: "\f285";
-}
-.fa-fort-awesome:before {
- content: "\f286";
-}
-.fa-usb:before {
- content: "\f287";
-}
-.fa-product-hunt:before {
- content: "\f288";
-}
-.fa-mixcloud:before {
- content: "\f289";
-}
-.fa-scribd:before {
- content: "\f28a";
-}
-.fa-pause-circle:before {
- content: "\f28b";
-}
-.fa-pause-circle-o:before {
- content: "\f28c";
-}
-.fa-stop-circle:before {
- content: "\f28d";
-}
-.fa-stop-circle-o:before {
- content: "\f28e";
-}
-.fa-shopping-bag:before {
- content: "\f290";
-}
-.fa-shopping-basket:before {
- content: "\f291";
-}
-.fa-hashtag:before {
- content: "\f292";
-}
-.fa-bluetooth:before {
- content: "\f293";
-}
-.fa-bluetooth-b:before {
- content: "\f294";
-}
-.fa-percent:before {
- content: "\f295";
-}
-.fa-gitlab:before {
- content: "\f296";
-}
-.fa-wpbeginner:before {
- content: "\f297";
-}
-.fa-wpforms:before {
- content: "\f298";
-}
-.fa-envira:before {
- content: "\f299";
-}
-.fa-universal-access:before {
- content: "\f29a";
-}
-.fa-wheelchair-alt:before {
- content: "\f29b";
-}
-.fa-question-circle-o:before {
- content: "\f29c";
-}
-.fa-blind:before {
- content: "\f29d";
-}
-.fa-audio-description:before {
- content: "\f29e";
-}
-.fa-volume-control-phone:before {
- content: "\f2a0";
-}
-.fa-braille:before {
- content: "\f2a1";
-}
-.fa-assistive-listening-systems:before {
- content: "\f2a2";
-}
-.fa-asl-interpreting:before,
-.fa-american-sign-language-interpreting:before {
- content: "\f2a3";
-}
-.fa-deafness:before,
-.fa-hard-of-hearing:before,
-.fa-deaf:before {
- content: "\f2a4";
-}
-.fa-glide:before {
- content: "\f2a5";
-}
-.fa-glide-g:before {
- content: "\f2a6";
-}
-.fa-signing:before,
-.fa-sign-language:before {
- content: "\f2a7";
-}
-.fa-low-vision:before {
- content: "\f2a8";
-}
-.fa-viadeo:before {
- content: "\f2a9";
-}
-.fa-viadeo-square:before {
- content: "\f2aa";
-}
-.fa-snapchat:before {
- content: "\f2ab";
-}
-.fa-snapchat-ghost:before {
- content: "\f2ac";
-}
-.fa-snapchat-square:before {
- content: "\f2ad";
-}
-.fa-pied-piper:before {
- content: "\f2ae";
-}
-.fa-first-order:before {
- content: "\f2b0";
-}
-.fa-yoast:before {
- content: "\f2b1";
-}
-.fa-themeisle:before {
- content: "\f2b2";
-}
-.fa-google-plus-circle:before,
-.fa-google-plus-official:before {
- content: "\f2b3";
-}
-.fa-fa:before,
-.fa-font-awesome:before {
- content: "\f2b4";
-}
-.fa-handshake-o:before {
- content: "\f2b5";
-}
-.fa-envelope-open:before {
- content: "\f2b6";
-}
-.fa-envelope-open-o:before {
- content: "\f2b7";
-}
-.fa-linode:before {
- content: "\f2b8";
-}
-.fa-address-book:before {
- content: "\f2b9";
-}
-.fa-address-book-o:before {
- content: "\f2ba";
-}
-.fa-vcard:before,
-.fa-address-card:before {
- content: "\f2bb";
-}
-.fa-vcard-o:before,
-.fa-address-card-o:before {
- content: "\f2bc";
-}
-.fa-user-circle:before {
- content: "\f2bd";
-}
-.fa-user-circle-o:before {
- content: "\f2be";
-}
-.fa-user-o:before {
- content: "\f2c0";
-}
-.fa-id-badge:before {
- content: "\f2c1";
-}
-.fa-drivers-license:before,
-.fa-id-card:before {
- content: "\f2c2";
-}
-.fa-drivers-license-o:before,
-.fa-id-card-o:before {
- content: "\f2c3";
-}
-.fa-quora:before {
- content: "\f2c4";
-}
-.fa-free-code-camp:before {
- content: "\f2c5";
-}
-.fa-telegram:before {
- content: "\f2c6";
-}
-.fa-thermometer-4:before,
-.fa-thermometer:before,
-.fa-thermometer-full:before {
- content: "\f2c7";
-}
-.fa-thermometer-3:before,
-.fa-thermometer-three-quarters:before {
- content: "\f2c8";
-}
-.fa-thermometer-2:before,
-.fa-thermometer-half:before {
- content: "\f2c9";
-}
-.fa-thermometer-1:before,
-.fa-thermometer-quarter:before {
- content: "\f2ca";
-}
-.fa-thermometer-0:before,
-.fa-thermometer-empty:before {
- content: "\f2cb";
-}
-.fa-shower:before {
- content: "\f2cc";
-}
-.fa-bathtub:before,
-.fa-s15:before,
-.fa-bath:before {
- content: "\f2cd";
-}
-.fa-podcast:before {
- content: "\f2ce";
-}
-.fa-window-maximize:before {
- content: "\f2d0";
-}
-.fa-window-minimize:before {
- content: "\f2d1";
-}
-.fa-window-restore:before {
- content: "\f2d2";
-}
-.fa-times-rectangle:before,
-.fa-window-close:before {
- content: "\f2d3";
-}
-.fa-times-rectangle-o:before,
-.fa-window-close-o:before {
- content: "\f2d4";
-}
-.fa-bandcamp:before {
- content: "\f2d5";
-}
-.fa-grav:before {
- content: "\f2d6";
-}
-.fa-etsy:before {
- content: "\f2d7";
-}
-.fa-imdb:before {
- content: "\f2d8";
-}
-.fa-ravelry:before {
- content: "\f2d9";
-}
-.fa-eercast:before {
- content: "\f2da";
-}
-.fa-microchip:before {
- content: "\f2db";
-}
-.fa-snowflake-o:before {
- content: "\f2dc";
-}
-.fa-superpowers:before {
- content: "\f2dd";
-}
-.fa-wpexplorer:before {
- content: "\f2de";
-}
-.fa-meetup:before {
- content: "\f2e0";
-}
-.sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- border: 0;
-}
-.sr-only-focusable:active,
-.sr-only-focusable:focus {
- position: static;
- width: auto;
- height: auto;
- margin: 0;
- overflow: visible;
- clip: auto;
-}
diff --git a/assets/scss/admin-importer.scss b/assets/scss/admin-importer.scss
deleted file mode 100644
index 5585932e5c..0000000000
--- a/assets/scss/admin-importer.scss
+++ /dev/null
@@ -1,105 +0,0 @@
-@import "_includes/vars";
-@import "_includes/mixins";
-
-.llms-import-file-wrap {
- background: #fafafa;
- border: 1px solid #ccd0d4;
- padding: 10px;
- margin: 20px auto;
- display: inline-flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.llms-cloud-import-help.button-link {
- color: inherit;
- vertical-align: top;
- text-decoration: none;
-}
-
-.wrap.llms-import-export {
-
- ul.llms-importable-courses {
- display: flex;
- flex-direction: column;
- flex-wrap: wrap;
- gap: 20px;
-
- @media only screen and ( min-width: 680px ) {
- flex-direction: row;
- }
-
- .llms-importable-course {
- background-color: #FFF;
- border: 1px solid #dedede;
- border-radius: 12px;
- box-shadow: 0px 0px 1px rgba(48, 49, 51, 0.05), 0px 2px 4px rgba(48, 49, 51, 0.1);
- list-style: none;
- margin: 20px 0;
- overflow: hidden;
-
- @media only screen and ( min-width: 680px ) {
- -webkit-box-flex: 0;
- flex-grow: 0;
- flex-shrink: 1;
- flex-basis: calc(31%);
- }
-
- img {
- display: block;
- max-width: 100%;
- }
-
- h3 {
- color: #1d2327;
- font-size: 20px;
- line-height: 1.5;
- margin: 30px 20px 15px;
- }
-
- p {
- font-size: 15px;
- line-height: 1.5;
- margin: 0 20px 15px;
- }
-
- &.has-action-button {
- padding-bottom: 10px;
- position: relative;
- .button {
- bottom: 20px;
- right: 20px;
- position: absolute;
- }
- }
-
- }
-
- }
-
- @media only screen and (min-width: 600px) {
- ul.llms-importable-courses {
- display: flex;
- flex-wrap: wrap;
- }
- }
-
- @media only screen and (min-width: 600px) and (max-width: 767px) {
- ul.llms-importable-courses {
- li.llms-importable-course {
- flex: 1 0 calc( 50% - 20px );
- max-width: calc( 50% - 20px );
- }
- }
- }
-
- @media only screen and (min-width: 768px) {
- ul.llms-importable-courses {
- li.llms-importable-course {
- flex: 1 0 calc( 33% - 20px );
- max-width: calc( 33% - 20px );
- }
- }
- }
-
-}
diff --git a/assets/scss/admin-wizard.scss b/assets/scss/admin-wizard.scss
deleted file mode 100644
index f35241514e..0000000000
--- a/assets/scss/admin-wizard.scss
+++ /dev/null
@@ -1,322 +0,0 @@
-@import "_includes/vars";
-
-$input_padding: 0.3em 0.6em;
-
-#wpadminbar, #adminmenumain, #wpfooter {
- display: none;
-}
-
-#llms-setup-wizard {
-
- background-color: #F0F0F1;
- height: 100%;
- left: 0;
- overflow: scroll;
- position: fixed;
- top: 0;
- width: 100%;
-
-}
-
-.llms-setup-wrapper {
- margin: 30px auto;
- max-width: 640px;
-}
-
-#llms-logo {
- text-align: center;
-
- a {
- display: inline-block;
- }
-
- img {
- max-width: 200px;
- }
-
-}
-
-.llms-setup-content {
- background-color: #FFF;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
- padding: 30px;
-
- h1, h2, h3, h4, h5, h6 {
- color: #3C434A;
- }
-
- a:not( .llms-button-primary ):not( .llms-button-secondary ) {
- color: $color-brand-blue;
- }
-
- p, li {
- color: #3C434A;
- font-size: 16px;
- }
-
- p.error {
- color: $color-red;
- background: rgba($color-red,0.1);
- border: 1px solid currentColor;
- padding: 1em;
- border-radius: 4px;
- margin: 1.5em 0 0;
- font-size: 15px;
- text-align: left;
- }
-
- label {
- font-weight: 500;
- }
-
- table {
- border-bottom: 1px solid #f1f1f1;
- border-collapse: collapse;
- width: 100%;
- }
-
- td {
- border-top: 1px solid #f1f1f1;
-
- &:first-child {
- padding-right: 10px;
- width: 33%;
-
- a {
- font-size: 16px;
- font-weight: 500;
- }
-
- i {
- display: block;
- font-size: 13px;
- margin-top: 10px;
-
- a {
- font-size: 13px;
- }
- }
-
- }
-
- }
-
- small {
- font-size: small;
- }
-
- .has-checkbox-field {
- display: flex;
- flex-direction: row-reverse;
- justify-content: flex-end;
- align-items: center;
- align-content: center;
- gap: 0.5em;
- font-weight: normal;
- font-size: 16px;
-
- input {
- margin: 0;
- }
- }
-
- label {
- font-size: 16px;
- display: block;
- margin-bottom: 0.5em;
- }
-
- small + label {
- margin-top: 1em;
- }
-
- [type=text] {
- width: 100%;
- clear: both;
- margin: 0;
- padding: $input_padding;
- }
-
- .has-date-field {
- font-size: 16px;
- display: flex;
- margin-bottom: 0.5em;
- flex-wrap: wrap;
- flex-direction: column;
- gap: 0.5em;
- }
-
- [type=date] {
- padding: $input_padding;
- }
-
- .is-hidden {
- display: none;
- }
-}
-
-.llms-setup-date-fields {
- display: flex;
- justify-content: space-between;
- gap: 1em;
- padding-top: 2em;
-}
-
-.llms-setup-date-field {
- flex: 1;
-}
-
-.llms-setup-actions {
- margin-top: 2em;
- text-align: right;
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 1em;
-
- .llms-button-primary {
- display: inline-block;
-
- &:after {
- content: "\f054";
- font-family: 'FontAwesome';
- font-weight: 900;
- padding-left: 10px;
- }
-
- }
-}
-
-.llms-exit-setup {
- color: inherit !important;
- margin-right: 10px;
-
- + .llms-button-primary,
- + .llms-button-secondary {
- margin-left: auto;
- }
-}
-
-.llms-importing-msgs {
- a {
- color: $color-brand-blue;
- }
-
- .llms-importing-msg {
- display: none;
- font-size: 14px;
- font-style: italic;
- text-align: right;
- }
-}
-
-ul.llms-importable-courses {
- border-bottom: 1px solid #f1f1f1;
- display: block;
-
- li.llms-importable-course {
- border-top: 1px solid #f1f1f1;
- display: block;
- margin: 0;
- max-width: 100%;
- padding: 20px 40px 20px 0;
-
- h3 {
- margin: 0;
- }
-
- p {
- margin: 10px 0 0 0;
- }
-
- label {
- font-weight: 400;
- }
-
- img {
- float: left;
- margin-right: 15px;
- width: 22%;
- }
-
- .llms-switch {
- float: right;
- right: -40px;
-
- input.llms-toggle-round:checked + label {
- border-color: $color-brand-blue;
- background-color: $color-brand-blue;
- }
-
- }
-
- }
-}
-
-.llms-setup-progress {
- display: flex;
- margin: 20px 0;
-
- li {
- border-bottom: 4px solid $color-brand-blue;
- display: inline-block;
- font-size: 14px;
- padding-bottom: 10px;
- position: relative;
- text-align: center;
- flex: 1;
-
- a {
- color: $color-brand-blue;
- text-decoration: none;
- }
-
- &:after {
- background: $color-brand-blue;
- bottom: 0;
- content: '';
- border: 4px solid $color-brand-blue;
- border-radius: 100%;
- height: 4px;
- position: absolute;
- left: 50%;
- margin-left: -6px;
- margin-bottom: -8px;
- width: 4px;
- }
-
- &.current {
- font-weight: 700;
-
- &:after {
- background: #fff;
- }
- }
-
- &.current ~ li {
- border-bottom-color: #ccc;
-
- &:after {
- background: #ccc;
- border-color: #ccc;
- }
-
- a {
- color: #bbb;
- }
- }
-
- }
-}
-
-.llms-setup-wrapper {
-
- // Calendar icon for custom date fields.
- [type="text"].llms-datepicker {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' fill='%23ccc'%3E%3Cpath d='M152 24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H64C28.7 64 0 92.7 0 128v320c0 35.3 28.7 64 64 64h320c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64h-40V24c0-13.3-10.7-24-24-24s-24 10.7-24 24v40H152V24zM48 192h352v256c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16V192z'/%3E%3C/svg%3E");
- background-repeat: no-repeat;
- background-position: right 8px center;
- background-size: 16px 16px;
- padding-right: 40px;
- }
-}
diff --git a/assets/scss/admin.scss b/assets/scss/admin.scss
deleted file mode 100644
index 545fc80d5b..0000000000
--- a/assets/scss/admin.scss
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-// Main Admin CSS File
-//
-
-@import "_includes/vars";
-@import "_includes/vars-brand-colors";
-
-@import "_includes/extends";
-@import "_includes/buttons";
-@import "_includes/mixins";
-
-@import "_includes/tooltip";
-
-// wp menu item
-@import "admin/_wp-menu";
-
-// grid layout for breakpoints
-@import "admin/partials/grid";
-
-// forms
-@import "admin/modules/forms";
-
-// voucher
-@import "admin/modules/voucher";
-
-// widgets
-@import "admin/modules/widgets";
-
-// icons
-@import "admin/modules/icons";
-
-// icons
-@import "admin/modules/mb-tabs";
-
-// icons
-@import "admin/modules/top-modal";
-
-@import "admin/modules/merge-codes";
-
-// Base (mobile)
-@import "admin/breakpoints/base";
-
-// Larger mobile devices
-@media only screen and (min-width: 481px) {
- @import "admin/breakpoints/481up";
-}
-
-// Tablets and smaller laptops
-@media only screen and (min-width: 768px) {
- @import "admin/breakpoints/768up";
-}
-
-// Desktops
-@media only screen and (min-width: 1030px) {
- @import "admin/breakpoints/1030up";
-}
-
-// Larger Monitors and TVs
-@media only screen and (min-width: 1240px) {
- @import "admin/breakpoints/1240up";
-}
-
-@import "admin/main";
-
-@import "admin/llms-table";
-@import "admin/modules/llms-order-note";
-
-// metabox related
-@import "admin/metaboxes/llms-metabox";
-@import "admin/metaboxes/metabox-instructors";
-@import "admin/metaboxes/metabox-orders";
-@import "admin/metaboxes/metabox-engagements-type";
-@import "admin/metaboxes/metabox-product";
-@import "admin/metaboxes/metabox-students";
-@import "admin/metaboxes/metabox-field-repeater";
-@import "admin/metaboxes/builder-launcher";
-
-@import "admin/post-tables/llms_orders";
-@import "admin/post-tables/post-tables";
-
-@import "admin/tabs";
-@import "admin/fonts";
-@import "admin/reporting";
-
-@import "admin/settings";
-
-@import "admin/dashboard";
-@import "admin/dashboard-widget";
-@import "admin/resources";
-
-@import "admin/quiz-attempt-review";
-
-@import "_includes/llms-form-field";
-@import "_includes/vendor/_font-awesome";
diff --git a/assets/scss/admin/_course-builder.scss b/assets/scss/admin/_course-builder.scss
deleted file mode 100644
index 41a15927c9..0000000000
--- a/assets/scss/admin/_course-builder.scss
+++ /dev/null
@@ -1,1663 +0,0 @@
-body.admin_page_llms-course-builder {
- background: #fff;
-
- #adminmenumain { display: none; }
- #wpbody-content { padding-bottom: 0; }
- #wpfooter { display: none; }
-
- #wpcontent, #wpfooter {
- margin-left: 0;
- }
-
- .llms-button-secondary {
- .fa {
- margin-right: 5px;
- }
- }
-
- // &.folded {
- // .llms-course-builder {
- // left: 56px;
- // }
- // }
-
- .webui-popover {
- .select2-container--default {
- .select2-results__group {
- font-size: 16px;
- }
- .select2-results__option .select2-results__option {
- padding-left: 2em;
- }
- }
-
- }
-}
-
-.wrap.lifterlms.llms-builder {
- margin: 0;
- padding: 0;
- position: relative;
-
-
- &.editor-active {
- .llms-builder-sidebar {
- padding: 10px;
- width: calc( 100% - 200px );
- z-index: 3;
- }
- @media only screen and ( min-width: 1200px ) {
- .llms-builder-main {
- width: 560px;
- }
- .llms-builder-sidebar {
- width: calc( 100% - 640px );
- }
- }
- @media only screen and ( min-width: 1440px ) {
- .llms-builder-main {
- width: calc( 100% - 780px );
- }
- .llms-builder-sidebar {
- width: 720px;
- }
- }
- @media only screen and ( min-width: 1680px ) {
- .llms-builder-main {
- width: calc( 100% - 1000px );
- }
- .llms-builder-sidebar {
- width: 940px;
- }
- }
- @media only screen and ( max-width: 782px ) {
- .llms-builder-sidebar {
- position: absolute;
- top: 0;
- width: auto;
- }
- }
- }
-
- .llms-headline {
- display: inline-block;
- font-weight: 400;
- margin: 0;
- padding: 0;
- transition: width 0.3s ease-in-out;
- vertical-align: middle;
- }
-
- .llms-builder-main {
- padding: 30px 30px 30px 0;
- position: relative;
- width: calc( 100% - 450px );
- z-index: 2;
-
- .llms-action-icons {
- display: inline-block;
- position: relative;
- vertical-align: middle;
- button {
- background: none;
- color: inherit;
- border: none;
- padding: 0;
- cursor: pointer;
- outline: inherit;
- }
- }
-
- .llms-action-icons-lesson-id {
- vertical-align: top;
- }
-
- // Course
- .llms-course-header {
- align-items: center;
- display: flex;
- flex-wrap: wrap;
- gap: 15px;
- position: relative;
- z-index: 1;
- .llms-button-secondary {
- margin-right: 10px;
- }
- }
-
-
- // Sections
- ul.llms-sections {
- box-shadow: 0 0 0 3px transparent;
- min-height: 60px;
- padding: 10px 0;
- transition: box-shadow 0.6s ease, min-height 0.2s ease;
- &.dragging {
- box-shadow: 0 0 0 3px $color-brand-blue;
- }
- }
-
- li.llms-section {
- background: #fff;
- border: 1px solid #efefef;
- border-radius: 6px;
- box-shadow: 2px 2px 8px rgba( 0, 0, 0, 0.08 );
- position: relative;
- margin: 0 0 20px 0;
- padding: 0;
-
- > .llms-builder-header {
- align-items: center;
- display: flex;
- gap: 15px;
- justify-content: space-between;
- padding: 20px 10px 20px 30px;
- .llms-action-icons {
- align-items: center;
- display: flex;
- gap: 15px;
- justify-content: space-between;
- .llms-action-icons-right {
- display: flex;
- gap: 0px;
- margin-top: -5px;
- .llms-action-icon {
- padding: 5px 10px;
- }
- }
- .llms-action-icons-left {
- display: flex;
- gap: 15px;
- }
- @media only screen and ( min-width: 1200px ) {
- .llms-action-icons-right,
- .llms-action-icons-left {
- white-space: nowrap;
- }
- }
- }
- }
-
- &.expanded {
- .llms-lessons { overflow: visible; }
- }
- &.selected {
- .llms-drag-utility.drag-section {
- border-color: $color-brand-blue;
- }
- > .llms-builder-header .llms-headline {
- font-weight: 400;
- color: $color-brand-blue;
- }
- }
-
- > .llms-builder-footer {
- border-top: 1px solid #efefef;
- padding: 20px 20px 20px 30px;
- }
- }
-
- li.llms-section:first-child:before {
- top: 30px;
- }
-
- li.llms-section:last-child:before {
- bottom: 55px;
- }
-
- li.llms-section.expanded:last-child:before {
- bottom: 86px;
- }
-
- // Lessons
- ul.llms-lessons {
- box-shadow: 0 0 0 3px transparent;
- height: 0;
- margin: 0;
- padding: 0;
- transition: box-shadow 0.6s ease, min-height 0.2s ease;
- &.dragging {
- box-shadow: 0 0 0 3px $color-brand-blue;
- min-height: 60px;
- }
- &.expanded, // added via backbone view events
- &.drag-expanded { // added only during dragover events and ignores model attrs
- height: auto;
- li.llms-lesson {
- pointer-events: auto;
- visibility: visible;
- }
- }
-
- }
-
- li.llms-lesson {
- background: #fff;
- border-top: 1px solid #efefef;
- margin: 0;
- padding: 20px 10px 20px 30px;
- position: relative;
- pointer-events: none;
- visibility: hidden;
-
- &.selected {
- .llms-drag-utility.drag-lesson {
- border-color: $color-brand-blue;
- }
- > .llms-builder-header .llms-headline {
- color: $color-brand-blue;
- }
- }
-
- > .llms-builder-header {
- .llms-headline {
- font-weight: 700;
- margin-left: 10px;
- cursor: pointer;
- }
- align-items: center;
- display: flex;
- flex-wrap: wrap;
- gap: 15px;
- justify-content: space-between;
- .llms-action-icons {
- align-items: center;
- display: flex;
- flex: 1;
- gap: 15px;
- justify-content: space-between;
- .llms-action-icons-left {
- display: flex;
- gap: 15px;
- }
- .llms-action-icons-right {
- display: flex;
- gap: 0;
- margin-top: -5px;
- .llms-action-icon {
- padding: 5px 10px;
- }
- }
- @media only screen and ( min-width: 1200px ) {
- .llms-action-icons-right,
- .llms-action-icons-left {
- white-space: nowrap;
- }
- }
- }
-
- @media only screen and ( min-width: 1200px ) {
- > .llms-builder-header {
- flex-wrap: nowrap;
- }
- }
- }
-
- }
-
- // Drag Utilities
- li.llms-section .llms-drag-utility {
- background: #fff;
- border: 2px solid #ccc;
- border-radius: 50%;
- height: 10px;
- left: 13px;
- position: absolute;
- top: 24px;
- width: 10px;
- }
-
- li.llms-lesson .llms-drag-utility {
- height: 6px;
- left: 14px;
- top: 25px !important;
- width: 6px;
- }
-
- .llms-section:hover > .llms-drag-utility,
- .llms-lesson:hover > .llms-drag-utility {
- border-color: #fff;
- cursor: move;
- &:hover:after {
- color: $color-brand-blue;
- }
- &:after {
- background: #fff;
- content: '\00b7\00b7\A\00b7\00b7\A\00b7\00b7';
- color: #ccc;
- display: block;
- font-size: 36px;
- height: 29px;
- letter-spacing: -1px;
- line-height: 8px;
- left: -7px;
- position: absolute;
- text-align: center;
- top: -12px;
- width: 23px;
- }
- }
-
- // Sortable
- li.llms-section,
- li.llms-lesson {
- &.ui-sortable-helper,
- &.ui-draggable-dragging {
- border: 1px solid #ccc;
- background: #fff;
- transform: rotate( 2deg );
- visibility: visible !important;
- z-index: 999;
- }
-
- &.llms-sortable-placeholder {
- border: 3px dashed $color-brand-blue;
- background: rgba( $color-brand-blue, 0.3 );
- margin: 0 10px;
- padding: 5px;
- &:before { display: none; }
- }
- }
-
- ul.llms-sections > li.llms-lesson.ui-draggable-dragging .llms-drag-utility {
- position: relative;
- &:after {
- left: -35px;
- top: -28px;
- }
- }
-
- }
-
- // Editable
- .llms-input-wrapper {
- position: relative;
- }
-
- .llms-input-formatting.ql-container {
- font-size: inherit;
- font-family: inherit;
- .ql-editor.ql-blank::before {
- color: #a0a0a0;
- left: 8px;
- right: 8px;
- }
- .ql-editor {
- p {
- font-size: inherit;
- line-height: 1;
- }
- }
- .ql-tooltip {
- z-index: 1;
- }
- }
-
- .llms-input,
- .llms-input-formatting .ql-editor {
- border: none;
- border-bottom: 2px dotted transparent;
- box-shadow: none;
- cursor: text;
- display: inline-block;
- font-size: inherit;
- font-weight: 700;
- height: auto;
- line-height: 1;
- margin: 0 8px;
- min-width: 60px;
- padding: 0;
- transition: border 0.2s ease, box-shadow 0.2s ease;
- &:empty:before {
- color: #a0a0a0;
- content: attr( data-placeholder );
- }
- &:hover {
- border-bottom-color: $color-brand-blue;
- }
- &[disabled] {
- cursor: not-allowed;
- &:hover {
- border-bottom-color: transparent;
- }
- }
- &:focus {
- background: #fff;
- box-shadow: 0 0 0 4px #fff, 0 0 0 6px $color-brand-blue;
- border-bottom: none;
- outline: none;
- }
- b, strong {
- font-weight: 700;
- }
- &.standard {
- border: 1px solid #e6e6e6;
- margin: 2px;
- padding: 5px 3px;
- &:hover {
- border-color: #d6d6d6;
- }
- &:focus {
- box-shadow: 0 0 0 2px $color-brand-blue;
- }
- }
- &.permalink {
- display: none;
- }
- }
-
- .llms-input-formatting .ql-editor {
- padding: 0 1px;
- }
-
- .llms-label {
- font-weight: 500;
- .fa {
- color: #aaa;
- padding-left: 6px;
- }
- }
-
- // .llms-editable-image,
- // .llms-editable-video,
- // .llms-editable-editor {
- // }
-
- .llms-editable-editor {
- .llms-label {
- float: left;
- margin-right: 10px;
- position: relative;
- top: 10px;
- }
- textarea {
- border: none;
- padding: 10px;
- display: block;
- width: 100%;
- }
- }
-
- .llms-editable-image {
- button.llms-add-image {
- width: 130px;
- }
- .llms-image {
- display: inline-block;
- position: relative;
- &:hover .llms-action-icon {
- opacity: 1;
- }
- .llms-action-icon {
- color: #fff;
- font-size: 24px;
- opacity: 0;
- padding: 0;
- position: absolute;
- transition: opacity 0.2s ease;
- right: 3px;
- top: 1px;
- z-index: 1;
- }
- img {
- display: block;
- height: 100px;
- max-width: 100%;
- width: auto;
- }
- }
- }
-
- .llms-settings-field,
- .llms-editable-toggle-group {
- background: #f4f4f4;
- padding: 10px;
- position: relative;
- margin: 0 1px;
-
- &.has-label-after {
- align-items: center;
- display: flex;
- flex-wrap: wrap;
-
- .llms-label {
- min-width: 100%;
- }
- .llms-editable-input {
- flex: 2;
- }
- .llms-label--after {
- color: #888;
- min-width: auto;
- font-size: 85%;
- padding-left: 10px;
- }
- }
-
- .llms-switch {
- display: block;
- width: 100%;
- @include clearfix;
-
- .llms-label {
- width: calc( 100% - 34px );
- }
- }
-
- .llms-editable-image,
- .llms-editable-video,
- .llms-editable-editor {
- margin-top: 2px;
- }
-
- .llms-input.standard {
- display: block;
- width: 100%;
- &.two-digits,
- &.three-digits,
- &.four-digits {
- display: inline-block;
- }
- }
-
- }
-
- .llms-editable-number {
- .llms-input {
- color: #888;
- min-width: 30px;
- text-align: right;
- &.two-digits {
- width: 30px;
- }
- &.three-digits {
- width: 40px;
- }
- &.four-digits {
- width: 60px;
- }
- }
- small {
- color: #888;
- text-transform: uppercase;
- }
- }
-
- .llms-model-settings {
- background-color: #FFF;
- -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
- @include clearfix();
-
- .llms-settings-group-header {
- border-bottom: 1px solid #efefef;
- padding: 10px;
-
- .fa-caret-up { display: block; }
- .fa-caret-down { display: none; }
- }
- &.hidden {
- .llms-settings-group-header {
- .fa-caret-up { display: none; }
- .fa-caret-down { display: block; }
- }
- .llms-settings-group-body { display: none; }
- }
- }
-
- .llms-settings-group-header {
- @include clearfix();
- .llms-settings-group-title {
- display: inline-block;
- font-size: 16px;
- font-weight: 700;
- line-height: 1.5;
- margin: 0 5px;
- padding: 0;
- }
- .llms-settings-group-toggle {
- float: right;
- font-size: 18px;
- padding: 2px;
- }
- }
-
- .llms-settings-group-body {
- padding: 16px;
- }
-
- .llms-settings-row {
- display: flex;
- flex-wrap: wrap;
- margin: 2px 0;
-
- .llms-settings-field,
- .llms-editable-toggle-group {
- flex: 1;
- &:first-child {
- margin-left: 0;
- }
- &:last-child {
- margin-right: 0;
- }
- }
-
- .llms-breaker {
- margin: 2px 0;
- width: 100%;
- }
- }
-
- .llms-editable-select {
- margin: 2px 0;
- .select2-container--default.select2-container--focus .select2-selection--multiple {
- border-color: #aaa;
- }
- }
-
- .llms-editable-radio {
- label {
- display: block;
- }
- &.has-images {
- input { display: none; }
- label {
- display: inline-block;
- margin: 0 3px;
- }
- label > span {
- transition: background 0.2s ease;
- display: inline-block;
- padding: 3px;
- }
- img { display: block; }
- input:checked + span {
- background: $color-brand-blue;
- }
- }
- }
-
- .settings-field--disabled {
- opacity: 0.5;
- }
-
- // Icons
- .llms-action-icon {
- color: #666;
- display: inline-block;
- font-size: 13px;
- text-decoration: none;
- &:hover {
- color: $color-brand-blue;
- &.danger { color: $color-danger; }
- }
- &.circle {
- border: 2px solid #aaa;
- border-radius: 50%;
- font-size: 9px;
- height: 8px;
- line-height: 1;
- padding: 5px;
- text-align: center;
- width: 8px;
- &:hover {
- border-color: $color-brand-blue;
- &.danger {
- border-color: $color-danger;
- }
- }
- }
- }
-
- ul.llms-info-list {
- display: flex;
- flex-wrap: wrap;
- gap: 15px;
- margin: 10px 0 0 8px;
- padding: 0;
- li.llms-info-item {
- color: #666;
- font-size: 13px;
- margin: 0;
- &.active,
- &.active .llms-action-icon {
- color: $color-brand-blue;
- .fa {
- margin-right: 5px;
- }
- }
- button {
- background: none;
- color: inherit;
- border: none;
- padding: 0;
- cursor: pointer;
- outline: inherit;
- &:hover {
- text-decoration: underline;
- }
- }
- }
- }
-
- // Sidebar
- .llms-builder-sidebar {
- background: #e6e6e6;
- bottom: 0;
- overflow: hidden;
- padding: 30px;
- position: fixed;
- transition: width 0.3s ease-in-out;
- top: 32px;
- right: 0;
- width: 360px;
- z-index: 1;
-
- .llms-utilities {
-
- ul, li {
- margin: 0;
- padding: 0;
- }
-
- ul {
- display: flex;
- gap: 15px;
- li {
- flex: 1;
- &:last-child {
- margin-right: 0;
- }
- }
- }
-
- .llms-utility {
- background: #efefef;
- border: 1px solid #ccc;
- border-radius: 8px;
- color: inherit;
- cursor: pointer;
- display: block;
- overflow: hidden;
- padding: 6px 12px;
- position: relative;
- text-align: center;
- width: 100%;
-
- &:hover {
- background: #fefefe;
- }
-
- .fa {
- background: #848484;
- position: absolute;
- left: 0;
- top: 0;
- padding: 7px;
- color: #fff;
- }
- }
-
- }
-
- .llms-sidebar-headline {
- margin: 0 0 10px;
- font-size: 22px;
- }
-
- .llms-elements-list {
- margin-bottom: 30px;
- li {
- margin-bottom: 10px;
- }
- }
-
- .llms-utility {
- color: #444;
- text-decoration: none;
- }
-
- .llms-element-button {
- background: $color-brand-blue;
- border-radius: 8px;
- border: none;
- color: #fff;
- cursor: pointer;
- display: block;
- margin: 0;
- overflow: hidden;
- padding: 17px 20px;
- position: relative;
- transition: background 0.2s ease, color 0.2s ease;
- text-align: center;
- width: 100%;
-
- &:hover {
- background: $color-brand-blue-dark;
- }
-
- &.secondary {
- background: #efefef;
- color: #444;
- &:hover {
- background: #fefefe;
- }
- .fa {
- background: #848484;
- }
- }
-
- .fa {
- background: $color-brand-dark-blue;
- border-radius: 4px 0 0 4px;
- color: #fff;
- display: block;
- font-size: 20px;
- padding: 15px 20px;
- position: absolute;
- top: 0;
- left: 0;
- }
-
- &[disabled="disabled"] {
- opacity: 0.4;
- }
-
- &.small {
-
- padding: 8px 10px 8px 46px;
- .fa {
- font-size: 15px;
- padding: 9px 10px;
- width: 20px;
- }
-
- }
-
- &.right {
-
- &.small {
- padding-left: 10px;
- padding-right: 46px;
- }
-
- .fa {
- border-radius: 0 4px 4px 0;
- left: auto;
- right: 0;
- }
-
- }
-
- }
-
-
-
- .llms-editor {
- height: 100%;
- min-height: 100%;
- position: relative;
- }
-
- // .llms-builder-close-editor {
- // background: $color-brand-blue;
- // border: none;
- // border-radius: 50%;
- // color: #fff;
- // cursor: pointer;
- // display: inline-block;
- // font-size: 18px;
- // height: 30px;
- // margin: 0;
- // position: absolute;
- // right: 0;
- // text-align: center;
- // top: 3px;
- // width: 30px;
- // z-index: 3;
- // }
-
- .llms-editor-nav {
- background-color: $color-brand-dark-blue;
- margin: 0;
- padding: 8px 0 0 8px;
- font-size: 0;
- margin: -10px -10px 10px -10px;
- position: relative;
- z-index: 2;
-
- .llms-editor-menu {
- list-style-type: none;
- margin: 0;
- padding: 0;
- position: relative;
-
- .llms-editor-menu-item {
- display: inline-block;
- margin: 0 6px 0 0;
- padding: 0;
-
- > .llms-editor-menu {
- display: none;
- &:before {
- border: 8px solid transparent;
- border-left-color: #cacaca;
- content: '';
- position: absolute;
- top: 11px;
- left: 0;
- }
-
- .llms-editor-menu-item:hover > a,
- .llms-editor-menu-item.active > a {
- background: #dfdfdf;
- }
-
- }
-
- &:hover > a {
- background-color: $color-brand-blue;
- }
-
- &.active > a {
- background-color: #e6e6e6;
- color: $color-brand-blue;
- font-weight: 700;
-
- &:focus {
- box-shadow: none;
- }
- }
-
- &.active > .llms-editor-menu {
- display: inline-block;
- }
-
- a {
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- color: #FFF;
- display: inline-block;
- padding: 9px 18px;
- text-decoration: none;
- transition: background 0.2s ease;
- font-size: 15px;
- }
-
-
- &.right {
- float: right;
-
- a,
- &:hover {
- background: transparent;
- }
- }
-
- }
- }
- }
-
- .llms-editor-tab {
- display: none;
- height: calc( 100% - 90px );
- overflow: scroll;
- position: relative;
- z-index: 1;
- &.active {
- display: block;
-
- &.tab--quiz {
-
- display: flex;
- flex-direction: column;
-
- .llms-quiz-questions {
- flex: 1 0 auto;
- overflow: scroll;
-
- // groups
- .llms-quiz-questions {
- overflow: visible;
- }
- }
-
- }
- }
-
- }
-
-
- // .llms-builder-editor {
-
- // opacity: 0;
- // margin: 10px 0;
- // transition: opacity 0.2s linear;
-
- // &.ready {
- // opacity: 1;
- // }
-
- // textarea {
- // border: none;
- // display: block;
- // width: 100%;
- // }
- // }
-
- .llms-builder-save {
-
- bottom: 10px;
- left: 10px;
- position: absolute;
- right: 10px;
- z-index: 1;
-
- .llms-builder-error {
- background: $color-danger;
- border-radius: 4px;
- color: #fff;
- display: inline-block;
- font-style: italic;
- padding: 5px 15px 7px 25px;
- margin: 0 0 10px;
-
- li {
- margin: 0;
- padding: 0;
- }
-
- }
-
- .llms-save {
- width: 75%;
- }
- .llms-exit {
- padding-left: 5px;
- padding-right: 5px;
- width: 23%;
- }
-
- button {
- position: relative;
- i {
- position: absolute;
- left: 10px;
- top: 10px;
-
- .llms-spinner {
- border-color: #fff;
- }
- }
- }
- button[data-status] .llms-status-indicator { display: none; }
- button[data-status="saved"] .status--saved { display: block; }
- button[data-status="unsaved"] {
- background-color: $color-brand-orange;
- .status--unsaved { display: block; }
- }
- button[data-status="saving"] .status--saving { display: block; }
- button[data-status="error"] .status--error { display: block; }
-
- }
-
- }
- @media only screen and ( max-width: 782px ) {
- .llms-builder-sidebar {
- margin-right: 10px;
- position: relative;
- top: 0;
- width: auto;
- }
- .llms-builder-main {
- padding-right: 10px;
- width: auto;
- }
- }
-
- // Search Popover
- .select2-container {
- z-index: 99999999;
- }
-
- .select2-results__option {
- padding: 0;
- }
-
- .select2-container--default .select2-results__option--highlighted[aria-selected] {
- background: $color-brand-blue;
- .llms-existing-action {
- color: #fff;
- }
- }
-
- .llms-existing-lesson-result {
-
- align-items: center;
- display: flex;
- padding: 5px 5px 5px 0;
-
- .llms-existing-info {
- flex: 6;
-
- h4, h5 {
- margin: 0;
- }
-
- h4 {
- font-weight: 400;
- }
-
- h5 {
- font-weight: 300;
- }
- }
-
- .llms-existing-action {
- color: $color-brand-blue;
- flex: 1;
- text-align: center;
-
- .fa {
- display: block;
- font-size: 30px;
- }
-
- small {
- text-transform: uppercase;
- }
-
- }
-
-
- }
-
-
- // Quiz
- .llms-quiz-empty {
- margin: 100px auto;
- text-align: center;
-
- p { font-size: 18px; }
- button.llms-element-button {
- max-width: 320px;
- margin: 0 auto;
- }
-
- }
-
- .llms-editor-tab.tab--quiz {
- .llms-model-header {
- .llms-model-title {
- width: calc( 100% - 310px );
- }
- .llms-quiz-points {
- float: left;
- margin-right: 10px;
- width: 100px;
- }
- }
- }
-
- .llms-model-header {
- background-color: #FFF;
- -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
- padding: 10px;
- @include clearfix();
-
- .llms-model-title {
- float: left;
- margin-right: 10px;
- width: calc( 100% - 200px );
- .llms-input {
- width: calc( 100% - 65px );
- }
- }
- .llms-model-status.llms-switch {
- float: left;
- margin-right: 10px;
- position: relative;
- text-align: right;
- top: -2px;
- width: 100px;
- }
- .llms-action-icons {
- float: left;
- position: relative;
- text-align: right;
- width: 80px;
- z-index: 1;
- .llms-action-icon {
- margin-left: 10px;
- }
- .fa {
- max-width: 15px;
- }
- }
- }
-
- .llms-model-header + .llms-model-settings.active {
- margin-top: -10px;
- }
-
- .llms-model-settings {
- clear: both;
- display: none;
-
- &.active {
- display: block;
- margin-top: 10px;
- }
- }
-
- .llms-quiz-footer {
- display: flex;
- button.llms-element-button {
- flex: 1;
- margin: 0 5px;
- &:first-child { margin-left: 0; }
- &:last-child { margin-right: 0; }
- &.llms-show-question-bank {
- flex: 2;
- }
- }
- }
-
- // Question Bank
- .llms-quiz-tools {
- display: none;
- width: 100%;
- position: relative;
-
- // .llms-quiz-tools-search {
- // padding: 0 10px;
- // margin-bottom: 15px;
-
- // .fa {
- // color: #888;
- // font-size: 16px;
- // }
-
- // input[type="search"] {
- // background: inherit;
- // border: none;
- // border-bottom: 1px solid #bbb;
- // box-shadow: none;
- // font-size: 16px;
- // margin: 8px 0 0;
- // padding: 2px 5px;
- // width: calc( 100% - 200px );
-
- // &:focus {
- // border-bottom-color: $color-brand-blue;
- // }
- // }
-
- // }
-
- }
-
- ul.llms-question-bank {
-
- list-style-type: none;
- margin: 0;
- padding: 0;
- @include clearfix;
-
- li.llms-question-bank-header {
- clear: both;
- padding-top: 20px;
- &:first-child {
- padding-top: 0;
- }
- h4 {
- font-size: 20px;
- margin: 10px 5px;
- }
- }
-
- li.llms-question-type {
- box-sizing: border-box;
- float: left;
- margin: 0;
- padding: 3px;
- width: 33.3333%;
- transition: opacity 0.3s ease-in-out;
-
- &.filtered {
- opacity: 0.3;
- }
-
- .llms-type-unavailable {
- display: block;
- position: relative;
- text-decoration: none;
- .llms-element-button {
- opacity: 0.5;
- pointer-events: none;
- }
- }
-
- }
-
- }
-
- // Quiz Questions
- ul.llms-quiz-questions {
-
- margin: 10px 3px;
- padding: 5px;
- transition: box-shadow 0.6s ease;
-
- &.dragging {
- box-shadow: 0 0 0 3px $color-brand-blue;
- }
-
- &:empty:before {
- background: #fff;
- content: attr(data-empty-msg);
- display: block;
- font-size: 18px;
- margin: 0 auto;
- padding: 100px 0;
- text-align: center;
- }
-
- li.llms-question {
-
- background: #fff;
- margin: 0 0 3px;
- padding: 15px 12px 10px;
-
- &:hover {
- > .llms-builder-header .llms-action-icons {
- opacity: 1;
- pointer-events: auto;
- }
- }
-
- // groups
- ul.llms-quiz-questions {
- margin-left: 12px;
- .llms-question {
- border-bottom: 2px solid #e6e6e6;
- }
- &:empty:before {
- content: attr(data-empty-msg);
- display: block;
- font-size: 18px;
- text-align: center;
- margin: 20px auto;
- }
- li.llms-question.llms-sortable-placeholder.qtype--group {
- display: none !important;
- }
- }
-
- .llms-builder-header {
- @include clearfix;
- > * {
- float: left;
- }
- }
-
- .llms-question-body {
- display: none;
- &.active {
- display: block;
- }
- }
-
- .llms-data-stamp {
- background: $color-brand-blue;
- border-radius: 4px;
- color: #fff;
- cursor: move;
- font-size: 90%;
- margin-top: -5px;
- padding: 4px 10px 6px;
-
- small, .fa {
- line-height: 1.2;
- vertical-align: middle;
- }
-
- .fa {
- margin-right: 4px;
- }
-
- }
-
- .llms-headline {
- width: calc( 100% - 110px - 90px - 55px );
- .ql-editor {
- width: calc( 100% - 16px );
- }
- }
-
- .llms-action-icons {
- width: 110px;
- opacity: 0;
- pointer-events: none;
- }
-
- .llms-question-points {
- width: 90px;
- }
-
- .llms-question-features {
- margin: 10px 0 0;
- &:last-child {
- margin: 0;
- }
- .llms-switch {
- margin-right: 15px;
- }
- }
-
- .llms-editable-video {
- position: relative;
- z-index: 1;
- }
-
- }
-
- .llms-question-choices-wrapper {
- background: #f4f4f4;
- margin: 2px 1px;
- padding: 10px;
- }
-
- .llms-question-choices-list-header {
- @include clearfix;
- margin-bottom: 10px;
-
- .llms-switch {
- float: right;
- text-align: right;
- width: 260px;
- }
- }
-
- ul.llms-question-choices {
- border: 3px solid #f4f4f4;
- margin: -3px;
- padding: 0;
- transition: box-shadow 0.6s ease;
-
- &.dragging {
- box-shadow: 0 0 0 3px $color-brand-blue;
- }
-
- &.multi-choices li.llms-question-choice .llms-choice-id span {
- border-radius: 4px;
- }
-
- }
-
- li.llms-question-choice {
- margin: 0 0 5px;
- padding: 0;
- &:last-child { margin-bottom: 0; }
-
- .llms-choice-id {
-
- input[type="checkbox"] {
- display: none;
- }
-
- input[type="checkbox"]:checked + .llms-marker {
- background: $color-green;
- }
-
- .llms-marker {
- border-radius: 50%;
- background: #d0d0d0;
- box-shadow: inset 0 0 1px #848484;
- color: #444;
- display: inline-block;
- font-size: 16px;
- height: 20px;
- line-height: 20px;
- padding: 5px;
- position: relative;
- text-align: center;
- transition: background 0.1s ease;
- width: 20px;
-
- .fa {
- left: 7px;
- opacity: 0;
- position: absolute;
- top: 7px;
- }
- &.selectable:hover {
- b { opacity: 0 }
- .fa { opacity: 1; }
- }
-
- }
-
- }
-
- .llms-input-wrapper,
- .llms-editable-image {
- display: inline-block;
- // action icons width, label width, ul margins
- width: calc( 100% - 55px - 35px - 5px );
- }
-
- .llms-input {
- width: calc( 100% - 16px );
- }
-
- .llms-editable-image .llms-image {
- vertical-align: middle;
- img {
- height: 50px;
- }
- }
-
- .llms-action-icons {
- display: inline-block;
- opacity: 1;
- pointer-events: auto;
- text-align: right;
- width: 55px;
- }
-
- }
-
- li.llms-question-choice.llms-sortable-placeholder {
- border: 3px dashed $color-brand-blue !important;
- background: rgba( $color-brand-blue, 0.3 );
- }
-
- li.llms-question-choice.ui-sortable-helper {
- border: 1px solid #ccc;
- background: #fff;
- padding: 10px;
- transform: rotate( 2deg );
- z-index: 999;
- }
-
- li.llms-question.ui-sortable-helper,
- li.llms-question.ui-draggable-dragging {
- border: 1px solid #ccc;
- background: #fff;
- transform: rotate( 2deg );
- z-index: 999;
- }
-
- li.llms-question.llms-sortable-placeholder {
- border: 3px dashed $color-brand-blue !important;
- background: rgba( $color-brand-blue, 0.3 );
- }
-
- }
-
-
- .llms-switch {
- display: inline-block;
- float: none;
- width: auto;
-
- input[type="checkbox"] {
- display: none;
- }
-
- input[type="checkbox"]:checked + .llms-switch-slider {
- background: $color-green;
- }
-
- input[type="checkbox"]:checked + .llms-switch-slider:after {
- transform: translateX( 14px );
- }
-
- .llms-label {
- display: inline-block;
- vertical-align: top;
- }
-
- .llms-switch-slider {
- background: #e0e0e0;
- border-radius: 8px;
- display: inline-block;
- height: 16px;
- margin-top: 2px;
- position: relative;
- transition: background 0.2s ease;
- vertical-align: top;
- width: 30px;
-
- &:after {
- background: #fff;
- border-radius: 8px;
- content: '';
- display: block;
- height: 12px;
- left: 2px;
- position: relative;
- transition: transform 0.2s ease;
- top: 2px;
- width: 12px;
- }
-
- }
-
- }
-
-}
-
-.llms-multi-input .llms-input-wrapper {
- width: 50%;
- float: left;
- margin-bottom: 6px;
- font-size: 12px;
-}
-
-.llms-multi-input .llms-input-wrapper .llms-input {
- font-size: 12px;
- padding: 5px;
-}
diff --git a/assets/scss/admin/_dashboard-widget.scss b/assets/scss/admin/_dashboard-widget.scss
deleted file mode 100644
index bdb6da73d7..0000000000
--- a/assets/scss/admin/_dashboard-widget.scss
+++ /dev/null
@@ -1,101 +0,0 @@
-#llms_dashboard_widget {
-
- .inside {
- margin: 0;
- padding-bottom: 8px;
- }
-
- .llms-dashboard-widget-wrap {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-top: 12px;
- }
-
- .activity-block {
- padding-bottom: 8px;
- border-color: #e8e8e8;
- }
-
- h3 {
- margin-bottom: 0;
- }
-
- .llms-charts-wrapper {
- display: none;
- }
-
- .llms-widget-row {
- display: flex;
- justify-content: space-between;
- gap: 8px;
- width: 100%;
- align-items: stretch;
- padding: 4px 0;
-
- &:before,
- &:after {
- display: none;
- }
- }
-
- .llms-widget-1-4 {
- padding: 0;
- flex: 1;
- }
-
- .llms-widget {
- padding: 8px 8px 12px;
- margin: 0;
- border-radius: 6px;
- border: 1px solid #e8e8e8;
- box-shadow: 0px 2px 4px rgb(246 247 247);
- height: 100%;
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- align-items: flex-end;
- }
-
- .llms-label {
- font-size: 14px;
- width: 100%;
- align-self: flex-start;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- .llms-widget-content {
- font-size: 20px;
- margin: 0;
- }
-
- .llms-widget-info-toggle {
- display: none;
- }
-
- a {
- border: 0;
- }
-
- .subsubsub {
- color: #dcdcde;
- }
-}
-
-.llms-dashboard-widget-feed {
- margin: 0 -12px;
- padding: 0;
- background-color: #f6f7f7;
-
- li {
- margin: 0;
- padding: 8px 12px;
- border-bottom: 1px solid #e8e8e8;
- }
-
- span {
- display: block;
- }
-}
diff --git a/assets/scss/admin/_dashboard.scss b/assets/scss/admin/_dashboard.scss
deleted file mode 100644
index af676ec486..0000000000
--- a/assets/scss/admin/_dashboard.scss
+++ /dev/null
@@ -1,183 +0,0 @@
-.wrap.llms-dashboard {
-
- .llms-inside-wrap {
- padding-top: 30px;
- }
-
- #poststuff {
-
- h2 {
- padding: 12px 20px;
- }
-
- }
-
- .llms-dashboard-activity {
-
- h2 {
- font-size: 20px;
- font-weight: 700;
- line-height: 1.5;
- margin-top: 0;
- text-align: center;
- }
- }
-
- .postbox {
- background-color: #FFF;
- border: none;
- box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.13 );
-
- .postbox-header {
- border-bottom-color: #efefef;
-
- }
-
- .inside {
- padding: 20px;
- }
- }
-
- #llms_dashboard_addons {
-
- .llms-addons-wrap {
- margin-top: 0;
-
- .llms-add-on-item {
- margin-top: 0;
-
- p {
- text-align: left;
- }
-
- footer.llms-actions {
- padding-top: 0;
- }
- }
-
- }
-
- p {
- text-align: center;
-
- .llms-button-primary {
- display: inline-block;
- margin-top: 15px;
- }
- }
-
- }
-
- #llms_dashboard_quick_links {
-
- ul {
- list-style: disc;
- margin: 5px 0 0 20px;
-
- li {
- font-size: 15px;
- line-height: 1.5;
- }
- }
-
- .llms-quick-links {
- display: grid;
- grid-template-columns: 1fr;
- grid-gap: 30px;
-
- a {
- display: inline-block;
- }
-
- .llms-list {
-
- h3 {
- margin: 0 0 10px 0;
- }
-
- ul {
- margin-bottom: 20px;
-
- &.llms-checklist {
- list-style: none;
- margin-left: 0;
- }
- }
-
- .fa {
- text-align: center;
- width: 16px;
- }
-
- .fa-check {
- color: #008a20;
- }
-
- .fa-times {
- color: #d63638;
- }
-
- .llms-button-primary,
- .llms-button-secondary,
- .llms-button-action {
- display: block;
- text-align: center;
- }
-
- }
-
- @media only screen and (min-width: 782px) {
- grid-template-columns: 1fr 1fr 1fr;
- }
- }
-
- .llms-help-links {
- display: grid;
- grid-template-columns: 1fr 1fr;
- grid-gap: 20px;
-
- .llms-list {
-
- h3 {
- margin: 0;
-
- .dashicons {
- color: #AAA;
- }
- }
-
- }
-
- @media only screen and (min-width: 782px) {
- grid-template-columns: 1fr 1fr 1fr 1fr;
- }
-
- }
-
- }
- #llms_dashboard_blog,
- #llms_dashboard_podcast {
-
- ul {
- margin: 0;
-
- li {
- margin: 0 0 15px 0;
-
- a {
- display: block;
- }
- }
- }
-
- p {
- margin: 15px 0;
- text-align: center;
-
- a {
- display: inline-block;
- }
- }
- }
-
-}
diff --git a/assets/scss/admin/_fonts.scss b/assets/scss/admin/_fonts.scss
deleted file mode 100644
index d2b834e87e..0000000000
--- a/assets/scss/admin/_fonts.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-#llms-options-page-contents {
- h2 {
- color: #999;
- font-weight: 500;
- letter-spacing: 2px;
- border-bottom: 1px solid #999;
- }
-}
diff --git a/assets/scss/admin/_llms-table.scss b/assets/scss/admin/_llms-table.scss
deleted file mode 100644
index 6f286150d6..0000000000
--- a/assets/scss/admin/_llms-table.scss
+++ /dev/null
@@ -1,222 +0,0 @@
-.llms-table-wrap {
- position: relative;
-}
-
-.llms-table-header {
- padding: 0;
-
- @include clearfix();
-
- h2 {
- font-size: 20px;
- padding: 0;
- display: inline-block;
- line-height: 1.5;
- margin: 0 0 20px 0;
- vertical-align: middle;
- }
-
- .llms-table-search,
- .llms-table-filters {
- float: right;
- padding-left: 10px;
- }
-
- .llms-table-search input {
- margin: 0;
- padding: 0 8px;
- }
-
-}
-
-.llms-table {
-
- border: 1px solid #c3c4c7;
- border-collapse: collapse;
- width: 100%;
-
- a:not(.small) {
- color: $color-brand-blue;
- &:hover {
- color: $color-brand-blue-dark;
- }
- }
-
- td, th {
- border-bottom: 1px solid #c3c4c7;
- padding: 10px 12px;
- text-align: center;
-
- &.expandable.closed {
- display: none;
- }
-
- .llms-button-primary,
- .llms-button-secondary,
- .llms-button-action,
- .llms-button-danger {
- display: inline-block;
- }
-
- }
-
- tr.llms-quiz-pending {
- td {
- font-weight: 700;
- }
- }
-
- thead th,
- tfoot th {
- background-color: #FFF;
- font-weight: 700;
-
- a.llms-sortable {
- // display: block;
- padding-right: 16px;
- position: relative;
- text-decoration: none;
- width: 100%;
- &.active {
- // show the current sorted when a sort is active
- &[data-order="DESC"] .asc { opacity: 1; }
- &[data-order="ASC"] .desc { opacity: 1; }
- }
- // show the opposite on hover
- &:hover {
- &[data-order="DESC"] {
- .asc { opacity: 0; }
- .desc { opacity: 1; }
- }
- &[data-order="ASC"] {
- .asc { opacity: 1; }
- .desc { opacity: 0; }
- }
- }
- .dashicons {
- color: #444;
- font-size: 16px;
- height: 16px;
- opacity: 0;
- position: absolute;
- width: 16px;
- }
- }
- }
-
- tfoot th {
- border-bottom: none;
-
- .llms-table-export {
- float: left;
- .llms-table-progress {
- background: #efefef;
- display: none;
- margin-left: 8px;
- vertical-align: middle;
- width: 100px;
- }
- }
-
- .llms-table-pagination {
- float: right;
- }
-
- }
-
- &.zebra tbody tr:nth-child( even ) {
- th, td { background-color: #fff; }
- }
-
- &.zebra tbody tr:nth-child( odd ) {
- th, td { background-color: #f6f7f7; }
- }
-
- &.text-left {
- td, th {
- text-align: left;
- }
- }
-
- &.size-large {
- td, th {
- font-size: 14px;
- padding: 10px 12px;
- }
- }
-
- .llms-drag-handle {
- color: #777;
- cursor: pointer;
- -webkit-transition: color 0.4s ease;
- transition: color 0.4s ease;
- }
-
- .llms-action-icon {
- color: #777;
- text-decoration: none;
-
- .tooltip {
- cursor: pointer;
- }
-
- &:hover {
- color: $color-blue;
- }
-
- &.danger:hover {
- color: $color-danger;
- }
- }
-
- .llms-table-page-count {
- font-size: 12px;
- padding: 0 5px;
- }
-
-}
-
-// progress bars within the tables
-.llms-table-progress {
- text-align: center;
- .llms-table-progress-bar {
- background: #eee;
- border-radius: 10px;
- height: 16px;
- overflow: hidden;
- position: relative;
- .llms-table-progress-inner {
- background: $color-brand-blue;
- height: 100%;
- transition: width 0.2s ease;
- }
- }
- .llms-table-progress-text {
- color: $color-brand-blue;
- font-size: 12px;
- font-weight: 700;
- line-height: 16px;
- }
-}
-
-
-.llms-table.llms-gateway-table,
-.llms-table.llms-integrations-table {
- .status {
- .fa {
- color: $color-brand-blue;
- font-size: 22px;
- }
- }
- .sort {
- cursor: move;
- text-align: center;
- width: 10px;
- }
-}
-
-.llms-gb-table-notifications {
- th, td {
- text-align: left;
- }
-}
diff --git a/assets/scss/admin/_main.scss b/assets/scss/admin/_main.scss
deleted file mode 100644
index 79588aa040..0000000000
--- a/assets/scss/admin/_main.scss
+++ /dev/null
@@ -1,340 +0,0 @@
-.wrap.lifterlms {
- margin-top: 0;
-}
-
-.llms-header {
- background-color: #FFF;
- margin: 0 0 0 -20px;
- padding: 20px 10px;
- position: relative;
- z-index: 1;
-
- .llms-inside-wrap {
- display: flex;
- padding: 0 10px;
- }
-
-
- .lifterlms-logo {
- flex: 0 0 190px;
- max-height: 52px;
- margin-right: 10px;
- }
-
- .llms-meta {
- align-self: center;
- display: flex;
- flex: 1;
- font-size: 16px;
- justify-content: space-between;
- line-height: 1.5;
-
- .llms-version {
- background-color: #1d2327;
- color: #FFF;
- border-radius: 999px;
- font-size: 13px;
- font-weight: 700;
- padding: 6px 12px;
- }
-
- a {
- display: inline-block;
- }
-
- .llms-license {
- border-right: 1px solid #CCC;
- font-weight: 700;
- margin-right: 12px;
- padding-right: 12px;
-
- a {
- text-decoration: none;
-
- &:before {
- content: "\f534";
- display: inline-block;
- font: 400 16px/1 dashicons;
- left: 0;
- padding-right: 3px;
- position: relative;
- text-decoration: none;
- vertical-align: text-bottom;
- }
-
- &.llms-license-none {
- color: #888;
-
- &:before {
- content: "\f335";
- }
- }
-
- &.llms-license-active {
- color: #008a20;
-
- &:before {
- content: "\f112";
- }
-
- }
-
- }
- }
-
- .llms-support {
- font-weight: 700;
-
- a {
- color: #1d2327;
- text-decoration: none;
- }
-
- }
-
- }
-
-}
-
-.llms-subheader {
- align-items: center;
- background-color: #FFF;
- box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.15 );
- display: flex;
- flex-direction: row;
- margin-left: -20px;
- padding: 10px 20px;
- width: 100%;
- z-index: 1;
-
- h1 {
- font-weight: 700;
- padding: 0;
-
- a {
- color: inherit;
- text-decoration: none;
- }
- }
-
-}
-
-#post_course_difficulty {
- min-width: 200px;
-}
-#_video-embed, #_audio-embed {
- width: 100%;
-}
-
-hr {
- background-color: #CCC;
- border: none;
- height: 1px;
- margin: 30px 0;
- padding: 0;
-}
-
-.llms_certificate_default_image, .llms_certificate_image {
- width: 300px;
-}
-
-.llms_achievement_default_image, .llms_achievement_image {
- width: 120px;
-}
-
-div[id^="lifterlms-"] .inside {
- overflow: visible;
-}
-
-.notice.llms-admin-notice {
- background-color: #FFF;
- border: 1px solid #ccd0d4;
- box-shadow: 0 1px 4px rgba( 0, 0, 0, 0.15 );
- display: flex;
- padding: 0 !important;
- position: relative;
-
- .notice-dismiss {
- text-decoration: none;
- }
-
- &.notice-warning {
- border-left: 4px solid #F8954F;
-
- .llms-admin-notice-icon {
- background-color: #FEF4ED;
- }
- }
-
- &.notice-info {
- border-left: 4px solid #466DD8;
-
- h3 {
- color: #466DD8;
- }
-
- .llms-admin-notice-icon {
- background-color: #EDF0FB;
- }
-
- }
-
- &.notice-success {
- border-left: 4px solid #18A957;
-
- h3 {
- color: #18A957;
- }
-
- .llms-admin-notice-icon {
- background-color: #E8F6EE;
- }
-
- }
-
- &.notice-error {
- border-left: 4px solid #DF1642;
-
- h3 {
- color: #9C0F2E;
- }
-
- .llms-admin-notice-icon {
- background-color: #FCE8EC;
- }
-
- }
-
- .llms-admin-notice-icon {
- background-image: url(../../assets/images/lifterlms-icon-color.png);
- background-position: center center;
- background-repeat: no-repeat;
- background-size: 30px;
- min-width: 70px;
-
- }
-
- .llms-admin-notice-content {
- color: #111;
- padding: 20px;
- }
-
- h3 {
- font-size: 18px;
- font-weight: 700;
- line-height: 25px;
- margin: 0 0 15px 0;
- }
-
- button,
- .llms-button-primary {
- display: inline-block;
- }
-
- p {
- font-size: 14px;
- line-height: 22px;
- margin: 0 0 15px 0;
- max-width: 65em;
- padding: 0;
- }
-
- p:last-of-type {
- margin-bottom: 0;
- }
-}
-
-.llms-button-action,
-.llms-button-danger,
-.llms-button-primary,
-.llms-button-secondary {
- &.small .dashicons {
- font-size: 13px;
- height: 13px;
- width: 13px;
- }
-
- &:hover {
- box-shadow: none;
- }
-}
-
-a.llms-view-as {
- line-height: 2;
- margin-right: 8px;
-}
-
-.llms-image-field-preview {
- max-height: 80px;
- vertical-align: middle;
- width: auto;
-}
-
-.llms-image-field-remove {
- &.hidden { display: none; }
-}
-
-.llms-log-viewer {
- background: #fff;
- border: 1px solid #e5e5e5;
- box-shadow: 0 1px 1px rgba(0,0,0,.04);
- margin: 20px 0;
- padding: 25px;
-
- pre {
- font-family: monospace;
- margin: 0;
- padding: 0;
- white-space: pre-wrap;
- }
-}
-
-.llms-status--tools {
- .llms-table {
- background: #fff;
- border: 1px solid #e5e5e5;
- box-shadow: 0 1px 1px rgba(0,0,0,.04);
- td, th {
- padding: 10px;
- vertical-align: top;
- }
- th {
- width: 28%;
- }
- p {
- margin: 0 0 10px;
- }
- }
-}
-
-.llms-error {
- color: $color-red;
- font-style: italic;
-}
-
-.llms-rating-stars {
- color: #ffb900;
- text-decoration: none;
-}
-
-@media screen and (max-width: 782px) {
- .llms-header {
- top: 46px;
-
- .llms-inside-wrap {
- flex-direction: column;
- gap: 20px;
-
- .lifterlms-logo {
- align-self: center;
- flex: inherit;
- max-height: initial;
- max-width: 200px;
- }
-
- .llms-meta {
- column-gap: 10px;
- }
- }
- }
-}
diff --git a/assets/scss/admin/_quiz-attempt-review.scss b/assets/scss/admin/_quiz-attempt-review.scss
deleted file mode 100644
index a4b7d85f41..0000000000
--- a/assets/scss/admin/_quiz-attempt-review.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-.llms-remarks {
-
- .llms-remarks-field {
- height: 120px;
- width: 100%;
- }
-
- input[type="number"] {
- width: 60px;
- }
-
-
-}
-
-
-button[name="llms_quiz_attempt_action"] {
- .save { display: none; }
- &.grading {
- .default { display: none };
- .save { display: inline; }
- }
-}
-
diff --git a/assets/scss/admin/_reporting.scss b/assets/scss/admin/_reporting.scss
deleted file mode 100644
index f1c2d703e5..0000000000
--- a/assets/scss/admin/_reporting.scss
+++ /dev/null
@@ -1,452 +0,0 @@
-.llms-reporting.wrap {
-
- .llms-options-page-contents {
-
- .llms-nav-tab-wrapper.llms-nav-secondary {
- box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.15 );
- margin: 0;
- padding: 0;
-
- }
- }
-
- .llms-stab-title {
- color: $color-brand-dark-blue;
- font-size: 36px;
- font-weight: 300;
- margin-bottom: 20px;
- }
-
- td.id a {
- text-decoration: none;
- }
-
- th.id, td.id,
- th.name, td.name,
- th.registered, td.registered,
- th.last_seen, td.last_seen,
- th.overall_progress, td.overall_progress,
- th.title, td.title,
- th.course, td.course,
- th.lesson, td.lesson { text-align: left; }
-
- td.section-title {
- background: #eaeaea;
- text-align: left;
- font-weight: 700;
- padding: 16px 4px;
- }
-
- td.questions-table {
- text-align: left;
-
- .correct,
- .question,
- .selected {
- text-align: left;
- max-width: 300px;
-
- img {
- height: auto;
- max-width: 64px;
- }
- }
- }
-
- table.quiz-attempts {
- margin-bottom: 40px;
- }
-
- &.tab--students {
- .llms-options-page-contents {
-
- #llms-award-certificate-wrapper .components-button.is-secondary {
- background: #e1e1e1;
- border-radius: 8px;
- box-shadow: none;
- color: #414141;
- font-size: 13px;
- font-weight: 700;
- height: auto;
- padding: 8px 14px;
- }
-
- }
- }
-
- &.tab--enrollments,
- &.tab--sales {
-
- .llms-nav-tab-wrapper.llms-nav-secondary {
- margin-bottom: 0;
- }
-
- .llms-options-page-contents {
- box-shadow: none;
- background: none;
- margin-top: 20px;
- padding: 0;
- }
-
- .llms-nav-style-filters {
-
- .llms-analytics-form {
- align-items: center;
- align-self: center;
- color: #FFF;
- display: flex;
- font-size: 13px;
- gap: 5px;
-
- label {
- font-weight: 700;
- }
-
- input {
- border: 0;
- font-size: 13px;
- margin: 0;
- padding: 0 4px;
- vertical-align: middle;
- width: 110px;
- }
-
- .select2-container {
- input {
- width: 100% !important;
- }
- }
- }
-
- }
-
- .button.small {
- height: 23px;
- line-height: 23px;
- }
-
- .llms-analytics-filters {
- display: none;
-
- .llms-inside-wrap {
- background-color: #FFF;
- background-color: #FFF;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
- margin: -20px 10px 20px 10px;
- padding: 20px;
- }
-
- .llms-nav-items {
- display: flex;
- flex-direction: column;
- gap: 20px;
- justify-content: space-between;
- margin: 0;
-
- @media only screen and (min-width: 782px) {
- flex-direction: row;
- }
-
- }
-
- .llms-nav-item {
- box-sizing: border-box;
- width: 100%;
-
- label {
- display: block;
- font-weight: 700;
- margin: 0 0 5px 0;
- }
-
- .select2-selection__rendered{
- word-wrap: break-word;
- text-overflow: inherit;
- white-space: normal;
- }
-
- }
-
- p {
- margin: 0;
- text-align: right;
-
- .llms-button-primary {
- display: inline-block;
- }
-
- }
-
- }
- }
-
- .llms-reporting-tab.llms-reporting-quiz .llms-table-filter-wrap {
- width: 160px;
- }
-
-
-}
-
-.llms-charts-wrapper {
- background-color: #FFF;
- border: 1px solid #dedede;
- border-radius: 12px;
- box-shadow: 0px 0px 1px rgba(48, 49, 51, 0.05), 0px 2px 4px rgba(48, 49, 51, 0.1);
- padding: 20px;
-}
-
-.llms-reporting-tab {
-
- h1, h2, h3, h4, h5, h6 {
- margin: 0;
- a {
- color: $color-brand-blue;
- text-decoration: none;
- &:hover {
- color: $color-brand-blue;
- }
- }
- }
-
- h2 {
- font-size: 22px;
- line-height: 1.5;
-
- &.llms-table-title {
- margin-bottom: 20px;
- }
-
- }
-
- h5 {
- font-size: 15px;
- line-height: 1.5;
- }
-
- .llms-reporting-body {
- background-color: #FFF;
- box-shadow: 0 1px 3px rgba( 0, 0, 0, .13 );
- margin: 20px auto;
- padding: 0;
-
- .llms-reporting-stab {
- padding: 30px;
-
- .llms-table-header {
- margin: 0;
- }
- }
-
- .llms-gb-tab {
- padding: 30px;
- }
-
- .llms-reporting-header {
- padding: 30px;
- margin: 0;
-
- .llms-reporting-header-img {
- border-radius: 50%;
- display: inline-block;
- margin-right: 10px;
- overflow: hidden;
- vertical-align: middle;
- img {
- display: block;
- max-height: 64px;
- width: auto;
- }
- }
-
- .llms-reporting-header-info {
- display: inline-block;
- vertical-align: middle;
-
- }
-
- }
- }
-
-}
-
-.llms-reporting-breadcrumbs {
- margin: 0;
- padding: 0;
- a {
- color: $color-brand-blue;
- font-size: 15px;
- text-decoration: none;
- &:hover {
- color: $color-brand-blue-dark;
- }
- &:after {
- content: ' > ';
- color: #646970;
- }
-
- &:last-child {
- color: #000;
- font-weight: 700;
- &:after { display: none; }
- }
- }
-}
-
-#llms-students-table .name {
- text-align: left;
-}
-
-.llms-reporting-tab-content {
- display: flex;
-
- > header {
- @include clearfix;
- }
-
- h3 {
- margin-bottom: 20px;
- }
-
- .llms-reporting-tab-filter {
- float: right;
- position: relative;
- margin-right: 0.75em;
- width: 180px;
- top: -3px;
- }
-
-
- .llms-reporting-tab-main {
- flex: 3;
- max-width: 75%;
- }
- .llms-reporting-tab-side {
- flex: 1;
- margin-left: 20px;
- }
-
- > .llms-table-wrap {
- flex: 1;
- }
-
-}
-
-
-.llms-reporting-widgets {
- @include clearfix;
-}
-
-.llms-reporting-widget {
-
- border-top: 4px solid $color-brand-blue;
- background: #fafafa;
- margin-bottom: 10px;
- padding: 30px;
- @include clearfix;
-
- .fa {
- color: #999;
- float: left;
- font-size: 32px;
- margin-right: 10px;
- }
-
- strong {
- color: #333;
- font-size: 20px;
- line-height: 1.2;
- }
-
- &.llms-reporting-student-address {
- strong {
- line-height: 1.1;
- }
- }
-
- sup,
- .llms-price-currency-symbol {
- font-size: 75%;
- position: relative;
- top: -4px;
- vertical-align: baseline;
- }
-
- small {
- font-size: 13px;
- &.compare {
- margin-left: 5px;
- &.positive {
- color: $color-green;
- }
- &.negative {
- color: $color-red;
- }
- }
- }
-}
-
-
-.llms-reporting-event {
- border-left: 4px solid #555;
- background: #fafafa;
- font-size: 11px;
- line-height: 1.2;
- margin-bottom: 0.75em;
- padding: 10px;
- @include clearfix;
-
- &.color--blue {
- border-left-color: $color-blue;
- }
-
- &.color--green,
- &._enrollment_trigger,
- &._is_complete.yes {
- border-left-color: $color-green;
- }
-
- &.color--purple,
- &._status.enrolled {
- border-left-color: $color-purple;
- }
-
- &.color--red,
- &._status.expired,
- &._status.cancelled {
- border-left-color: $color-red;
- }
- &.color--orange,
- &._achievement_earned,
- &._certificate_earned,
- &._email_sent {
- border-left-color: $color-orange;
- }
-
- time {
- color: #888;
- }
-
- .llms-student-avatar {
- margin-left: 10px;
- float: right;
- }
-
- a {
- text-decoration: none;
- color: inherit;
- }
-
-}
-
-@media only screen and (min-width: 782px) {
- .llms-reporting.wrap {
- .llms-options-page-contents {
- .llms-nav-tab-wrapper.llms-nav-secondary {
- margin-left: 0;
- margin-right: 0;
- }
- }
- }
-}
-
-@import "../_includes/quiz-result-question-list";
diff --git a/assets/scss/admin/_resources.scss b/assets/scss/admin/_resources.scss
deleted file mode 100644
index c39557bff2..0000000000
--- a/assets/scss/admin/_resources.scss
+++ /dev/null
@@ -1,162 +0,0 @@
-.wrap.llms-resources {
-
- .llms-inside-wrap {
- padding-top: 30px;
- }
-
- #poststuff {
-
- #post-body.columns-2 {
- margin-right: 350px;
-
- #side-sortables {
- width: 330px;
-
- @media only screen and (max-width: 850px) {
- width: auto;
- }
-
- }
-
- }
-
- #postbox-container-1 {
- float: right;
- margin-right: -350px;
- width: 330px
- }
-
- h2 {
- padding: 12px 20px;
- }
-
- }
-
- #poststuff
- .postbox {
- background-color: #FFF;
- border: none;
- box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.13 );
-
- .postbox-header {
- border-bottom-color: #efefef;
-
- }
-
- .inside {
- margin: 0;
- padding: 20px;
- }
- }
-
- #llms_dashboard_welcome_video {
-
- .llms-welcome-video {
-
- p {
- font-size: 15px;
- line-height: 1.5;
- margin: 0 0 40px 0;
- }
-
- .llms-welcome-video-container {
- height: 0;
- overflow: hidden;
- padding-top: 30px;
- padding-bottom: 56.25%;
- position: relative;
-
- iframe,
- object,
- embed {
- left: 0;
- height: 100%;
- position: absolute;
- top: 0;
- width: 100%;
- }
-
- }
-
- }
-
- }
-
- #llms_dashboard_getting_started {
-
- ul {
- margin: 0 0 20px 0;
-
- li {
- font-size: 15px;
- line-height: 1.5;
- margin-bottom: 15px;
- }
- }
-
- .llms-button-primary {
- display: block;
- margin-top: auto;
- max-width: 300px;
- text-align: center;
- }
-
- }
-
- #llms_dashboard_resource_links {
-
- ul {
- list-style: disc;
- margin: 5px 0 0 20px;
-
- li {
- font-size: 15px;
- line-height: 1.5;
- }
- }
-
- .llms-resource-links {
- display: grid;
- grid-template-columns: 1fr;
- grid-gap: 60px;
-
- a {
- display: inline-block;
- }
-
- .llms-list {
- display: flex;
- flex-direction: column;
-
- h3 {
- margin: 0 0 10px 0;
-
- .dashicons {
- color: #AAA;
- }
-
- }
-
- ul {
- margin-bottom: 20px;
- }
-
- .llms-button-primary,
- .llms-button-secondary,
- .llms-button-action {
- display: block;
- margin-top: auto;
- max-width: 300px;
- text-align: center;
- }
-
- }
-
- @media only screen and (min-width: 782px) {
- grid-template-columns: 1fr 1fr 1fr;
- }
- }
-
- }
-
-}
diff --git a/assets/scss/admin/_settings.scss b/assets/scss/admin/_settings.scss
deleted file mode 100644
index 92bbe89ecd..0000000000
--- a/assets/scss/admin/_settings.scss
+++ /dev/null
@@ -1,194 +0,0 @@
-.wrap.llms-reporting,
-.wrap.lifterlms-settings,
-.wrap.llms-status {
-
- .llms-inside-wrap {
- box-sizing: border-box;
- margin: 0 auto;
-
- .llms-nav-text {
- margin: 0 auto;
- }
- }
-
- .llms-subheader {
-
- .llms-save {
- flex: auto;
- text-align: right;
- }
-
- }
-
- .llms-nav-tab-wrapper.llms-nav-secondary {
- background-color: #FFF;
- box-shadow: 0 1px 3px rgba( 0, 0, 0, 0.15 );
- margin: 0 -20px 20px -10px;
-
- .llms-nav-items {
- padding-left: 0;
- }
-
- .llms-nav-item {
- .llms-nav-link:hover {
- background: #f0f0f1;
- color: #222222;
- }
-
- &.llms-active .llms-nav-link {
- background: #fafafa;
- color: $color-blue;
- border-top-color: $color-blue;
- }
-
- &.llms-active .llms-nav-link {
- font-weight: 700;
- }
- }
-
- .llms-nav-link {
- border-top: 2px solid transparent;
- padding: 14px;
- }
-
- }
-
- .llms-setting-group {
- background-color: #FFF;
- box-shadow: 0 1px 3px rgba( 0, 0, 0, .13 );
- margin: 20px auto;
- padding: 20px;
-
- .llms-label {
- border-bottom: 1px solid #efefef;
- font-weight: 700;
- font-size: 20px;
- padding: 20px;
- margin: -20px -20px 20px;
-
- .llms-button-primary {
- margin-left: 10px;
- }
- }
-
- .llms-help-tooltip .dashicons {
- color: #444;
- cursor: help;
- }
-
- .form-table {
- margin: 0;
- tr:first-child .llms-subtitle {
- margin-top: 0;
- }
- }
-
- td[colspan="2"] {
- padding-top: 0;
- padding-left: 0;
- }
-
- tr.llms-disabled-field {
- opacity: 0.5;
- pointer-events: none;
- }
-
- p {
- font-size: 14px;
- }
- input[type="text"],
- input[type="password"],
- input[type="datetime"],
- input[type="datetime-local"],
- input[type="date"],
- input[type="month"],
- input[type="time"],
- input[type="week"],
- input[type="number"],
- input[type="email"],
- input[type="url"],
- input[type="search"],
- input[type="tel"],
- input[type="color"],
- select,
- textarea:not(.wp-editor-area) {
- width: 50%;
- &.medium { width: 30%; }
- &.small { width: 20%; }
- &.tiny { width: 10%; }
- }
- }
-
- @media only screen and (min-width: 782px) {
- .llms-nav-tab-wrapper.llms-nav-secondary {
- .llms-nav-item {
- &.llms-active .llms-nav-link {
- background: #fff;
- }
- }
- }
- }
-
- // Email Delivery providers.
- #llms-mailhawk-connect {
- height: auto;
- margin: 0 0 6px;
- position: relative;
-
- .dashicons {
- margin: -4px 4px 0 0;
- vertical-align: middle;
- }
- }
- #llms-sendwp-connect {
- height: auto;
- margin: 0 0 6px;
- position: relative;
-
- .fa {
- margin-right: 4px;
- }
- }
-
-}
-
-@media only screen and (min-width: 782px) {
- .wrap.lifterlms-settings {
- .llms-subheader {
- height: 40px;
- position: sticky;
- top: 32px;
- }
- .llms-nav-tab-wrapper.llms-nav-secondary {
- margin: 0 -20px 20px -22px;
-
- .llms-nav-items {
- padding-left: 10px;
- }
-
- }
- }
-
- .wrap.llms-reporting {
- .llms-nav-tab-wrapper.llms-nav-secondary {
- margin: 0 -20px 20px -22px;
-
- .llms-nav-items {
- padding-left: 10px;
- }
-
- }
- }
-
-
- .wrap.llms-status {
- .llms-nav-tab-wrapper.llms-nav-secondary {
- margin: 0 -20px 20px -22px;
-
- .llms-nav-items {
- padding-left: 10px;
- }
-
- }
- }
-}
diff --git a/assets/scss/admin/_tabs.scss b/assets/scss/admin/_tabs.scss
deleted file mode 100644
index 6ec7c3af44..0000000000
--- a/assets/scss/admin/_tabs.scss
+++ /dev/null
@@ -1,162 +0,0 @@
-.llms-nav-tab-wrapper {
- background: $color-blue;
- margin: 20px 0;
-
- &.llms-nav-secondary {
- background: #e1e1e1;
-
- .llms-nav-item {
- margin: 0;
-
- .llms-nav-link:hover,
- &.llms-active .llms-nav-link {
- background: darken( #e1e1e1, 8 );
- }
-
- }
-
- .llms-nav-link {
- color: #414141;
- font-size: 15px;
- padding: 8px 14px;
-
- .dashicons {
- font-size: 15px;
- height: 15px;
- width: 15px;
- }
- }
-
- }
-
- &.llms-nav-text {
- background: inherit;
- .llms-nav-items {
- padding-left: 0;
- }
- .llms-nav-item {
- background: inherit;
- color: #646970;
- &:last-child:after {
- display: none;
- }
- &:after {
- content: '|';
- display: inline-block;
- margin: 0 8px 0 6px;
- }
- .llms-nav-link:hover {
- background: inherit;
- color: $color-brand-blue;
- }
- &.llms-active .llms-nav-link {
- background: inherit;
- color: #000;
- font-weight: 600;
- text-decoration: none;
- }
- .llms-nav-link {
- color: $color-brand-blue;
- display: inline-block;
- letter-spacing: 0;
- margin: 0;
- padding: 0;
- text-decoration: underline;
- text-transform: none;
- }
- }
- }
-
- &.llms-nav-style-tabs {
- background-color: $color-brand-dark-blue;
- margin: 0;
- padding-top: 8px;
-
- .llms-nav-item {
- margin: 0 3px;
-
- .llms-nav-link {
- border-top-left-radius: 4px;
- border-top-right-radius: 4px;
- }
-
- &.llms-active .llms-nav-link {
- background-color: #FFF;
- color: $color-brand-blue;
- font-weight: 700;
- }
- }
-
- }
-
- &.llms-nav-style-filters {
- background-color: $color-brand-blue;
- border-radius: 12px;
- margin: 20px 0;
- overflow: hidden;
- padding: 0;
-
- .llms-nav-items {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- padding-left: 0;
-
- @media only screen and (min-width: 782px) {
- flex-direction: row;
- }
-
- .llms-nav-item {
- float: none;
-
- .llms-nav-link {
- padding: 14px;
- }
- }
- }
- }
-
- .llms-nav-items {
- @include clearfix;
- margin: 0;
- padding-left: 10px;
- }
-
- .llms-nav-item {
- margin: 0;
-
- .llms-nav-link:hover {
- background: $color-brand-blue;
- }
- &.llms-active .llms-nav-link {
- background: $color-brand-dark-blue;
- }
-
- &.llms-active .llms-nav-link {
- font-weight: 400;
- }
-
- @media only screen and (min-width: 768px) {
- float: left;
-
- &.llms-nav-item-right {
- float: right;
- }
- }
-
- }
-
- .llms-nav-link {
-
- color: #fff;
- cursor: pointer;
- display: block;
- font-weight: 400;
- font-size: 15px;
- padding: 9px 18px;
- text-align: center;
- text-decoration: none;
- transition: all .3s ease;
-
- }
-}
diff --git a/assets/scss/admin/_wp-menu.scss b/assets/scss/admin/_wp-menu.scss
deleted file mode 100644
index ba81cd6235..0000000000
--- a/assets/scss/admin/_wp-menu.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-#adminmenu {
-
- .toplevel_page_lifterlms .wp-menu-image img {
- padding-top: 6px;
- width: 20px;
- }
-
- .toplevel_page_lifterlms,
- .opensub .wp-submenu li.current,
- .wp-submenu li.current,
- .wp-submenu li.current,
- .wp-submenu li.current,
- a.wp-has-current-submenu:focus+.wp-submenu li.current {
- a[href*="page=llms-add-ons"] {
- color: $color-orange;
- }
- }
-
-}
-
-
diff --git a/assets/scss/admin/breakpoints/_1030up.scss b/assets/scss/admin/breakpoints/_1030up.scss
deleted file mode 100644
index 2c94494dcd..0000000000
--- a/assets/scss/admin/breakpoints/_1030up.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-/******************************************************************
-
-Desktop Stylesheet
-
-******************************************************************/
-
-//option page tab menu
-.llms-nav-tab {
- display: inline-block;
- width: 33.333%;
-}
-.llms-nav-tab-settings {
- display: inline-block;
- width: 25%;
-}
-
-//select box form wrapper
-#llms-form-wrapper {
- .llms-select {
- display: inline-block;
- width: 47.5%;
- &:first-child {
- margin-right: 5%;
- }
-
- }.llms-filter-options {
- display: inline-block;
- width: 47.5%;
-
- &.date-filter {
- margin-right: 5%;
- }.llms-date-select {
- margin-bottom: 0;
- }
-
- }.llms-date-select {
- width: 47.5%;
-
- &:first-child {
- margin-right: 5%
- }
-
- }
-}
-
-.llms-widget-row {
- @include clearfix;
- .llms-widget-1-5 {
- vertical-align: top;
- width: 20%;
- float: left;
- box-sizing: border-box;
- padding: 0 5px;
- }
- .llms-widget-1-4 {
- vertical-align: top;
- width: 25%;
- float: left;
- box-sizing: border-box;
- padding: 0 5px;
- }
- .llms-widget-1-3 {
- width: 33.3%;
- float: left;
- box-sizing: border-box;
- padding: 0 5px;
- }
- .llms-widget-1-2 {
- width: 50%;
- float: left;
- box-sizing: border-box;
- padding: 0 5px;
- vertical-align: top;
- }
-
-}
diff --git a/assets/scss/admin/breakpoints/_1240up.scss b/assets/scss/admin/breakpoints/_1240up.scss
deleted file mode 100644
index 2b3457079d..0000000000
--- a/assets/scss/admin/breakpoints/_1240up.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-/******************************************************************
-
-large Monitor Stylesheet
-
-******************************************************************/
-
-.llms-nav-tab-filters,
-.llms-nav-tab-settings {
- float: left;
- width: 12.5%;
-}
diff --git a/assets/scss/admin/breakpoints/_481up.scss b/assets/scss/admin/breakpoints/_481up.scss
deleted file mode 100644
index 05e7bc2f76..0000000000
--- a/assets/scss/admin/breakpoints/_481up.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-/******************************************************************
-
-Larger Phones
-
-******************************************************************/
-
-//select box form wrapper
-#llms-form-wrapper {
-
- .llms-checkbox {
- width: 33%;
- //text-align: center;
-
- }
-}
diff --git a/assets/scss/admin/breakpoints/_768up.scss b/assets/scss/admin/breakpoints/_768up.scss
deleted file mode 100644
index be119130ce..0000000000
--- a/assets/scss/admin/breakpoints/_768up.scss
+++ /dev/null
@@ -1,73 +0,0 @@
-/******************************************************************
-
-Tablets and small computers
-
-******************************************************************/
-
-ul.tabs li{
- display: inline-block;
- }
-
-//option page tab menu
-.llms-nav-tab {
- display: inline-block;
- width: 33%;
-}
-.llms-nav-tab-settings {
- display: inline-block;
- width: 25%;
-}
-
-//select box form wrapper
-#llms-form-wrapper {
- .llms-select {
- width: 50%;
- max-width: 500px;
-
- }.llms-filter-options {
- width: 50%;
- //display: inline-block;
- max-width: 500px;
-
- }.llms-date-select {
- width: 47.5%;
-
- &:first-child {
- margin-right: 5%
- }
-
- }
-}
-
-.llms-widget {
- input[type="text"],
- input[type="password"],
- input[type="datetime"],
- input[type="datetime-local"],
- input[type="date"],
- input[type="month"],
- input[type="time"],
- input[type="week"],
- input[type="number"],
- input[type="email"],
- input[type="url"],
- input[type="search"],
- input[type="tel"],
- input[type="color"],
- select,
- textarea, {
- width: 50%;
-
- &.medium { width: 30%; }
- &.small { width: 20%; }
- &.tiny { width: 10%; }
- }
-
- // .form-table th {
- // width: 140px;
- // }
-
-}
-
-
-
diff --git a/assets/scss/admin/breakpoints/_base.scss b/assets/scss/admin/breakpoints/_base.scss
deleted file mode 100644
index 4e196a12a8..0000000000
--- a/assets/scss/admin/breakpoints/_base.scss
+++ /dev/null
@@ -1,93 +0,0 @@
-/******************************************************************
-
-Base Mobile
-
-******************************************************************/
-
-.llms-nav-tab,
-.llms-nav-tab-filters {
- display: block;
- width: 100%;
-}
-
-form.llms-nav-tab-filters.full-width {
- width: 100%;
-
- label {
- display: inline-block;
- width: 10%;
- text-align: left;
- }
-
- .select2-container {
- width: 85% !important;
- }
-}
-
-.llms-nav-tab-settings {
- display: block;
- width: 100%;
-}
-
-//select box form wrapper
-#llms-form-wrapper {
- .llms-select {
- width: 100%;
- margin-bottom: 20px;
-
- }.llms-checkbox {
- display: inline-block;
- width: 100%;
- text-align: left;
-
- }.llms-filter-options {
- width: 100%;
- //margin-bottom: 20px;
-
- }.llms-date-select {
- width: 100%;
- display: inline-block;
- margin-bottom: 20px;
- input[type="text"] {
- width: 100%;
- }
-
- }.llms-search-button {
- //display: inline-block;
- //width: 30%;
- #llms-search-button {
-
- //float: right;
- }
-
- }
-
-}
-
-// .llms-widget-full {
-// &.top {
-// margin-top: 20px;
-// }
-// }
-// .llms-widget {
-// .form-table td {
-// padding: 15px 0;
-// ul { margin: 5px 0 0; }
-
-
-// .conditional-field {
-// display: none;
-// margin-left: 25px;
-// }
-// .conditional-radio:checked ~ .conditional-field {
-// display: block;
-// }
-
-
-// }
-// }
-
-ul.tabs li{
- display: block;
- }
-
diff --git a/assets/scss/admin/metaboxes/_builder-launcher.scss b/assets/scss/admin/metaboxes/_builder-launcher.scss
deleted file mode 100644
index 0e40e50c30..0000000000
--- a/assets/scss/admin/metaboxes/_builder-launcher.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.llms-builder-launcher {
-
- p {
- margin-top: 0;
- }
-
- ol {
- margin-top: -6px;
- }
-
- .llms-button-primary {
- box-sizing: border-box;
- }
-
-}
diff --git a/assets/scss/admin/metaboxes/_llms-metabox.scss b/assets/scss/admin/metaboxes/_llms-metabox.scss
deleted file mode 100644
index 6bd7c3b382..0000000000
--- a/assets/scss/admin/metaboxes/_llms-metabox.scss
+++ /dev/null
@@ -1,215 +0,0 @@
-
-// This is a "legacy" rule that may be removable
-.llms-mb-list {
-
- label {
- font-size: 15px;
- font-weight: 700;
- line-height: 1.5;
- display: block;
- width: 100%;
- }
-
- .description {
- margin-bottom: 8px;
- }
-
- .input-full {
- width: 100%;
- }
-}
-
-
-#poststuff .llms-metabox {
-
- @extend %cf;
-
- h2, h3, h6 {
- margin: 0;
- padding: 0;
- }
-
- h2 {
- font-size: 18px;
- font-weight: 700;
- }
-
- h3 {
- color: #777;
- font-size: 16px;
- }
-
- h4 {
- border-bottom: 1px solid #e5e5e5;
- padding: 0;
- margin: 0;
- }
-
- .llms-transaction-test-mode {
- background: #ffffd7;
- font-style: italic;
- left: 0;
- padding: 2px;
- position: absolute;
- right: 0;
- top: 0;
- text-align: center;
- }
-
- a.llms-editable,
- .llms-metabox-icon,
- button.llms-editable {
- color: $color-grey;
- text-decoration: none;
- &:hover {
- color: $color-brand-blue;
- }
- }
-
- button.llms-editable {
- border: none;
- background: none;
- cursor: pointer;
- padding: 0;
- vertical-align: top;
- }
-
- h4 button.llms-editable {
- float: right;
- }
-
- .llms-table {
- margin-top: 10px;
-
- }
-}
-
-.llms-metabox-section {
- background: #fff;
- margin-top: 25px;
- position: relative;
-
- &.no-top-margin {
- margin-top: 0;
- }
-
- .llms-metabox-field {
- margin: 15px 0;
- position: relative;
- label {
- color: #777;
- display: block;
- margin-bottom: 5px;
- font-weight: 500;
- vertical-align: baseline;
- }
-
- select,
- textarea,
- input[type="text"],
- input[type="number"] {
- width: 100%;
- }
-
- input.md-text {
- width: 105px;
- }
-
- input.sm-text {
- width: 45px;
- }
-
-
- .llms-datetime-field {
-
- .llms-date-input {
- width: 95px;
- }
- .llms-time-input {
- width: 45px;
- }
- em {
- font-style: normal;
- padding: 0 3px;
- }
-
- }
-
- }
-
-
-}
-
-.llms-collapsible {
-
- @extend %clearfix;
-
- border: 1px solid #e5e5e5;
- position: relative;
- margin-top: 0;
- margin-bottom: -1px;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- &.opened .llms-collapsible-header {
- .dashicons-arrow-down {
- display: none;
- }
- .dashicons-arrow-up {
- display: inline;
- }
- }
-
- .llms-collapsible-header {
- @extend %clearfix;
- padding: 10px;
-
- h3 {
- color: #777;
- margin: 0;
- font-size: 16px;
- }
-
- .dashicons-arrow-up {
- display: inline;
- }
- .dashicons-arrow-up {
- display: none;
- }
-
- a {
- text-decoration: none;
- }
-
- .dashicons {
- color: #777;
- cursor: pointer;
- transition: color .4s ease;
- &:hover {
- color: $color-blue;
- }
-
- &.dashicons-warning,&.dashicons-warning:hover,
- &.dashicons-trash:hover,
- &.dashicons-no:hover {
- color: $color-danger;
- }
- &.dashicons-warning.medium-danger {
- &,
- &:hover {
- color: $color-orange;
- }
- }
- }
-
- }
-
- .llms-collapsible-body {
- @extend %clearfix;
- display: none;
- padding: 10px;
- }
-
-}
diff --git a/assets/scss/admin/metaboxes/_metabox-engagements-type.scss b/assets/scss/admin/metaboxes/_metabox-engagements-type.scss
deleted file mode 100644
index d50c36466b..0000000000
--- a/assets/scss/admin/metaboxes/_metabox-engagements-type.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-.submitbox .llms-mb-section,
-.llms-award-engagement-submitbox .llms-mb-list {
- margin-bottom: 12px;
- &:last-of-type {
- margin-bottom: 0;
- }
- @at-root .sync-action,
- &.student-info,
- &.post_author_override label {
- &:before {
- // dashicons-admin-users.
- font: normal 20px/1 dashicons;
- speak: never;
- display: inline-block;
- margin-left: -1px;
- padding-right: 3px;
- vertical-align: top;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- position: relative;
- top: -1px;
- color: #8c8f94;
- body:not(.admin-color-fresh) & {
- color: currentColor; // Used when selecting a different admin color scheme from the default one.
- }
- }
- }
- &.student-info,
- &.post_author_override label {
- &:before {
- content: '\f110';
- }
- }
- @at-root .sync-action:before {
- content: '\f113';
- color: white;
- }
- &.post_author_override label {
- display: inline-block;
- width: auto;;
- }
-}
diff --git a/assets/scss/admin/metaboxes/_metabox-field-repeater.scss b/assets/scss/admin/metaboxes/_metabox-field-repeater.scss
deleted file mode 100644
index 8c64687be5..0000000000
--- a/assets/scss/admin/metaboxes/_metabox-field-repeater.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-.llms-mb-container .tab-content ul:not(.select2-selection__rendered).llms-mb-repeater-fields > li.llms-mb-list {
- border-bottom: none;
- padding: 0 0 10px;
-}
-
-.llms-mb-list.repeater {
-
- .llms-repeater-rows {
- position: relative;
- margin-top: 10px;
- min-height: 10px;
-
- &.dragging {
- background: #efefef;
- box-shadow: inset 0 0 0 1px #e5e5e5;
- }
- }
-
- .llms-repeater-row {
- background: #fff;
- }
-
- .llms-mb-repeater-fields {
-
- }
-
- .llms-mb-repeater-footer {
- text-align: right;
- margin-top: 20px;
- }
-
- .tmce-active .wp-editor-area {
- color: #32373c; // wp core default color
- }
-
-}
diff --git a/assets/scss/admin/metaboxes/_metabox-instructors.scss b/assets/scss/admin/metaboxes/_metabox-instructors.scss
deleted file mode 100644
index f03294ceed..0000000000
--- a/assets/scss/admin/metaboxes/_metabox-instructors.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-._llms_instructors_data.repeater {
- .llms-repeater-rows .llms-repeater-row:first-child {
- .llms-repeater-remove { display: none; }
- }
-
- .llms-mb-list {
- padding: 0 5px !important;
- }
-}
diff --git a/assets/scss/admin/metaboxes/_metabox-orders.scss b/assets/scss/admin/metaboxes/_metabox-orders.scss
deleted file mode 100644
index 3f4a603064..0000000000
--- a/assets/scss/admin/metaboxes/_metabox-orders.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-.post-type-llms_order #post-body-content { display: none; }
-#lifterlms-order-details {
- .handlediv,
- .handlediv.button-link,
- .postbox-header { display: none;}
- .inside {
- padding: 20px;
- margin-top: 0;
-
- }
-}
-
-// failed transaction color
-.llms-table tbody tr.llms-txn-failed td {
- background-color: rgba( $color-red, 0.5 );
- border-bottom-color: rgba( $color-red, 0.5 );
-}
-
-// refunded transaction color
-.llms-table tbody tr.llms-txn-refunded td {
- background-color: rgba( orange, 0.5 );
- border-bottom-color: rgba( orange, 0.5 );
-}
-
-.llms-txn-refund-form,
-.llms-manual-txn-form {
- .llms-metabox-section {
- margin-top: 0;
- }
- .llms-metabox-field {
- text-align: right;
- input {
- &[type="number"] { max-width: 100px; }
- &[type="text"] { max-width: 340px; }
-
- }
- }
-}
-
-.llms-manual-txn-form {
- background-color: #eaeaea;
- .llms-metabox-section {
- background-color: #eaeaea;
- }
-}
-
-#llms-remaining-edit {
- display: none;
-}
-.llms-remaining-edit--content {
- label, span, textarea {
- display: block;
- }
-
- label {
- margin-bottom: 20px;
- }
-
- textarea, input {
- width: 100%;
- }
-}
diff --git a/assets/scss/admin/metaboxes/_metabox-product.scss b/assets/scss/admin/metaboxes/_metabox-product.scss
deleted file mode 100644
index c4c4e758a8..0000000000
--- a/assets/scss/admin/metaboxes/_metabox-product.scss
+++ /dev/null
@@ -1,75 +0,0 @@
-.llms-metabox {
-
- #llms-new-access-plan-model {
- display: none;
- }
-
- .llms-access-plans {
- @extend %clearfix;
- margin-top: 10px;
-
- > .llms-no-plans-msg { display: none; }
- > .llms-no-plans-msg:last-child {
- box-shadow: inset 0 0 0 1px #e5e5e5;
- display: block;
- text-align: center;
- padding: 10px;
- }
-
- &.dragging {
- background: #efefef;
- box-shadow: inset 0 0 0 1px #e5e5e5;
- }
-
- .llms-spinning {
- z-index: 1;
- }
-
- .llms-invalid {
- border-color: $color-danger;
- .dashicons-warning {
- display: inline;
- }
- }
- .llms-needs-attention .dashicons-warning.medium-danger {
- display: inline;
- }
- .dashicons-warning {
- display: none;
- }
- }
-
- .llms-access-plan {
-
- text-align: left;
-
- [data-tip]:before {
- text-align: center;
- }
-
- .llms-plan-link,
- [data-controller] {
- display: none;
- }
-
- &:hover,
- &.opened {
- .llms-plan-link {
- display: inline-block;
- }
- }
-
- .llms-metabox-field {
- margin: 5px 0;
- }
-
- .llms-required {
- color: $color-danger;
- margin-left: 3px;
- }
- .notice {
- margin-left: 0;
- }
- }
-
-}
diff --git a/assets/scss/admin/metaboxes/_metabox-students.scss b/assets/scss/admin/metaboxes/_metabox-students.scss
deleted file mode 100644
index 7b0aef36ef..0000000000
--- a/assets/scss/admin/metaboxes/_metabox-students.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.llms-metabox-students {
- .llms-table {
- tr .name {
- text-align: left;
- }
- }
-
- .llms-add-student:hover {
- color: $color-green;
- }
- .llms-remove-student:hover {
- color: $color-red;
- }
-
-}
diff --git a/assets/scss/admin/modules/_forms.scss b/assets/scss/admin/modules/_forms.scss
deleted file mode 100644
index fac4a4b803..0000000000
--- a/assets/scss/admin/modules/_forms.scss
+++ /dev/null
@@ -1,190 +0,0 @@
-/******************************************************************
-
-Form Styles
-
-******************************************************************/
-
-// lifterlms form wrapper
-#llms-form-wrapper {
-
- // setup defaults
- input[type="text"],
- input[type="password"],
- input[type="datetime"],
- input[type="datetime-local"],
- input[type="date"],
- input[type="month"],
- input[type="time"],
- input[type="week"],
- input[type="number"],
- input[type="email"],
- input[type="url"],
- input[type="search"],
- input[type="tel"],
- input[type="color"],
- input[type="checkbox"],
- select,
- textarea,
- .llms-field {
-
- // a focused input (or hovered on)
- &:focus,
- &:active {
-
- } // end hover or focus
- }
-
- // sub wrapper for search filter form (analytics)
- .llms-search-form-wrapper {
- border-bottom: 1px solid $color-grey;
- margin: 20px 0;
-
- }
-
-
- #llms_analytics_search {
- border:none !important;
- text-shadow: none !important;
- border: none !important;
- outline: none !important;
- box-shadow: none !important;
- margin: 0 !important;
- color: $color-white !important;
- background: $color-blue !important;
- border-radius: 0;
- transition: .5s;
-
- &:hover {
- background: $color-darkblue !important;
-
- }&:active {
- background: $color-lightblue !important;
- }
- }
-
-} // end input defaults
-
-
-#llms-skip-setup-form {
- .llms-admin-link {
- background:none!important;
- border:none;
- padding:0!important;
- color:#0074a2;
- cursor:pointer;
- &:hover {
- color:#2ea2cc
- }&:focus{
- color:#124964;
- }
-
- }
-
-}
-
-/**
- * Toggle Switch ( replaces checkbox on admin panels )
- */
-.llms-switch {
- position: relative;
-
- .llms-toggle {
- position: absolute;
- margin-left: -9999px;
- visibility: hidden;
- }
-
- .llms-toggle + label {
- box-sizing: border-box;
- display: block;
- position: relative;
- cursor: pointer;
- outline: none;
- user-select: none;
- }
-
- input.llms-toggle-round + label {
- border: 2px solid #6c7781;
- border-radius: 10px;
- height: 20px;
- width: 36px;
- }
- input.llms-toggle-round + label:before,
- input.llms-toggle-round + label:after {
- box-sizing: border-box;
- content: '';
- display: block;
- position: absolute;
- transition: background 0.4s;
- }
-
- input.llms-toggle-round:checked + label {
- border-color: #11a0d2;
- background-color: #11a0d2;
- }
-
- // Primary dot (that moves.)
- input.llms-toggle-round + label:after {
- height: 12px;
- left: 2px;
- top: 2px;
- background-color: #6c7781;
- border-radius: 50%;
- transition: margin 0.4s;
- width: 12px;
- z-index: 3;
- }
-
- // Primary dot when toggle on.
- input.llms-toggle-round:checked + label:after {
- background-color: $color-white;
- margin-left: 16px;
- }
-
- // Secondary dot: empty on the right side of the toggle when toggled off.
- input.llms-toggle-round + label:before {
- height: 8px;
- top: 4px;
- border: 1px solid #6c7781;
- border-radius: 50%;
- right: 4px;
- width: 8px;
- z-index: 2;
- }
-
- input.llms-toggle-round:checked + label:before {
- border-color: $color-white;
- border-radius: 0;
- left: 6px;
- right: auto;
- width: 2px;
- }
-
-}
-
-#llms-profile-fields {
- margin: 50px 0;
- .llms-form-field {
- padding-left: 0;
- }
- label {
- display: block;
- font-weight: bold;
- padding: 8px 0 2px;
- }
- .type-checkbox .type-checkbox {
- label {
- display: initial;
- font-weight: initial;
- padding: 0;
- }
- input {
- display: inline-block;
- margin-bottom: 0;
- width: 1rem;
- }
- }
- select {
- max-width: 100%;
- }
-}
diff --git a/assets/scss/admin/modules/_icons.scss b/assets/scss/admin/modules/_icons.scss
deleted file mode 100644
index b937c1072c..0000000000
--- a/assets/scss/admin/modules/_icons.scss
+++ /dev/null
@@ -1,92 +0,0 @@
-/******************************************************************
-
-SVG Styles
-
-******************************************************************/
-
-svg {
- &.icon {
- height: 24px;
- width: 24px;
- display: inline-block;
- fill: currentColor; // Inherit color
- vertical-align: baseline; // Options: baseline, sub, super, text-top, text-bottom, middle, top, bottom
-
- // Different styling for when an icon appears in a button element
- button & {
- height: 18px;
- width: 18px;
- margin: 4px -4px 0 4px;
- filter: drop-shadow( 0 1px #eee );
- float: right;
-
- }&.menu-icon {
- height: 20px;
- width: 20px;
- display: inline-block;
- fill: currentColor;
- vertical-align: text-bottom;
- margin-right: 10px;
-
- }&.tree-icon {
- height: 13px;
- width: 13px;
- vertical-align: middle;
-
- }&.section-icon {
- height: 16px;
- width: 16px;
- vertical-align: text-bottom;
-
- }&.button-icon {
- height: 16px;
- width: 16px;
- vertical-align: text-bottom;
-
- }&.button-icon-attr {
- height: 10px;
- width: 10px;
- vertical-align: middle;
-
- }&.list-icon {
- height: 12px;
- width: 12px;
- vertical-align: middle;
-
- &.on {
- color: $color-blue;
-
- }&.off {
- color: $color-cinder;
- }
-
- }&.detail-icon {
- height: 16px;
- width: 16px;
- vertical-align: text-bottom;
- cursor:default;
-
- &.on {
- color: $color-blue;
-
- }&.off {
- color: $color-lightgrey;
- }
- }
-
- }
-
- &.icon-ion {}
-
- &.icon-ion-edit {}
-
- // rotate for arrow tips
- &.icon-ion-arrow-up {
- transform: rotate(90deg);
- }
-
- use {
- pointer-events: none;
- }
-
-}
\ No newline at end of file
diff --git a/assets/scss/admin/modules/_llms-order-note.scss b/assets/scss/admin/modules/_llms-order-note.scss
deleted file mode 100644
index b055ebb144..0000000000
--- a/assets/scss/admin/modules/_llms-order-note.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-.llms-order-note {
-
- .llms-order-note-content {
- background: #efefef;
- margin-bottom: 10px;
- padding: 10px;
- position: relative;
- &:after {
- border-style: solid;
- border-color: #efefef transparent;
- border-width: 10px 10px 0 0;
- bottom: -10px;
- content: '';
- display: block;
- height: 0;
- left: 20px;
- position: absolute;
- width: 0;
-
- }
- p {
- font-size: 13px;
- margin: 0;
- line-height: 1.5;
- }
- }
-
- .llms-order-note-meta {
- color: #999;
- font-size: 11px;
- margin-left: 10px;
- }
-
-
-}
diff --git a/assets/scss/admin/modules/_mb-tabs.scss b/assets/scss/admin/modules/_mb-tabs.scss
deleted file mode 100644
index d8ea24e292..0000000000
--- a/assets/scss/admin/modules/_mb-tabs.scss
+++ /dev/null
@@ -1,57 +0,0 @@
-/******************************************************************
-
-Metabox Tabs
-
-******************************************************************/
-
-// free space up if the metabox is on the side
-#side-sortables .tab-content {
- padding: 0;
-}
-
-.llms-mb-container .tab-content{
- display: none;
- background-color: #FFF;
- padding: 0 20px;
-
- ul:not(.select2-selection__rendered) {
- margin: 0 0 15px 0;
-
- > li {
- padding: 20px 0;
- margin: 0;
-
- &.select:not([class*="d-"]) {
- width: 100%;
- }
-
- &:last-child {
- border: 0;
- padding-bottom: 0;
-
- }
-
- &.top {
- border-bottom: 0;
- padding-bottom: 0;
- }
-
- }
- }
-
- .full-width { width: 100%; }
-
- #wp-content-editor-tools {
- background: none;
- }
-
-}
-
-.llms-mb-container .tab-content.llms-active{
- display: inherit;
-}
-
-
-.llms-mb-container .tab-content .no-border {
- border-bottom: 0px;
-}
diff --git a/assets/scss/admin/modules/_merge-codes.scss b/assets/scss/admin/modules/_merge-codes.scss
deleted file mode 100644
index 51a800991b..0000000000
--- a/assets/scss/admin/modules/_merge-codes.scss
+++ /dev/null
@@ -1,61 +0,0 @@
-.button.llms-merge-code-button {
- vertical-align: middle;
- svg {
- margin-right: 2px;
- position: relative;
- top: 4px;
- width: 16px;
- g {
- fill: currentColor;
- }
- }
-}
-
-.llms-mb-container {
- .llms-merge-code-wrapper {
- float: right;
- top: -5px;
- }
-}
-
-.llms-merge-code-wrapper {
- display: inline;
- position: relative;
-}
-
-.llms-merge-codes {
- background: #f7f7f7;
- border: 1px solid #ccc;
- border-radius: 3px;
- box-shadow: 0 1px 0 #ccc;
- color: #555;
- display: none;
- left: 1px;
- overflow: hidden;
- position: absolute;
- top: 30px;
- width: 200px;
-
- ul {
- margin: 0;
- padding: 0;
- }
-
- li {
- cursor: pointer;
- margin: 0;
- padding: 4px 8px !important;
- border-bottom: 1px solid #ccc;
- }
-
- li:hover {
- color: #23282d;
- background: #fefefe;
- }
-
- &.active {
- display: block;
- z-index: 777;
- }
-
-}
diff --git a/assets/scss/admin/modules/_top-modal.scss b/assets/scss/admin/modules/_top-modal.scss
deleted file mode 100644
index 9a118bcebc..0000000000
--- a/assets/scss/admin/modules/_top-modal.scss
+++ /dev/null
@@ -1,203 +0,0 @@
-/******************************************************************
-
-Styles for topModal modal
-
-******************************************************************/
-
-/**
- * Base modal styles
- */
-.topModal {
- display:none;
- position:relative;
- border:4px solid #808080;
- background:#fff;
- z-index:1000001;
- padding:2px;
- max-width:500px;
- margin: 34px auto 0;
- box-sizing: border-box;
- box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
- background-color: #ffffff;
- border-radius: 2px;
- border: 1px solid #dddddd;
-
-}.topModalClose {
- float:right;
- cursor:pointer;
- margin-right: 10px;
- margin-top: 10px;
-
-}.topModalContainer {
- display: none;
- overflow: auto;
- overflow-y: hidden;
- position: fixed;
- top: 0 !important;
- right: 0;
- bottom: 0;
- left: 0;
- -webkit-overflow-scrolling: touch;
- width: auto !important;
- margin-left: 0 !important;
- background-color: transparent !important;
- z-index: 100002 !important;
-
-}.topModalBackground {
- display:none;
- background:#000;
- position:fixed;
- height:100%;
- width:100%;
- top:0 !important;
- left:0;
- margin-left: 0 !important;
- z-index: 100002 !important;
- box-sizing: border-box;
- overflow: auto;
- overflow-y: hidden;
-
-}body.modal-open {
- overflow: hidden;
-
-}.llms-modal-header {
- border-top-right-radius: 1px;
- border-top-left-radius: 1px;
- background: $color-blue;
- color: #eeeeee;
- padding: 10px 15px;
- font-size: 18px;
-
-}#llms-icon-modal-close {
- width:16px;
- height: 16px;
- fill: $color-white;
-
-}.llms-modal-content {
- padding: 20px;
-
- h3 {
- margin-top: 0;
- }
-
-}
-
-/**
- * Custom Modal Styles for LifterLMS
- */
-.llms-modal-form {
-
- h1 {
- margin-top: 0;
- }
-
- input[type=text] {
- width: 100%;
- }
-
- textarea,
- input[type="text"],
- input[type="password"],
- input[type="file"],
- input[type="datetime"],
- input[type="datetime-local"],
- input[type="date"],
- input[type="month"],
- input[type="time"],
- input[type="week"],
- input[type="number"],
- input[type="email"],
- input[type="url"],
- input[type="search"],
- input[type="tel"],
- input[type="color"] {
- padding: 0 .4em 0 .4em;
- margin-bottom: 2em;
- vertical-align: middle;
- border-radius: 3px;
- min-width: 50px;
- max-width: 635px;
- width: 100%;
- min-height: 32px;
- background-color: #fff;
- border: 1px solid $color-lightgrey;
- margin: 0 0 24px 0;
- outline: none;
- transition: border 0.3s ease-in-out 0s;
-
- &:focus {
- background: $color-white;
- border: 1px solid $color-blue;
-
- }
- }
-
- textarea {
- padding: .4em !important;
- height: 100px !important;
- border-radius: 3px;
- vertical-align: middle;
- min-height: 32px;
- outline: none;
- box-sizing: border-box;
-
- &:focus {
- background: $color-white;
- border: 1px solid $color-blue;
-
- }
-
- }
-
- .chosen-container-single .chosen-single {
- border-radius: 3px;
- vertical-align: middle;
- min-height: 32px;
- border: 1px solid $color-lightgrey;
- width: 100%;
- background: $color-white !important;
- outline: none;
- box-sizing: border-box;
- box-shadow: 0 0 0 #fff;
- line-height: 32px;
- margin: 0 0 24px 0;
-
- &:focus {
- background: $color-white;
- border: 1px solid $color-blue;
- }
- }
-
- .chosen-container-single .chosen-single div b {
- margin-top: 4px;
- }
-
- .chosen-search input[type=text] {
- border: 1px solid $color-lightgrey;
-
- &:focus {
- background-color: $color-white;
- border: 1px solid $color-blue;
- }
-
- }
-
- .chosen-container-single .chosen-drop {
- margin-top: -28px;
- }
-
- .llms-button-primary, .llms-button-secondary {
- padding: 10px 10px;
- border-radius: 0;
- transition: .5s;
- box-shadow: 0 1px 1px #ccc;
-
- &.full {
- width: 100%;
- }
- }
-}
-
-.modal-open .select2-dropdown {
- z-index: 1000005;
-}
diff --git a/assets/scss/admin/modules/_voucher.scss b/assets/scss/admin/modules/_voucher.scss
deleted file mode 100644
index 7262f52e43..0000000000
--- a/assets/scss/admin/modules/_voucher.scss
+++ /dev/null
@@ -1,133 +0,0 @@
-a.llms-voucher-delete {
- background: $color-danger;
- color: $color-white;
- display: block;
- padding: 4px 2px;
- text-decoration: none;
- transition: ease .3s all;
-
- &:hover {
- background: #af3a26;
- }
-}
-
-
-
-.llms-voucher-codes-wrapper,
-.llms-voucher-redemption-wrapper {
-
- table {
- width: 100%;
- border-collapse: collapse;
-
- th, td {
- border: none;
- }
-
- thead {
- background-color: $color-blue;
- color:#fff;
- th {
- padding: 10px 10px;
- }
- }
-
- tr {
- counter-increment: row-counter;
- &:nth-child(even) {
- background-color: #F1F1F1;
- }
-
- td {
- padding: 5px;
- &:first-child:before {
- content: counter( row-counter );
- }
- }
- }
- }
-}
-
-.llms-voucher-codes-wrapper {
-
- table {
- width: 100%;
- border-collapse: collapse;
-
- th, td {
- border: none;
- }
-
- thead {
- background-color: $color-blue;
- color:#fff;
- }
-
- tr {
- &:nth-child(even) {
- background-color: #F1F1F1;
- }
-
- td {
-
- span {
- display: inline-block;
- min-width: 30px;
- }
- }
- }
- }
-
- button {
- cursor: pointer;
- }
-
- .llms-voucher-delete {
- float: right;
- margin-right: 15px;
- }
-
- .llms-voucher-uses {
- width: 50px;
- }
-
- .llms-voucher-add-codes {
- float: right;
-
- input[type="text"] {
- width: 30px;
- }
- }
-}
-
-.llms-voucher-export-wrapper {
-
- .llms-voucher-export-type {
- width: 100%;
-
- p {
- margin: 0 0 0 15px;
- }
- }
-
- .llms-voucher-email-wrapper {
- width: 100%;
- margin: 25px 0;
-
- input[type="text"] {
- width: 100%;
- }
-
- p {
- margin: 0;
- }
- }
-
- > button {
- float: right;
- }
-}
-
-.postbox .inside {
- overflow: auto;
-}
diff --git a/assets/scss/admin/modules/_widgets.scss b/assets/scss/admin/modules/_widgets.scss
deleted file mode 100644
index a596a084d6..0000000000
--- a/assets/scss/admin/modules/_widgets.scss
+++ /dev/null
@@ -1,174 +0,0 @@
-.llms-widget {
- background-color: #FFF;
- border: 1px solid #dedede;
- border-radius: 12px;
- box-shadow: 0px 0px 1px rgba(48, 49, 51, 0.05), 0px 2px 4px rgba(48, 49, 51, 0.1);
- box-sizing: border-box;
- margin-bottom: 20px;
- padding: 20px;
- position: relative;
- width: 100%;
-
- &.alt {
-
- border: 1px solid $color-lightgrey;
- background-color: #efefef;
- margin-bottom: 10px;
-
- .llms-label {
- color: #777;
- font-size: 14px;
- margin-bottom: 10px;
- padding-bottom: 5px;
- }
-
- h2 {
- color: #444;
- font-weight: 300;
- }
-
- }
-
- a {
- border-bottom: 1px dotted $color-brand-blue;
- display: inline-block;
- text-decoration: none;
-
- &:hover {
- border-bottom: 1px dotted $color-brand-blue-dark;
- }
- }
-
- // Nested for specificity (matches h1).
- .llms-widget-content {
- margin: .67em 0;
- color: $color-brand-blue;
- font-size: 2.4em;
- font-weight: 700;
- line-height: 1;
- word-break: break-all;
- }
-
- h2 {
- font-size: 1.8em;
- }
-
- .llms-label {
- box-sizing: border-box;
- font-size: 18px;
- font-weight: 400;
- margin: 0 0 10px 0;
- text-align: center;
- }
-
- .llms-chart {
- width: 100%;
- padding: 10px;
- box-sizing: border-box;
- }
-
- mark.yes {
- background-color: #7ad03a;
- }
-
- .llms-subtitle {
- margin-bottom: 0;
- }
-
- .spinner {
- float: none;
- left: 50%;
- margin: -10px 0 0 -10px;
- position: absolute;
- top: 50%;
- z-index: 2;
- }
-
- &.is-loading {
-
- &:before {
- background: $color-white;
- bottom: 0;
- content: '';
- left: 0;
- opacity: 0.9;
- position: absolute;
- right: 0;
- top: 0;
- z-index: 1;
- }
-
- .spinner {
- visibility: visible;
- }
-
- }
-
- td[colspan="2"] {
- padding-left: 0;
- }
-
- tr.llms-disabled-field {
- opacity: 0.5;
- pointer-events: none;
- }
-
-}
-
-.llms-widget-1-3,
-.llms-widget-1-4,
-.llms-widget-1-5 {
- text-align: center;
-}
-
-
-.llms-widget {
- .llms-widget-info-toggle {
- color: #AAA;
- cursor: pointer;
- font-size: 16px;
- position: absolute;
- right: 20px;
- top: 20px;
- }
-
- &.info-showing {
- .llms-widget-info {
- display: block;
- }
- }
-}
-.llms-widget-info {
- background: $color-cinder;
- color: $color-white;
- bottom: -50px;
- display: none;
- padding: 15px;
- position: absolute;
- text-align: center;
- left: 10px;
- right: 15px;
- z-index: 3;
- &:before {
- content: '';
- border: 12px solid transparent;
- border-bottom-color: $color-cinder;
- left: 50%;
- margin-left: -12px;
- position: absolute;
- top: -24px;
- }
-
- p {
- margin: 0;
- }
-
-}
-
-.llms-widget-row {
- @include clearfix();
-}
-
-.llms-widget-row .no-padding {
- padding: 0 !important;
-}
diff --git a/assets/scss/admin/partials/_grid.scss b/assets/scss/admin/partials/_grid.scss
deleted file mode 100644
index add6846568..0000000000
--- a/assets/scss/admin/partials/_grid.scss
+++ /dev/null
@@ -1,276 +0,0 @@
-/******************************************************************
-
-Grids for Breakpoints
-
-******************************************************************/
-
-// using a mixin since we can't use placeholder selectors
-@mixin grid-col {
- float: left;
- padding-right: 0.75em;
- box-sizing: border-box;
-
-}
-
-// the last column
-.last-col {
- float: right;
- padding-right: 0 !important;
-}
-.last-col:after {
- clear: both;
-}
-
-/*
-Mobile Grid Styles
-These are the widths for the mobile grid.
-There are four types, but you can add or customize
-them however you see fit.
-*/
-@media (max-width: 767px) {
-
- .m-all {
- @include grid-col;
- width: 100%;
- padding-right: 0;
- }
-
- .m-1of2 {
- @include grid-col;
- width: 50%;
- }
-
- .m-1of3 {
- @include grid-col;
- width: 33.33%;
- }
-
- .m-2of3 {
- @include grid-col;
- width: 66.66%;
- }
-
- .m-1of4 {
- @include grid-col;
- width: 25%;
- }
-
- .m-3of4 {
- @include grid-col;
- width: 75%;
- }
-
- .m-right {
- text-align: center;
- }
- .m-center {
- text-align: center;
- }
- .m-left {
- text-align: center;
- }
-
- .d-right {
- text-align: right;
- }
- .d-center {
- text-align: center;
- }
- .d-left {
- text-align: left;
- }
-
-} // end mobile styles
-
-
-/* Portrait tablet to landscape */
-@media (min-width: 768px) and (max-width: 1029px) {
-
- .t-all {
- @include grid-col;
- width: 100%;
- padding-right: 0;
- }
-
- .t-1of2 {
- @include grid-col;
- width: 50%;
- }
-
- .t-1of3 {
- @include grid-col;
- width: 33.33%;
- }
-
- .t-2of3 {
- @include grid-col;
- width: 66.66%;
- }
-
- .t-1of4 {
- @include grid-col;
- width: 25%;
- }
-
- .t-3of4 {
- @include grid-col;
- width: 75%;
- }
-
- .t-1of5 {
- @include grid-col;
- width: 20%;
- }
-
- .t-2of5 {
- @include grid-col;
- width: 40%;
- }
-
- .t-3of5 {
- @include grid-col;
- width: 60%;
- }
-
- .t-4of5 {
- @include grid-col;
- width: 80%;
- }
-
- .d-right {
- text-align: right;
- }
- .d-center {
- text-align: center;
- }
- .d-left {
- text-align: left;
- }
-
-} // end tablet
-
-/* Landscape to small desktop */
-@media (min-width: 1030px) {
-
- .d-all {
- @include grid-col;
- width: 100%;
- padding-right: 0;
- }
-
- .d-1of2 {
- @include grid-col;
- width: 50%;
- }
-
- .d-1of3 {
- @include grid-col;
- width: 33.33%;
- }
-
- .d-2of3 {
- @include grid-col;
- width: 66.66%;
- }
-
- .d-1of4 {
- @include grid-col;
- width: 25%;
- }
-
- .d-3of4 {
- @include grid-col;
- width: 75%;
- }
-
- .d-1of5 {
- @include grid-col;
- width: 20%;
- }
-
- .d-2of5 {
- @include grid-col;
- width: 40%;
- }
-
- .d-3of5 {
- @include grid-col;
- width: 60%;
- }
-
- .d-4of5 {
- @include grid-col;
- width: 80%;
- }
-
- .d-1of6 {
- @include grid-col;
- width: 16.6666666667%;
- }
-
- .d-1of7 {
- @include grid-col;
- width: 14.2857142857%;
- }
-
- .d-2of7 {
- @include grid-col;
- width: 28.5714286%;
- }
-
- .d-3of7 {
- @include grid-col;
- width: 42.8571429%;
- }
-
- .d-4of7 {
- @include grid-col;
- width: 57.1428572%;
- }
-
- .d-5of7 {
- @include grid-col;
- width: 71.4285715%;
- }
-
- .d-6of7 {
- @include grid-col;
- width: 85.7142857%;
- }
-
- .d-1of8 {
- @include grid-col;
- width: 12.5%;
- }
-
- .d-1of9 {
- @include grid-col;
- width: 11.1111111111%;
- }
-
- .d-1of10 {
- @include grid-col;
- width: 10%;
- }
-
- .d-1of11 {
- @include grid-col;
- width: 9.09090909091%;
- }
-
- .d-1of12 {
- @include grid-col;
- width: 8.33%;
- }
-
- .d-right {
- text-align: right;
- }
- .d-center {
- text-align: center;
- }
- .d-left {
- text-align: left;
- }
-
-} // end desktop styles
diff --git a/assets/scss/admin/post-tables/_llms_orders.scss b/assets/scss/admin/post-tables/_llms_orders.scss
deleted file mode 100644
index 27bf992fb0..0000000000
--- a/assets/scss/admin/post-tables/_llms_orders.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-.wp-list-table {
- @include order_status_badges();
-}
-
-#lifterlms-order-transactions .llms-table tfoot th {
- text-align: right;
-}
diff --git a/assets/scss/admin/post-tables/_post-tables.scss b/assets/scss/admin/post-tables/_post-tables.scss
deleted file mode 100644
index f3843b0916..0000000000
--- a/assets/scss/admin/post-tables/_post-tables.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.llms-post-table-post-filter {
- display: inline-block;
- margin-right: 6px;
- max-width: 100%;
- width: 220px;
-}
diff --git a/assets/scss/builder.scss b/assets/scss/builder.scss
deleted file mode 100644
index f6341b9f07..0000000000
--- a/assets/scss/builder.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-@import "_includes/vars";
-@import "_includes/vars-brand-colors";
-@import "_includes/extends";
-@import "_includes/mixins";
-
-@import "admin/course-builder";
diff --git a/assets/scss/certificates.scss b/assets/scss/certificates.scss
deleted file mode 100644
index 7837f8fc64..0000000000
--- a/assets/scss/certificates.scss
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * Reset.
- */
-body {
- background-color: #fff;
- background-image: none;
- margin: 0 auto;
-}
-
-.header, .footer,
-.wrap-header, .wrap-footer,
-.site-header, .site-footer,
-.nav-primary, .primary-nav {
- display: none;
-}
-
-.llms-certificate-container {
- margin: 40px auto 0;
-}
-
-/**
- * Legacy Template.
- */
-.llms-certificate-container:not(.cert-template-v2) {
-
- padding: 0;
- overflow: hidden;
-
- .certificate-background {
- position: relative;
- z-index: 1;
- width: 100%;
- display: block;
- }
-
- .llms_certificate,
- .llms_my_certificate {
- margin: 80px;
- position: relative;
- z-index: 2;
- }
-
- h1:first-child {
- text-align: center;
- }
-
-}
-
-/**
- * V2 Template
- */
-.llms-certificate-wrapper {
- margin: 0 auto;
-}
-.llms-certificate-container.cert-template-v2 {
- width: 100%;
- height: 100%;
- background-size: 100% 100% !important;
- box-sizing: border-box;
-
- .wp-block-columns .wp-block-column > * {
- margin-top: 0 !important;
- margin-bottom: 0 !important;
- }
-}
-
-/**
- * Certificate Actions Footer.
- */
-.llms-print-certificate {
- margin-top: 40px;
- margin-bottom: 40px;
- text-align: center;
-
- form {
- display: inline;
- }
-}
-
-
-
-@media print {
-
- html, body {
- print-color-adjust: exact !important;
- height: 100%;
- overflow: hidden;
- }
-
- @page { size: auto; }
-
- .no-print {
- display: none;
- }
-
- // Make everything on the page invisible.
- body * {
- visibility: hidden !important;
- background: #fff none;
- }
-
- .site, .site-content {
- overflow: visible;
- }
-
- // Remove all headers, menus and footers.
- header, aside, nav, footer {
- display: none !important;
- }
-
- // Make sure a .container parent doesn't shift the certificate see: https://github.com/gocodebox/lifterlms/issues/1163.
- .single-llms_my_certificate .container,
- .single-llms_certificate .container {
- width: 100%;
- }
-
- // Make only the certificate container and its children visible.
- .llms-certificate-container,
- .llms-certificate-container * {
- visibility: visible !important;
- background: transparent none;
- }
-
- // Position certificate absolutely and center horizontally.
- .llms-certificate-container:not(.cert-template-v2) {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- margin: 0 auto;
- background: #fff none;
- }
-
- .llms-certificate-container.cert-template-v2 {
- margin: 0 auto !important;
- print-color-adjust: exact !important;
- transform: scale( 0.95 ); // Don't ram the edge of the paper.
- }
-
-}
diff --git a/assets/scss/editor.scss b/assets/scss/editor.scss
deleted file mode 100644
index 4b18002422..0000000000
--- a/assets/scss/editor.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-.components-panel__row > .components-base-control,
-.components-form-token-field,
-.components-number-control,
-.components-range-control {
- width: 100%;
-}
-
-.llms-block-empty,
-.llms-block-error {
- padding: 1em;
- border: 1px solid #e0e0e0;
- font-size: 16px;
-}
-
-.llms-block-empty {
- font-style: italic;
-}
-
-.wp-block .components-button {
- height: auto;
-}
-
-.wp-block .llms-button-primary {
- text-decoration: none;
-}
-
-.llms-navigation-link-settings .components-panel__row > .components-base-control {
- width: 100%;
-}
-
-.llms-block-icon {
- transform: scale(0.75);
-}
-
-// Fixes core range control component flex issue.
-.components-range-control__root {
- height: 3em;
-}
-
-.llms-loop-item {
- .llms-video-wrapper {
- aspect-ratio: 16/9;
- position: relative;
- iframe {
- width: 100%;
- height: 100%;
- position: absolute;
- left: 0;
- top: 0;
- object-fit: cover;
- }
- }
-}
diff --git a/assets/scss/frontend/_checkout.scss b/assets/scss/frontend/_checkout.scss
deleted file mode 100644
index 1ac62a5fbe..0000000000
--- a/assets/scss/frontend/_checkout.scss
+++ /dev/null
@@ -1,184 +0,0 @@
-.llms-checkout-wrapper {
- form.llms-login {
- border: 3px solid $color-brand-blue;
- display: none;
- margin-bottom: 10px;
- }
- .llms-form-heading {
- background: $color-brand-blue;
- color: #fff;
- margin: 0 0 5px;
- padding: 10px;
- }
-}
-
-.llms-checkout {
- background: #fff;
- position: relative;
-}
-
-.llms-checkout-cols-2 {
- @extend %clearfix;
-
- @media all and (min-width: 800px) {
-
- .llms-checkout-col {
- float: left;
-
- &.llms-col-1 {
- margin-right: 5px;
- width: calc( 58% - 5px );
- }
- &.llms-col-2 {
- margin-left: 5px;
- width: calc( 42% - 5px );
-
- button {
- width: 100%;
- }
- }
- }
-
- }
-
-}
-
- .llms-checkout-section {
- border: 3px solid $color-brand-blue;
- margin-bottom: 10px;
- position: relative;
- }
-
- .llms-checkout-section-content {
- margin: 10px;
- &.llms-form-fields {
- margin: 0px;
- }
-
- .llms-label {
- font-weight: 400;
- font-variant: small-caps;
- text-transform: lowercase;
- }
-
- .llms-order-summary {
- list-style-type: none;
- margin: 0;
- padding: 0;
-
- li { list-style-type: none; }
-
- li.llms-pricing {
- &.on-sale,
- &.has-coupon {
- .price-regular { text-decoration: line-through; }
- }
- }
-
-
- }
-
- .llms-coupon-wrapper {
- border-top: 1px solid #dadada;
- margin-top: 10px;
- padding-top: 10px;
-
- .llms-coupon-entry {
- display: none;
- margin-top: 10px;
- }
- }
-
- }
-
- .llms-form-field.llms-payment-gateway-option {
-
- label + span.llms-description {
- display: inline-block;
- margin-left: 5px;
- }
-
- .llms-description {
- a {
- border: none;
- box-shadow: none;
- text-decoration: none;
- }
- img {
- display: inline;
- max-height: 22px;
- vertical-align: middle;
- }
- }
-
- }
-
- .llms-checkout-wrapper ul.llms-payment-gateways {
- margin: 5px 0 0;
- padding: 0;
- }
- ul.llms-payment-gateways {
- list-style-type: none;
-
- li:last-child:after {
- border-bottom: 1px solid #dadada;
- content: '';
- display: block;
- margin: 10px;
- }
-
- .llms-payment-gateway {
- margin-bottom: 5px;
- list-style-type: none;
- &:last-child {
- margin-bottom: none;
- }
-
- &.is-selected {
- .llms-payment-gateway-option label {
- font-weight: 700;
- }
- .llms-gateway-fields {
- display: block;
-
- .llms-notice {
- margin-left: 10px;
- margin-right: 10px;
- }
- }
- }
-
- .llms-form-field {
- padding-bottom: 0;
- }
- }
-
- .llms-gateway-description {
- margin-left: 40px;
- }
-
- .llms-gateway-fields {
- display: none;
- margin: 5px 0 20px;
- }
-
- .llms-payment-gateway-error {
- padding: 0 10px;
- }
- }
-
- .llms-checkout-confirm {
- margin: 0 10px;
- }
-
- .llms-payment-method {
- margin: 10px 10px 0;
- }
-
- .llms-gateway-description {
- p {
- font-size: 85%;
- font-style: italic;
- margin-bottom: 0;
- }
- }
diff --git a/assets/scss/frontend/_course.scss b/assets/scss/frontend/_course.scss
deleted file mode 100644
index 2296f78086..0000000000
--- a/assets/scss/frontend/_course.scss
+++ /dev/null
@@ -1,21 +0,0 @@
-.course {
- .llms-meta-info {
- margin: 20px 0;
- .llms-meta-title {
- margin-bottom: 5px;
- }
- .llms-meta {
- p {
- margin-bottom: 0;
- }
- span {
- font-weight: 700;
- }
- }
- }
- .llms-course-progress {
- margin: 40px auto;
- max-width: 480px;
- text-align: center;
- }
-}
diff --git a/assets/scss/frontend/_llms-access-plans.scss b/assets/scss/frontend/_llms-access-plans.scss
deleted file mode 100644
index 59a67b73b6..0000000000
--- a/assets/scss/frontend/_llms-access-plans.scss
+++ /dev/null
@@ -1,192 +0,0 @@
-.llms-access-plans {
- @extend %clearfix;
-
- @media all and (min-width: 600px) {
- $cols: 1;
- @while $cols <= 5 {
- &.cols-#{$cols} .llms-access-plan {
- width: calc( 100% / $cols );
- }
- $cols: $cols + 1;
- }
- }
-
-}
-
-.llms-free-enroll-form {
- margin-bottom: 0;
-}
-
-.llms-access-plan {
- box-sizing: border-box;
- float: left;
- text-align: center;
- width: 100%;
-
- .llms-access-plan-footer,
- .llms-access-plan-content {
- background: #f1f1f1;
- }
-
- &.featured {
-
- .llms-access-plan-featured {
- background: lighten( $color-brand-blue, 8 );
- }
-
- .llms-access-plan-footer,
- .llms-access-plan-content {
- border-left: 3px solid $color-brand-blue;
- border-right: 3px solid $color-brand-blue;
- }
-
- .llms-access-plan-footer {
- border-bottom-color: $color-brand-blue;
- }
-
- }
-
- &.on-sale {
- .price-regular { text-decoration: line-through; }
- }
-
- .stamp {
- background: $color-brand-blue;
- color: #fff;
- font-size: 11px;
- font-style: normal;
- font-weight: 300;
- padding: 2px 3px;
- vertical-align: top;
- }
-
- .llms-access-plan-restrictions ul { margin: 0; }
-
-}
- .llms-access-plan-featured {
- color: #fff;
- font-size: 14px;
- font-weight: 400;
- margin: 0 2px 0 2px;
- }
-
- .llms-access-plan-content {
- margin: 0 2px 0;
-
- .llms-access-plan-pricing {
- padding: 10px 0 0;
- }
- }
-
- .llms-access-plan-title {
- background: $color-brand-blue;
- color: #fff;
- margin-bottom: 0;
- padding: 10px;
- }
-
- .llms-access-plan-pricing {
-
- .llms-price-currency-symbol {
- font-size: 14px;
- vertical-align: top;
- }
-
- }
-
- .llms-access-plan-price {
- font-size: 18px;
- font-variant: small-caps;
- line-height: 20px;
-
- .lifterlms-price {
- font-weight: 700;
- }
-
- &.sale {
- padding: 5px 0;
- border-top: 1px solid #d0d0d0;
- border-bottom: 1px solid #d0d0d0;
- }
- }
-
- .llms-access-plan-trial,
- .llms-access-plan-schedule,
- .llms-access-plan-sale-end,
- .llms-access-plan-expiration {
- font-size: 15px;
- font-variant: small-caps;
- line-height: 1.2;
- }
-
- .llms-access-plan-description {
- font-size: 16px;
- padding: 10px 10px 0;
-
- ul {
- margin: 0;
- li {
- border-bottom: 1px solid #d0d0d0;
- list-style-type: none;
- &:last-child {
- border-bottom: none;
- }
- }
- }
-
- div, img, p, ul, li {
- &:last-child { margin-bottom: 0; }
- }
- }
-
- .llms-access-plan-restrictions {
- .stamp {
- vertical-align: baseline;
- }
- ul {
- margin: 0;
- li {
- font-size: 12px;
- line-height: 14px;
- list-style-type: none;
- }
- }
- a {
- color: $color-brand-orange;
- &:hover {
- color: $color-brand-orange-dark;
- }
- }
- }
-
- .llms-access-plan-footer {
- border-bottom: 3px solid #f1f1f1;
- padding: 10px;
- margin: 0 2px 2px 2px;
-
- .llms-access-plan-pricing {
- padding: 0 0 10px;
- }
- }
-
-
-.webui-popover-content .llms-members-only-restrictions {
- text-align: center;
- ul,ol,li,p {
- margin: 0;
- padding: 0;
- }
- ul,ol,li {
- list-style-type: none;
- }
- li {
- padding: 8px 0;
- border-top: 1px solid #3b3b3b;
- &:first-child {
- border-top: none;
- }
- a {
- display: block;
- }
- }
-}
diff --git a/assets/scss/frontend/_llms-achievements-certs.scss b/assets/scss/frontend/_llms-achievements-certs.scss
deleted file mode 100644
index 61e4e266be..0000000000
--- a/assets/scss/frontend/_llms-achievements-certs.scss
+++ /dev/null
@@ -1,106 +0,0 @@
-ul.llms-achievements-loop,
-.lifterlms ul.llms-achievements-loop,
-ul.llms-certificates-loop,
-.lifterlms ul.llms-certificates-loop {
-
- @include clearfix();
- list-style-type: none;
- margin: 0 -10px;
- padding: 0;
-
- li.llms-achievement-loop-item,
- li.llms-certificate-loop-item {
- box-sizing: border-box;
- display: block;
- float: left;
- list-style-type: none;
- margin: 0;
- padding: 10px;
- width: 100%;
- }
-
- @media all and (min-width: 600px) {
- $cols: 1;
- @while $cols <= 5 {
- &.loop-cols-#{$cols} li.llms-achievement-loop-item,
- &.loop-cols-#{$cols} li.llms-certificate-loop-item {
- width: calc( 100% / $cols );
- }
- $cols: $cols + 1;
- }
- }
-
-}
-
-.llms-achievement,
-.llms-certificate {
-
- background: #f1f1f1;
- border: none;
- color: inherit;
- display: block;
- text-decoration: none;
- width: 100%;
-
- &:hover {
- background: #eaeaea;
- }
-
- .llms-achievement-img {
- display: block;
- margin: 0;
- width: 100%;
- }
-
- .llms-achievement-title {
- font-size: 16px;
- margin: 0;
- padding: 10px;
- text-align: center;
- }
-
- .llms-certificate-title {
- font-size: 16px;
- margin: 0;
- padding: 0 0 10px;
- }
-
- .llms-achievement-info,
- .llms-achievement-date {
- display: none;
- }
-
- .llms-achievement-content {
- padding: 20px;
- &:empty {
- padding: 0;
- }
- *:last-child {
- margin-bottom: 0;
- }
- }
-
-}
-
-.llms-certificate {
- border: 4px double #f1f1f1;
- padding: 20px 10px;
- background: #fff;
- text-align: center;
- &:hover {
- background: #fff;
- border-color: #eaeaea;
- }
-}
-
-.llms-achievement-modal {
- .llms-achievement {
- background: #fff;
- }
- .llms-achievement-info {
- display: block;
- }
- .llms-achievement-title {
- display: none;
- }
-}
diff --git a/assets/scss/frontend/_llms-author.scss b/assets/scss/frontend/_llms-author.scss
deleted file mode 100644
index 5f92792c91..0000000000
--- a/assets/scss/frontend/_llms-author.scss
+++ /dev/null
@@ -1,62 +0,0 @@
-.llms-author {
- .name {
- margin-left: 5px;
- }
- .label {
- margin-left: 5px;
- }
- .avatar {
- border-radius: 50%;
- }
- .bio {
- margin-top: 5px;
- }
-}
-
-
-.llms-instructor-info {
- .llms-instructors {
-
- .llms-col {
- &:first-child .llms-author {
- margin-left: 0;
- }
- &:last-child .llms-author {
- margin-right: 0;
- }
- }
-
- .llms-author {
-
- background: #f5f5f5;
- border-top: 4px solid $color-brand-blue;
- text-align: center;
- margin: 45px 5px 5px;
- padding: 0 10px 10px;
-
- .avatar {
- background: $color-brand-blue;
- border: 4px solid $color-brand-blue;
- display: block;
- margin: -35px auto 10px;
- }
-
- .llms-author-info {
- display: block;
- // margin: 0 0 5px;
- &.name {
- font-weight: 700;
- }
- &.label {
- font-size: 85%;
- }
- &.bio {
- font-size: 90%;
- margin-bottom: 0;
- }
- }
- }
-
- }
-
-}
diff --git a/assets/scss/frontend/_llms-notifications.scss b/assets/scss/frontend/_llms-notifications.scss
deleted file mode 100644
index a8f3c3fdc0..0000000000
--- a/assets/scss/frontend/_llms-notifications.scss
+++ /dev/null
@@ -1,205 +0,0 @@
-.llms-notification {
-
- @include clearfix();
-
- background: #fff;
- box-shadow: 0 1px 2px -2px #333, 0 1px 1px -1px #333;
- border-top: 4px solid $color-blue;
- opacity: 0;
- padding: 12px;
- position: fixed;
- right: -800px;
- top: 24px;
- transition:
- opacity 0.4s ease-in-out,
- right 0.4s ease-in-out,
- ;
- visibility: hidden;
- width: auto;
- z-index: 9999999;
-
- &.visible {
- left: 12px;
- opacity: 1;
- right: 12px;
- transition:
- opacity 0.4s ease-in-out,
- right 0.4s ease-in-out,
- top 0.1s ease-in-out,
- background 0.2s ease-in-out,
- transform 0.2s ease-in-out
- ;
- visibility: visible;
-
- &:hover {
- .llms-notification-dismiss {
- opacity: 1;
- }
- }
-
- }
-
- .llms-notification-content {
- align-items: center;
- display: flex;
-
- }
-
- .llms-notification-main {
- align-self: flex-start;
- flex: 4;
- order: 2;
- }
-
- .llms-notification-title {
- font-size: 18px;
- margin: 0;
- }
-
- .llms-notification-body {
- font-size: 14px;
- line-height: 1.4;
- p, li {
- font-size: inherit;
- }
- p {
- margin-bottom: 8px;
- }
-
- .llms-mini-cert {
- background: #f6f6f6;
- border: 1px solid #d0d0d0;
- box-shadow: inset 0 0 0 3px #fefefe, inset 0 0 0 4px #dedede;
- padding: 16px 16px 24px;
- margin-top: 12px;
- .llms-mini-cert-title {
- font-size: 16px;
- font-weight: 700;
- margin: 12px auto;
- text-align: center;
- }
- .llms-mini-cert--body {
- width: 100%;
- > div {
- &:nth-child(1) { width:65%; }
- &:nth-child(2) { width:35%; }
- &:nth-child(3) { width:85%; }
- &:nth-child(4) { width:75%; margin-top: 18px; }
- &:nth-child(5) { width:70%; }
- &:nth-child(6) { margin-left: 12px; margin-bottom:-24px; } // Dot.
- &:nth-child(7) { width:65%; margin-right: 12px; }
- }
- }
- .llms-mini-cert--mock-line {
- border-radius: 2px;
- height: 8px;
- background: #b0b0b0;
- background-image: linear-gradient( to right, #b0b0b0 0%, #a0a0a0 20%, #b0b0b0 40%, #b0b0b0 100% );
- background-repeat: no-repeat;
- margin: 4px auto;
- }
- .llms-mini-cert--mock-dot {
- background: #b0b0b0;
- border-radius: 50%;
- display: inline-block;
- content: '';
- height: 24px;
- width: 24px;
- }
- p { margin-bottom: 0; }
- }
- }
-
- .llms-notification-aside {
- align-self: flex-start;
- flex: 1;
- margin-right: 12px;
- order: 1;
- }
-
- .llms-notification-icon {
- display: block;
- max-width: 64px;
- }
-
- .llms-notification-footer {
- border-top: 1px solid #e5e5e5;
- font-size: 12px;
- margin-top: 12px;
- padding: 6px 6px 0;
- text-align: right;
- }
-
- .llms-notification-dismiss {
- color: $color-danger;
- cursor: pointer;
- font-size: 22px;
- position: absolute;
- right: 10px;
- top: 8px;
- transition: opacity 0.4s ease-in-out;
- }
-
-}
-
-.llms-sd-notification-center {
-
- .llms-notification-list,
- .llms-notification-list-item {
- list-style-type: none;
- margin: 0;
- padding: 0;
- }
-
- .llms-notification-list-item {
- &:hover .llms-notification {
- background: #fcfcfc;
- }
- }
-
- .llms-notification {
- opacity: 1;
- border-top: 1px solid #e5e5e5;
- left: auto;
- padding: 24px;
- position: relative;
- right: auto;
- top: auto;
- visibility: visible;
- width: auto;
- .llms-notification-aside {
- max-width: 64px;
- }
- .llms-notification-footer {
- border: none;
- padding: 0;
- text-align: left;
- }
- .llms-progress {
- display: none !important;
- }
- .llms-notification-date {
- color: #515151;
- float: left;
- margin-right: 6px;
- }
- .llms-mini-cert {
- margin: 0 auto;
- max-width: 380px;
- }
- }
-}
-
-@media all and (min-width: 480px) {
- .llms-notification {
- right: -800px;
- width: 360px;
- &.visible {
- left: auto;
- right: 24px;
- }
- .llms-notification-dismiss {
- opacity: 0;
- }
- }
-}
diff --git a/assets/scss/frontend/_llms-outline-collapse.scss b/assets/scss/frontend/_llms-outline-collapse.scss
deleted file mode 100644
index ad109277a6..0000000000
--- a/assets/scss/frontend/_llms-outline-collapse.scss
+++ /dev/null
@@ -1,39 +0,0 @@
-.llms-widget-syllabus--collapsible {
-
- .llms-section {
-
- .section-header {
-
- cursor: pointer;
-
- }
-
- &.llms-section--opened {
-
- .llms-collapse-caret {
- .fa-caret-right { display: none; }
- }
-
- }
-
- &.llms-section--closed {
-
- .llms-collapse-caret {
- .fa-caret-down { display: none; }
- }
-
- .llms-lesson {
- display: none;
- }
-
- }
-
- }
-
- .llms-syllabus-footer {
-
- text-align: left;
-
- }
-
-}
diff --git a/assets/scss/frontend/_llms-pagination.scss b/assets/scss/frontend/_llms-pagination.scss
deleted file mode 100644
index d0585c70a2..0000000000
--- a/assets/scss/frontend/_llms-pagination.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-.llms-pagination {
-
- ul {
- list-style-type: none;
- @extend %cf;
-
- li {
-
- float: left;
-
- a {
- border-bottom: 0;
- text-decoration: none;
- }
-
- .page-numbers {
- padding: 0.5em;
- text-decoration: underline;
-
- &.current {
- text-decoration: none;
- }
- }
-
- }
-
- }
-
-}
\ No newline at end of file
diff --git a/assets/scss/frontend/_llms-progress.scss b/assets/scss/frontend/_llms-progress.scss
deleted file mode 100644
index a43869179d..0000000000
--- a/assets/scss/frontend/_llms-progress.scss
+++ /dev/null
@@ -1,32 +0,0 @@
-/* progress bar */
-.llms-progress {
- clear: both;
- display: flex;
- flex-direction: row-reverse;
- position: relative;
- height: 1em;
- width: 100%;
- margin: 15px 0;
-}
-
-.llms-progress .llms-progress-bar {
- background-color: #f1f2f1;
- position: relative;
- height: .4em;
- top: .3em;
- width: 100%;
-}
-
-.llms-progress .progress-bar-complete {
- background-color: $color-brand-pink;
- height: 100%;
-}
-
-.progress__indicator {
- float: right;
- text-align: right;
- height: 1em;
- line-height: 1em;
- margin-left: 5px;
- white-space: nowrap;
-}
diff --git a/assets/scss/frontend/_llms-quizzes.scss b/assets/scss/frontend/_llms-quizzes.scss
deleted file mode 100644
index 485ffa4e99..0000000000
--- a/assets/scss/frontend/_llms-quizzes.scss
+++ /dev/null
@@ -1,359 +0,0 @@
-.single-llms_quiz {
- #llms-quiz-wrapper {
- @import "../_includes/quiz-result-question-list";
- }
- .llms-return {
- margin-bottom: 10px;
- }
-
- .llms-quiz-results {
- @include clearfix();
-
- .llms-donut {
- &.passing {
- color: $color-success;
- svg path {
- stroke: $color-success;
- }
- }
- &.pending {
- color: #555;
- svg path {
- stroke: #555;
- }
- }
- &.failing {
- color: $color-danger;
- svg path {
- stroke: $color-danger;
- }
- }
- }
-
- .llms-quiz-results-aside,
- .llms-quiz-results-main,
- .llms-quiz-results-history {
- margin-bottom: 20px;
- }
-
-
- @media all and (min-width: 600px) {
- .llms-quiz-results-aside {
- float: left;
- width: 220px;
- }
- .llms-quiz-results-main,
- .llms-quiz-results-history {
- float: right;
- width: calc( 100% - 300px );
- }
- .llms-quiz-results-history {
- clear: right;
- }
- }
-
- }
-
- ul.llms-quiz-meta-info,
- ul.llms-quiz-meta-info li {
- list-style-type: none;
- margin: 0;
- padding: 0
- }
-
- ul.llms-quiz-meta-info {
- margin-bottom: 10px;
- }
-
- .llms-quiz-buttons {
- margin-top: 10px;
- text-align: left;
-
- form { display: inline-block; }
- }
-
-}
-
-.llms-quiz-question-wrapper {
- min-height: 140px;
- position: relative;
-
- .llms-quiz-loading {
- bottom: 20px;
- left: 0;
- position: absolute;
- right: 0;
- text-align: center;
- z-index: 1;
- }
-
- .llms-question-image {
-
- img {
- height: auto;
- max-width: 100%;
- }
-
- }
-}
-
-.llms-quiz-ui {
- background: #fcfcfc;
- padding: 20px;
- position: relative;
-
- .llms-quiz-header {
- align-items: center;
- display: flex;
- margin: 0 0 30px;
- }
-
- .llms-progress {
- background-color: #f1f2f1;
- flex-direction: row;
- height: 8px;
- margin: 0;
- overflow: hidden;
- .progress-bar-complete {
- transition: width 0.3s ease-in;
- width: 0;
- }
- }
-
- .llms-error {
- @include clearfix();
- background: $color-danger;
- border-radius: 4px;
- color: #fff;
- margin: 10px 0;
- padding: 10px;
-
- a {
- color: rgba( #fff, 0.6 );
- float: right;
- font-size: 22px;
- line-height: 1;
- text-decoration: none;
- }
-
- }
-
- .llms-quiz-counter {
- display: none;
-
- color: #6a6a6a;
- float: right;
- font-size: 18px;
-
- .llms-sep {
- margin: 0 5px;
- }
- }
-
- .llms-quiz-nav {
- margin-top: 20px;
- button {
- margin: 0 10px 0 0;
- }
- }
-
-}
-
-// single question wrapper
-.llms-question-wrapper {
-
- .llms-question-text {
- font-size: 30px;
- font-weight: 400;
- margin-bottom: 15px;
- }
-
- ol.llms-question-choices {
- list-style-type: none;
- margin: 0;
- padding: 0;
-
- li.llms-choice {
- border-bottom: 1px solid #e8e8e8;
- margin: 0;
- padding: 0;
- position: relative;
-
- &:last-child {
- border-bottom: none;
- }
-
- &.type--picture {
- border-bottom: none;
- label {
- display: inline-block;
- padding: 0;
- }
- .llms-marker {
- bottom: 10px;
- margin: 0;
- position: absolute;
- right: 10px;
- }
- .llms-choice-image {
- margin: 2px;
- padding: 20px;
- transition: background 0.4s ease;
- img {
- display: block;
- height: auto;
- width: 100%;
- }
- }
- input:checked ~ .llms-choice-image {
- background: #efefef
- }
- }
-
- input {
- display: none;
- left: 0;
- pointer-events: none;
- position: absolute;
- top: 0;
- visibility: hidden;
- }
-
- label {
- display: block;
- margin: 0;
- padding: 10px 20px;
- position: relative;
- // &:hover {
- &.hovered {
- .llms-marker:not(.type--lister) {
- .iterator {
- display: none;
- }
- .fa {
- display: inline;
- }
- }
- }
- }
-
- .llms-marker {
-
- background: #f0f0f0;
- display: inline-block;
- font-size: 20px;
- height: 40px;
- line-height: 40px;
- margin-right: 10px;
- text-align: center;
- transition: all 0.2s ease;
- vertical-align: middle;
- width: 40px;
-
- .fa {
- display: none;
- }
-
- &.type--lister,
- &.type--checkbox { border-radius: 4px; }
- &.type--radio { border-radius: 50%; }
-
- }
-
- input:checked + .llms-marker {
- background: $color-brand-pink;
- color: #fff;
- .iterator {
- display: none;
- }
- .fa {
- display: inline;
- }
- }
-
- .llms-choice-text {
- display: inline-block;
- font-size: 18px;
- font-weight: 400;
- line-height: 1.6;
- margin-bottom: 0;
- vertical-align: middle;
- width: calc( 100% - 60px );
- }
-
- }
- }
-
-}
-
-.llms-quiz-timer {
- background: #fff;
- border: 1px solid $color-green;
- border-radius: 4px;
- color: $color-green;
- float: right;
- font-size: 18px;
- line-height: 1;
- margin-left: 20px;
- padding: 8px 12px;
- position: relative;
- white-space: nowrap;
- z-index: 1;
-
- &.color-half {
- border-color: $color-orange;
- color: $color-orange
- }
-
- &.color-empty {
- border-color: $color-danger;
- color: $color-danger
- }
-
- .llms-tiles {
- display: inline-block;
- margin-left: 5px;
- }
-}
-
-
-// /* My Quizzes */
-// .llms-quiz-results {
-// @extend %cf;
-// font-family: "Open Sans",Verdana,Geneva,sans-serif,sans-serif;
-// position: relative;
-// }
-// .llms-quiz-results > h3 {
-// background-color: #f5f5f5;
-// padding: 4px;
-// }
-
-// .llms-quiz-result-details {
-// float: left;
-// ul {
-// list-style-type: none;
-// float: left;
-// li {
-// list-style-type: none;
-// font-size: 20px;
-// }
-// }
-// }
-// .llms-attempts {
-// font-weight: bold;
-// }
-
-// .llms-pass-perc {
-// font-weight: bold;
-// }
-// .llms-content-block {
-// margin: 6px 0;
-// }
-// .llms-question-wrapper {
-// margin: 40px 0 20px 0;
-// }
-// .llms-question-count {
-// margin-bottom: 20px;
-// }
-
-
diff --git a/assets/scss/frontend/_llms-table.scss b/assets/scss/frontend/_llms-table.scss
deleted file mode 100644
index 5eb2234cd1..0000000000
--- a/assets/scss/frontend/_llms-table.scss
+++ /dev/null
@@ -1,59 +0,0 @@
-.llms-table {
- border: 1px solid #efefef;
- width: 100%;
-
- thead {
- th,td {
- font-weight: 700;
- }
- }
-
- tbody {
- tr:nth-child( odd ) {
- td, th {
- background: #f9f9f9;
- }
- }
- tr:last-child {
- border-bottom-width: 0;
- }
- }
-
- tfoot {
- tr {
- background: #f9f9f9;
- .llms-pagination .page-numbers {
- margin: 0;
- }
- .llms-table-sort {
- text-align: right;
- form, select, input, button {
- margin: 0;
- }
- }
- }
- }
-
- th {
- font-weight: 700;
- }
-
- th, td {
- border-bottom: 1px solid #efefef;
- padding: 8px 12px;
-
- // launchpad compat...
- &:first-child { padding-left: 12px; }
- &:last-child { padding-right: 12px; }
-
- }
-
-}
-
-// launchpad compat...
-#page .llms-table tfoot label {
- display: inline;
-}
-#page .llms-table tfoot select {
- height: auto;
-}
diff --git a/assets/scss/frontend/_loop.scss b/assets/scss/frontend/_loop.scss
deleted file mode 100644
index 827f30715c..0000000000
--- a/assets/scss/frontend/_loop.scss
+++ /dev/null
@@ -1,263 +0,0 @@
-.llms-loop-list {
- @extend %clearfix;
-
- list-style: none;
- margin: 0 -10px;
- padding: 0;
-
- @media all and (min-width: 600px) {
- $cols: 1;
- @while $cols <= 6 {
- &.cols-#{$cols} .llms-loop-item {
- width: calc( 100% / $cols );
- }
- $cols: $cols + 1;
- }
- }
-
-
-}
-
-.llms-loop-item {
- float: left;
- list-style: none;
- margin: 0;
- padding: 0;
- width: 100%;
-}
-
-
- .llms-loop-item-content {
- background: #f1f1f1;
- padding-bottom: 10px;
- margin: 10px;
-
- &:hover {
- background: #eaeaea;
- }
-
- .llms-loop-link {
- color: #212121;
- display: block;
- &:visited {
- color: #212121;
- }
- }
-
- .llms-featured-image {
- display: block;
- max-width: 100%;
- }
-
- .llms-loop-title {
- margin-top: 5px;
- &:hover {
- color: $color-brand-blue;
- }
- }
-
- .llms-meta,
- .llms-author,
- .llms-loop-title {
- padding: 0 10px;
- }
-
- .llms-meta,
- .llms-author {
- color: #444;
- display: block;
- font-size: 13px;
- margin-bottom: 3px;
- &:last-child {
- margin-bottom: 0;
- }
- }
-
- .llms-featured-img-wrap {
- overflow: hidden;
- }
-
- p {
- margin-bottom: 0;
- }
-
- .llms-progress {
- margin: 0;
- height: .4em;
-
- .progress__indicator {
- display: none;
- }
-
- .llms-progress-bar {
- background-color: #f6f6f6;
- right: 0;
- top: 0;
- }
- }
-
- }
-
-
-
-// .llms-membership-list .memberships {
-// border-top: 1px solid #f6f6f6;
-// width: 100%;
-// display: inline-block;
-// text-align: center;
-// list-style: none;
-// clear: both;
-// margin: 0;
-// padding: 0;
-// }
-
-
-
-// .llms-course-list {
-
-// .llms-membership-link {
-// @extend %llms-element;
-
-// display: block
-// }
-
-// .llms-membership-footer {
-// border-top: 3px solid $color-white;
-// margin: 15px -15px 0;
-// padding: 15px 15px 0;
-// text-align: center;
-// }
-
-// }
-
-
-
-
-// .llms-membership-list .memberships li {
-// width: 300px;
-// margin: 15px;
-// list-style: none;
-// vertical-align: top;
-// display: inline-block;
-// text-align: left;
-// }
-
-// .llms-membership-list .memberships li.first {
-// margin-left: 0;
-// }
-
-// .llms-membership-list .memberships li.last {
-// margin-right: 15px;
-// }
-
-// .llms-membership-list .memberships li .llms-title {
-// display: block;
-// font-weight: 700;
-// margin-bottom: .5em;
-// font-size: 18px;
-// text-decoration: none;
-// line-height: 30px;
-// }
-
-// .llms-membership-list .memberships li .llms-price {
-// display: block;
-// font-weight: 700;
-// // margin-bottom: .5em;
-// // font-size: 24px;
-// text-decoration: none;
-// line-height: 30px;
-// }
-
-// .llms-course-list {
-// //margin: 30px 0;
-// padding: 30px;
-// //background: #FFF;
-// // border-radius: 2px;
-// display: inline-block;
-// width: 100%;
-// box-sizing: border-box;
-
-// .llms-course-link {
-// @extend %llms-element;
-
-// display: block
-// }
-
-// .llms-course-footer {
-// border-top: 3px solid $color-white;
-// margin: 15px -15px 0;
-// padding: 15px 15px 0;
-// text-align: center;
-// }
-
-// .llms-progress {
-// margin-top: 0;
-// // .progress-bar {
-// // background-color: $color-white;
-// // }
-// }
-
-// }
-
-// .llms-course-list .courses {
-// //border-top: 1px solid #f6f6f6;
-// width: 100%;
-// display: inline-block;
-// text-align: center;
-// list-style: none;
-// clear: both;
-// margin: 0;
-// padding: 0;
-// }
-
-// .llms-course-list .courses li {
-// width: 300px;
-// padding-top: 0; // twentyfifteen compat
-// margin: 15px;
-// list-style: none;
-// vertical-align: top;
-// display: inline-block;
-// text-align: left;
-// }
-// @media screen and (max-width: $break-small) {
-// .llms-course-list {
-// padding: 30px 10px;
-
-// .courses li {
-// width: auto;
-// }
-// }
-// }
-
-// // .llms-course-list .courses li.first {
-// // margin-left: 0;
-// // }
-
-// .llms-course-list .courses li.last {
-// margin-right: 15px;
-// }
-
-// .llms-course-list .courses li .llms-title {
-// display: block;
-// font-weight: 700;
-// margin-bottom: .5em;
-// font-size: 18px;
-// text-decoration: none;
-// line-height: 30px;
-// }
-
-// .llms-course-list .courses li .llms-price {
-// display: block;
-// font-weight: 700;
-// // margin-bottom: .5em;
-// // font-size: 24px;
-// text-decoration: none;
-// line-height: 30px;
-// }
-
-
-
-
-// .courses a.llms-course-link:hover {
-// text-decoration: none;
-// }
diff --git a/assets/scss/frontend/_main.scss b/assets/scss/frontend/_main.scss
deleted file mode 100644
index eca058c72f..0000000000
--- a/assets/scss/frontend/_main.scss
+++ /dev/null
@@ -1,502 +0,0 @@
-
-
-
-
-.llms-membership-image {
- display: block;
- margin: 0 auto;
-}
-
-
-
-.llms-course-image {
- display: block;
- margin: 0 auto;
- max-width: 100%;
-}
-.llms-featured-image {
- display: block;
- margin: 0 auto;
-}
-.llms-image-thumb {
- width: 150px;
-}
-
-// Responsive Videos.
-.llms-video-wrapper {
-
- .center-video {
- height: auto;
- max-width: 100%;
- overflow: hidden;
- position: relative;
- padding-top: 56.25%;
- text-align: center;
-
- & > .wp-video,
- .fluid-width-video-wrapper,
- iframe, object, embed {
- height: 100%;
- left: 0;
- position: absolute;
- top: 0;
- width: 100%;
- }
-
- & > .wp-video {
- width: 100% !important;
- }
- .fluid-width-video-wrapper {
- padding-top: 0 !important;
- }
- }
-
-}
-
-
-
-
-
-
-
-
-
-
-
-.clear {
- clear: both;
- width: 100%;
-}
-.llms-featured-image {
- text-align: center;
-}
-
-#main-content .llms-payment-options p {
- margin: 0;
- font-size: 16px;
-}
-
-.llms-option {
- display: block;
- position: relative;
- margin: 20px 0;
- padding-left:40px;
- font-size: 16px;
-
- label {
- cursor: pointer;
- position: static;
- }
-}
-.llms-option:first-child {
- margin-top:0;
-}
-.llms-option:last-child {
- margin-bottom:0;
-}
-#main-content .llms-option:last-child {
- margin-bottom:0;
-}
-
-.llms-option input[type="radio"] {
- display: block;
- position: absolute;
- top:3px;
- left:0;
- z-index: 0;
-}
-
-.llms-option input[type="radio"] {
- display: inline-block;
-}
-.llms-option input[type="radio"] + label span.llms-radio {
- display: none;
-}
-
-.llms-option input[type="radio"] + label span.llms-radio {
- appearance: none;
-
- z-index: 20;
- position: absolute;
- top: 0;
- left: -2px;
- display: inline-block;
- width: 24px;
- height: 24px;
- border-radius: 50%;
- cursor: pointer;
- vertical-align: middle;
- box-shadow: hsla(0,0%,100%,.15) 0 1px 1px, inset hsla(0,0%,0%,.5) 0 0 0 1px;
-
- background: #efefef;
- background-image: radial-gradient(ellipse at center, $color-red 0%,$color-red 40%,#efefef 45%);
- background-repeat: no-repeat;
-
- transition: background-position .15s cubic-bezier(.8, 0, 1, 1);
-}
-.llms-option input[type="radio"]:checked + label span.llms-radio {
- transition: background-position .2s .15s cubic-bezier(0, 0, .2, 1);
-}
-
-.llms-option input[type="radio"] + label span.llms-radio {
- background-position: -24px 0;
-}
-.llms-option input[type="radio"]:checked + label span.llms-radio {
- background-position: 0 0;
-}
-
-.llms-option input[type="submit"] {
- border:none;
- background:$color-red;
- color:#fff;
- font-size:20px;
- padding:10px 0;
- border-radius:3px;
- cursor:pointer;
- width:100%;
-}
-.llms-styled-text {
- padding: 14px 0;
-}
-.llms-notice-box {
- border-radius: 3px;
- z-index: 10;
- margin: 10px 0;
- padding: 15px 20px;
- //background: #fffef4;
- border: 1px solid #ccc;
- list-style-type: none;
- width: 100%;
- overflow: auto;
- input[type="text"] {
- height: auto;
- }
- .col-1-1 {
- width: 100%;
- input[type=text] {
- width: 100%;
- }
- }
- .col-1-2 {
- width: 50%;
- float: left;
- @media screen and (max-width: $break-medium) {
- width: 100%;
- }
- }
- .col-1-3 {
- width: 33%;
- float: left;
- margin-right: 10px;
- }
- .col-1-4 {
- width: 25%;
- float: left;
- margin-right: 10px;
- @media screen and (max-width: $break-medium) {
- width: 100%;
- }
- }
- .col-1-6 {
- width: 16.6%;
- float: left;
- margin-right: 10px;
- }
- .col-1-8 {
- width: 11%;
- float: right;
- }
- .llms-pad-right {
- padding-right: 10px;
- @media screen and (max-width: $break-medium) {
- padding-right: 0;
- }
- }
-}
-input[type="text"].cc_cvv,
-#cc_cvv {
- margin-right: 0;
- width: 23%;
- float: right;
-}
-.llms-clear-box {
- border-radius: 3px;
- z-index: 10;
- margin: 10px 0;
- padding: 15px 20px;
- list-style-type: none;
- width: 100%;
- overflow: auto;
-}
-.llms-price-label {
- font-weight: normal;
-}
-.llms-final-price {
- font-weight: bold;
- float: right;
-}
-.llms-center-content {
- text-align: center;
-}
-.llms-input-text {
- background-color: #fff;
- border: 1px solid #ddd;
- color: #333;
- font-size: 18px;
- font-weight: 300;
- padding: 16px;
- width: 100%;
-}
-.llms-price {
- margin-bottom: 0;
- font-weight: bold;
-}
-.llms-price-loop {
- margin-bottom: 0;
- font-weight: bold;
-}
-
-// hentry overrides
-.courses .entry {
- padding: 0
-}
-.list-view .site-content .llms-course-list .hentry, .list-view .site-content .llms-membership-list .hentry {
- border-top: 0;
- padding-top: 0;
-}
-.llms-content {
- width: 100%;
-}
-
-.llms-lesson-button-wrapper {
- width: 100%;
- display: block;
- clear: both;
- text-align: center;
-}
-.llms-template-wrapper {
- width: 100%;
- display: block;
- clear: both;
-}
-.llms-button-wrapper {
- width: 100%;
- display: block;
- clear: both;
- text-align: center;
-}
-
-
-//custom select box
-.llms-styled-select {
- border: 1px solid #ccc;
- box-sizing: border-box;
- border-radius: 3px;
- overflow: hidden;
- position: relative;
-}
-.llms-styled-select, .llms-styled-select select {
- width: 100%;
-}
-select:focus { outline: none; }
-.llms-styled-select select {
- height: 34px;
- padding: 5px 0 5px 5px;
- background: transparent;
- border: none;
- -webkit-appearance: none;
- font-size: 16px;
- color: #444444;
-}
-
-@-moz-document url-prefix(){
- .--ms-styled-select select { width: 110%; }
-}
-
-.llms-styled-select .fa-sort-desc {
- position: absolute;
- top: 0;
- right: 12px;
- font-size: 24px;
- color: #ccc;
-}
-
-select::-ms-expand { display: none; }
-
-_:-o-prefocus, .selector {
- .llms-styled-select { background: none; }
-}
-
-.llms-form-item-wrapper {
- margin-bottom: 1em;
-}
-
-/* Circle Graph */
-.llms-progress-circle {
- position: relative;
- width: 200px;
- height: 200px;
- float: left;
-}
-
-.llms-progress-circle-count {
- top: 27%;
- position: absolute;
- width: 94%;
- text-align: center;
- color: #666;
- font-size:44px;
-}
-.llms-progress-circle svg {
- position: absolute;
- width: 200px;
- height: 200px;
-}
-.llms-progress-circle circle {
- fill: transparent;
-}
-svg .llms-background-circle {
- fill: transparent;
- stroke-width: 10px;
- stroke: #f1f2f1;
- stroke-dasharray: 430;
-}
-
-svg .llms-animated-circle {
- fill: transparent;
- stroke-width: 10px;
- stroke: #e5554e;
- stroke-dasharray: 430;
- stroke-dashoffset: 430 - 20
-}
-
-
-
-
-
-
-
-.llms-widget-syllabus {
-
- .llms-lesson.current-lesson .lesson-title {
- font-weight: 700;
- }
-
- .llms-lesson-complete, .lesson-complete-placeholder {
- font-size: 1.2em;
- margin-right: 6px;
- color: #ccc;
- &.done {
- color: #e5554e;
- }
- }.section-title {
- font-weight: bold;
- }.lesson-title {
- a {
- text-decoration: none;
- &:hover {
- text-decoration: none !important;
- }
- }
- &.done {
- a {
- color: #999;
- text-decoration: line-through;
- }
- }
- }
- ul {
- list-style-type: none;
- li {
- list-style-type: none;
- ul li {
- margin: 0 0 2px 0;
- padding: 0;
- }
- }
- }
-}
-
-
-
-.llms-remove-coupon {
- float: right;
-}
-
-
-
-
-
-.llms-lesson-link-locked, .llms-lesson-link-locked:hover {
- background: #f1f1f1;
- box-shadow: 0 1px 2px 0 rgba(1, 1, 1, 0.4);
- display: block;
- color: #a6a6a6;
- min-height: 85px;
- padding: 15px;
- text-decoration: none;
- position: relative;
-}
-
-.llms-lesson-preview.is-complete .llms-lesson-link-locked {
- padding-left: 75px;
-}
-
-.llms-lesson-tooltip { display: none;
- position:absolute;
- color: #000000;
- background-color: #c0c0c0;
- padding:.25em;
- border-radius: 2px;
- z-index: 100;
- top:0;
- left:50%;
- text-align: center;
- -webkit-transform: translateX(-50%) translateY(-100%);
- transform: translateX(-50%) translateY(-100%);
- }
-
-/* arrows - :after */
-.llms-lesson-tooltip:after {
- content: "";
- width: 0;
- height: 0;
- border-top: 8px solid #c0c0c0;
- border-left: 8px solid transparent;
- border-right: 8px solid transparent;
- position:absolute;
- bottom:-8px;
- left:50%;
- transform: translateX(-50%);
-}
-
-.llms-lesson-tooltip.active {
- display: inline-block;
-}
-
-// Favorites.
-.llms-favorite-wrapper {
- cursor: pointer;
-
- .fa-heart {
- color: #EF476F;
- }
-}
-
-.llms-has-favorite .llms-parent-course-link {
- display: inline-block;
- margin-bottom: 20px;
-
- + .llms-favorite-wrapper {
- float: right;
- margin: 0;
- }
-}
-
-.llms-syllabus-wrapper .llms-has-favorite {
- text-align: left;
-
- .llms-favorite-wrapper {
- display: inline-block;
- }
-}
diff --git a/assets/scss/frontend/_notices.scss b/assets/scss/frontend/_notices.scss
deleted file mode 100644
index 477078366d..0000000000
--- a/assets/scss/frontend/_notices.scss
+++ /dev/null
@@ -1,41 +0,0 @@
-.llms-notice {
- background: rgba( $color-brand-blue, .3 );
- border-color: $color-brand-blue;
- border-style: solid;
- border-width: 3px;
- padding: 10px;
- margin-bottom: 10px;
-
- p, ul {
- &:last-child { margin-bottom: 0; }
- }
-
- li {
- list-style-type: none;
- }
-
- &.llms-debug {
- background: rgba( #cacaca, .3 );
- border-color: #cacaca;
- }
-
- &.llms-error {
- background: rgba( $color-red, .3 );
- border-color: $color-red;
- }
-
- &.llms-success {
- background: rgba( $color-green, .3 );
- border-color: $color-green;
- }
-
-}
-
-// this helps genesis and numerous other themes out a bit
-// by being slightly more specific
-.entry-content .llms-notice {
- margin: 0 0 10px;
- li {
- list-style-type: none;
- }
-}
diff --git a/assets/scss/frontend/_reviews.scss b/assets/scss/frontend/_reviews.scss
deleted file mode 100644
index 6df33a0307..0000000000
--- a/assets/scss/frontend/_reviews.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-.llms_review {
- margin: 20px 0px;
- padding: 10px;
-
- h5 {
- font-size: 17px;
- margin: 3px 0px;
- }
-
- h6 {
- font-size: 13px;
- }
-
- p {
- font-size: 15px;
- }
-}
-
-.review_box {
-
- [type=text] {
- margin: 10px 0px
- }
-
- h5 {
- color: red;
- display: none;
- }
-
- + .thank_you_box {
- display: none;
- }
-}
diff --git a/assets/scss/frontend/_student-dashboard.scss b/assets/scss/frontend/_student-dashboard.scss
deleted file mode 100644
index bbbd85a2a3..0000000000
--- a/assets/scss/frontend/_student-dashboard.scss
+++ /dev/null
@@ -1,348 +0,0 @@
-.llms-student-dashboard {
-
- .llms-sd-nav {}
-
- .llms-sd-title {
- margin: 25px 0;
- }
-
- .llms-sd-items { // ul
- @extend %clearfix;
- list-style-type: none;
- margin: 0;
- padding: 0;
- }
- .llms-sd-item { // li
- float: left;
- list-style-type: none;
- margin: 0;
- padding: 0;
-
- &:last-child {
- .llms-sep {
- display: none;
- }
- }
-
- .llms-sep {
- color: #333;
- margin: 0 5px;
- }
- }
-
- .llms-sd-section {
- margin-bottom: 25px;
- .llms-sd-section-footer {
- margin-top: 10px;
- }
- }
-
- .orders-table {
-
- border: 1px solid #f5f5f5;
- width: 100%;
-
- thead {
- display: none;
- th,td {
- font-weight: 700;
- }
- @media all and ( min-width: 600px ) {
- display: table-header-group;
- }
- }
-
- tbody {
- tr:nth-child( even ) {
- td, th {
- background: #f9f9f9;
- }
- }
- }
-
- tfoot {
- th, td {
- padding: 10px;
- text-align: right;
- &:last-child { border-bottom-width: 0; }
- }
- }
-
- th {
- font-weight: 700;
- }
-
- th, td {
- border-color: #efefef;
- border-style: solid;
- border-width: 0;
- display: block;
- padding: 8px 12px;
- text-align: center;
-
- .llms-button-primary {
- display: inline-block;
- }
-
- &:last-child {
- border-bottom-width: 1px;
- }
-
- &:before {
- content: attr( data-label );
- }
-
- @media all and ( min-width: 600px ) {
- border-bottom-width: 1px;
- display: table-cell;
- text-align: left;
- &:first-child { width: 220px; }
- &:before { display: none; }
- }
-
- }
-
- @media all and ( min-width: 600px ) {
- &.transactions th:first-child {width: auto; }
- }
-
- }
-
- @include order_status_badges();
-
- .llms-person-form-wrapper {
- .llms-change-password { display: none; }
- }
-
- .order-primary {
-
- @media all and ( min-width: 600px ) {
- float: left;
- width: 68%;
- }
-
- }
- .order-secondary {
-
- @media all and ( min-width: 600px ) {
- float: left;
- width: 32%;
- }
-
- form {
- margin-bottom: 0;
- }
-
- }
-
- // stack columns when alternate layout declared via filter
- @media all and ( min-width: 600px ) {
- .llms-view-order.llms-stack-cols {
- .order-primary,
- .order-secondary {
- float: none;
- width: 100%;
- }
- }
- }
-
- .llms-switch-payment-source {
- .llms-notice,
- .entry-content .llms-notice {
- margin-left: 10px;
- margin-right: 10px;
- }
- }
-
- .llms-switch-payment-source-main {
- border: none;
- display: none;
- margin: 0;
- ul.llms-payment-gateways {
- padding: 10px 15px 0;
- margin: 0;
- }
- .llms-payment-method,
- ul.llms-order-summary {
- padding: 0 25px 10px;
- margin: 0;
- list-style-type: none;
- li { list-style-type: none; }
- }
- }
-
- /**
- * Dashboard Home
- */
- .llms-loop-list {
- margin: 0 -10px;
- }
-
-}
-
-// My Grades course list
-.llms-sd-grades {
- .llms-table {
- .llms-progress {
- display: block;
- margin: 0;
- .llms-progress-bar {
- top: 0;
- height: 1.4em;
- }
- .progress__indicator {
- font-size: 1em;
- position: relative;
- right: 0.4em;
- top: 0.2em;
- z-index: 1;
- }
- }
- }
-}
-
-// grades table for a single course
-.llms-table.llms-single-course-grades {
-
- tbody {
- tr:first-child td, tr:first-child th {
- background-color: #eaeaea;
- }
- }
-
- th {
- font-weight: 400;
- }
-
- td {
- .llms-donut {
- display: inline-block;
- vertical-align: middle;
- }
- .llms-status {
- margin-right: 4px;
- }
- .llms-donut + .llms-status {
- margin-left: 4px;
- }
- }
-
- th.llms-section_title {
- font-size: 110%;
- font-weight: 700;
- }
-
- td.llms-lesson_title {
- padding-left: 36px;
- max-width: 40%;
- }
- td.llms-associated_quiz {
- .llms-donut {
- display: inline-block;
- margin-right: 5px;
- vertical-align: middle;
- }
- }
- td.llms-lesson_title {
- a[href="#"] {
- pointer-events: none;
- }
- a[href^="#"] {
- color: inherit;
- position: relative;
- .llms-tooltip {
- max-width: 380px;
- width: 380px;
- &.show {
- top: -54px;
- }
- }
- }
- }
-}
-
-.llms-sd-widgets {
- display: flex;
-
- .llms-sd-widget {
- background: #f9f9f9;
- flex: 1;
- margin: 10px 10px 20px;
- padding: 0 0 20px;
- &:first-child {
- margin-left: 0;
- }
- &:last-child {
- margin-right: 0;
- }
-
- .llms-sd-widget-title {
- background: $color-brand-blue;
- color: #fff;
- font-size: 18px;
- line-height: 1;
- margin: 0 0 20px;
- padding: 10px;
- }
-
- .llms-sd-widget-empty {
- font-size: 14px;
- font-style: italic;
- opacity: 0.5;
- text-align: center;
- }
-
- .llms-donut {
- margin: 0 auto;
- }
-
- .llms-sd-date {
- opacity: 0.8;
- text-align: center;
- font-size: 22px;
- line-height: 1.1;
- span {
- display: block;
- &.day {
- font-size: 52px;
- }
- &.diff {
- font-size: 12px;
- font-style: italic;
- margin-top: 8px;
- opacity: 0.75;
- }
- }
- }
-
- .llms-achievement {
- background: transparent;
- margin: 0 auto;
- max-width: 120px;
- .llms-achievement-title {
- display: none;
- }
- }
-
- }
-
-
-}
-
-
-.llms-sd-pagination {
- margin-top: 24px;
- @include clearfix;
- .llms-button-secondary {
- display: inline-block;
- &.prev { float: left; }
- &.next { float: right; }
- }
-}
-
-
-.llms-sd-notification-center {
- .llms-notification {
- z-index: 1;
- }
-}
diff --git a/assets/scss/frontend/_syllabus.scss b/assets/scss/frontend/_syllabus.scss
deleted file mode 100644
index 95c553fbdc..0000000000
--- a/assets/scss/frontend/_syllabus.scss
+++ /dev/null
@@ -1,147 +0,0 @@
-.llms-syllabus-wrapper {
-
- margin: 15px;
- text-align: center;
-
- .llms-section-title {
- margin: 25px 0 0;
- }
-
-}
-
-.llms-course-navigation {
- @extend %clearfix;
-
- .llms-prev-lesson,
- .llms-next-lesson,
- .llms-back-to-course {
- width: 49%;
- }
-
- .llms-prev-lesson,
- .llms-back-to-course {
- float: left;
- margin-right: 0.5%;
- }
-
- .llms-next-lesson,
- .llms-prev-lesson + .llms-back-to-course {
- float: right;
- margin-left: 0.5%;
- }
-
-}
-
-.llms-lesson-preview {
- display: inline-block;
- margin-top: 15px;
- max-width: 100%;
- position: relative;
- width: 480px;
-
- .llms-lesson-link {
- background: #f1f1f1;
- color: #212121;
- display: block;
- // height: 100%;
- padding: 15px;
- text-decoration: none;
-
- @include clearfix();
-
- &:hover {
- background: #eaeaea;
- }
-
- &:visited {
- color: #212121;
- }
-
- }
-
- .llms-lesson-thumbnail {
- margin-bottom: 10px;
- img {
- display: block;
- width: 100%;
- }
- }
-
- .llms-pre-text {
- text-align: left;
- }
-
- .llms-lesson-title {
- font-weight: 700;
- margin: 0 auto 10px;
- text-align: left;
- &:last-child {
- margin-bottom: 0;
- }
- }
-
- .llms-lesson-excerpt {
- text-align: left;
- }
-
- .llms-main {
- float: left;
- width: 100%;
- }
- .llms-extra {
- float: right;
- width: 15%;
- }
-
- .llms-extra + .llms-main {
- width: 85%;
- }
-
- .llms-lesson-counter,
- .llms-free-lesson-svg,
- .llms-lesson-complete,
- .llms-lesson-complete-placeholder {
- display: block;
- font-size: 32px;
- margin-bottom: 15px;
- }
-
- &.is-free,
- &.is-complete {
- .llms-lesson-complete {
- color: $color-brand-blue;
- }
- }
-
- .llms-icon-free {
- background: $color-brand-blue;
- border-radius: 4px;
- color: #f1f1f1;
- display: inline-block;
- padding: 5px 6px 4px;
- line-height: 1;
- font-size: 14px;
- }
-
- &.is-incomplete {
- .llms-lesson-complete {
- color: #cacaca;
- }
- }
-
- .llms-lesson-counter {
- font-size: 16px;
- line-height: 1;
- }
-
- .llms-free-lesson-svg {
- fill: currentColor;
- height: 23px;
- width: 50px;
- }
-
- p {
- margin-bottom: 0;
- }
-
-}
diff --git a/assets/scss/frontend/_tooltip.scss b/assets/scss/frontend/_tooltip.scss
deleted file mode 100644
index afa87788eb..0000000000
--- a/assets/scss/frontend/_tooltip.scss
+++ /dev/null
@@ -1,63 +0,0 @@
-.llms-tooltip {
-
- background: #2a2a2a;
- border-radius: 4px;
- color: #fff;
- font-size: 14px;
- line-height: 1.2;
- opacity: 0;
- top: -20px;
- padding: 8px 12px;
- left: 50%;
- position: absolute;
- pointer-events: none;
- transform: translateX( -50% );
- transition: opacity .2s ease, top .2s ease;
- max-width: 320px;
-
- &.show {
- top: -28px;
- opacity: 1;
- }
-
- &:after {
-
- bottom: -8px;
- border-top: 8px solid #2a2a2a;
- border-left: 8px solid transparent;
- border-right: 8px solid transparent;
- content: '';
- height: 0;
- left: 50%;
- position: absolute;
- transform: translateX( -50% );
- width: 0;
-
- }
-
-}
-
-
-
-.webui-popover-title {
- font-size: initial;
- font-weight: initial;
- line-height: initial;
-}
-.webui-popover-inverse {
- .webui-popover-inner .close {
- color: #fff;
- opacity: 0.6;
- text-shadow: none;
- &:hover {
- opacity: 0.8;
- }
- }
- .webui-popover-content a {
- color: #fff;
- text-decoration: underline;
- &:hover {
- text-decoration: none;
- }
- }
-}
diff --git a/assets/scss/frontend/_voucher.scss b/assets/scss/frontend/_voucher.scss
deleted file mode 100644
index 6c950121af..0000000000
--- a/assets/scss/frontend/_voucher.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.voucher-expand {
- display: none;
-}
\ No newline at end of file
diff --git a/assets/scss/lifterlms.scss b/assets/scss/lifterlms.scss
deleted file mode 100644
index b3474e9a0e..0000000000
--- a/assets/scss/lifterlms.scss
+++ /dev/null
@@ -1,40 +0,0 @@
-//
-// Main Frontend CSS File
-//
-
-@import "_includes/vars";
-@import "_includes/extends";
-@import "_includes/grid";
-@import "_includes/mixins";
-@import "_includes/buttons";
-@import "_includes/llms-donut";
-@import "_includes/tooltip";
-
-@import "frontend/main";
-@import "frontend/loop";
-@import "frontend/course";
-@import "frontend/syllabus";
-@import "frontend/llms-progress";
-@import "frontend/llms-author";
-@import "frontend/reviews";
-
-@import "frontend/notices";
-@import "frontend/llms-achievements-certs";
-@import "frontend/llms-notifications";
-@import "frontend/llms-pagination";
-@import "frontend/tooltip";
-
-
-@import "frontend/llms-quizzes";
-
-@import "frontend/voucher";
-@import "frontend/llms-access-plans";
-@import "frontend/checkout";
-@import "_includes/llms-form-field";
-
-@import "frontend/llms-outline-collapse";
-
-@import "frontend/student-dashboard";
-@import "frontend/llms-table";
-
-@import "_includes/vendor/_font-awesome";
diff --git a/babel.config.js b/babel.config.js
deleted file mode 100644
index 507dc879b2..0000000000
--- a/babel.config.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Babel config
- *
- * @package LifterLMS/Dev/Scripts
- *
- * @since Unknown
- * @version 6.0.0
- */
-
-const
- presets = [ '@wordpress/default' ],
- plugins = [ '@babel/plugin-proposal-class-properties' ];
-
-module.exports = { plugins, presets };
diff --git a/class-lifterlms.php b/class-lifterlms.php
index 87df8e3831..8c99d8bf03 100644
--- a/class-lifterlms.php
+++ b/class-lifterlms.php
@@ -34,7 +34,7 @@ final class LifterLMS {
*
* @var string
*/
- public $version = '7.6.0';
+ public $version = '7.6.1';
/**
* LLMS_Assets instance
diff --git a/composer.json b/composer.json
deleted file mode 100644
index f73eb82cc1..0000000000
--- a/composer.json
+++ /dev/null
@@ -1,142 +0,0 @@
-{
- "name": "gocodebox/lifterlms",
- "description": "LifterLMS, the #1 WordPress LMS solution, makes it easy to create, sell, and protect engaging online courses.",
- "keywords": [
- "WordPress",
- "LMS"
- ],
- "homepage": "https://lifterlms.com",
- "license": "GPL-3.0+",
- "authors": [
- {
- "name": "LifterLMS",
- "email": "help@lifterlms.com",
- "homepage": "https://lifterlms.com"
- }
- ],
- "type": "wordpress-plugin",
- "support": {
- "forum": "https://wordpress.org/support/plugin/lifterlms",
- "issues": "https://github.com/gocodebox/lifterlms/issues",
- "source": "https://github.com/gocodebox/lifterlms"
- },
- "autoload": {
- "psr-4": {
- "LLMS\\": "includes"
- }
- },
- "require": {
- "php": ">=7.4",
- "composer/installers": "~1.9.0",
- "deliciousbrains/wp-background-processing": "1.0.2",
- "lifterlms/lifterlms-blocks": "2.5.3",
- "lifterlms/lifterlms-cli": "0.0.3",
- "lifterlms/lifterlms-helper": "3.5.1",
- "lifterlms/lifterlms-rest": "1.0.0",
- "woocommerce/action-scheduler": "3.5.4"
- },
- "require-dev": {
- "lifterlms/lifterlms-tests": "^4.3",
- "lifterlms/lifterlms-cs": "dev-trunk"
- },
- "archive": {
- "exclude": [
- ".*",
- "*.lock",
- "*.xml",
- "*.xml.dist",
- "*.config.js",
-
- "CHANGELOG.md",
- "composer.json",
- "docker-compose.yml",
- "lerna.json",
- "package.json",
- "package-lock.json",
- "README.md",
-
- "/assets/scss",
-
- "_private",
- "dist",
- "docs",
- "gulpfile.js",
- "node_modules",
- "packages",
- "src",
- "tests",
- "tmp",
- "wordpress",
- "!/vendor",
-
- "!/libraries",
- "/libraries/README.md",
- "/libraries/**/composer.*",
- "/libraries/**/i18n",
-
- "/vendor/bin",
- "/vendor/**/**/composer.*",
- "/vendor/**/**/*.md",
- "/vendor/**/**/.*",
- "/vendor/composer/installers",
- "/vendor/composer/lifters",
-
- "!/assets/maps/js/vendor",
- "!/assets/vendor",
- "!/assets/js/vendor",
- "!/assets/js/builder/vendor"
- ]
- },
- "scripts": {
- "check-cs": "\"vendor/bin/phpcs\" --colors",
- "check-cs-errors": "\"vendor/bin/phpcs\" --colors --error-severity=1 --warning-severity=6",
- "config-cs": [
- "\"vendor/bin/phpcs\" --config-set default_standard 'LifterLMS Core'",
- "\"vendor/bin/phpcs\" --config-set ignore_warnings_on_exit 1"
- ],
- "env": "\"vendor/bin/llms-env\"",
- "env:setup": "./tests/bin/setup-e2e.sh",
- "fix-cs": "\"vendor/bin/phpcbf\"",
- "post-install-cmd": "@post-update-install-cmd",
- "post-update-cmd": "@post-update-install-cmd",
- "post-update-install-cmd": [
- "@config-cs",
- "rm -rf ./wp-content/",
- "rm composer.lock"
- ],
- "tests-remove": "\"vendor/bin/llms-tests\" teardown ${TESTS_DB_NAME:-llms_tests} ${TESTS_DB_USER:-root} \"${TESTS_DB_PASS:-password}\" ${TESTS_DB_HOST:-127.0.0.1}",
- "tests-install": "\"vendor/bin/llms-tests\" install ${TESTS_DB_NAME:-llms_tests} ${TESTS_DB_USER:-root} \"${TESTS_DB_PASS:-password}\" ${TESTS_DB_HOST:-127.0.0.1} ${WP_VERSION:-latest} false \"${WP_TESTS_VERSION:-false}\"",
- "tests-reinstall": [
- "@tests-remove",
- "@tests-install"
- ],
- "tests": [
- "Composer\\Config::disableProcessTimeout",
- "\"vendor/bin/phpunit\""
- ],
- "tests-run": [
- "Composer\\Config::disableProcessTimeout",
- "\"vendor/bin/phpunit\""
- ],
- "install-php8": "composer install --ignore-platform-reqs"
- },
- "extra": {
- "installer-paths": {
- "libraries/{$name}": [
- "lifterlms/lifterlms-blocks",
- "lifterlms/lifterlms-cli",
- "lifterlms/lifterlms-helper",
- "lifterlms/lifterlms-rest"
- ],
- "vendor/{$vendor}/{$name}": [
- "type:wordpress-plugin"
- ]
- }
- },
- "config": {
- "allow-plugins": {
- "dealerdirect/phpcodesniffer-composer-installer": true,
- "composer/installers": true
- }
- }
-}
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index b773b52d93..0000000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-version: '3.1'
-services:
- wordpress:
- volumes:
- - ./:/var/www/html/wp-content/plugins/lifterlms:rw
diff --git a/docs/block-development.md b/docs/block-development.md
deleted file mode 100644
index 4bd156ee24..0000000000
--- a/docs/block-development.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# Block Development
-
-Below are the steps for creating and registering a new block for LifterLMS.
-
-Please note that before beginning you will need to have Node and NPM installed on your machine. Please see [https://github.com/gocodebox/lifterlms/blob/trunk/docs/installing.md](https://github.com/gocodebox/lifterlms/blob/trunk/docs/installing.md) for installation details.
-
-#### Table of Contents
-- [1. Create block files](#1-create-block-files)
-- [2. Add block JSON data](#2-add-block-json-data)
-- [3. Adding Block JS](#3-adding-block-js)
-- [4. Compiling blocks](#4-compiling-blocks)
-- [5. Register with PHP](#5-register-with-php)
-- [6. Block design guidelines](#6-block-design-guidelines)
-
-### 1. Create block files
-
-Create a new folder in the `src/blocks` directory for your block. E.g. `/src/blocks/example-block/`. Then, add a `block.json` file and an `index.jsx` file to the new folder.
-
-The block directory structure should now look like this:
-
-```shell
-.
-└── project/
- ├── src/
- │ └── blocks/
- │ └── block/
- │ ├── block.json
- │ ├── index.jsx
- │ └── index.scss # optional.
- ├── package.json
- └── webpack.congif.js
-```
-
-### 2. Add block JSON data
-
-Next, add block information to the `block.json` file. Below is an example of a block.json file. Note that the category should be `lifterlms` to match the other LifterLMS blocks:
-
-```json
-{
- "$schema": "https://schemas.wp.org/trunk/block.json",
- "apiVersion": 2,
- "name": "llms/example-block",
- "title": "Example",
- "category": "llms-blocks",
- "description": "Block description",
- "textdomain": "lifterlms",
- "attributes": {},
- "supports": {},
- "editorScript": "file:./index.js"
-}
-```
-
-### 3. Adding Block JS
-
-Next, add the block’s JavaScript to the `index.jsx` file. We use the JSX file extension to indicate that the file contains JSX code.
-
-Below is an example of how to register a new block and access the block.json data to set the block’s name and attributes:
-
-```jsx
-import { registerBlockType } from '@wordpress/blocks';
-import blockJson from './block.json';
-
-registerBlockType( blockJson, {
- edit: ( props ) => {
- return { props.name }
;
- },
- save: ( props ) => {
- return { props.name }
;
- },
-} );
-```
-
-*Note that while it is common practise to separate the `edit` and `save` functions into separate files, this is not necessary unless the code becomes too complex to manage in a single file. We prefer to keep the code in a single file where possible.*
-
-### 4. Compiling blocks
-
-To compile the block, open the Terminal and run the following NPM script from the plugin root directory. This will compile all blocks to the main `/blocks/` directory:
-
-`npm run build:blocks`
-
-### 5. Register with PHP
-
-The last step is to register the block with PHP. This should be added to a PHP file or class where it makes sense. For example, shortcode blocks are registered in the `/includes/shortcodes/class.llms.shortcodes.blocks.php` file. Below is an example of how to register a block with PHP and allow WordPress to handle the loading of scripts and styles:
-
-```php
-add_action( 'init', 'llms_register_example_block' );
-/**
- * Register the example block.
- *
- * @since 1.0.0
- *
- * @return void
- */
-function llms_register_example_block() {
- register_block_type( LLMS_PLUGIN_DIR . 'blocks/example-block' );
-}
-```
-
-### 6. Block design guidelines
-
-Blocks should be designed to be as simple as possible.
-
-#### Icons
-
-Blocks should use FontAwesome icons. The complete list of icons can be found at [https://fontawesome.com/icons](https://fontawesome.com/icons). SVG icons need to be converted to React components and added to blocks with the `registerBlockType` function:
-
-```jsx
-import { registerBlockType } from '@wordpress/blocks';
-import { SVG, Path } from '@wordpress/primitives';
-
-const Icon = () => (
-
-
-
-);
-
-registerBlockType( blockJson, {
- icon: Icon,
- edit: Edit
-} );
-```
-
-#### Colors
-
-Blocks use the default core admin color palette. This ensures that hover and active states are consistent with other blocks.
-
-## Shortcodes
-
-For blocks with Server Side Rendering functionality, a shortcode should also be registered to support users who are not using the block editor. The shortcode should follow LifterLMS shortcode naming conventions and be registered in the `/includes/shortcodes/` directory and extend the `LLMS_Shortcode` class.
-
-Shortcode blocks can use the `llms_shortcode_blocks` filter provided by the `LLMS_Shortcode_Block` class to handle the block registration and rendering. Below is an example of how to register a block with the class from within a LifterLMS add-on plugin:
-
-```php
-add_filter( 'llms_shortcode_blocks', register_blocks( array $config ): array {
- $config['group-list'] = array(
- 'render' => array( 'LLMS_Groups_Shortcode_Group_List', 'output' ),
- 'path' => LLMS_GROUPS_PLUGIN_DIR . 'assets/blocks/group-list',
- );
-
- return $config;
-} );
-```
diff --git a/docs/coding-standards.md b/docs/coding-standards.md
deleted file mode 100644
index e25d33af7f..0000000000
--- a/docs/coding-standards.md
+++ /dev/null
@@ -1,141 +0,0 @@
-LifterLMS Coding Standards
-==========================
-
-The purpose of the LifterLMS Coding Standards is to create a baseline for collaboration and review within the open source LifterLMS codebase, project, and community.
-
-The WordPress community has developed coding standards and documented them in the [WordPress codex](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/). Wherever possible, the LifterLMS Coding Standards aim to obey these coding standards.
-
-## Naming Conventions
-
-### camelCase should not be used.
-
-LifterLMS avoids `camelCase` for class names, class methods, functions, and variables. Words should instead be separated by underscores.
-
-### Class Names
-
-Class names should use capitalized words separated by underscores.
-LifterLMS core class names should be prefixed with `LLMS_`.
-
-
-```php
-class LLMS_Student extends LLMS_Abstract_User_Data { [...] }
-class LLMS_Data { [...] }
-```
-
-LifterLMS add-on class names should be prefixed with `LLMS_` as well as an additional add-on prefix.
-
-```php
-class LLMS_AQ_Question_Types { [...] }
-class LLMS_SL_Story extends LLMS_Abstract_Database_Store { [...] }
-```
-
-### Trait Names
-
-Trait names should use capitalized words separated by underscores.
-LifterLMS core trait names should be prefixed with `LLMS_Trait`.
-
-```php
-trait LLMS_Trait_Singleton { [...] }
-```
-
-### Constants
-
-Constants should be in all upper-case with underscores separating words.
-LifterLMS core constants should be prefixed with `LLMS_`.
-
-```php
-define( 'LLMS_PLUGIN_FILE', __FILE__ );
-```
-
-LifterLMS add-on class names should be prefixed with `LLMS_` as well as an additional add-on prefix.
-
-```php
-define( 'LLMS_FORMIDABLE_FORMS_PLUGIN_FILE', __FILE__ );
-```
-
-### File names
-
-Files should be named descriptively using lower case letters. Hyphens should be used to separate words.
-
-```
-my-plugin-file.php
-```
-
-Class file names should be based on the class name with `class-` prepended and the underscores in the class name replaced with hyphens, for example `LLMS_Data` becomes:
-
-```
-class-llms-data.php
-```
-
-Files containing model classes should prepend `model-` instead of `class-`. For example the `LLMS_Student` model class becomes:
-
-```
-model-llms-student.php
-```
-
-Trait file names should be based on the trait name with underscores replaced by hyphens and the file stored in the
-`includes/traits` directory. For example `LLMS_Trait_Singleton` becomes:
-
-```
-includes/traits/llms-trait-singleton.php
-```
-
-### Functions & Variables
-
-Lowercase letters should be used for function names and variables. Separate words with underscores.
-LifterLMS core functions should be prepended with the prefix `llms_`.
-
-```php
-llms_current_time( $type, $gmt = 0 ) { [...] }
-```
-
-LifterLMS add-on function names should be prefixed with `llms_` as well as an additional add-on prefix.
-
-```php
-llms_ck_consent_form_field() { [...] }
-```
-
-### Hooks: Actions & Filters
-
-Lowercase letters should be used for hook names. Separate words with underscores.
-LifterLMS core hooks should be prepended with the prefix `llms_`.
-
-```php
-do_action( 'llms_user_enrolled_in_course', [...] );
-apply_filters( 'llms_get_enrollment_status', [...] );
-```
-
-LifterLMS add-on hook names should be prefixed with `llms_` as well as an additional add-on prefix.
-
-```php
-do_action( 'llms_pa_post_created_from_automation', [...] );
-apply_filters( 'llms_sl_story_can_user_manage', [...] );
-```
-
-When actions are set to run before and after items (templates, as an example) it is acceptable to use additional prefixes `before_` and `after_` prior to the `llms_` prefix.
-
-There are a number of legacy hooks which use the prefix `lifterlms_` instead of `llms_`. These are retained for backwards compatibility but should not be used as an example of an acceptable naming convention for new code.
-
-### CSS Classes and IDs
-
-Class names and IDs should be lowercase and prefixed with `llms-`.
-
-Words should be separated with hyphens (AKA "kebab case").
-
-```html
-
-```
-
-### Form Element `name` attributes
-
-The `name` attribute of HTML form elements should be prefixed with `llms_`.
-
-Lowercase letters should be used and words should be separated by underscores.
-
-```html
-
-```
-
-
diff --git a/docs/contributing.md b/docs/contributing.md
deleted file mode 100644
index aca594f40c..0000000000
--- a/docs/contributing.md
+++ /dev/null
@@ -1,4 +0,0 @@
-Contributor Guidelines
-----------------------
-
-See contributing guidelines at https://github.com/gocodebox/lifterlms/blob/trunk/.github/CONTRIBUTING.md
diff --git a/docs/documentation-standards.md b/docs/documentation-standards.md
deleted file mode 100644
index 865691d955..0000000000
--- a/docs/documentation-standards.md
+++ /dev/null
@@ -1,395 +0,0 @@
-LifterLMS Inline Documentation Standards
-========================================
-
-The LifterLMS documentation standard is heavily inspired by the [WordPress core's documentation standards][wp-core-docs]. We have made customizations to these standards in areas where it aids our core team's development and release workflows. By using the WordPress core documentation standard as a starting point any contributor already familiar with the WordPress core should be able to quickly add inline documentation to LifterLMS without the need to study our standards at length.
-
-## What should be documented
-
-The following elements should be documented using formatted documentation blocks (DocBlocks):
-
-+ Functions
-+ Classes
-+ Class methods
-+ Class members (including properties and constants)
-+ Requires and includes
-+ Hooks (actions and filters)
-+ File headers
-
-## DocBlock Formatting Guidelines
-
-Inline documentation in the LifterLMS code base is automatically parsed and output to the code reference [developer.lifterlms.com][llms-dev]. Adhering to these guidelines is essential to ensure optimum readability via the code reference.
-
-
-### Spacing
-
-DocBlocks should directly precede the element (hook, function, method, class, etc...). There should not be any opening/closing tags, white space, or anything else between the DocBlock and the declarations. This will ensure the parser can correctly associate the DocBlock with it's element.
-
-
-### Summary
-
-A short piece of text, usually one line, providing the basic function of the associated element. A good summary concisely describes what the element does and should not attempt to describe why the element exists.
-
-HTML may not be used in the summary. For example, if the function outputs an ` ` tag, the summary should read ```Outputs an image tag.``` instead of ```Outputs an ` ` tag.```.
-
-
-### Description
-
-An optional longer piece of text providing more details on the associated element’s function.
-
-HTML may not be used in the summary but markdown can be used to format a complicated description.
-
-**1. Lists**
-
-Use a hyphen (`-`) to create an unordered list, with a blank line before and after.
-
-```
- * Description which includes an unordered list:
- *
- * - This is item 1.
- * - This is item 2.
- * - This is item 3.
- *
- * The description continues on ...
-```
-
-Use numbers to create an ordered list, with a blank line before and after.
-
-```
- * Description which includes an ordered list:
- *
- * 1. This is item 1.
- * 2. This is item 2.
- * 3. This is item 3.
- *
- * The description continues on ...
-```
-
-**2. Code Samples**
-
-A code sample may be created by indenting every line of the code by 4 spaces, with a blank line before and after. Blank lines in code samples also need to be indented by four spaces. Note that examples added in this way will be output in `` tags and are not syntax-highlighted in the code reference.
-
-```
- * Description including a code sample:
- *
- * $status = array(
- * 'draft' => __( 'Draft' ),
- * 'pending' => __( 'Pending Review' ),
- * 'private' => __( 'Private' ),
- * 'publish' => __( 'Published' )
- * );
- *
- * The description continues on ...
-```
-
-**3. Links**
-
-A link in the form of a URL, such as related GitHub issue or other documentation, should be added in the appropriate place in the DocBlock using the `@link` tag.
-
-```
- * Description text.
- *
- * @link https://github.com/gocodebox/lifterlms/issues/1234567890
-```
-
-### Changelogs
-
-Whenever any code is changed within an element, a `@since`, `@version`, or `@deprecated` tag should be added to the element to document the change(s) which have been made.
-
-No HTML should be used in the descriptions for these tags, though limited Markdown can be used as necessary, such as for adding backticks around variables, e.g. `$variable`.
-
-All descriptions for any of these tags should be a full sentence ending with a full stop (a period, for example).
-
-#### Changes Warranting a Changelog Entry
-
-Most code changes warrant a changelog entry to be recorded for the element but there are some exceptions.
-
-+ **Classes**: Any breaking changes, deprecations, or the introduction of new class elements (elements which do not have their own changelog, such as class properties) require an accompanying `@since` tag entry. Changes to a class method should be recorded on the method's changelog, not on the class changelog.
-+ **Functions and class methods**: Any change made requires an accompanying `@since` tag entry
-
-Changes which do not affect the functionality or execution of the element *should not* be recorded on the element's changelog. For example, a coding standards change such as alignment or spacing should not be recorded.
-
-#### Recording the Version Number
-
-Versions should be expressed in the 3-digit `x.x.x` style.
-
-```
- * @since 3.29.0
-```
-
-When any change has been made to the element an additional `@since` tag can be added with a short description of the changes which were made.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Added optional 3rd argument.
-```
-
-#### Deprecations
-
-When an element is marked for deprecation this should be recorded at the end of the changelog with an `@deprecated` tag.
-
-A short description may be added to provide additional information about the deprecation. If a replacement function has been added in it's place, note as much with an `@see` tag.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Added optional 3rd argument.
- * @deprecated 3.10.0 Use `llms_new_function_name()` instead.
- *
- * @see llms_new_function_name()
-```
-
-When adding documentation on an existing element which does not yet have a changelog (common in code added prior to the creation and enforcement of these standards) if it is impossible to determine when the element was added the version may be expressed with `Unknown` instead of the `x.x.x` version number.
-
-#### File Headers
-
-Whenever an element within a file is updated, the `@version` tag in the header should be updated to the current version of the codebase.
-
-#### Tag alignment and order
-
-All changelog tags, `@since`, `@version`, and `@deprecated` should be grouped together with a space before the first `@since` tag and after the last tag in the group.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Changelog entry description.
- * @deprecated 3.10.0 Use `llms_new_function_name()` instead.
-```
-
-When multiple lines are required for a single entry, subsequent lines should be indented to match the starting point of the description.
-
-```
- * @since 3.3.0
- * @since 3.5.0 Changelog entry description.
- A second entry aligned to with the first entry.
-```
-
-Multiple logs with version numbers of differing lengths should not be aligned to one another.
-
-```
- * @since 3.3.0
- * @since 3.25.0 Changelog entry description.
- * @since 4.0.0 This entry should not be aligned with the 3.25.0 entry above it.
-```
-
-#### Using Placeholders
-
-When contributing code we recommend using the placeholder `[version]` in favor of trying to guess what version the element will be released with.
-
-Our release workflow automatically replaces with `@since`, `@version`, and `@deprecated` followed by `[version]` with the actual version of the release being packaged.
-
-For a new element:
-
-```
- * @since [version]
-```
-
-When updating an existing element:
-
-```
- * @since 3.5.0
- * @since [version] Updated element.
-```
-
-
-### Additional Tags
-
-#### 1. Parameters and Returns
-
-Functions and methods should define all parameter arguments and returns with the `@param` and `@return` tags.
-
-No HTML should be used in the descriptions for these tags, though limited Markdown can be used as necessary, such as for adding backticks around variables, e.g. `$variable`.
-
-All descriptions for any of these tags should be a full sentence ending with a full stop (a period, for example).
-
-```
- * @param string $var1 Description of the argument.
- * @param bool $var2 Description of the argument.
- * @return string
- */
-function my_function( $var1, $var2 = false ) {
- ...
- return $var1;
-}
-```
-
-Parameters that are arrays should be documented using WordPress’ flavor of hash notation style, each array value beginning with the `@type` tag, and and describing the value as follows:
-
-```
- * @type type $key Description. Default 'value'. Accepts 'value', 'value'.
- * (aligned with Description, if wraps to a new line)
-```
-
-A full array parameter would look like this:
-
-```
- * @param array $args {
- * Optional. An array of arguments.
- *
- * @type type $key Description. Default 'value'. Accepts 'value', 'value'.
- * (aligned with Description, if wraps to a new line)
- * @type type $key Description.
- * }
-```
-
-#### 2. Types
-
-Variables, constants, and class members should use the `@var` tag to describe the member's type.
-
-```
- * @var string
- */
-public $var = 'text';
-```
-
-#### 3. Relations and References
-
-Use `@see` to perform automatic links to other areas of the codebase. For example `{@see 'is_lifterlms'}` to link to the filter `is_lifterlms`.
-
-
-#### 4. Thrown Exceptions
-
-A function or method which throws an exception should document the thrown exception using an `@throws` tag.
-
-When present, the `@throws` tag should be added to the end of the docblock below the `@return` tag. An empty line should separate the `@return` and `@throws` tag.
-
-```
- * @return string
- *
- * @throws Exception A description of the raised exception.
- */
-```
-
-## DocBlock Examples
-
-
-### Functions and Class Methods
-
-Functions and class methods should be formatted as follows:
-
-+ Summary
-+ Description (optional)
-+ Changelog
-+ Links and References (where appropriate)
-+ Parameters
-+ Return
-
-```
-/**
- * Summary.
- *
- * Description.
- *
- * @since x.x.x
- * @since x.x.x Description of function/method changes.
- *
- * @see Function/method/class relied on
- * @link URL
- *
- * @param type $var Description.
- * @param type $var Optional. Description. Default.
- * @return type Description.
- */
-```
-
-
-### Classes
-
-Class DocBlocks should be formatted as follows:
-
-+ Summary
-+ Description (Optional)
-+ Links and References (as an example use `@see` to reference a super class when documenting a sub class)
-+ Changelog
-
-```
-/**
- * Summary.
- *
- * Description.
- *
- * @see Super_Class
- *
- * @since x.x.x
- * @since x.x.x Description of class changes.
- */
-```
-
-
-### Class Members
-
-Class properties and constants should be formatted as follows:
-
-+ Summary
-+ Changelog
-+ Type
-
-```
-/**
- * Summary.
- *
- * @since x.x.x
- * @since x.x.x Description of member changes.
- * @var type Optional description.
- */
-```
-
-
-### Hooks (Actions and Filters)
-
-Both action and filter hooks should be documented on the line immediately preceding the call to `do_action()` or `do_action_ref_array()`, `apply_filters()`, or `apply_filters_ref_array()`, and formatted as follows:
-
-+ Summary
-+ Description (Optional)
-+ Changelog
-+ Parameters
-
-Note that `@return` is not used for hook documentation, because action hooks return nothing, and filter hooks always return their first parameter.
-
-```
-/**
- * Summary.
- *
- * Description.
- *
- * @since x.x.x
- * @since x.x.x Description of hook changes.
- *
- * @param type $var Description.
- * @param array $args {
- * Short description about this hash.
- *
- * @type type $var Description.
- * @type type $var Description.
- * }
- * @param type $var Description.
- */
-```
-
-
-### File Headers
-
-The file header DocBlock is used to give an overview of what is contained in the file and should be formatted as follows:
-
-+ Summary
-+ Description (optional)
-+ Links and references
-+ Package
-+ Changelog
-
-```
-/**
- * Summary (no period for file headers)
- *
- * Description. (use period)
- *
- * @link URL
- *
- * @package LifterLMS/SecondaryPackage/TertiaryPackage
- *
- * @since x.x.x
- * @since x.x.x Description of file changes.
- * @version x.x.x
- */
-```
-
-
-[llms-dev]: https://developer.lifterlms.com/reference/
-[wp-core-docs]: https://developer.wordpress.org/coding-standards/inline-documentation-standards/
diff --git a/docs/e2e-tests-real.md b/docs/e2e-tests-real.md
deleted file mode 100644
index d4b4c2ba65..0000000000
--- a/docs/e2e-tests-real.md
+++ /dev/null
@@ -1,72 +0,0 @@
-Running E2E (End to End) Tests Against a Real Website
-=====================================================
-
-_The core E2E test suite is primarily designed to be run locally against managed Docker containers. However, it is possible to run the test suite against any WordPress website with a publicly accessible URL by following this guide._
-
-_To run tests locally against managed Docker containers, see the [E2E Testing README](../tests/e2e/README.md)._
-
-**NOTE: This is an experimental process! Proceed with caution. We are developing this process for internal use and thought it might be useful to some other folks.**
-
-**Another note: This process will import courses, create fake users, and add other data to your website and there is no cleanup proccess. If you choose to use this against a live production site that means that the database will have a bunch of fake test data added to it. So don't run this against a real production website. Use a staging website instead!**
-
-## Prerequisites
-
-+ Ability to use a terminal
-+ git
-+ node.js
-+ npm
-
-
-## Setup your local environment
-
-+ Install the LifterLMS repo: `git clone https://github.com/gocodebox/lifterlms`
-+ Move into the cloned directory: `cd liferlms`
-+ Install node packages: `npm ci`
-+ Create a new file in the created directory named `.llmsenv`.
-+ Use your favorite text editor to edit the file and add the following to the file (replacing the example data with your site's information):
-
-```
-WP_BASE_URL=https://yourwebsiteurl.tld
-WP_USERNAME=adminusername
-WP_PASSWORD=adminpassword
-```
-
-**This will store a password in a PLAIN TEXT which we know is wrong. Our internal use case uses this process with temporary sites which are regularly destroyed so the danger is acceptable to our use case. If you decide to use this process on a real website with real user information you have been warned that storing your production site's WP admin password in a plain text file in order to use this process is a bad idea. We recommend instead using environment variables to pass your password to the script later and removing the WP_PASSWORD from the `.llmsenv` file.**
-
-+ Save the file
-
-
-## Setup your production site
-
-+ Install and activate the LifterLMS plugin on your site
-
-
-## Run the tests
-
-There are two ways to run the E2E tests:
-
-### Headless mode
-
-Runs the tests and shows you the results.
-
-If errors are encountered, a screenshot of the page will be taken and saved in the `tmp/e2e-screenshots/` directory so you can see what the page looked like when things went sour.
-
-Error logs will be output in your terminal to review.
-
-Run headless tests by executing `npm run tests` in your terminal.
-
-
-### Interactive mode
-
-Launches an automated Chromium browser and runs the tests in "slow motion" so you can watch as the tests run.
-
-No screenshots are takeng in interactive mode.
-
-Error logs are output to the terminal for review.
-
-Run interactive tests by executing `npm run tests:dev` in your terminal.
-
-
-### Using environment variables
-
-If you don't want to store you admin password in a plaintext file you can define the WP_PASSWORD variable at runtime `WP_PASSWORD=yourpassword npm run tests`
diff --git a/docs/installing.md b/docs/installing.md
deleted file mode 100644
index 49fbca5a23..0000000000
--- a/docs/installing.md
+++ /dev/null
@@ -1,83 +0,0 @@
-Installing for Development
-==========================
-
-## Requirements
-
-In order to build and develop LifterLMS locally, you'll need the following:
-
-+ PHP
-+ MySQL / MariaDB
-+ [Composer](https://getcomposer.org/download/)
-+ [Node.js](https://nodejs.org/en/download/)
-+ [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
-
-
-## Building LifterLMS
-
-### 1. Clone source from GitHub
-
-```sh
-$ git clone https://github.com/gocodebox/lifterlms
-$ cd lifterlms
-```
-
-If you're planning to contribute code, you should fork this repository and clone your fork instead and switch to the dev branch before continuing the install.
-
-```sh
-$ git checkout dev
-```
-
-### 2. Install composer dependencies:
-
-```sh
-$ composer install
-```
-
-### 3. Install npm dependencies:
-
-```sh
-$ npm install --global gulp
-$ npm install
-```
-
-### 4. Build static assets
-
-```sh
-$ npm run build
-```
-
-The `lifterlms` directory is now an installable plugin that can be moved into your local server's `wp-content/plugins` directory.
-
-
-## Running PHPCS
-
-When contributing you should ensure your contributions follow our [coding](./coding-standards.md) and [documentation](./documentation-standards.md) standards.
-
-To check for errors and warnings in your code, run PHPCS:
-
-```sh
-$ composer run check-cs
-```
-
-To check for errors only:
-
-```sh
-$ composer run check-cs-errors
-```
-
-These reports may include issues that can be automatically fixed using PHPCBF:
-
-```sh
-$ composer run fix-cs
-```
-
-## Running Test Suites
-
-New code should also strive to be covered by automated tests.
-
-LifterLMS has unit and integration tests via phpunit and End-to-End tests via Jest and Puppeteer.
-
-For guides on running and contributing tests, see the relevant guides:
-
-+ [phpunit](../tests/phpunit/README.md)
-+ [e2e](../tests/e2e/README.md)
diff --git a/docs/releases.md b/docs/releases.md
deleted file mode 100644
index 52b07d0d75..0000000000
--- a/docs/releases.md
+++ /dev/null
@@ -1,111 +0,0 @@
-Releasing LifterLMS Builds
-==========================
-
-This document outlines the workflow used by LifterLMS core maintainers to build and publish LifterLMS releases.
-
-This document assumes you have already installed LifterLMS for development following the [Installing for Development guide](./installing.md).
-
-## 0. Get ready
-
-Make sure you have your local repository up to date. If your origin is set to the gocodebox/lifterlms repo, these commands will get you up to date.
-
-1. `git checkout dev` and `git pull`
-2. `git checkout trunk` and `git pull`
-
-Make sure your @lifterlms/dev package in package.json is on the latest version. If it needs to be updated, update it, commit to the dev branch, and then run `npm install`.
-
-Make sure you are back on the dev branch.
-
-1. `git checkout dev`
-
-Make sure you have the latest `@lifterlms` JS packages. Note that this will update node_modules using the latest published/stable version of the packages, and won't include any updates made to those packages by this release itself.
-
-1. `npm install`
-
-Make sure that the dev version (or trunk since it will merge automatically) are tested up to the latest version of WordPress.
-
-For Add-ons, also confirm that the plugin headers include appropriate values for LLMS minimum version and LLMS tested up to as follows:
-
-1. Adjust these lines in the header of the main plugin .php file.
-
-* ` * Tested up to: 6.4.1` (this is the WordPress tested up to value)
-* ` * LLMS requires at least: 6.0.0` (only update this value if you are sure that the update breaks backwards compatibility)
-* ` * LLMS tested up to: 7.5.0 ` (this should be updated to the latest LifterLMS stable version)
-
-## 1. Build the Release
-
-Prepare the release: `npm run dev release prepare`:
-
-When running this command, the following happens:
-
-1. Determines the version number based on the significance values found in `.changelogs/` files. Unless `-F` is passed to the command to force a specific version number.
-2. Write the changelog entries to `CHANGELOG.md`.
-3. Updates version numbers of placeholder `[version]` tags, `package.json`, etc...
-4. Runs the release build command, `npm run build`.
-
-## 2. Run tests and coding standards checks
-
-0. Ensure phpunit tests are installed: `composer run tests-install`.
-1. Ensure phpunit tests pass: `composer run tests-run`.
-2. Ensure phpcs checks pass: `composer run check-cs-errors`.
-3. Ensure e2e tests pass: `npm run test`.
-4. Ensure eslint checks pass: `npm run lint:js`.
-
-## 3. Commit and push
-
-After building and testing the built release, all changes should be committed and pushed to GitHub.
-
-1. `git commit -a`
-2. Enter something like "build version 7.1.1" for the commit message.
-3. `git push`
-
-## 4. Generate the Distribution Archive
-
-Run `npm run dev release archive -- -i`.
-
-This is a more pedantic version of `npm run dev release archive` that will allow to easily inspect
-the archive: once created, the archive will be unpacked into the `dist` directory so that its content
-could be easily inspected. E.g. make sure it doesn't contain undesired files such as unwanted dependencies
-into the `vendor` directory, and so on.
-
-## 5. Run pre-release tests on the archived
-
-Install and activate the zip file on a temporary sandbox site.
-
-Note: If you are reusing a testing site that already has LifterLMS installed, you can add this line to your wp-config.php and then uninstall and delete LifterLMS from the plugins screen and it will delete all of the LifterLMS data.
-
-`define( 'LLMS_REMOVE_ALL_DATA', true );`
-
- 1. Run the setup wizard.
- 2. Import sample course
- 3. Enroll a student into the course.
- 4. Complete a lesson.
-
-_This manual testing ensures no errors occurred in the build steps above._
-
-## 6. Publish the Release
-
-Run `npm run dev release create`.
-
-The following steps are performed automatically by the above task:
-
-1. Publish to GitHub
- 1. The contents of the distribution archive is force-pushed to the `release` branch.
- 1. A new release tag draft is created for the current version number using `release` as the commit target.
- 1. The distribution archive is uploaded to the release.
- 1. The release is published.
- 1. A webhook ping notifies the `llms-releaser` server which performs the remaining steps of the release:
-1. Publish to WordPress plugin repository
- 1. Create a new SVN tag using the release asset (distribution archive) as the base.
- 1. Update the `trunk` branch to match the new tag.
-1. A changelog blog post is published to make.lifterlms.com.
-1. The number is updated at LifterLMS.com
-1. The distribution archive is synced to the release asset bucket in AWS S3 as a backup.
-
-## 7. Update Trunk
-
-After everything is complete, the final version of should be committed and pushed to GitHub trunk branch. It is possible this can also be done on GitHub.com directly by create a Pull Request from `dev` to `trunk`
-
-1. `git checkout trunk`
-2. `git merge dev`
-3. `git push`
diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js
deleted file mode 100644
index e79cf6ffc6..0000000000
--- a/gulpfile.js/index.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Main Gulp File
- *
- * Requires all task files
- */
-var gulp = require('gulp');
-
-// All custom tasks.
-require( './tasks/hacky-clean' );
-require( './tasks/js-additional' );
-require( './tasks/js-builder' );
-
-// All tasks from lib-tasks.
-require( 'lifterlms-lib-tasks' )( gulp );
diff --git a/gulpfile.js/tasks/hacky-clean.js b/gulpfile.js/tasks/hacky-clean.js
deleted file mode 100644
index d4bfd14bbc..0000000000
--- a/gulpfile.js/tasks/hacky-clean.js
+++ /dev/null
@@ -1,22 +0,0 @@
-const gulp = require( 'gulp' ),
- { unlinkSync } = require( 'fs' ),
- filesToRemove = [
- 'assets/css/dancing-script-rtl.css',
- 'assets/css/imperial-script-rtl.css',
- 'assets/css/pirata-one-rtl.css',
- 'assets/css/unifraktur-maguntia-rtl.css',
- ];
-
-/**
- * A hacky clean script that deletes RTL css files generated by the legacy styles-rtl tasks.
- *
- * The deleted files are webfont definition files which we don't need RTL stylesheets for.
- */
-gulp.task( 'hacky-clean', function( cb ) {
-
- filesToRemove.forEach( file => {
- unlinkSync( file );
- } );
-
- return cb();
-} );
diff --git a/gulpfile.js/tasks/js-additional.js b/gulpfile.js/tasks/js-additional.js
deleted file mode 100644
index 6e13545bab..0000000000
--- a/gulpfile.js/tasks/js-additional.js
+++ /dev/null
@@ -1,49 +0,0 @@
-var gulp = require( 'gulp' )
- , header = require( 'gulp-header' )
- , include = require( 'gulp-include' )
- , maps = require( 'gulp-sourcemaps' )
- , pump = require( 'pump' )
- , rename = require( 'gulp-rename' )
- , uglify = require( 'gulp-uglify' )
- , gulpignore = require( 'gulp-ignore' )
-
- , path = require( 'path' )
-;
-
-gulp.task( 'js-additional', function( cb ) {
-
- var notice = [
- '/****************************************************************',
- ' *',
- ' * Contributor\'s Notice',
- ' * ',
- ' * This is a compiled file and should not be edited directly!',
- ' * The uncompiled script is located in the "assets/private" directory',
- ' * ',
- ' ****************************************************************/',
- '',
- '',
- ];
-
- pump( [
- gulp.src( 'assets/js/private/**/*.js' ),
- include(),
- maps.init(),
- header( notice.join( '\n' ) ),
- maps.write('../maps/js', { destPath: 'assets/js' } ),
- gulp.dest( 'assets/js' ),
-
- // Don't pass maps any further.
- gulpignore.exclude( file => '.js' !== path.extname( file.basename ) ),
-
- uglify(),
- rename( {
- suffix: '.min',
- } ),
- maps.write('../maps/js', { destPath: 'assets/js' } ),
- gulp.dest( 'assets/js' )
- ],
- cb
- );
-
-} );
diff --git a/gulpfile.js/tasks/js-builder.js b/gulpfile.js/tasks/js-builder.js
deleted file mode 100644
index 22be2d3059..0000000000
--- a/gulpfile.js/tasks/js-builder.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * -----------------------------------------------------------
- * js-builder
- * -----------------------------------------------------------
- * Compile Admin builder Javascript
- */
-
-var gulp = require( 'gulp' )
- , requirejsOptimize = require( 'gulp-requirejs-optimize' )
- , rename = require( 'gulp-rename' )
- , sourcemaps = require( 'gulp-sourcemaps' )
-;
-
-gulp.task( 'js-builder', function( cb ) {
-
- gulp.src( 'assets/js/builder/main.js' )
- // unminified
- .pipe( sourcemaps.init() )
- .pipe( requirejsOptimize( function( file ) {
- return {
- name: 'vendor/almond',
- optimize: 'none',
- wrap: {
- start: "(function($){",
- end: "}(jQuery));"
- },
- baseUrl: 'assets/js/builder/',
- include: [ 'main' ],
- preserveLicenseComments: false
- };
- } ).on( 'error', ( err ) => console.log( err ) ) )
- .pipe( rename( 'llms-builder.js' ) )
- .pipe( sourcemaps.write( '../maps/js', { destPath: 'assets/js' } ) )
- .pipe( gulp.dest( 'assets/js/' ) )
-
- // minified
- .pipe( sourcemaps.init() )
- .pipe( requirejsOptimize( function( file ) {
- return {
- name: 'vendor/almond',
- optimize: 'uglify2',
- wrap: {
- start: "(function($){",
- end: "}(jQuery));"
- },
- baseUrl: 'assets/js/builder/',
- include: [ 'main' ],
- preserveLicenseComments: false
- };
- } ).on( 'error', ( err ) => console.log( err ) ) )
- .pipe( rename( 'llms-builder.min.js' ) )
- .pipe( sourcemaps.write( '../maps/js', { destPath: 'assets/js' } ) )
- .pipe( gulp.dest( 'assets/js/' ) );
-
- cb();
-
-});
diff --git a/includes/admin/class.llms.admin.builder.php b/includes/admin/class.llms.admin.builder.php
index 308ce85ae0..2f7a401026 100644
--- a/includes/admin/class.llms.admin.builder.php
+++ b/includes/admin/class.llms.admin.builder.php
@@ -574,6 +574,8 @@ public static function output() {
'post_title' => __( 'New Course', 'lifterlms' ),
)
);
+
+ $post = get_post( $course_id );
}
$course = llms_get_post( $post );
diff --git a/includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php b/includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php
index 74fda5731f..50726d6ec7 100644
--- a/includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php
+++ b/includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php
@@ -161,7 +161,7 @@ public function get_fields() {
),
);
- if ( 'yes' !== $course->get( 'lesson_drip' ) || ! $course->get( 'drip_method' ) ) {
+ if ( ! $course || 'yes' !== $course->get( 'lesson_drip' ) || ! $course->get( 'drip_method' ) ) {
$fields['drip']['fields'][] = array(
'class' => 'llms-select2',
'desc_class' => 'd-all',
@@ -212,6 +212,10 @@ public function get_fields() {
* @return string
*/
public function get_drip_course_settings_info_html( $course ) {
+ if ( ! $course ) {
+ return '';
+ }
+
$output = 'yes' === $course->get( 'lesson_drip' ) && $course->get( 'drip_method' ) ?
__( 'Drip settings are currently set at the course level, under the Restrictions settings tab. If you would like to set individual drip settings for each lesson, you must disable the course level drip settings first.', 'lifterlms' )
:
diff --git a/languages/README.md b/languages/README.md
deleted file mode 100644
index 4858d32b7a..0000000000
--- a/languages/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-LifterLMS Localization and Language Files
-=========================================
-
-This directory contains localization and language files for the LifterLMS plugin.
-
-## Translating LifterLMS
-
-LifterLMS is fully translatable. The main `.pot` file contained in this directory ([lifterlms.pot](lifterlms.pot)) contains all translatable strings available in the source code. This file is automatically generated on release.
-
-
-## Localization Information Files
-
-The `.php` files contained within this directory contain lists of localization information (such as country, address, and currency formatting data). These files are loaded by LifterLMS core functions to various areas of the LifterLMS plugin.
-
-The data contained within these files is compiled from regularly updated sources and converted into a format used by our internal API. These files are automatically generated during a release step.
-
-Information for these files is derived from the following projects and sources:
-
-+ [Countries States Cities Database](https://github.com/dr5hn/countries-states-cities-database)
-+ [Currency Formatter](https://github.com/smirzaei/currency-formatter)
-+ [addressfield.json](https://github.com/tableau-mkt/addressfield.json)
-+ [LocalePlanet](https://www.localeplanet.com/)
-
-If you locate any incorrect information in any of these files, please let us know by opening [a new issue](https://github.com/gocodebox/lifterlms/issues/new/choose).
diff --git a/languages/lifterlms.pot b/languages/lifterlms.pot
index 1426f893bb..6ad0978573 100644
--- a/languages/lifterlms.pot
+++ b/languages/lifterlms.pot
@@ -2,21 +2,20 @@
# This file is distributed under the GPLv3.
msgid ""
msgstr ""
-"Project-Id-Version: LifterLMS 7.6.0\n"
+"Project-Id-Version: LifterLMS 7.6.1\n"
"Report-Msgid-Bugs-To: https://lifterlms.com/my-account/my-tickets\n"
"Last-Translator: Team LifterLMS \n"
"Language-Team: Team LifterLMS \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"POT-Creation-Date: 2024-04-18T14:41:26+00:00\n"
+"POT-Creation-Date: 2024-05-02T19:34:27+00:00\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"X-Generator: llms/dev 0.2.1\n"
+"X-Generator: llms/dev 0.2.2\n"
"X-Domain: lifterlms\n"
#. Plugin Name of the plugin
#. Author of the plugin
-#: lifterlms.php
#: includes/class.llms.nav.menus.php:73
#: includes/privacy/class-llms-privacy.php:33
#: libraries/lifterlms-blocks/assets/js/llms-blocks.js:5
@@ -26,12 +25,10 @@ msgstr ""
#. Plugin URI of the plugin
#. Author URI of the plugin
-#: lifterlms.php
msgid "https://lifterlms.com/"
msgstr ""
#. Description of the plugin
-#: lifterlms.php
msgid "Complete e-learning platform to sell online courses, protect lessons, offer memberships, and quiz students."
msgstr ""
@@ -912,31 +909,31 @@ msgid "New Course"
msgstr ""
#. Translators: %s = Item id.
-#: includes/admin/class.llms.admin.builder.php:693
+#: includes/admin/class.llms.admin.builder.php:695
msgid "Unable to detach \"%s\". Invalid ID."
msgstr ""
#. Translators: %s = Item id.
-#: includes/admin/class.llms.admin.builder.php:773
+#: includes/admin/class.llms.admin.builder.php:775
msgid "Unable to delete \"%s\". Invalid ID."
msgstr ""
#. Translators: %s = Question choice ID.
-#: includes/admin/class.llms.admin.builder.php:853
+#: includes/admin/class.llms.admin.builder.php:855
msgid "Error deleting the question choice \"%s\""
msgstr ""
#. Translators: %s = Post type name.
-#: includes/admin/class.llms.admin.builder.php:891
+#: includes/admin/class.llms.admin.builder.php:893
msgid "%s cannot be deleted via the Course Builder."
msgstr ""
#. Translators: %1$s = Post type singular name; %2$d = Post id.
-#: includes/admin/class.llms.admin.builder.php:915
+#: includes/admin/class.llms.admin.builder.php:917
msgid "Error deleting the %1$s \"%2$d\"."
msgstr ""
-#: includes/admin/class.llms.admin.builder.php:1047
+#: includes/admin/class.llms.admin.builder.php:1049
#: includes/class.llms.l10n.js.php:94
#: includes/class.llms.l10n.js.php:372
#: includes/class.llms.post-types.php:538
@@ -945,27 +942,27 @@ msgid "New Lesson"
msgstr ""
#. Translators: %s = Lesson post id.
-#: includes/admin/class.llms.admin.builder.php:1063
+#: includes/admin/class.llms.admin.builder.php:1065
msgid "Unable to update lesson \"%s\". Invalid lesson ID."
msgstr ""
#. Translators: %s = Question post id.
-#: includes/admin/class.llms.admin.builder.php:1176
+#: includes/admin/class.llms.admin.builder.php:1178
msgid "Unable to update question \"%s\". Invalid question ID."
msgstr ""
#. Translators: %s = Question choice ID.
-#: includes/admin/class.llms.admin.builder.php:1215
+#: includes/admin/class.llms.admin.builder.php:1217
msgid "Unable to update choice \"%s\". Invalid choice ID."
msgstr ""
#. Translators: %s = Quiz post id.
-#: includes/admin/class.llms.admin.builder.php:1276
+#: includes/admin/class.llms.admin.builder.php:1278
msgid "Unable to update quiz \"%s\". Invalid quiz ID."
msgstr ""
#. Translators: %s = Section post id.
-#: includes/admin/class.llms.admin.builder.php:1356
+#: includes/admin/class.llms.admin.builder.php:1358
msgid "Unable to update section \"%s\". Invalid section ID."
msgstr ""
@@ -2504,17 +2501,17 @@ msgstr ""
msgid "Time Available"
msgstr ""
-#: includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php:216
+#: includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php:220
msgid "Drip settings are currently set at the course level, under the Restrictions settings tab. If you would like to set individual drip settings for each lesson, you must disable the course level drip settings first."
msgstr ""
-#: includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php:218
+#: includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php:222
#: includes/class.llms.l10n.js.php:152
#: includes/class.llms.l10n.js.php:362
msgid "Drip settings can be set at the course level to release course content at a specified interval, in the Restrictions settings tab."
msgstr ""
-#: includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php:220
+#: includes/admin/post-types/meta-boxes/class.llms.meta.box.lesson.php:224
#: includes/class.llms.l10n.js.php:151
#: includes/class.llms.l10n.js.php:361
#: includes/class.llms.post-types.php:463
@@ -2860,6 +2857,7 @@ msgstr ""
msgid "Template"
msgstr ""
+#. Translators: %s: Post title.
#: includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php:117
#: includes/admin/post-types/post-tables/class-llms-admin-post-table-awards.php:325
msgid "Delete Permanently"
@@ -4353,6 +4351,7 @@ msgstr ""
msgid "Deletes pending batches generated by LifterLMS background processors."
msgstr ""
+#. Translators: %d = the number of pending batches.
#: includes/admin/tools/class-llms-admin-tool-batch-eraser.php:44
msgid "There is currently %d pending batch that will be deleted."
msgid_plural "There are currently %d pending batches that will be deleted."
@@ -4396,6 +4395,7 @@ msgstr ""
msgid "The method used to determine when a limited-billing recurring order has completed its payment plan changed during version 5.3.0. This tool provides a report of orders which may been affected by this change. %1$sRead more%2$s about this change."
msgstr ""
+#. Translators: %d = the number of pending batches.
#: includes/admin/tools/class-llms-admin-tool-limited-billing-order-locator.php:134
msgid "There is %d order that should be reviewed."
msgid_plural "There are %d orders that should be reviewed."
@@ -4414,6 +4414,7 @@ msgstr ""
msgid "Check active recurring orders to ensure their recurring payment action is properly scheduled for the next payment. If a recurring payment is due and not scheduled it will be rescheduled."
msgstr ""
+#. Translators: %d = the number of pending batches.
#: includes/admin/tools/class-llms-admin-tool-recurring-payment-rescheduler.php:46
msgid "There is %d order that will be checked."
msgid_plural "There are %d orders that will be checked in batches of 50."
diff --git a/lerna.json b/lerna.json
deleted file mode 100644
index a2bb50ba7c..0000000000
--- a/lerna.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "packages": [
- "packages/*"
- ],
- "version": "independent"
-}
diff --git a/libraries/README.md b/libraries/README.md
deleted file mode 100644
index 44ab13842b..0000000000
--- a/libraries/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-External Libraries
-==================
-
-Installation directory for plugin libraries included in the core plugin but developed outside of this repository.
-
-See [Installing for Development](../docs/installing.md) for installation instructions.
diff --git a/libraries/lifterlms-blocks/CHANGELOG.md b/libraries/lifterlms-blocks/CHANGELOG.md
new file mode 100644
index 0000000000..f8ceed52f5
--- /dev/null
+++ b/libraries/lifterlms-blocks/CHANGELOG.md
@@ -0,0 +1,541 @@
+LifterLMS Blocks Changelog
+==========================
+
+v2.5.4 - 2024-04-25
+-------------------
+
+##### Bug Fixes
+
++ Re-adds the Launch Course Builder button to the top of the Course Edit page. [#220](https://github.com/gocodebox/lifterlms-blocks/issues/220)
+
+
+v2.5.3 - 2024-04-17
+-------------------
+
+##### New Features
+
++ Added LifterLMS icon to LifterLMS Blocks block category.
+
+##### Bug Fixes
+
++ Fixed Lesson Progression (Mark Complete) button appearance in editor.
+
+
+v2.5.2 - 2023-11-01
+-------------------
+
+##### Bug Fixes
+
++ Fixed an issue when duplicating a LifterLMS preset form field. [#169](https://github.com/gocodebox/lifterlms-blocks#169)
+
+##### Developer Notes
+
++ Improved compatibility with PHP 8.2.
+
+
+v2.5.1 - 2023-06-13
+-------------------
+
+##### Bug Fixes
+
++ Fixes Launch Course Builder buttons not working when WordPress was installed in a subdirectory.
+
+
+v2.5.0 - 2023-06-06
+-------------------
+
+##### New Features
+
++ Replaced font-based block icons with SVG icons.
+
+##### Updates and Enhancements
+
++ Changes to Launch Course Builder buttons and Course Builder meta-box.
++ Update default icon color to `currentColor`.
++ Updated minimum LifterLMS core version to 7.2.0.
+
+##### Bug Fixes
+
++ Fixed issue when new Course/Membership visibility options were added via JS filter hook. [#190](https://github.com/gocodebox/lifterlms-blocks#190)
+
+##### Developer Notes
+
++ Deprecated LLMS_Blocks_Course_Syllabus_Block class. The Syllabus Block is now implemented in the LifterLMS core plugin.
+
+
+v2.4.3 - 2022-06-09
+-------------------
+
+##### Bug Fixes
+
++ Fixed an issue that prevented editing form confirmation fields when running WordPress 6.0. [#170](https://github.com/gocodebox/lifterlms-blocks#170)
++ Fixed field columns sizing in the block editor.
+
+
+v2.4.2 - 2022-04-07
+-------------------
+
+##### Bug Fixes
+
++ Fixed issue where the User Login form field was shown to logged-in users. [gocodebox/lifterlms#2071](https://github.com/gocodebox/lifterlms#2071)
+
+
+v2.4.1 - 2022-03-30
+-------------------
+
+##### Bug Fixes
+
++ Fixed issue when adding two custom fields of the same type resulting in the first changing its usermeta key. [#160](https://github.com/gocodebox/lifterlms-blocks/issues/160)
+
+
+v2.4.0 - 2022-02-25
+-------------------
+
+##### Updates and Enhancements
+
++ Components added to `window.llms.components` are now aware of components added to the object from other sources.
+
+##### Bug Fixes
+
++ Fixed access to non-existing variable when current user tries to edit course/membership instructors without proper permissions. [#140](https://github.com/gocodebox/lifterlms-blocks#140)
+
+
+v2.3.2 - 2022-02-22
+-------------------
+
+##### Updates and Enhancements
+
++ Added an option to specify a custom checkout form title for free access plans.
+
+
+v2.3.1 - 2022-01-26
+-------------------
+
+##### Updates and Enhancements
+
++ Resolved PHP 8.1 deprecation warnings.
+
+
+v2.3.0 - 2022-01-25
+-------------------
+
+##### New Features
+
++ Added the llms/php-template block, used by the Site Editor to load php templates.
+
+##### Updates and Enhancements
+
++ Adds support for WordPress 5.9.
++ The minimum required WordPress version is now 5.5.
+
+
+v2.2.1 - 2021-09-29
+-------------------
+
++ Bugfix: Fixed deprecated filter warning encountered when using certain development versions of the WordPress core.
+
+
+v2.2.0 - 2021-07-19
+-------------------
+
+##### Updates
+
++ **Increases minimum WordPress Core version requirement to version 5.4!**.
++ Tested and compatible with WordPress core 5.8
++ Don't load block editor assets on the "blockified" widgets screen.
++ Remove timeouts and subscription debouncing used by blocks watcher which handles the `llms/user-info-fields` redux store.
++ Stop debouncing the blocks watcher.
+
+##### Bug fixes
+
++ Confirm group blocks now configure the block's id, name, and match attributes instead of being configured in the block render via the `blocks/form-fields/group-data` module.
++ Don't define the `match` attribute during creation of a user password block.
+
+
+v2.1.1 - 2021-07-08
+-------------------
+
++ Fixed issue causing visibility controls to display for blocks which have no visibility attributes defined.
+
+
+v2.1.0 - 2021-06-28
+-------------------
+
+##### Updates
+
++ Adjusted priority of block editor JS assets to load at priority `5` instead of `999`. Resolves plugin conflicts encountered when using block-level visibility on blocks registered after visibility filters are applied.
++ Removed usage of [react-sortable-hoc](https://github.com/clauderic/react-sortable-hoc) and replaced with [dndkit](https://github.com/clauderic/dnd-kit) for drag and drop UX within the editor.
++ Refactored the instructors sidebar (on courses and memberships) as well as the option shorting (for fields with options) to utilize `dndkit`.
+
+##### Bugfixes
+
++ Fixed an issue encountered on password confirmation fields when adjusting the minimum password length option on the user password block.
+
+
+v2.0.1 - 2021-06-21
+-------------------
+
++ Use non-unique error notice IDs for reusable multiple error notice.
+
+
+v2.0.0 - 2021-06-21
+-------------------
+
+##### Updates
+
++ Adds LifterLMS User Information form building via the block editor.
++ Initially compatibility for WordPress 5.8 (full site editing). Ensures core functionality but doesn't add any exciting features.
++ Improve the visual feedback inside the editor for a block with visibility restrictions.
++ Added reusable block support for form fields.
++ Adds a user information (`[llms-user]`) shortcode inserter to rich text block toolbars.
++ Use rich text `allowedFormats` in favor of deprecated `formattingControls`
++ Improved localization of Javascript files.
+
+##### Bug Fixes
+
++ Fixed issue encountered when using lesson progression blocks outside of a lesson, thanks [@reedhewitt](https://github.com/reedhewitt)!
++ Fixed fatal errors encountered if LifterLMS core isn't active when this plugin is activated.
++ Currently selected instructors are excluded from queries for instructor users.
++ Fixed issue encountered on courses and memberships when attempting to edit instructor information.
+
+##### Backwards Incompatible Changes
+
++ Major refactor of all field-related blocks.
++ The names of many field blocks have changed.
++ Use `getDisallowedBlocks()` in favor of removed `getBlacklist()` in `block-visibility/check`.
++ Blocks restricted to specific posts have had the post object stored on the block attribute reduced to include only the minimum required properties.
++ The `Search`, `SearchPost`, and `SearchUser` components have had major changes to make them more extendable.
++ Don't render InspectorControls since the block doesn't have any actual settings.
+
+
+v2.0.0-rc.2 - 2021-06-18
+------------------------
+
++ Only load the plugin if LifterLMS is loaded
++ Update version checking method.
++ Fixed typo causing errors on WP 5.6 and earlier.
++ Fix WP 5.7 compatibility issues
++ Fixed issue encountered when using lesson progression blocks outside of a lesson, thanks [@reedhewitt](https://github.com/reedhewitt)!
+
+
+v2.0.0-rc.1 - 2021-06-15
+------------------------
+
++ Fixes issue encountered when adding a confirm group
++ Stop using merge codes in the password block
++ Improve block duplication handlers
++ Prevent confirm fields from being manually pasted outside of a confirm group
++ Adds the `llms/user-information-fields` redux store to allow for better field validation and handling
++ Improves and adds field attribute validation
++ Use rich text `allowedFormats` in favor of deprecated `formattingControls`
++ Remove the now unnecessary `uuid` field block attribute.
++ Adds WP core 5.8 compatibility on the widget and customizer screens.
++ Exclude LifterLMS field block reusables from the widgets reusable blocks screen.
++ Adds backwards compatibility for WordPress < 5.6
+
+
+v2.0.0-beta.6 - 2021-06-01
+--------------------------
+
++ (Re-)introduces user information shortcode through a block editor rich text area format button.
++ Prevent usage the "User Login" block on account edit forms (usersnames cannot be edited in WordPress).
++ Only prevent form posts from being made "draft" status on the "core" forms.
++ Modifies field localization data strategy for field validation and others.
+
+
+v2.0.0-beta.5 - 2021-05-18
+--------------------------
+
++ Add WP core 5.8 compatibility for deprecated filter `block_categories`.
++ Fixed issue encountered on courses and memberships when attempting to edit instructor information.
++ Added validation to ensure all fields have unique HTML name attributes.
++ Simplified field data storage interface to enable saving only to the usermeta table.
+
+
+v2.0.0-beta.4 - 2021-05-07
+--------------------------
+
++ Fixed error encountered when opening the block editor options menu on an `llms_form` post type.
++ Added UUID generation to all form field blocks.
++ Fixed visual issues encountered with form field blocks on wide screens in the block editor.
++ Fixed issue preventing column widths from being set after switching from a stacked layout to a columns layout for a field group.
++ Added CSS classes to various option elements in the block editor
++ Moved most inline css in the editor into a static file
++ Fixed issue encountered when reverting a form to it's default
++ Fixed dynamic block rendering errors encountered when the block is restricted to specific courses/memberships.
++ Added CSS to make input placeholder text look like a placeholder
+
+
+v2.0.0-beta.3 - 2021-04-26
+--------------------------
+
++ All form field blocks refactored and many were removed or renamed.
++ Added column support to form field blocks.
++ Added reusable block support to form field blocks.
++ Removed support for block visibility on required field blocks (email and password).
++ Added reusable block filtering to only show "supported" reusable blocks when editing a form.
++ Added utility function support for reusable blocks.
++ Fixed issues related to visual rendering of checkboxes / radio elements on custom fields.
+
+
+v2.0.0-beta.2 - 2021-03-22
+--------------------------
+
++ Fixed block editor visual issues encountered on certain blocks when block-level visibility restrictions are enabled.
+
+
+v2.0.0-beta.1 - 2021-03-22
+--------------------------
+
++ Improved Javascript localization.
++ Updated JS source files to follow (slightly modified) eslint standards as defined by `@wordpress/eslint-plugin/recommended`.
++ Disabled import of incomplete module `./formats/merge-codes`.
++ Improved the information displayed for a restricted block.
++ Don't render `InspectorControls` for the Course Syllabus block since it doesn't have any actual settings to inspect.
++ Improved the Search, SearchPost, and SearchUser components and made backwards incompatible changes to their usage.
+
+
+v1.12.0 - 2021-01-07
+--------------------
+
++ Various form and field updates in preparation for LifterLMS 5.0.0.
+
+
+v1.11.1 - 2021-01-05
+--------------------
+
++ Update the hook used for the Instructors block when displayed on membership post types.
+
+
+v1.11.0 - 2020-12-29
+--------------------
+
++ Allow the "Instructors" block to be used for memberships, thanks [@alaa-alshamy](https://github.com/alaa-alshamy)!
+
+
+v1.10.0 - 2020-11-24
+--------------------
+
++ Use the `LLMS_Assets` class to define, register, and enqueue plugin assets.
++ Added Javascript localization for block editor scripts.
+
+
+v1.9.1 - 2020-04-29
+-------------------
+
++ Fix course progress block template used when migrating a course to the block editor.
+
+
+v1.9.0 - 2020-04-29
+-------------------
+
++ Converted the course progress block into a dynamic block. Fixes an issue allowing the progress block to be visible to non-enrolled students.
++ Added a filter on the output of the Pricing Table block: `llms_blocks_render_pricing_table_block`.
+
+
+v1.8.0 - 2020-04-28
+-------------------
+
+##### Updates
+
++ Improved script dependencies definitions.
++ Updated asset paths for consistency with other LifterLMS projects.
++ Updated various WP Core references that have been deprecated (maintains backwards compatibility).
++ The Lesson Progression block is no longer rendered server-side in the block editor (minor performance improvement).
+
+##### Changes to the Classic Editor Block
+
++ The classic editor block will no longer show block visibility settings because it is impossible to use those settings to filter the block on the frontend.
++ In order to apply visibility settings to the classic editor block, place the Classic Editor within a "Group" block and apply visibility settings to the Group.
+
+##### Bug fixes
+
++ Fixed an issue encountered when using the WP Core "Table" block.
++ Fixed a few areas where `class` was being used instead of `className` to define CSS classes on elements in the block editor.
++ Fixed a user-experience issues encountered on the Course Information block when all possible information is disabled.
++ Fixed an issue causing visibility attributes to render on blocks that don't support them.
++ Fixed an issue preventing 3rd party blocks from modifying default block visibility settings.
++ Fixed a spelling error visible inside the block editor.
++ Fixed an issue causing the "Course Progress" block to be shown to non-enrolled students and visitors.
++ Removed redundant CSS from frontend.
++ Stop outputting editor CSS on the frontend.
++ Dynamic blocks with no content to render will now only output their empty render messages inside the block editor, not on the frontend.
+
+
+v1.7.3 - 2019-12-19
+-------------------
+
++ Move form ready event from domReady to block registration to ensure blocks are exposed before blocks are parsed.
+
+
+v1.7.2 - 2019-12-09
+-------------------
+
++ Bug fix: fix issue causing the block editor to encounter a fatal error when using custom post types that don't support custom fields.
+
+
+v1.7.1 - 2019-12-05
+-------------------
+
++ Bug fix: Fixed a WordPress 5.3 issues with JSON data affecting the ability to save course/membership instructors.
++ Update: Added filter, `llms_block_supports_visibility` to allow modification of the return of the check.
++ Update: Disabled block visibility on registration & account forms to prevent a potentially confusing form creation experience.
++ Update: Added block editor rendering for password type fields.
+
+
+v1.7.0 - 2019-11-08
+-------------------
+
+##### Updates
+
++ Membership post types can now use the LifterLMS Pricing Table block.
++ Membership post types are automatically migrated to the block editor (use the pricing table block instead of the pricing table action).
++ Added a block editor template for the Membership post type.
++ The block 'llms/form-field-redeem-voucher' is now only available on registration forms.
+
+##### Bug Fixes
+
++ Backwards compatibility fixes for WP Core 5.2 and earlier.
++ Perform post migrations on `current_screen` instead of `admin_enqueue_scripts`.
++ Fix an issue causing "No HTML Returned" to be displayed in place of the Lesson Progression block on free lessons when viewed by a logged-out user.
++ Import `InspectorControls` from `wp.blockEditor` and fallback to `wp.editor` to maintain backwards compatibility.
++ Fall back to `wp.editor` for `RichText` import when `wp.blockEditor` is not found.
++ Import from `wp.editor` when `wp.blockEditor` is not available.
++ Return early during renders on WP Core 5.2 and earlier where the `PluginDocumentSettingPanel` doesn't exist.
+
+
+v1.6.0 - 2019-10-24
+-------------------
+
++ Feature: Added form field blocks for use on the Forms manager.
++ Feature: Add logic for `logged_in` and `logged_out` block visibility options.
++ Update: Added isDisabled property to Search component.
++ Update: Adjusted priority of `render_block` filter to 20.
++ Bug fix: Import `InspectorControls` from `wp.blockEditor` in favor of deprecated `wp.editor`
++ Bug fix: Automatically store course/membership instructor with `post_author` data when the post is created.
++ Bug fix: Pass style rules as camelCase.
+
+
+v1.5.2 - 2019-08-14
+-------------------
+
++ Only enable REST for authenticated users with the `lifterlms_instructor` capability.
+
+
+v1.5.1 - 2019-05-17
+-------------------
+
++ Only register block visibility settings on static blocks. Fixes an issue causing core (or 3rd party) dynamic blocks from being managed within the block editor.
+
+
+v1.5.0 - 2019-05-16
+-------------------
+
++ All blocks are now registered only for post types where they can actually be used.
+
+
+v1.4.1 - 2019-05-13
+-------------------
+
++ Fixed double slashes in asset path of CSS and JS files, thanks [@pondermatic](https://github.com/pondermatic)!
+
+
+v1.4.0 - 2019-04-26
+-------------------
+
++ Added an "unmigration" utility to LifterLMS -> Status -> Tools & Utilities which can be used to remove LifterLMS blocks from courses and lessons which were migrated to the block editor structure. This tool is only available when the Classic Editor plugin is installed and enabled and it will remove blocks from ALL courses and lessons regardless of whether or not the block editor is being utilized on that post.
+
+
+v1.3.8 - 2019-03-19
+-------------------
+
++ Explicitly import jQuery when used within blocks.
+
+
+v1.3.7 - 2019-02-27
+-------------------
+
++ Fixed an issue preventing "Pricing Table" blocks from displaying on the admin panel when the current user was enrolled in the course or no payment gateways were enabled on the site.
+
+
+v1.3.6 - 2019-02-22
+-------------------
+
++ Updated the editor icons to use the new LifterLMS Icon
++ Change method for Pricing Table block re-rendering to prevent an issue resulting it always appearing that the post has unsaved data.
+
+
+v1.3.5 - 2019-02-21
+-------------------
+
++ Automatically re-renders Pricing Table blocks when access plans are saved or deleted via the course / membership access plan metabox.
+
+
+v1.3.4 - 2019-01-30
+-------------------
+
++ Add support for the Divi Builder's "Classic Editor" mode
++ Skip post migration when "Classic" mode is enabled
+
+
+v1.3.3 - 2019-01-23
+-------------------
+
++ Add conditions to check for Classic Editor settings configured to enforce classic/block for all posts.
+
+
+v1.3.2 - 2019-01-16
+-------------------
+
++ Fix issue preventing template actions from being removed from migrated courses & lessons.
+
+
+v1.3.1 - 2019-01-15
+-------------------
+
++ Move post migration checks to a callable function `llms_blocks_is_post_migrated()`
+
+
+v1.3.0 - 2019-01-09
+-------------------
+
++ Add course and membership catalog visibility settings into the block editor.
++ Fixed issue preventing the course instructors metabox from displaying when using the classic editor plugin.
+
+v1.2.0 - 2018-12-27
+-------------------
+
++ Add conditional support for page builders: Beaver Builder, Divi Builder, and Elementor.
++ Fixed issue causing LifterLMS core sales pages from outputting automatic content (like pricing tables) on migrated posts.
+
+
+v1.1.2 - 2018-12-17
+-------------------
+
++ Add a filter to the migration check on lessons & courses.
+
+
+v1.1.1 - 2018-12-14
+-------------------
+
++ Fix issue causing LifterLMS Core Actions to be removed when using the Classic Editor plugin.
+
+
+v1.1.0 - 2018-12-12
+-------------------
+
++ Editor blocks now display a lock icon when hovering/selecting a block which corresponds to the enrollment visibility settings of the block.
++ Removal of core actions is now handled by a general migrator function instead of by individual blocks.
++ Fix issue causing block visibility options to not be properly set when enrollment visibility is first enabled for a block.
+
+
+v1.0.1 - 2018-12-05
+-------------------
+
++ Made plugin url relative
+
+
+v1.0.0 - 2018-12-05
+-------------------
+
++ Initial public release
diff --git a/libraries/lifterlms-blocks/assets/css/llms-blocks-rtl.css b/libraries/lifterlms-blocks/assets/css/llms-blocks-rtl.css
new file mode 100644
index 0000000000..fd8209f6eb
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/css/llms-blocks-rtl.css
@@ -0,0 +1 @@
+.llms-cols:after,.llms-cols:before{content:" ";display:table}.llms-cols:after{clear:both}.llms-cols .llms-col{width:100%}@media (min-width:600px){.llms-cols [class*=llms-col-]{float:right}.llms-cols .llms-col-1{width:100%}.llms-cols .llms-col-2{width:50%}.llms-cols .llms-col-3{width:33.3333333333%}.llms-cols .llms-col-4{width:25%}.llms-cols .llms-col-5{width:20%}.llms-cols .llms-col-6{width:16.6666666667%}.llms-cols .llms-col-7{width:14.2857142857%}.llms-cols .llms-col-8{width:12.5%}.llms-cols .llms-col-9{width:11.1111111111%}.llms-cols .llms-col-10{width:10%}.llms-cols .llms-col-11{width:9.0909090909%}.llms-cols .llms-col-12{width:8.3333333333%}.edit-post-visual-editor .editor-block-list__block .editor-block-list__block-edit{padding-right:0;padding-left:0}}.llms-block-visibility{margin-right:auto;margin-left:auto;max-width:840px;position:relative}.llms-block-visibility>:first-child{margin-bottom:28px;margin-top:28px}.llms-block-visibility:before{border:1px solid #e0e0e0;bottom:-6px;content:"";right:-6px;position:absolute;left:-6px;top:-6px}.llms-block-visibility .llms-block-visibility--indicator{border-top:1px solid #e0e0e0;color:#555d66;margin-top:-22px;padding:0 6px}.llms-block-visibility .llms-block-visibility--indicator .dashicon,.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{vertical-align:middle}.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;font-style:italic;line-height:1.4;margin-right:6px}.edit-post-settings-sidebar__panel-block .components-panel__body .llms-search input,.edit-post-sidebar .components-panel__body .llms-search input{box-shadow:none}.llms-search__menu{background:#fff!important;z-index:9999999!important}.llms-search__value-container{width:100%}#wpwrap .edit-post-visual-editor .wp-block-llms-course-information ul{list-style-type:none;margin-right:0;margin-top:.5em}.wp-block-llms-course-progress{display:flex}.wp-block-llms-course-progress .progress-bar{background:#dedede;border-radius:4px;flex:1;margin:10px 0;overflow:hidden}.wp-block-llms-course-progress .progress-bar .progress--fill{background:#2295ff;height:100%;width:50%}.wp-block-llms-course-progress span{padding-right:5px;vertical-align:middle}.llms-author .label,.llms-author .name{margin-right:5px}.llms-author .avatar{border-radius:50%}.llms-author .bio{margin-top:5px}.llms-instructor-info .llms-instructors .llms-col:first-child .llms-author{margin-right:0}.llms-instructor-info .llms-instructors .llms-col:last-child .llms-author{margin-left:0}.llms-instructor-info .llms-instructors .llms-author{background:#f5f5f5;border-top:4px solid #2295ff;margin:45px 5px 5px;padding:0 10px 10px;text-align:center}.llms-instructor-info .llms-instructors .llms-author .avatar{background:#2295ff;border:4px solid #2295ff;display:block;margin:-35px auto 10px}.llms-instructor-info .llms-instructors .llms-author .llms-author-info{display:block}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.name{font-weight:700}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.label{font-size:85%}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.bio{font-size:90%;margin-bottom:0}.wp-block[data-type="llms/lesson-progression"]{text-align:center}.wp-block[data-type="llms/lesson-progression"] button{margin:0 2px}.llms-access-plans:after,.llms-access-plans:before{content:" ";display:table}.llms-access-plans:after{clear:both}@media (min-width:600px){.llms-access-plans.cols-1 .llms-access-plan{width:100%}.llms-access-plans.cols-2 .llms-access-plan{width:50%}.llms-access-plans.cols-3 .llms-access-plan{width:33.3333333333%}.llms-access-plans.cols-4 .llms-access-plan{width:25%}.llms-access-plans.cols-5 .llms-access-plan{width:20%}}.llms-free-enroll-form{margin-bottom:0}.llms-access-plan{box-sizing:border-box;float:right;text-align:center;width:100%}.llms-access-plan .llms-access-plan-content,.llms-access-plan .llms-access-plan-footer{background:#f1f1f1}.llms-access-plan.featured .llms-access-plan-featured{background:#4ba9ff}.llms-access-plan.featured .llms-access-plan-content,.llms-access-plan.featured .llms-access-plan-footer{border-right:3px solid #2295ff;border-left:3px solid #2295ff}.llms-access-plan.featured .llms-access-plan-footer{border-bottom-color:#2295ff}.llms-access-plan.on-sale .price-regular{text-decoration:line-through}.llms-access-plan .stamp{background:#2295ff;color:#fff;font-size:11px;font-style:normal;font-weight:300;padding:2px 3px;vertical-align:top}.llms-access-plan .llms-access-plan-restrictions ul{margin:0}.llms-access-plan-featured{color:#fff;font-size:14px;font-weight:400;margin:0 2px}.llms-access-plan-content{margin:0 2px}.llms-access-plan-content .llms-access-plan-pricing{padding:10px 0 0}.llms-access-plan-title{background:#2295ff;color:#fff;margin-bottom:0;padding:10px}.llms-access-plan-pricing .llms-price-currency-symbol{font-size:14px;vertical-align:top}.llms-access-plan-price{font-size:18px;font-variant:small-caps;line-height:20px}.llms-access-plan-price .lifterlms-price{font-weight:700}.llms-access-plan-price.sale{border-bottom:1px solid #d0d0d0;border-top:1px solid #d0d0d0;padding:5px 0}.llms-access-plan-expiration,.llms-access-plan-sale-end,.llms-access-plan-schedule,.llms-access-plan-trial{font-size:15px;font-variant:small-caps;line-height:1.2}.llms-access-plan-description{font-size:16px;padding:10px 10px 0}.llms-access-plan-description ul{margin:0}.llms-access-plan-description ul li{border-bottom:1px solid #d0d0d0;list-style-type:none}.llms-access-plan-description ul li:last-child{border-bottom:none}.llms-access-plan-description div:last-child,.llms-access-plan-description img:last-child,.llms-access-plan-description li:last-child,.llms-access-plan-description p:last-child,.llms-access-plan-description ul:last-child{margin-bottom:0}.llms-access-plan-restrictions .stamp{vertical-align:baseline}.llms-access-plan-restrictions ul{margin:0}.llms-access-plan-restrictions ul li{font-size:12px;line-height:14px;list-style-type:none}.llms-access-plan-restrictions a{color:#f8954f}.llms-access-plan-restrictions a:hover{color:#f67d28}.llms-access-plan-footer{border-bottom:3px solid #f1f1f1;margin:0 2px 2px;padding:10px}.llms-access-plan-footer .llms-access-plan-pricing{padding:0 0 10px}.llms-invalid-control{margin-bottom:24px}.llms-invalid-control .components-base-control{margin-bottom:0}.llms-invalid-control .components-base-control .components-text-control__input{background-color:rgba(204,24,24,.05);border-color:#cc1818}.llms-invalid-control .llms-invalid-control--msg{background-color:rgba(204,24,24,.05);border-right:4px solid #cc1818;color:#cc1818;font-size:12px;font-style:italic;margin-bottom:0;padding:6px 8px 6px 2px}.llms-pwd-meter{border:1px solid #e35b5b;border-radius:4px;margin-top:5px;overflow:hidden}.llms-pwd-meter>div{background:rgba(227,91,91,.25);font-size:75%;padding:0 5px;width:25%}.llms-field-group .block-editor-block-list__layout *{box-sizing:border-box}.llms-fields input,.llms-fields textarea{border:1px solid #999;color:#757575;padding:4px 8px}.llms-fields input:focus::-moz-placeholder,.llms-fields textarea:focus::-moz-placeholder{opacity:0}.llms-fields input:focus:-ms-input-placeholder,.llms-fields textarea:focus:-ms-input-placeholder{opacity:0}.llms-fields input:focus::placeholder,.llms-fields textarea:focus::placeholder{opacity:0}.llms-fields input::-moz-placeholder,.llms-fields textarea::-moz-placeholder{color:#757575}.llms-fields input:-ms-input-placeholder,.llms-fields textarea:-ms-input-placeholder{color:#757575}.llms-fields input::placeholder,.llms-fields textarea::placeholder{color:#757575}.llms-fields input:not([type=radio]):not([type=checkbox]),.llms-fields select,.llms-fields textarea{width:100%}.llms-fields input:not([type=radio]){border-radius:4px}.llms-fields textarea{resize:none}.llms-fields select{max-Width:none;pointer-events:none}.llms-fields .llms-field .block-editor-rich-text__editable{display:block}.llms-fields .llms-field label.llms-is-required>div{display:inline}.llms-fields .llms-field label.llms-is-required:after{color:#dc5757;content:" *"}.llms-field-option{align-items:top;display:flex;margin-bottom:4px}.llms-field-option.llms-sort-helper{background:#fff;border:1px solid #dedede;height:auto!important;padding:5px 10px;z-index:999}.llms-field-option .llms-field-opt-default{margin-top:6px}.llms-field-option .llms-field-opt-default .components-radio-control__input{margin-left:0}.llms-field-option .llms-field-opt-default,.llms-field-option .llms-field-opt-text,.llms-field-option .llms-field-opt-text .components-base-control__field{margin-bottom:0!important}.llms-field-option .llms-field-opt-db-key{display:flex;margin-top:2px}.llms-field-option .llms-field-opt-db-key .dashicon{color:#5a5a5a;margin-top:5px}.llms-field-option .llms-field-opt-db-key .components-text-control__input{background:#f5f5f5;font-family:monospace}.llms-field-option .llms-drag-handle{cursor:-webkit-grab;cursor:grab;flex:.8;margin-top:3px;padding-top:6px}.llms-field-option .llms-del-field-opt-wrap,.llms-field-option .llms-field-opt-default-wrap{flex:1;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.llms-field-option .llms-del-field-opt-wrap{margin-right:4px}.llms-field-option .llms-del-field-opt-wrap button{margin-top:3px}.llms-field-option .llms-del-field-opt-wrap button:hover,.llms-field-option .llms-del-field-opt-wrap button[aria-expanded=true]{color:#cc1818}.llms-field-option .llms-field-opt-text-wrap{flex:7}.llms-field-options--footer{margin-top:10px}.llms-cols-12 .llms-field{width:100%}.llms-cols-9 .llms-field{width:75%}.llms-cols-8 .llms-field{width:66.66%}.llms-cols-6 .llms-field{width:50%}.llms-cols-4 .llms-field{width:33.33%}.llms-cols-3 .llms-field{width:25%}.llms-field-group[data-field-layout=columns] .llms-cols-12,.llms-field-group[data-field-layout=columns] [class*=llms-cols-] .llms-field{width:100%}.llms-field-group[data-field-layout=columns] .llms-cols-9{width:75%}.llms-field-group[data-field-layout=columns] .llms-cols-8{width:66.66%}.llms-field-group[data-field-layout=columns] .llms-cols-6{width:50%}.llms-field-group[data-field-layout=columns] .llms-cols-4{width:33.33%}.llms-field-group[data-field-layout=columns] .llms-cols-3{width:25%}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields{display:inline-block}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(odd){padding-left:28px}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(2n){padding-right:28px}.llms-shortcodes-modal{width:800px}.llms-shortcodes-modal .llms-shortcodes-modal--main{display:flex}.llms-shortcodes-modal .llms-shortcodes-modal--main aside{flex:1;padding-left:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main section{flex:2;padding-right:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr td,.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr th{text-align:right}.llms-instructor{border:1px solid #dedede;margin-bottom:-1px;padding:10px;position:relative;z-index:100}.llms-instructor .llms-instructor--header{align-items:center;display:flex}.llms-instructor .llms-instructor--header section{flex:2}.llms-instructor .llms-instructor--header section small{margin-right:3px}.llms-instructor .llms-instructor--header aside{flex:1;text-align:left}.llms-instructor .llms-instructor--header .components-button.is-small.has-icon:not(.has-text){min-width:24px;padding:0}.llms-instructor .llms-instructor--header .dashicons-star-filled{color:#ffb900;margin:2px 0 0 2px}.llms-instructor.llms-is-dragging{background:#fff;border:1px solid #dedede;box-shadow:0 4px 8px 2px #dedede;z-index:999}.llms-instructor .llms-instructor--settings{margin-top:10px}.edit-post-post-status{display:flex;flex-direction:column;flex-wrap:wrap}.edit-post-post-status h2{order:-2}.llms-launch-course-builder{margin:0 0 16px;order:-1}.llms-primary-background-color{background-color:#466dd8!important;color:#fff!important}.llms-primary-background-color.clicked,.llms-primary-background-color:hover{background-color:#2b55cb!important}.llms-course-builder-panel--close .components-panel__body-title{margin-bottom:-16px!important}.llms-course-builder-panel--close .components-panel__arrow{transform:rotate(-180deg) translateY(50%)!important}#side-sortables #course_builder,.llms-course-builder-panel--close>div{display:none}.post-type-course .llms-button-primary,.post-type-lesson .llms-button-primary,.post-type-llms_membership .llms-button-primary{-webkit-appearance:none;border-radius:2px;display:inline-flex;font-size:14px;font-weight:400;height:38px;justify-content:center}.llms-launch-course-builder .llms-button-primary{width:100%}
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/assets/css/llms-blocks.css b/libraries/lifterlms-blocks/assets/css/llms-blocks.css
new file mode 100644
index 0000000000..2a0b8938c3
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/css/llms-blocks.css
@@ -0,0 +1 @@
+.llms-cols:after,.llms-cols:before{content:" ";display:table}.llms-cols:after{clear:both}.llms-cols .llms-col{width:100%}@media (min-width:600px){.llms-cols [class*=llms-col-]{float:left}.llms-cols .llms-col-1{width:100%}.llms-cols .llms-col-2{width:50%}.llms-cols .llms-col-3{width:33.3333333333%}.llms-cols .llms-col-4{width:25%}.llms-cols .llms-col-5{width:20%}.llms-cols .llms-col-6{width:16.6666666667%}.llms-cols .llms-col-7{width:14.2857142857%}.llms-cols .llms-col-8{width:12.5%}.llms-cols .llms-col-9{width:11.1111111111%}.llms-cols .llms-col-10{width:10%}.llms-cols .llms-col-11{width:9.0909090909%}.llms-cols .llms-col-12{width:8.3333333333%}.edit-post-visual-editor .editor-block-list__block .editor-block-list__block-edit{padding-left:0;padding-right:0}}.llms-block-visibility{margin-left:auto;margin-right:auto;max-width:840px;position:relative}.llms-block-visibility>:first-child{margin-bottom:28px;margin-top:28px}.llms-block-visibility:before{border:1px solid #e0e0e0;bottom:-6px;content:"";left:-6px;position:absolute;right:-6px;top:-6px}.llms-block-visibility .llms-block-visibility--indicator{border-top:1px solid #e0e0e0;color:#555d66;margin-top:-22px;padding:0 6px}.llms-block-visibility .llms-block-visibility--indicator .dashicon,.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{vertical-align:middle}.llms-block-visibility .llms-block-visibility--indicator .llms-block-visibility--msg{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif;font-size:13px;font-style:italic;line-height:1.4;margin-left:6px}.edit-post-settings-sidebar__panel-block .components-panel__body .llms-search input,.edit-post-sidebar .components-panel__body .llms-search input{box-shadow:none}.llms-search__menu{background:#fff!important;z-index:9999999!important}.llms-search__value-container{width:100%}#wpwrap .edit-post-visual-editor .wp-block-llms-course-information ul{list-style-type:none;margin-left:0;margin-top:.5em}.wp-block-llms-course-progress{display:flex}.wp-block-llms-course-progress .progress-bar{background:#dedede;border-radius:4px;flex:1;margin:10px 0;overflow:hidden}.wp-block-llms-course-progress .progress-bar .progress--fill{background:#2295ff;height:100%;width:50%}.wp-block-llms-course-progress span{padding-left:5px;vertical-align:middle}.llms-author .label,.llms-author .name{margin-left:5px}.llms-author .avatar{border-radius:50%}.llms-author .bio{margin-top:5px}.llms-instructor-info .llms-instructors .llms-col:first-child .llms-author{margin-left:0}.llms-instructor-info .llms-instructors .llms-col:last-child .llms-author{margin-right:0}.llms-instructor-info .llms-instructors .llms-author{background:#f5f5f5;border-top:4px solid #2295ff;margin:45px 5px 5px;padding:0 10px 10px;text-align:center}.llms-instructor-info .llms-instructors .llms-author .avatar{background:#2295ff;border:4px solid #2295ff;display:block;margin:-35px auto 10px}.llms-instructor-info .llms-instructors .llms-author .llms-author-info{display:block}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.name{font-weight:700}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.label{font-size:85%}.llms-instructor-info .llms-instructors .llms-author .llms-author-info.bio{font-size:90%;margin-bottom:0}.wp-block[data-type="llms/lesson-progression"]{text-align:center}.wp-block[data-type="llms/lesson-progression"] button{margin:0 2px}.llms-access-plans:after,.llms-access-plans:before{content:" ";display:table}.llms-access-plans:after{clear:both}@media (min-width:600px){.llms-access-plans.cols-1 .llms-access-plan{width:100%}.llms-access-plans.cols-2 .llms-access-plan{width:50%}.llms-access-plans.cols-3 .llms-access-plan{width:33.3333333333%}.llms-access-plans.cols-4 .llms-access-plan{width:25%}.llms-access-plans.cols-5 .llms-access-plan{width:20%}}.llms-free-enroll-form{margin-bottom:0}.llms-access-plan{box-sizing:border-box;float:left;text-align:center;width:100%}.llms-access-plan .llms-access-plan-content,.llms-access-plan .llms-access-plan-footer{background:#f1f1f1}.llms-access-plan.featured .llms-access-plan-featured{background:#4ba9ff}.llms-access-plan.featured .llms-access-plan-content,.llms-access-plan.featured .llms-access-plan-footer{border-left:3px solid #2295ff;border-right:3px solid #2295ff}.llms-access-plan.featured .llms-access-plan-footer{border-bottom-color:#2295ff}.llms-access-plan.on-sale .price-regular{text-decoration:line-through}.llms-access-plan .stamp{background:#2295ff;color:#fff;font-size:11px;font-style:normal;font-weight:300;padding:2px 3px;vertical-align:top}.llms-access-plan .llms-access-plan-restrictions ul{margin:0}.llms-access-plan-featured{color:#fff;font-size:14px;font-weight:400;margin:0 2px}.llms-access-plan-content{margin:0 2px}.llms-access-plan-content .llms-access-plan-pricing{padding:10px 0 0}.llms-access-plan-title{background:#2295ff;color:#fff;margin-bottom:0;padding:10px}.llms-access-plan-pricing .llms-price-currency-symbol{font-size:14px;vertical-align:top}.llms-access-plan-price{font-size:18px;font-variant:small-caps;line-height:20px}.llms-access-plan-price .lifterlms-price{font-weight:700}.llms-access-plan-price.sale{border-bottom:1px solid #d0d0d0;border-top:1px solid #d0d0d0;padding:5px 0}.llms-access-plan-expiration,.llms-access-plan-sale-end,.llms-access-plan-schedule,.llms-access-plan-trial{font-size:15px;font-variant:small-caps;line-height:1.2}.llms-access-plan-description{font-size:16px;padding:10px 10px 0}.llms-access-plan-description ul{margin:0}.llms-access-plan-description ul li{border-bottom:1px solid #d0d0d0;list-style-type:none}.llms-access-plan-description ul li:last-child{border-bottom:none}.llms-access-plan-description div:last-child,.llms-access-plan-description img:last-child,.llms-access-plan-description li:last-child,.llms-access-plan-description p:last-child,.llms-access-plan-description ul:last-child{margin-bottom:0}.llms-access-plan-restrictions .stamp{vertical-align:baseline}.llms-access-plan-restrictions ul{margin:0}.llms-access-plan-restrictions ul li{font-size:12px;line-height:14px;list-style-type:none}.llms-access-plan-restrictions a{color:#f8954f}.llms-access-plan-restrictions a:hover{color:#f67d28}.llms-access-plan-footer{border-bottom:3px solid #f1f1f1;margin:0 2px 2px;padding:10px}.llms-access-plan-footer .llms-access-plan-pricing{padding:0 0 10px}.llms-invalid-control{margin-bottom:24px}.llms-invalid-control .components-base-control{margin-bottom:0}.llms-invalid-control .components-base-control .components-text-control__input{background-color:rgba(204,24,24,.05);border-color:#cc1818}.llms-invalid-control .llms-invalid-control--msg{background-color:rgba(204,24,24,.05);border-left:4px solid #cc1818;color:#cc1818;font-size:12px;font-style:italic;margin-bottom:0;padding:6px 2px 6px 8px}.llms-pwd-meter{border:1px solid #e35b5b;border-radius:4px;margin-top:5px;overflow:hidden}.llms-pwd-meter>div{background:rgba(227,91,91,.25);font-size:75%;padding:0 5px;width:25%}.llms-field-group .block-editor-block-list__layout *{box-sizing:border-box}.llms-fields input,.llms-fields textarea{border:1px solid #999;color:#757575;padding:4px 8px}.llms-fields input:focus::-moz-placeholder,.llms-fields textarea:focus::-moz-placeholder{opacity:0}.llms-fields input:focus:-ms-input-placeholder,.llms-fields textarea:focus:-ms-input-placeholder{opacity:0}.llms-fields input:focus::placeholder,.llms-fields textarea:focus::placeholder{opacity:0}.llms-fields input::-moz-placeholder,.llms-fields textarea::-moz-placeholder{color:#757575}.llms-fields input:-ms-input-placeholder,.llms-fields textarea:-ms-input-placeholder{color:#757575}.llms-fields input::placeholder,.llms-fields textarea::placeholder{color:#757575}.llms-fields input:not([type=radio]):not([type=checkbox]),.llms-fields select,.llms-fields textarea{width:100%}.llms-fields input:not([type=radio]){border-radius:4px}.llms-fields textarea{resize:none}.llms-fields select{max-Width:none;pointer-events:none}.llms-fields .llms-field .block-editor-rich-text__editable{display:block}.llms-fields .llms-field label.llms-is-required>div{display:inline}.llms-fields .llms-field label.llms-is-required:after{color:#dc5757;content:" *"}.llms-field-option{align-items:top;display:flex;margin-bottom:4px}.llms-field-option.llms-sort-helper{background:#fff;border:1px solid #dedede;height:auto!important;padding:5px 10px;z-index:999}.llms-field-option .llms-field-opt-default{margin-top:6px}.llms-field-option .llms-field-opt-default .components-radio-control__input{margin-right:0}.llms-field-option .llms-field-opt-default,.llms-field-option .llms-field-opt-text,.llms-field-option .llms-field-opt-text .components-base-control__field{margin-bottom:0!important}.llms-field-option .llms-field-opt-db-key{display:flex;margin-top:2px}.llms-field-option .llms-field-opt-db-key .dashicon{color:#5a5a5a;margin-top:5px}.llms-field-option .llms-field-opt-db-key .components-text-control__input{background:#f5f5f5;font-family:monospace}.llms-field-option .llms-drag-handle{cursor:-webkit-grab;cursor:grab;flex:.8;margin-top:3px;padding-top:6px}.llms-field-option .llms-del-field-opt-wrap,.llms-field-option .llms-field-opt-default-wrap{flex:1;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content}.llms-field-option .llms-del-field-opt-wrap{margin-left:4px}.llms-field-option .llms-del-field-opt-wrap button{margin-top:3px}.llms-field-option .llms-del-field-opt-wrap button:hover,.llms-field-option .llms-del-field-opt-wrap button[aria-expanded=true]{color:#cc1818}.llms-field-option .llms-field-opt-text-wrap{flex:7}.llms-field-options--footer{margin-top:10px}.llms-cols-12 .llms-field{width:100%}.llms-cols-9 .llms-field{width:75%}.llms-cols-8 .llms-field{width:66.66%}.llms-cols-6 .llms-field{width:50%}.llms-cols-4 .llms-field{width:33.33%}.llms-cols-3 .llms-field{width:25%}.llms-field-group[data-field-layout=columns] .llms-cols-12,.llms-field-group[data-field-layout=columns] [class*=llms-cols-] .llms-field{width:100%}.llms-field-group[data-field-layout=columns] .llms-cols-9{width:75%}.llms-field-group[data-field-layout=columns] .llms-cols-8{width:66.66%}.llms-field-group[data-field-layout=columns] .llms-cols-6{width:50%}.llms-field-group[data-field-layout=columns] .llms-cols-4{width:33.33%}.llms-field-group[data-field-layout=columns] .llms-cols-3{width:25%}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields{display:inline-block}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(odd){padding-right:28px}.llms-field-group[data-field-layout=columns] .block-editor-block-list__layout>.wp-block.llms-fields:nth-child(2n){padding-left:28px}.llms-shortcodes-modal{width:800px}.llms-shortcodes-modal .llms-shortcodes-modal--main{display:flex}.llms-shortcodes-modal .llms-shortcodes-modal--main aside{flex:1;padding-right:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main section{flex:2;padding-left:16px}.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr td,.llms-shortcodes-modal .llms-shortcodes-modal--main .llms-table tr th{text-align:left}.llms-instructor{border:1px solid #dedede;margin-bottom:-1px;padding:10px;position:relative;z-index:100}.llms-instructor .llms-instructor--header{align-items:center;display:flex}.llms-instructor .llms-instructor--header section{flex:2}.llms-instructor .llms-instructor--header section small{margin-left:3px}.llms-instructor .llms-instructor--header aside{flex:1;text-align:right}.llms-instructor .llms-instructor--header .components-button.is-small.has-icon:not(.has-text){min-width:24px;padding:0}.llms-instructor .llms-instructor--header .dashicons-star-filled{color:#ffb900;margin:2px 2px 0 0}.llms-instructor.llms-is-dragging{background:#fff;border:1px solid #dedede;box-shadow:0 4px 8px 2px #dedede;z-index:999}.llms-instructor .llms-instructor--settings{margin-top:10px}.edit-post-post-status{display:flex;flex-direction:column;flex-wrap:wrap}.edit-post-post-status h2{order:-2}.llms-launch-course-builder{margin:0 0 16px;order:-1}.llms-primary-background-color{background-color:#466dd8!important;color:#fff!important}.llms-primary-background-color.clicked,.llms-primary-background-color:hover{background-color:#2b55cb!important}.llms-course-builder-panel--close .components-panel__body-title{margin-bottom:-16px!important}.llms-course-builder-panel--close .components-panel__arrow{transform:rotate(180deg) translateY(50%)!important}#side-sortables #course_builder,.llms-course-builder-panel--close>div{display:none}.post-type-course .llms-button-primary,.post-type-lesson .llms-button-primary,.post-type-llms_membership .llms-button-primary{-webkit-appearance:none;border-radius:2px;display:inline-flex;font-size:14px;font-weight:400;height:38px;justify-content:center}.llms-launch-course-builder .llms-button-primary{width:100%}
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.asset.php b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.asset.php
new file mode 100644
index 0000000000..7e1565e685
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.asset.php
@@ -0,0 +1 @@
+ array('lodash', 'wp-polyfill', 'wp-redux-routine'), 'version' => '53993548b9007dfafb6c');
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.js b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.js
new file mode 100644
index 0000000000..141927a3d0
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks-backwards-compat.js
@@ -0,0 +1 @@
+(()=>{var e={909:e=>{"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t(e)}function r(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:this;this._map.forEach((function(o,i){null!==i&&"object"===t(i)&&(o=o[1]),e.call(n,o,i,r)}))}},{key:"clear",value:function(){this._map=new Map,this._arrayTreeMap=new Map,this._objectTreeMap=new Map}},{key:"size",get:function(){return this._map.size}}],i&&r(o.prototype,i),e}();e.exports=o},884:e=>{e.exports=function(e){var t,r=Object.keys(e);return t=function(){var e,t,n;for(e="return {",t=0;t{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{"use strict";var e={};r.r(e),r.d(e,{getCachedResolvers:()=>U,getIsResolving:()=>N,hasFinishedResolution:()=>j,hasStartedResolution:()=>A,isResolving:()=>L});var t={};function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;tk,finishResolutions:()=>x,invalidateResolution:()=>V,invalidateResolutionForStore:()=>C,invalidateResolutionForStoreSelector:()=>D,startResolution:()=>P,startResolutions:()=>F});var c="function"==typeof Symbol&&Symbol.observable||"@@observable",u=function(){return Math.random().toString(36).substring(7).split("").join(".")},a={INIT:"@@redux/INIT"+u(),REPLACE:"@@redux/REPLACE"+u(),PROBE_UNKNOWN_ACTION:function(){return"@@redux/PROBE_UNKNOWN_ACTION"+u()}};function l(e){if("object"!=typeof e||null===e)return!1;for(var t=e;null!==Object.getPrototypeOf(t);)t=Object.getPrototypeOf(t);return Object.getPrototypeOf(e)===t}function f(e,t,r){var n;if("function"==typeof t&&"function"==typeof r||"function"==typeof r&&"function"==typeof arguments[3])throw new Error(s(0));if("function"==typeof t&&void 0===r&&(r=t,t=void 0),void 0!==r){if("function"!=typeof r)throw new Error(s(1));return r(f)(e,t)}if("function"!=typeof e)throw new Error(s(2));var o=e,i=t,u=[],p=u,d=!1;function h(){p===u&&(p=u.slice())}function g(){if(d)throw new Error(s(3));return i}function y(e){if("function"!=typeof e)throw new Error(s(4));if(d)throw new Error(s(5));var t=!0;return h(),p.push(e),function(){if(t){if(d)throw new Error(s(6));t=!1,h();var r=p.indexOf(e);p.splice(r,1),u=null}}}function v(e){if(!l(e))throw new Error(s(7));if(void 0===e.type)throw new Error(s(8));if(d)throw new Error(s(9));try{d=!0,i=o(i,e)}finally{d=!1}for(var t=u=p,r=0;r({storeKey:t,selectorName:r,args:n})=>e.select(t)[r](...n))),"@@data/RESOLVE_SELECT":_((e=>({storeKey:t,selectorName:r,args:n})=>{const o=e.select(t)[r].hasResolver?"resolveSelect":"select";return e[o](t)[r](...n)})),"@@data/DISPATCH":_((e=>({storeKey:t,actionName:r,args:n})=>e.dispatch(t)[r](...n)))},w=()=>e=>t=>{return!(r=t)||"object"!=typeof r&&"function"!=typeof r||"function"!=typeof r.then?e(t):t.then((t=>{if(t)return e(t)}));var r},E="core/data",m=(e,t)=>()=>r=>n=>{const o=e.select(E).getCachedResolvers(t);return Object.entries(o).forEach((([r,o])=>{const i=(0,h.get)(e.stores,[t,"resolvers",r]);i&&i.shouldInvalidate&&o.forEach(((o,s)=>{!1===o&&i.shouldInvalidate(n,...s)&&e.dispatch(E).invalidateResolution(t,r,s)}))})),r(n)},T=("selectorName",e=>(t={},r)=>{const n=r.selectorName;if(void 0===n)return t;const o=e(t[n],r);return o===t[n]?t:{...t,[n]:o}})(((e=new(S()),t)=>{switch(t.type){case"START_RESOLUTION":case"FINISH_RESOLUTION":{const r="START_RESOLUTION"===t.type,n=new(S())(e);return n.set(t.args,r),n}case"START_RESOLUTIONS":case"FINISH_RESOLUTIONS":{const r="START_RESOLUTIONS"===t.type,n=new(S())(e);for(const e of t.args)n.set(e,r);return n}case"INVALIDATE_RESOLUTION":{const r=new(S())(e);return r.delete(t.args),r}}return e}));const I=(e={},t)=>{switch(t.type){case"INVALIDATE_RESOLUTION_FOR_STORE":return{};case"INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR":return(0,h.has)(e,[t.selectorName])?(0,h.omit)(e,[t.selectorName]):e;case"START_RESOLUTION":case"FINISH_RESOLUTION":case"START_RESOLUTIONS":case"FINISH_RESOLUTIONS":case"INVALIDATE_RESOLUTION":return T(e,t)}return e};function N(e,t,r){const n=(0,h.get)(e,[t]);if(n)return n.get(r)}function A(e,t,r=[]){return void 0!==N(e,t,r)}function j(e,t,r=[]){return!1===N(e,t,r)}function L(e,t,r=[]){return!0===N(e,t,r)}function U(e){return e}function P(e,t){return{type:"START_RESOLUTION",selectorName:e,args:t}}function k(e,t){return{type:"FINISH_RESOLUTION",selectorName:e,args:t}}function F(e,t){return{type:"START_RESOLUTIONS",selectorName:e,args:t}}function x(e,t){return{type:"FINISH_RESOLUTIONS",selectorName:e,args:t}}function V(e,t){return{type:"INVALIDATE_RESOLUTION",selectorName:e,args:t}}function C(){return{type:"INVALIDATE_RESOLUTION_FOR_STORE"}}function D(e){return{type:"INVALIDATE_RESOLUTION_FOR_STORE_SELECTOR",selectorName:e}}function M(r,n){return{name:r,instantiate:o=>{const i=n.reducer,s=function(e,t,r,n){const o={...t.controls,...R},i=(0,h.mapValues)(o,(e=>e.isRegistryControl?e(r):e)),s=[m(r,e),w,b()(i)];var c;t.__experimentalUseThunks&&s.push((c=n,()=>e=>t=>"function"==typeof t?t(c):e(t)));const u=[d(...s)];"undefined"!=typeof window&&window.__REDUX_DEVTOOLS_EXTENSION__&&u.push(window.__REDUX_DEVTOOLS_EXTENSION__({name:e,instanceId:e}));const{reducer:a,initialState:l}=t;return f(y()({metadata:I,root:a}),{root:l},(0,h.flowRight)(u))}(r,n,o,{registry:o,get dispatch(){return Object.assign((e=>s.dispatch(e)),v())},get select(){return Object.assign((e=>e(s.__unstableOriginalGetState())),g())},get resolveSelect(){return O()}}),c=function(){const e={};return{isRunning:(t,r)=>e[t]&&e[t].get(r),clear(t,r){e[t]&&e[t].delete(r)},markAsRunning(t,r){e[t]||(e[t]=new(S())),e[t].set(r,!0)}}}();let u;const a=function(e,t){return(0,h.mapValues)(e,(e=>(...r)=>Promise.resolve(t.dispatch(e(...r)))))}({...t,...n.actions},s);let l=function(e,t){return(0,h.mapValues)(e,(e=>{const r=function(){const r=arguments.length,n=new Array(r+1);n[0]=t.__unstableOriginalGetState();for(let e=0;e(t,...r)=>e(t.metadata,...r))),...(0,h.mapValues)(n.selectors,(e=>(e.isRegistrySelector&&(e.registry=o),(t,...r)=>e(t.root,...r))))},s);if(n.resolvers){const e=function(e,t,r,n){const o=(0,h.mapValues)(e,(e=>e.fulfill?e:{...e,fulfill:e}));return{resolvers:o,selectors:(0,h.mapValues)(t,((t,i)=>{const s=e[i];if(!s)return t.hasResolver=!1,t;const c=(...e)=>(async function(){const t=r.getState();if(n.isRunning(i,e)||"function"==typeof s.isFulfilled&&s.isFulfilled(t,...e))return;const{metadata:c}=r.__unstableOriginalGetState();A(c,i,e)||(n.markAsRunning(i,e),setTimeout((async()=>{n.clear(i,e),r.dispatch(P(i,e)),await async function(e,t,r,...n){const o=(0,h.get)(t,[r]);if(!o)return;const i=o.fulfill(...n);i&&await e.dispatch(i)}(r,o,i,...e),r.dispatch(k(i,e))})))}(...e),t(...e));return c.hasResolver=!0,c}))}}(n.resolvers,l,s,c);u=e.resolvers,l=e.selectors}const p=function(e,t){return(0,h.mapValues)((0,h.omit)(e,["getIsResolving","hasStartedResolution","hasFinishedResolution","isResolving","getCachedResolvers"]),((r,n)=>(...o)=>new Promise((i=>{const s=()=>e.hasFinishedResolution(n,o),c=()=>r.apply(null,o),u=c();if(s())return i(u);const a=t.subscribe((()=>{s()&&(a(),i(c()))}))}))))}(l,s),g=()=>l,v=()=>a,O=()=>p;s.__unstableOriginalGetState=s.getState,s.getState=()=>s.__unstableOriginalGetState().root;const _=s&&(e=>{let t=s.__unstableOriginalGetState();return s.subscribe((()=>{const r=s.__unstableOriginalGetState(),n=r!==t;t=r,n&&e()}))});return{reducer:i,store:s,actions:a,selectors:l,resolvers:u,getSelectors:g,getResolveSelectors:O,getActions:v,subscribe:_}}}}const G=function(e={},t=null){const r={};let n=[];const o=new Set;function i(){n.forEach((e=>e()))}const s=e=>(n.push(e),()=>{n=(0,h.without)(n,e)});function c(e,t){if("function"!=typeof t.getSelectors)throw new TypeError("config.getSelectors must be a function");if("function"!=typeof t.getActions)throw new TypeError("config.getActions must be a function");if("function"!=typeof t.subscribe)throw new TypeError("config.subscribe must be a function");r[e]=t,t.subscribe(i)}let u={registerGenericStore:c,stores:r,namespaces:r,subscribe:s,select:function(e){const n=(0,h.isObject)(e)?e.name:e;o.add(n);const i=r[n];return i?i.getSelectors():t&&t.select(n)},resolveSelect:function(e){const n=(0,h.isObject)(e)?e.name:e;o.add(n);const i=r[n];return i?i.getResolveSelectors():t&&t.resolveSelect(n)},dispatch:function(e){const n=(0,h.isObject)(e)?e.name:e,o=r[n];return o?o.getActions():t&&t.dispatch(n)},use:function(e,t){return u={...u,...e(u,t)},u},register:function(e){c(e.name,e.instantiate(u))},__experimentalMarkListeningStores:function(e,t){o.clear();const r=e.call(this);return t.current=Array.from(o),r},__experimentalSubscribeStore:function(e,n){return e in r?r[e].subscribe(n):t?t.__experimentalSubscribeStore(e,n):s(n)},registerStore:(e,t)=>{if(!t.reducer)throw new TypeError("Must specify store reducer");const r=M(e,t).instantiate(u);return c(e,r),r.store}};return c(E,function(e){const t=t=>(r,...n)=>e.select(r)[t](...n),r=t=>(r,...n)=>e.dispatch(r)[t](...n);return{getSelectors:()=>["getIsResolving","hasStartedResolution","hasFinishedResolution","isResolving","getCachedResolvers"].reduce(((e,r)=>({...e,[r]:t(r)})),{}),getActions:()=>["startResolution","finishResolution","invalidateResolution","invalidateResolutionForStore","invalidateResolutionForStoreSelector"].reduce(((e,t)=>({...e,[t]:r(t)})),{}),subscribe:()=>()=>{}}}(u)),Object.entries(e).forEach((([e,t])=>u.registerStore(e,t))),t&&t.subscribe(i),a=u,(0,h.mapValues)(a,((e,t)=>"function"!=typeof e?e:function(){return u[t].apply(null,arguments)}));var a}(),H=(G.select,G.resolveSelect,G.dispatch,G.subscribe,G.registerGenericStore,G.registerStore,G.use,G.register);window.wp.blockEditor.store="core/block-editor",window.wp.editor.store="core/editor",window.wp.notices.store="core/notices",window.wp.data={...window.wp.data,createReduxStore:M,register:H}})()})();
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks.asset.php b/libraries/lifterlms-blocks/assets/js/llms-blocks.asset.php
new file mode 100644
index 0000000000..f84d3e1f26
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks.asset.php
@@ -0,0 +1 @@
+ array('jquery', 'lodash', 'react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-polyfill', 'wp-primitives', 'wp-rich-text', 'wp-server-side-render', 'wp-url'), 'version' => '76110ae09aa41cf7417e');
diff --git a/libraries/lifterlms-blocks/assets/js/llms-blocks.js b/libraries/lifterlms-blocks/assets/js/llms-blocks.js
new file mode 100644
index 0000000000..5b252cef0e
--- /dev/null
+++ b/libraries/lifterlms-blocks/assets/js/llms-blocks.js
@@ -0,0 +1,28 @@
+(()=>{var e={679:(e,t,n)=>{"use strict";var r=n(296),l={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},s={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},o={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function a(e){return r.isMemo(e)?o:i[e.$$typeof]||l}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=o;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,m=Object.getPrototypeOf,f=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(f){var l=m(n);l&&l!==f&&e(t,l,r)}var o=u(n);d&&(o=o.concat(d(n)));for(var i=a(t),h=a(n),g=0;g{"use strict";var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,l=n?Symbol.for("react.portal"):60106,s=n?Symbol.for("react.fragment"):60107,o=n?Symbol.for("react.strict_mode"):60108,i=n?Symbol.for("react.profiler"):60114,a=n?Symbol.for("react.provider"):60109,c=n?Symbol.for("react.context"):60110,u=n?Symbol.for("react.async_mode"):60111,d=n?Symbol.for("react.concurrent_mode"):60111,p=n?Symbol.for("react.forward_ref"):60112,m=n?Symbol.for("react.suspense"):60113,f=n?Symbol.for("react.suspense_list"):60120,h=n?Symbol.for("react.memo"):60115,g=n?Symbol.for("react.lazy"):60116,v=n?Symbol.for("react.block"):60121,b=n?Symbol.for("react.fundamental"):60117,_=n?Symbol.for("react.responder"):60118,y=n?Symbol.for("react.scope"):60119;function w(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case u:case d:case s:case i:case o:case m:return e;default:switch(e=e&&e.$$typeof){case c:case p:case g:case h:case a:return e;default:return t}}case l:return t}}}function E(e){return w(e)===d}t.AsyncMode=u,t.ConcurrentMode=d,t.ContextConsumer=c,t.ContextProvider=a,t.Element=r,t.ForwardRef=p,t.Fragment=s,t.Lazy=g,t.Memo=h,t.Portal=l,t.Profiler=i,t.StrictMode=o,t.Suspense=m,t.isAsyncMode=function(e){return E(e)||w(e)===u},t.isConcurrentMode=E,t.isContextConsumer=function(e){return w(e)===c},t.isContextProvider=function(e){return w(e)===a},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return w(e)===p},t.isFragment=function(e){return w(e)===s},t.isLazy=function(e){return w(e)===g},t.isMemo=function(e){return w(e)===h},t.isPortal=function(e){return w(e)===l},t.isProfiler=function(e){return w(e)===i},t.isStrictMode=function(e){return w(e)===o},t.isSuspense=function(e){return w(e)===m},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===s||e===d||e===i||e===o||e===m||e===f||"object"==typeof e&&null!==e&&(e.$$typeof===g||e.$$typeof===h||e.$$typeof===a||e.$$typeof===c||e.$$typeof===p||e.$$typeof===b||e.$$typeof===_||e.$$typeof===y||e.$$typeof===v)},t.typeOf=w},296:(e,t,n)=>{"use strict";e.exports=n(103)},703:(e,t,n)=>{"use strict";var r=n(414);function l(){}function s(){}s.resetWarningCache=l,e.exports=function(){function e(e,t,n,l,s,o){if(o!==r){var i=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw i.name="Invariant Violation",i}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:s,resetWarningCache:l};return n.PropTypes=n,n}},697:(e,t,n)=>{e.exports=n(703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},639:(e,t,n)=>{"use strict";var r=Object.assign||function(e){for(var t=1;t=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}(this.props,[]);return function(e){u.forEach((function(t){return delete e[t]}))}(l),l.className=this.props.inputClassName,l.id=this.state.inputId,l.style=n,o.default.createElement("div",{className:this.props.className,style:t},this.renderStyles(),o.default.createElement("input",r({},l,{ref:this.inputRef})),o.default.createElement("div",{ref:this.sizerRef,style:c},e),this.props.placeholder?o.default.createElement("div",{ref:this.placeHolderSizerRef,style:c},this.props.placeholder):null)}}]),t}(s.Component);f.propTypes={className:i.default.string,defaultValue:i.default.any,extraWidth:i.default.oneOfType([i.default.number,i.default.string]),id:i.default.string,injectStyles:i.default.bool,inputClassName:i.default.string,inputRef:i.default.func,inputStyle:i.default.object,minWidth:i.default.oneOfType([i.default.number,i.default.string]),onAutosize:i.default.func,onChange:i.default.func,placeholder:i.default.string,placeholderIsMinWidth:i.default.bool,style:i.default.object,value:i.default.any},f.defaultProps={minWidth:1,injectStyles:!0},t.Z=f},774:function(e,t){!function(e){"use strict";function t(e,t,n,r){var l,s=!1,o=0;function i(){l&&clearTimeout(l)}function a(){for(var a=arguments.length,c=new Array(a),u=0;ue?m():!0!==t&&(l=setTimeout(r?f:m,void 0===r?e-p:e)))}return"boolean"!=typeof t&&(r=n,n=t,t=void 0),a.cancel=function(){i(),s=!0},a}e.debounce=function(e,n,r){return void 0===r?t(e,n,!1):t(e,r,!1!==n)},e.throttle=t,Object.defineProperty(e,"__esModule",{value:!0})}(t)},196:e=>{"use strict";e.exports=window.React}},t={};function n(r){var l=t[r];if(void 0!==l)return l.exports;var s=t[r]={exports:{}};return e[r].call(s.exports,s,s.exports,n),s.exports}n.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{"use strict";var e={};n.r(e),n.d(e,{addField:()=>sl,deleteField:()=>ol,editField:()=>il,loadField:()=>al,receiveFields:()=>ul,renameField:()=>dl,resetFields:()=>pl,unloadField:()=>cl});var t={};n.r(t),n.d(t,{fieldExists:()=>ml,getField:()=>fl,getFieldBy:()=>hl,getFields:()=>gl,getLoadedFields:()=>vl,isDuplicate:()=>bl,isLoaded:()=>_l});var r={};n.r(r),n.d(r,{name:()=>Ol,postTypes:()=>Il,settings:()=>Tl});var l={};n.r(l),n.d(l,{name:()=>Ll,postTypes:()=>Rl,settings:()=>Vl});var s={};n.r(s),n.d(s,{name:()=>Fl,postTypes:()=>Al,settings:()=>Bl});var o={};n.r(o),n.d(o,{name:()=>ql,postTypes:()=>Gl,settings:()=>Wl});var i={};n.r(i),n.d(i,{name:()=>Yl,postTypes:()=>Xl,settings:()=>Ql});var a={};n.r(a),n.d(a,{name:()=>Jl,postTypes:()=>es,settings:()=>ts});var c={};n.r(c),n.d(c,{name:()=>os,postTypes:()=>is,settings:()=>as});var u={};n.r(u),n.d(u,{name:()=>cs,settings:()=>us});var d={};n.r(d),n.d(d,{Search:()=>Br,SearchPost:()=>Hr,SearchUser:()=>Cs,SortableDragHandle:()=>Ei,SortableList:()=>Ci});var p={};n.r(p),n.d(p,{composed:()=>ji,name:()=>$i,postTypes:()=>Ui,settings:()=>Qi});var m={};n.r(m),n.d(m,{composed:()=>ta,name:()=>Ji,postTypes:()=>ea,settings:()=>na});var f={};n.r(f),n.d(f,{composed:()=>oa,name:()=>la,postTypes:()=>sa,settings:()=>ia});var h={};n.r(h),n.d(h,{composed:()=>da,name:()=>ca,postTypes:()=>ua,settings:()=>pa});var g={};n.r(g),n.d(g,{composed:()=>Ea,name:()=>ya,postTypes:()=>wa,settings:()=>Ca});var v={};n.r(v),n.d(v,{composed:()=>Oa,name:()=>Sa,postTypes:()=>wa,settings:()=>Ia});var b={};n.r(b),n.d(b,{composed:()=>Ma,name:()=>Pa,postTypes:()=>wa,settings:()=>Da});var _={};n.r(_),n.d(_,{composed:()=>Va,name:()=>Ra,postTypes:()=>wa,settings:()=>Na});var y={};n.r(y),n.d(y,{composed:()=>Ba,name:()=>Fa,postTypes:()=>wa,settings:()=>Ha});var w={};n.r(w),n.d(w,{composed:()=>$a,name:()=>za,postTypes:()=>wa,settings:()=>Ua});var E={};n.r(E),n.d(E,{composed:()=>Ga,name:()=>qa,postTypes:()=>wa,settings:()=>Wa});var x={};n.r(x),n.d(x,{composed:()=>Ya,name:()=>Ka,postTypes:()=>wa,settings:()=>Xa});var C={};n.r(C),n.d(C,{composed:()=>Ja,name:()=>Qa,postTypes:()=>Za,settings:()=>ec});var k={};n.r(k),n.d(k,{composed:()=>nc,name:()=>tc,postTypes:()=>wa,settings:()=>lc});var S={};n.r(S),n.d(S,{composed:()=>ac,name:()=>oc,postTypes:()=>ic,settings:()=>cc});var O={};n.r(O),n.d(O,{composed:()=>pc,name:()=>uc,postTypes:()=>dc,settings:()=>mc});var I={};n.r(I),n.d(I,{composed:()=>gc,name:()=>hc,postTypes:()=>wa,settings:()=>vc});var T={};n.r(T),n.d(T,{composed:()=>_c,name:()=>bc,postTypes:()=>wa,settings:()=>yc});var P={};n.r(P),n.d(P,{composed:()=>xc,name:()=>Ec,postTypes:()=>wa,settings:()=>Cc});var M={};n.r(M),n.d(M,{composed:()=>Oc,name:()=>Sc,postTypes:()=>ua,settings:()=>Ic});var D={};n.r(D),n.d(D,{composed:()=>Mc,name:()=>Tc,postTypes:()=>Pc,settings:()=>Dc});var L={};n.r(L),n.d(L,{composed:()=>Vc,name:()=>Rc,postTypes:()=>ua,settings:()=>Nc});var R={};n.r(R),n.d(R,{composed:()=>Bc,name:()=>Fc,postTypes:()=>wa,settings:()=>Hc});var V={};n.r(V),n.d(V,{composed:()=>$c,name:()=>zc,postTypes:()=>wa,settings:()=>Uc});var N={};n.r(N),n.d(N,{checkboxes:()=>m,confirmGroup:()=>p,radio:()=>f,redeemVoucher:()=>b,select:()=>h,text:()=>g,textarea:()=>v,userAddress:()=>S,userAddressCity:()=>P,userAddressCountry:()=>M,userAddressPostalCode:()=>R,userAddressRegion:()=>D,userAddressState:()=>L,userAddressStreet:()=>O,userAddressStreetPrimary:()=>I,userAddressStreetSecondary:()=>T,userDisplayName:()=>_,userEmail:()=>w,userFirstName:()=>E,userLastName:()=>x,userLogin:()=>y,userNames:()=>C,userPassword:()=>k,userPhone:()=>V});const A=window.wp.element,F=window.wp.primitives,B=()=>(0,A.createElement)(A.Fragment,null,(0,A.createElement)(F.SVG,{width:"20px",height:"20px",viewBox:"0 0 85 85",version:"1.1",style:{fillRule:"evenodd",clipRule:"evenodd",strokeLinejoin:"round",strokeMiterlimit:1.41421}},(0,A.createElement)(F.G,{id:"lifterlms-icon"},(0,A.createElement)(F.Path,{d:"M29.061,50.631l-2.258,-1.29l-6.066,10.452c-5.483,-7.613 -6.58,-17.873 -2.322,-26.712l0.064,-0.065c0.258,-0.581 0.581,-1.097 0.839,-1.613c4.323,-7.485 11.873,-12.067 19.873,-12.905c1.42,-1.935 2.969,-3.614 4.711,-5.226c-11.421,-0.645 -22.843,5.032 -28.972,15.615c-7.872,13.679 -4.258,30.841 7.872,40.263l6.065,-18.003c0.065,-0.128 0.13,-0.323 0.194,-0.516m36.908,-16.712c3.227,7.421 3.033,16.195 -1.291,23.681c-0.257,0.516 -0.58,1.031 -0.903,1.548l-0.064,0.066c-5.549,8.129 -14.97,12.323 -24.326,11.355l6.066,-10.453l-2.259,-1.291c-0.129,0.13 -0.258,0.259 -0.387,0.389l-12.518,14.259c14.196,5.808 30.907,0.323 38.779,-13.357c6.13,-10.581 5.356,-23.293 -0.967,-32.842c-0.517,2.257 -1.162,4.516 -2.13,6.645"}),(0,A.createElement)(F.Path,{d:"M44.999,50.243c-1.614,2.13 -4.194,3.228 -6.968,3.485c-0.839,0.065 -1.614,-0.387 -2.001,-1.161c-1.162,-2.517 -1.548,-5.291 -0.451,-7.743l-12.648,-7.291c-0.838,-0.516 -1.225,-1.356 -0.967,-2.258c0.193,-0.904 0.967,-1.55 1.871,-1.55l12.84,-0.451c0.968,-3.936 2.581,-7.678 4.904,-11.163c3.678,-5.484 8.904,-9.549 15.034,-12.001c1.485,-0.581 2.968,-1.096 4.453,-1.484c1.096,-0.258 2.193,0.388 2.451,1.421c0.452,1.482 0.775,3.031 1.033,4.579c0.903,6.582 -0.065,13.163 -2.903,19.099c-1.807,3.743 -4.324,6.97 -7.228,9.808l6.001,11.292c0.452,0.839 0.323,1.807 -0.387,2.452c-0.645,0.645 -1.614,0.71 -2.387,0.258l-12.647,-7.292Zm9.549,-27.035c1.936,1.162 2.581,3.614 1.485,5.549c-1.098,1.936 -3.613,2.582 -5.55,1.485c-1.935,-1.098 -2.58,-3.614 -1.484,-5.55c1.162,-1.935 3.614,-2.581 5.549,-1.484"}),(0,A.createElement)(F.Path,{d:"M26.093,72.118l13.679,-15.551c-0.516,0.065 -1.032,0.129 -1.549,0.194c-2.064,0.129 -4,-0.968 -4.902,-2.903c-0.259,-0.452 -0.453,-0.904 -0.646,-1.42l-6.582,19.68Z"})))),H=window.wp.hooks;function z(e,t){let n=!0;return(-1!==window.llms.dynamic_blocks.indexOf(t)||e.supports&&!1===e.supports.llms_visibility||(0,H.applyFilters)("llms_block_visibility_disallowed_blocks",["core/freeform","llms/php-template"]).includes(t))&&(n=!1),(0,H.applyFilters)("llms_block_supports_visibility",n,e,t)}const $=window.wp.i18n,U=window.wp.compose,j=window.wp.blockEditor,q=window.wp.components,G={all:(0,$.__)("everyone","lifterlms"),enrolled:(0,$.__)("enrolled users","lifterlms"),not_enrolled:(0,$.__)("non-enrolled users or visitors","lifterlms"),logged_in:(0,$.__)("logged in users","lifterlms"),logged_out:(0,$.__)("logged out users","lifterlms")},W=Object.keys(G).map((e=>({label:G[e],value:e})));class K extends A.Component{render(){const{llms_visibility:e}=this.props.attributes,{children:t}=this.props;return"all"===e?t:(0,A.createElement)("div",{className:"llms-block-visibility"},t,(0,A.createElement)("div",{className:"llms-block-visibility--indicator"},(0,A.createElement)(q.Dashicon,{icon:"visibility"}),(0,A.createElement)("span",{className:"llms-block-visibility--msg"},(0,$.sprintf)(
+// Translators: %s = visibility setting label.
+(0,$.__)("This block is only visible to %s","lifterlms"),G[n=e]||n))));var n}}function Y(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var X=n(774);function Q(){return Q=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(l[n]=e[n])}return l}var J=n(196),ee=n.n(J),te=function(){function e(e){var t=this;this._insertTag=function(e){var n;n=0===t.tags.length?t.insertionPoint?t.insertionPoint.nextSibling:t.prepend?t.container.firstChild:t.before:t.tags[t.tags.length-1].nextSibling,t.container.insertBefore(e,n),t.tags.push(e)},this.isSpeedy=void 0===e.speedy||e.speedy,this.tags=[],this.ctr=0,this.nonce=e.nonce,this.key=e.key,this.container=e.container,this.prepend=e.prepend,this.insertionPoint=e.insertionPoint,this.before=null}var t=e.prototype;return t.hydrate=function(e){e.forEach(this._insertTag)},t.insert=function(e){this.ctr%(this.isSpeedy?65e3:1)==0&&this._insertTag(function(e){var t=document.createElement("style");return t.setAttribute("data-emotion",e.key),void 0!==e.nonce&&t.setAttribute("nonce",e.nonce),t.appendChild(document.createTextNode("")),t.setAttribute("data-s",""),t}(this));var t=this.tags[this.tags.length-1];if(this.isSpeedy){var n=function(e){if(e.sheet)return e.sheet;for(var t=0;t0?ae(be,--ge):0,fe--,10===ve&&(fe=1,me--),ve}function Ee(){return ve=ge2||Se(ve)>3?"":" "}function Me(e,t){for(;--t&&Ee()&&!(ve<48||ve>102||ve>57&&ve<65||ve>70&&ve<97););return ke(e,Ce()+(t<6&&32==xe()&&32==Ee()))}function De(e){for(;Ee();)switch(ve){case e:return ge;case 34:case 39:34!==e&&39!==e&&De(ve);break;case 40:41===e&&De(e);break;case 92:Ee()}return ge}function Le(e,t){for(;Ee()&&e+ve!==57&&(e+ve!==84||47!==xe()););return"/*"+ke(t,ge-1)+"*"+re(47===e?e:Ee())}function Re(e){for(;!Se(xe());)Ee();return ke(e,ge)}var Ve="-ms-",Ne="-moz-",Ae="-webkit-",Fe="comm",Be="rule",He="decl",ze="@keyframes";function $e(e,t){for(var n="",r=de(e),l=0;l6)switch(ae(e,t+1)){case 109:if(45!==ae(e,t+4))break;case 102:return oe(e,/(.+:)(.+)-([^]+)/,"$1-webkit-$2-$3$1"+Ne+(108==ae(e,t+3)?"$3":"$2-$3"))+e;case 115:return~ie(e,"stretch")?je(oe(e,"stretch","fill-available"),t)+e:e}break;case 4949:if(115!==ae(e,t+1))break;case 6444:switch(ae(e,ue(e)-3-(~ie(e,"!important")&&10))){case 107:return oe(e,":",":"+Ae)+e;case 101:return oe(e,/(.+:)([^;!]+)(;|!.+)?/,"$1"+Ae+(45===ae(e,14)?"inline-":"")+"box$3$1"+Ae+"$2$3$1"+Ve+"$2box$3")+e}break;case 5936:switch(ae(e,t+11)){case 114:return Ae+e+Ve+oe(e,/[svh]\w+-[tblr]{2}/,"tb")+e;case 108:return Ae+e+Ve+oe(e,/[svh]\w+-[tblr]{2}/,"tb-rl")+e;case 45:return Ae+e+Ve+oe(e,/[svh]\w+-[tblr]{2}/,"lr")+e}return Ae+e+Ve+e+e}return e}function qe(e){return Ie(Ge("",null,null,null,[""],e=Oe(e),0,[0],e))}function Ge(e,t,n,r,l,s,o,i,a){for(var c=0,u=0,d=o,p=0,m=0,f=0,h=1,g=1,v=1,b=0,_="",y=l,w=s,E=r,x=_;g;)switch(f=b,b=Ee()){case 40:if(108!=f&&58==x.charCodeAt(d-1)){-1!=ie(x+=oe(Te(b),"&","&\f"),"&\f")&&(v=-1);break}case 34:case 39:case 91:x+=Te(b);break;case 9:case 10:case 13:case 32:x+=Pe(f);break;case 92:x+=Me(Ce()-1,7);continue;case 47:switch(xe()){case 42:case 47:pe(Ke(Le(Ee(),Ce()),t,n),a);break;default:x+="/"}break;case 123*h:i[c++]=ue(x)*v;case 125*h:case 59:case 0:switch(b){case 0:case 125:g=0;case 59+u:m>0&&ue(x)-d&&pe(m>32?Ye(x+";",r,n,d-1):Ye(oe(x," ","")+";",r,n,d-2),a);break;case 59:x+=";";default:if(pe(E=We(x,t,n,c,u,l,i,_,y=[],w=[],d),s),123===b)if(0===u)Ge(x,t,E,E,y,s,d,i,w);else switch(p){case 100:case 109:case 115:Ge(e,E,E,r&&pe(We(e,E,E,0,0,l,i,_,l,y=[],d),w),l,w,d,i,r?y:w);break;default:Ge(x,E,E,E,[""],w,0,i,w)}}c=u=m=0,h=v=1,_=x="",d=o;break;case 58:d=1+ue(x),m=f;default:if(h<1)if(123==b)--h;else if(125==b&&0==h++&&125==we())continue;switch(x+=re(b),b*h){case 38:v=u>0?1:(x+="\f",-1);break;case 44:i[c++]=(ue(x)-1)*v,v=1;break;case 64:45===xe()&&(x+=Te(Ee())),p=xe(),u=d=ue(_=x+=Re(Ce())),b++;break;case 45:45===f&&2==ue(x)&&(h=0)}}return s}function We(e,t,n,r,l,s,o,i,a,c,u){for(var d=l-1,p=0===l?s:[""],m=de(p),f=0,h=0,g=0;f0?p[v]+" "+b:oe(b,/&\f/g,p[v])))&&(a[g++]=_);return _e(e,t,n,0===l?Be:i,a,c,u)}function Ke(e,t,n){return _e(e,t,n,Fe,re(ve),ce(e,2,-2),0)}function Ye(e,t,n,r){return _e(e,t,n,He,ce(e,0,r),ce(e,r+1,-1),r)}var Xe=function(e,t,n){for(var r=0,l=0;r=l,l=xe(),38===r&&12===l&&(t[n]=1),!Se(l);)Ee();return ke(e,ge)},Qe=new WeakMap,Ze=function(e){if("rule"===e.type&&e.parent&&!(e.length<1)){for(var t=e.value,n=e.parent,r=e.column===n.column&&e.line===n.line;"rule"!==n.type;)if(!(n=n.parent))return;if((1!==e.props.length||58===t.charCodeAt(0)||Qe.get(n))&&!r){Qe.set(e,!0);for(var l=[],s=function(e,t){return Ie(function(e,t){var n=-1,r=44;do{switch(Se(r)){case 0:38===r&&12===xe()&&(t[n]=1),e[n]+=Xe(ge-1,t,n);break;case 2:e[n]+=Te(r);break;case 4:if(44===r){e[++n]=58===xe()?"&\f":"",t[n]=e[n].length;break}default:e[n]+=re(r)}}while(r=Ee());return e}(Oe(e),t))}(t,l),o=n.props,i=0,a=0;i-1&&!e.return)switch(e.type){case He:e.return=je(e.value,e.length);break;case ze:return $e([ye(e,{value:oe(e.value,"@","@"+Ae)})],r);case Be:if(e.length)return function(e,t){return e.map(t).join("")}(e.props,(function(t){switch(function(e,t){return(e=/(::plac\w+|:read-\w+)/.exec(e))?e[0]:e}(t)){case":read-only":case":read-write":return $e([ye(e,{props:[oe(t,/:(read-\w+)/,":-moz-$1")]})],r);case"::placeholder":return $e([ye(e,{props:[oe(t,/:(plac\w+)/,":-webkit-input-$1")]}),ye(e,{props:[oe(t,/:(plac\w+)/,":-moz-$1")]}),ye(e,{props:[oe(t,/:(plac\w+)/,Ve+"input-$1")]})],r)}return""}))}}];const tt=function(e){var t=e.key;if("css"===t){var n=document.querySelectorAll("style[data-emotion]:not([data-s])");Array.prototype.forEach.call(n,(function(e){-1!==e.getAttribute("data-emotion").indexOf(" ")&&(document.head.appendChild(e),e.setAttribute("data-s",""))}))}var r,l,s=e.stylisPlugins||et,o={},i=[];r=e.container||document.head,Array.prototype.forEach.call(document.querySelectorAll('style[data-emotion^="'+t+' "]'),(function(e){for(var t=e.getAttribute("data-emotion").split(" "),n=1;n=4;++r,l-=4)t=1540483477*(65535&(t=255&e.charCodeAt(r)|(255&e.charCodeAt(++r))<<8|(255&e.charCodeAt(++r))<<16|(255&e.charCodeAt(++r))<<24))+(59797*(t>>>16)<<16),n=1540483477*(65535&(t^=t>>>24))+(59797*(t>>>16)<<16)^1540483477*(65535&n)+(59797*(n>>>16)<<16);switch(l){case 3:n^=(255&e.charCodeAt(r+2))<<16;case 2:n^=(255&e.charCodeAt(r+1))<<8;case 1:n=1540483477*(65535&(n^=255&e.charCodeAt(r)))+(59797*(n>>>16)<<16)}return(((n=1540483477*(65535&(n^=n>>>13))+(59797*(n>>>16)<<16))^n>>>15)>>>0).toString(36)},ot={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1};var it=/[A-Z]|^ms/g,at=/_EMO_([^_]+?)_([^]*?)_EMO_/g,ct=function(e){return 45===e.charCodeAt(1)},ut=function(e){return null!=e&&"boolean"!=typeof e},dt=function(e){var t=Object.create(null);return function(e){return void 0===t[e]&&(t[e]=ct(n=e)?n:n.replace(it,"-$&").toLowerCase()),t[e];var n}}(),pt=function(e,t){switch(e){case"animation":case"animationName":if("string"==typeof t)return t.replace(at,(function(e,t,n){return ft={name:t,styles:n,next:ft},t}))}return 1===ot[e]||ct(e)||"number"!=typeof t||0===t?t:t+"px"};function mt(e,t,n){if(null==n)return"";if(void 0!==n.__emotion_styles)return n;switch(typeof n){case"boolean":return"";case"object":if(1===n.anim)return ft={name:n.name,styles:n.styles,next:ft},n.name;if(void 0!==n.styles){var r=n.next;if(void 0!==r)for(;void 0!==r;)ft={name:r.name,styles:r.styles,next:ft},r=r.next;return n.styles+";"}return function(e,t,n){var r="";if(Array.isArray(n))for(var l=0;l-1}function Jt(e){return Zt(e)?window.pageYOffset:e.scrollTop}function en(e,t){Zt(e)?window.scrollTo(0,t):e.scrollTop=t}function tn(e,t,n,r){return n*((e=e/r-1)*e*e+1)+t}function nn(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:200,r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:Wt,l=Jt(e),s=t-l,o=10,i=0;function a(){var t=tn(i+=o,l,s,n);en(e,t),i=m)return{placement:"bottom",maxHeight:t};if(x>=m&&!o)return s&&nn(a,C,S),{placement:"bottom",maxHeight:t};if(!o&&x>=r||o&&w>=r)return s&&nn(a,C,S),{placement:"bottom",maxHeight:o?w-b:x-b};if("auto"===l||o){var O=t,I=o?y:E;return I>=r&&(O=Math.min(I-b-i.controlHeight,t)),{placement:"top",maxHeight:O}}if("bottom"===l)return s&&en(a,C),{placement:"bottom",maxHeight:t};break;case"top":if(y>=m)return{placement:"top",maxHeight:t};if(E>=m&&!o)return s&&nn(a,k,S),{placement:"top",maxHeight:t};if(!o&&E>=r||o&&y>=r){var T=t;return(!o&&E>=r||o&&y>=r)&&(T=o?y-_:E-_),s&&nn(a,k,S),{placement:"top",maxHeight:T}}return{placement:"bottom",maxHeight:t};default:throw new Error('Invalid placement provided "'.concat(l,'".'))}return c}var un=function(e){return"auto"===e?"bottom":e},dn=(0,J.createContext)({getPortalPlacement:null}),pn=function(e){Bt(n,e);var t=Gt(n);function n(){var e;Vt(this,n);for(var r=arguments.length,l=new Array(r),s=0;se.length)&&(t=e.length);for(var n=0,r=new Array(t);n0,h=d-p-u,g=!1;h>t&&o.current&&(r&&r(e),o.current=!1),f&&i.current&&(s&&s(e),i.current=!1),f&&t>h?(n&&!o.current&&n(e),m.scrollTop=d,g=!0,o.current=!0):!f&&-t>u&&(l&&!i.current&&l(e),m.scrollTop=0,g=!0,i.current=!0),g&&function(e){e.preventDefault(),e.stopPropagation()}(e)}}),[]),d=(0,J.useCallback)((function(e){u(e,e.deltaY)}),[u]),p=(0,J.useCallback)((function(e){a.current=e.changedTouches[0].clientY}),[]),m=(0,J.useCallback)((function(e){var t=a.current-e.changedTouches[0].clientY;u(e,t)}),[u]),f=(0,J.useCallback)((function(e){if(e){var t=!!an&&{passive:!1};"function"==typeof e.addEventListener&&e.addEventListener("wheel",d,t),"function"==typeof e.addEventListener&&e.addEventListener("touchstart",p,t),"function"==typeof e.addEventListener&&e.addEventListener("touchmove",m,t)}}),[m,p,d]),h=(0,J.useCallback)((function(e){e&&("function"==typeof e.removeEventListener&&e.removeEventListener("wheel",d,!1),"function"==typeof e.removeEventListener&&e.removeEventListener("touchstart",p,!1),"function"==typeof e.removeEventListener&&e.removeEventListener("touchmove",m,!1))}),[m,p,d]);return(0,J.useEffect)((function(){if(t){var e=c.current;return f(e),function(){h(e)}}}),[t,f,h]),function(e){c.current=e}}({isEnabled:void 0===r||r,onBottomArrive:e.onBottomArrive,onBottomLeave:e.onBottomLeave,onTopArrive:e.onTopArrive,onTopLeave:e.onTopLeave}),s=function(e){var t=e.isEnabled,n=e.accountForScrollbars,r=void 0===n||n,l=(0,J.useRef)({}),s=(0,J.useRef)(null),o=(0,J.useCallback)((function(e){if(ur){var t=document.body,n=t&&t.style;if(r&&lr.forEach((function(e){var t=n&&n[e];l.current[e]=t})),r&&dr<1){var s=parseInt(l.current.paddingRight,10)||0,o=document.body?document.body.clientWidth:0,i=window.innerWidth-o+s||0;Object.keys(sr).forEach((function(e){var t=sr[e];n&&(n[e]=t)})),n&&(n.paddingRight="".concat(i,"px"))}t&&cr()&&(t.addEventListener("touchmove",or,pr),e&&(e.addEventListener("touchstart",ar,pr),e.addEventListener("touchmove",ir,pr))),dr+=1}}),[]),i=(0,J.useCallback)((function(e){if(ur){var t=document.body,n=t&&t.style;dr=Math.max(dr-1,0),r&&dr<1&&lr.forEach((function(e){var t=l.current[e];n&&(n[e]=t)})),t&&cr()&&(t.removeEventListener("touchmove",or,pr),e&&(e.removeEventListener("touchstart",ar,pr),e.removeEventListener("touchmove",ir,pr)))}}),[]);return(0,J.useEffect)((function(){if(t){var e=s.current;return o(e),function(){i(e)}}}),[t,o,i]),function(e){s.current=e}}({isEnabled:n});return Ot(ee().Fragment,null,n&&Ot("div",{onClick:mr,css:fr}),t((function(e){l(e),s(e)})))}var gr={clearIndicator:Tn,container:function(e){var t=e.isDisabled;return{label:"container",direction:e.isRtl?"rtl":null,pointerEvents:t?"none":null,position:"relative"}},control:function(e){var t=e.isDisabled,n=e.isFocused,r=e.theme,l=r.colors,s=r.borderRadius,o=r.spacing;return{label:"control",alignItems:"center",backgroundColor:t?l.neutral5:l.neutral0,borderColor:t?l.neutral10:n?l.primary:l.neutral20,borderRadius:s,borderStyle:"solid",borderWidth:1,boxShadow:n?"0 0 0 1px ".concat(l.primary):null,cursor:"default",display:"flex",flexWrap:"wrap",justifyContent:"space-between",minHeight:o.controlHeight,outline:"0 !important",position:"relative",transition:"all 100ms","&:hover":{borderColor:n?l.primary:l.neutral30}}},dropdownIndicator:In,group:function(e){var t=e.theme.spacing;return{paddingBottom:2*t.baseUnit,paddingTop:2*t.baseUnit}},groupHeading:function(e){var t=e.theme.spacing;return{label:"group",color:"#999",cursor:"default",display:"block",fontSize:"75%",fontWeight:"500",marginBottom:"0.25em",paddingLeft:3*t.baseUnit,paddingRight:3*t.baseUnit,textTransform:"uppercase"}},indicatorsContainer:function(){return{alignItems:"center",alignSelf:"stretch",display:"flex",flexShrink:0}},indicatorSeparator:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing.baseUnit,l=n.colors;return{label:"indicatorSeparator",alignSelf:"stretch",backgroundColor:t?l.neutral10:l.neutral20,marginBottom:2*r,marginTop:2*r,width:1}},input:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing,l=n.colors;return{margin:r.baseUnit/2,paddingBottom:r.baseUnit/2,paddingTop:r.baseUnit/2,visibility:t?"hidden":"visible",color:l.neutral80}},loadingIndicator:function(e){var t=e.isFocused,n=e.size,r=e.theme,l=r.colors,s=r.spacing.baseUnit;return{label:"loadingIndicator",color:t?l.neutral60:l.neutral20,display:"flex",padding:2*s,transition:"color 150ms",alignSelf:"center",fontSize:n,lineHeight:1,marginRight:n,textAlign:"center",verticalAlign:"middle"}},loadingMessage:hn,menu:function(e){var t,n=e.placement,r=e.theme,l=r.borderRadius,s=r.spacing,o=r.colors;return Y(t={label:"menu"},function(e){return e?{bottom:"top",top:"bottom"}[e]:"bottom"}(n),"100%"),Y(t,"backgroundColor",o.neutral0),Y(t,"borderRadius",l),Y(t,"boxShadow","0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)"),Y(t,"marginBottom",s.menuGutter),Y(t,"marginTop",s.menuGutter),Y(t,"position","absolute"),Y(t,"width","100%"),Y(t,"zIndex",1),t},menuList:function(e){var t=e.maxHeight,n=e.theme.spacing.baseUnit;return{maxHeight:t,overflowY:"auto",paddingBottom:n,paddingTop:n,position:"relative",WebkitOverflowScrolling:"touch"}},menuPortal:function(e){var t=e.rect,n=e.offset,r=e.position;return{left:t.left,position:r,top:n,width:t.width,zIndex:1}},multiValue:function(e){var t=e.theme,n=t.spacing,r=t.borderRadius;return{label:"multiValue",backgroundColor:t.colors.neutral10,borderRadius:r/2,display:"flex",margin:n.baseUnit/2,minWidth:0}},multiValueLabel:function(e){var t=e.theme,n=t.borderRadius,r=t.colors,l=e.cropWithEllipsis;return{borderRadius:n/2,color:r.neutral80,fontSize:"85%",overflow:"hidden",padding:3,paddingLeft:6,textOverflow:l?"ellipsis":null,whiteSpace:"nowrap"}},multiValueRemove:function(e){var t=e.theme,n=t.spacing,r=t.borderRadius,l=t.colors;return{alignItems:"center",borderRadius:r/2,backgroundColor:e.isFocused&&l.dangerLight,display:"flex",paddingLeft:n.baseUnit,paddingRight:n.baseUnit,":hover":{backgroundColor:l.dangerLight,color:l.danger}}},noOptionsMessage:fn,option:function(e){var t=e.isDisabled,n=e.isFocused,r=e.isSelected,l=e.theme,s=l.spacing,o=l.colors;return{label:"option",backgroundColor:r?o.primary:n?o.primary25:"transparent",color:t?o.neutral20:r?o.neutral0:"inherit",cursor:"default",display:"block",fontSize:"inherit",padding:"".concat(2*s.baseUnit,"px ").concat(3*s.baseUnit,"px"),width:"100%",userSelect:"none",WebkitTapHighlightColor:"rgba(0, 0, 0, 0)",":active":{backgroundColor:!t&&(r?o.primary:o.primary50)}}},placeholder:function(e){var t=e.theme,n=t.spacing;return{label:"placeholder",color:t.colors.neutral50,marginLeft:n.baseUnit/2,marginRight:n.baseUnit/2,position:"absolute",top:"50%",transform:"translateY(-50%)"}},singleValue:function(e){var t=e.isDisabled,n=e.theme,r=n.spacing,l=n.colors;return{label:"singleValue",color:t?l.neutral40:l.neutral80,marginLeft:r.baseUnit/2,marginRight:r.baseUnit/2,maxWidth:"calc(100% - ".concat(2*r.baseUnit,"px)"),overflow:"hidden",position:"absolute",textOverflow:"ellipsis",whiteSpace:"nowrap",top:"50%",transform:"translateY(-50%)"}},valueContainer:function(e){var t=e.theme.spacing;return{alignItems:"center",display:"flex",flex:1,flexWrap:"wrap",padding:"".concat(t.baseUnit/2,"px ").concat(2*t.baseUnit,"px"),WebkitOverflowScrolling:"touch",position:"relative",overflow:"hidden"}}},vr={borderRadius:4,colors:{primary:"#2684FF",primary75:"#4C9AFF",primary50:"#B2D4FF",primary25:"#DEEBFF",danger:"#DE350B",dangerLight:"#FFBDAD",neutral0:"hsl(0, 0%, 100%)",neutral5:"hsl(0, 0%, 95%)",neutral10:"hsl(0, 0%, 90%)",neutral20:"hsl(0, 0%, 80%)",neutral30:"hsl(0, 0%, 70%)",neutral40:"hsl(0, 0%, 60%)",neutral50:"hsl(0, 0%, 50%)",neutral60:"hsl(0, 0%, 40%)",neutral70:"hsl(0, 0%, 30%)",neutral80:"hsl(0, 0%, 20%)",neutral90:"hsl(0, 0%, 10%)"},spacing:{baseUnit:4,controlHeight:38,menuGutter:8}},br={"aria-live":"polite",backspaceRemovesValue:!0,blurInputOnSelect:rn(),captureMenuScroll:!rn(),closeMenuOnSelect:!0,closeMenuOnScroll:!1,components:{},controlShouldRenderValue:!0,escapeClearsValue:!1,filterOption:function(e,t){var n=Ut({ignoreCase:!0,ignoreAccents:!0,stringify:nr,trim:!0,matchFrom:"any"},undefined),r=n.ignoreCase,l=n.ignoreAccents,s=n.stringify,o=n.trim,i=n.matchFrom,a=o?tr(t):t,c=o?tr(s(e)):s(e);return r&&(a=a.toLowerCase(),c=c.toLowerCase()),l&&(a=er(a),c=Jn(c)),"start"===i?c.substr(0,a.length)===a:c.indexOf(a)>-1},formatGroupLabel:function(e){return e.label},getOptionLabel:function(e){return e.label},getOptionValue:function(e){return e.value},isDisabled:!1,isLoading:!1,isMulti:!1,isRtl:!1,isSearchable:!0,isOptionDisabled:function(e){return!!e.isDisabled},loadingMessage:function(){return"Loading..."},maxMenuHeight:300,minMenuHeight:140,menuIsOpen:!1,menuPlacement:"bottom",menuPosition:"absolute",menuShouldBlockScroll:!1,menuShouldScrollIntoView:!function(){try{return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)}catch(e){return!1}}(),noOptionsMessage:function(){return"No options"},openMenuOnFocus:!1,openMenuOnClick:!0,options:[],pageSize:5,placeholder:"Select...",screenReaderStatus:function(e){var t=e.count;return"".concat(t," result").concat(1!==t?"s":""," available")},styles:{},tabIndex:"0",tabSelectsValue:!0};function _r(e,t,n,r){return{type:"option",data:t,isDisabled:kr(e,t,n),isSelected:Sr(e,t,n),label:xr(e,t),value:Cr(e,t),index:r}}function yr(e,t){return e.options.map((function(n,r){if(n.options){var l=n.options.map((function(n,r){return _r(e,n,t,r)})).filter((function(t){return Er(e,t)}));return l.length>0?{type:"group",data:n,options:l,index:r}:void 0}var s=_r(e,n,t,r);return Er(e,s)?s:void 0})).filter((function(e){return!!e}))}function wr(e){return e.reduce((function(e,t){return"group"===t.type?e.push.apply(e,Hn(t.options.map((function(e){return e.data})))):e.push(t.data),e}),[])}function Er(e,t){var n=e.inputValue,r=void 0===n?"":n,l=t.data,s=t.isSelected,o=t.label,i=t.value;return(!Ir(e)||!s)&&Or(e,{label:o,value:i,data:l},r)}var xr=function(e,t){return e.getOptionLabel(t)},Cr=function(e,t){return e.getOptionValue(t)};function kr(e,t,n){return"function"==typeof e.isOptionDisabled&&e.isOptionDisabled(t,n)}function Sr(e,t,n){if(n.indexOf(t)>-1)return!0;if("function"==typeof e.isOptionSelected)return e.isOptionSelected(t,n);var r=Cr(e,t);return n.some((function(t){return Cr(e,t)===r}))}function Or(e,t,n){return!e.filterOption||e.filterOption(t,n)}var Ir=function(e){var t=e.hideSelectedOptions,n=e.isMulti;return void 0===t?n:t},Tr=1,Pr=function(e){Bt(n,e);var t=Gt(n);function n(e){var r;return Vt(this,n),(r=t.call(this,e)).state={ariaSelection:null,focusedOption:null,focusedValue:null,inputIsHidden:!1,isFocused:!1,selectValue:[],clearFocusValueOnUpdate:!1,inputIsHiddenAfterUpdate:void 0,prevProps:void 0},r.blockOptionHover=!1,r.isComposing=!1,r.commonProps=void 0,r.initialTouchX=0,r.initialTouchY=0,r.instancePrefix="",r.openAfterFocus=!1,r.scrollToFocusedOptionOnUpdate=!1,r.userIsDragging=void 0,r.controlRef=null,r.getControlRef=function(e){r.controlRef=e},r.focusedOptionRef=null,r.getFocusedOptionRef=function(e){r.focusedOptionRef=e},r.menuListRef=null,r.getMenuListRef=function(e){r.menuListRef=e},r.inputRef=null,r.getInputRef=function(e){r.inputRef=e},r.focus=r.focusInput,r.blur=r.blurInput,r.onChange=function(e,t){var n=r.props,l=n.onChange,s=n.name;t.name=s,r.ariaOnChange(e,t),l(e,t)},r.setValue=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"set-value",n=arguments.length>2?arguments[2]:void 0,l=r.props,s=l.closeMenuOnSelect,o=l.isMulti;r.onInputChange("",{action:"set-value"}),s&&(r.setState({inputIsHiddenAfterUpdate:!o}),r.onMenuClose()),r.setState({clearFocusValueOnUpdate:!0}),r.onChange(e,{action:t,option:n})},r.selectOption=function(e){var t=r.props,n=t.blurInputOnSelect,l=t.isMulti,s=t.name,o=r.state.selectValue,i=l&&r.isOptionSelected(e,o),a=r.isOptionDisabled(e,o);if(i){var c=r.getOptionValue(e);r.setValue(o.filter((function(e){return r.getOptionValue(e)!==c})),"deselect-option",e)}else{if(a)return void r.ariaOnChange(e,{action:"select-option",name:s});l?r.setValue([].concat(Hn(o),[e]),"select-option",e):r.setValue(e,"select-option")}n&&r.blurInput()},r.removeValue=function(e){var t=r.props.isMulti,n=r.state.selectValue,l=r.getOptionValue(e),s=n.filter((function(e){return r.getOptionValue(e)!==l})),o=t?s:s[0]||null;r.onChange(o,{action:"remove-value",removedValue:e}),r.focusInput()},r.clearValue=function(){var e=r.state.selectValue;r.onChange(r.props.isMulti?[]:null,{action:"clear",removedValues:e})},r.popValue=function(){var e=r.props.isMulti,t=r.state.selectValue,n=t[t.length-1],l=t.slice(0,t.length-1),s=e?l:l[0]||null;r.onChange(s,{action:"pop-value",removedValue:n})},r.getValue=function(){return r.state.selectValue},r.cx=function(){for(var e=arguments.length,t=new Array(e),n=0;n5||s>5}},r.onTouchEnd=function(e){r.userIsDragging||(r.controlRef&&!r.controlRef.contains(e.target)&&r.menuListRef&&!r.menuListRef.contains(e.target)&&r.blurInput(),r.initialTouchX=0,r.initialTouchY=0)},r.onControlTouchEnd=function(e){r.userIsDragging||r.onControlMouseDown(e)},r.onClearIndicatorTouchEnd=function(e){r.userIsDragging||r.onClearIndicatorMouseDown(e)},r.onDropdownIndicatorTouchEnd=function(e){r.userIsDragging||r.onDropdownIndicatorMouseDown(e)},r.handleInputChange=function(e){var t=e.currentTarget.value;r.setState({inputIsHiddenAfterUpdate:!1}),r.onInputChange(t,{action:"input-change"}),r.props.menuIsOpen||r.onMenuOpen()},r.onInputFocus=function(e){r.props.onFocus&&r.props.onFocus(e),r.setState({inputIsHiddenAfterUpdate:!1,isFocused:!0}),(r.openAfterFocus||r.props.openMenuOnFocus)&&r.openMenu("first"),r.openAfterFocus=!1},r.onInputBlur=function(e){r.menuListRef&&r.menuListRef.contains(document.activeElement)?r.inputRef.focus():(r.props.onBlur&&r.props.onBlur(e),r.onInputChange("",{action:"input-blur"}),r.onMenuClose(),r.setState({focusedValue:null,isFocused:!1}))},r.onOptionHover=function(e){r.blockOptionHover||r.state.focusedOption===e||r.setState({focusedOption:e})},r.shouldHideSelectedOptions=function(){return Ir(r.props)},r.onKeyDown=function(e){var t=r.props,n=t.isMulti,l=t.backspaceRemovesValue,s=t.escapeClearsValue,o=t.inputValue,i=t.isClearable,a=t.isDisabled,c=t.menuIsOpen,u=t.onKeyDown,d=t.tabSelectsValue,p=t.openMenuOnFocus,m=r.state,f=m.focusedOption,h=m.focusedValue,g=m.selectValue;if(!(a||"function"==typeof u&&(u(e),e.defaultPrevented))){switch(r.blockOptionHover=!0,e.key){case"ArrowLeft":if(!n||o)return;r.focusValue("previous");break;case"ArrowRight":if(!n||o)return;r.focusValue("next");break;case"Delete":case"Backspace":if(o)return;if(h)r.removeValue(h);else{if(!l)return;n?r.popValue():i&&r.clearValue()}break;case"Tab":if(r.isComposing)return;if(e.shiftKey||!c||!d||!f||p&&r.isOptionSelected(f,g))return;r.selectOption(f);break;case"Enter":if(229===e.keyCode)break;if(c){if(!f)return;if(r.isComposing)return;r.selectOption(f);break}return;case"Escape":c?(r.setState({inputIsHiddenAfterUpdate:!1}),r.onInputChange("",{action:"menu-close"}),r.onMenuClose()):i&&s&&r.clearValue();break;case" ":if(o)return;if(!c){r.openMenu("first");break}if(!f)return;r.selectOption(f);break;case"ArrowUp":c?r.focusOption("up"):r.openMenu("last");break;case"ArrowDown":c?r.focusOption("down"):r.openMenu("first");break;case"PageUp":if(!c)return;r.focusOption("pageup");break;case"PageDown":if(!c)return;r.focusOption("pagedown");break;case"Home":if(!c)return;r.focusOption("first");break;case"End":if(!c)return;r.focusOption("last");break;default:return}e.preventDefault()}},r.instancePrefix="react-select-"+(r.props.instanceId||++Tr),r.state.selectValue=Xt(e.value),r}return At(n,[{key:"componentDidMount",value:function(){this.startListeningComposition(),this.startListeningToTouch(),this.props.closeMenuOnScroll&&document&&document.addEventListener&&document.addEventListener("scroll",this.onScroll,!0),this.props.autoFocus&&this.focusInput()}},{key:"componentDidUpdate",value:function(e){var t,n,r,l,s,o=this.props,i=o.isDisabled,a=o.menuIsOpen,c=this.state.isFocused;(c&&!i&&e.isDisabled||c&&a&&!e.menuIsOpen)&&this.focusInput(),c&&i&&!e.isDisabled&&this.setState({isFocused:!1},this.onMenuClose),this.menuListRef&&this.focusedOptionRef&&this.scrollToFocusedOptionOnUpdate&&(t=this.menuListRef,n=this.focusedOptionRef,r=t.getBoundingClientRect(),l=n.getBoundingClientRect(),s=n.offsetHeight/3,l.bottom+s>r.bottom?en(t,Math.min(n.offsetTop+n.clientHeight-t.offsetHeight+s,t.scrollHeight)):l.top-s-1&&(o=i)}this.scrollToFocusedOptionOnUpdate=!(l&&this.menuListRef),this.setState({inputIsHiddenAfterUpdate:!1,focusedValue:null,focusedOption:s[o]},(function(){return t.onMenuOpen()}))}},{key:"focusValue",value:function(e){var t=this.state,n=t.selectValue,r=t.focusedValue;if(this.props.isMulti){this.setState({focusedOption:null});var l=n.indexOf(r);r||(l=-1);var s=n.length-1,o=-1;if(n.length){switch(e){case"previous":o=0===l?0:-1===l?s:l-1;break;case"next":l>-1&&l0&&void 0!==arguments[0]?arguments[0]:"first",t=this.props.pageSize,n=this.state.focusedOption,r=this.getFocusableOptions();if(r.length){var l=0,s=r.indexOf(n);n||(s=-1),"up"===e?l=s>0?s-1:r.length-1:"down"===e?l=(s+1)%r.length:"pageup"===e?(l=s-t)<0&&(l=0):"pagedown"===e?(l=s+t)>r.length-1&&(l=r.length-1):"last"===e&&(l=r.length-1),this.scrollToFocusedOptionOnUpdate=!0,this.setState({focusedOption:r[l],focusedValue:null})}}},{key:"getTheme",value:function(){return this.props.theme?"function"==typeof this.props.theme?this.props.theme(vr):Ut(Ut({},vr),this.props.theme):vr}},{key:"getCommonProps",value:function(){var e=this.clearValue,t=this.cx,n=this.getStyles,r=this.getValue,l=this.selectOption,s=this.setValue,o=this.props,i=o.isMulti,a=o.isRtl,c=o.options;return{clearValue:e,cx:t,getStyles:n,getValue:r,hasValue:this.hasValue(),isMulti:i,isRtl:a,options:c,selectOption:l,selectProps:o,setValue:s,theme:this.getTheme()}}},{key:"hasValue",value:function(){return this.state.selectValue.length>0}},{key:"hasOptions",value:function(){return!!this.getFocusableOptions().length}},{key:"isClearable",value:function(){var e=this.props,t=e.isClearable,n=e.isMulti;return void 0===t?n:t}},{key:"isOptionDisabled",value:function(e,t){return kr(this.props,e,t)}},{key:"isOptionSelected",value:function(e,t){return Sr(this.props,e,t)}},{key:"filterOption",value:function(e,t){return Or(this.props,e,t)}},{key:"formatOptionLabel",value:function(e,t){if("function"==typeof this.props.formatOptionLabel){var n=this.props.inputValue,r=this.state.selectValue;return this.props.formatOptionLabel(e,{context:t,inputValue:n,selectValue:r})}return this.getOptionLabel(e)}},{key:"formatGroupLabel",value:function(e){return this.props.formatGroupLabel(e)}},{key:"startListeningComposition",value:function(){document&&document.addEventListener&&(document.addEventListener("compositionstart",this.onCompositionStart,!1),document.addEventListener("compositionend",this.onCompositionEnd,!1))}},{key:"stopListeningComposition",value:function(){document&&document.removeEventListener&&(document.removeEventListener("compositionstart",this.onCompositionStart),document.removeEventListener("compositionend",this.onCompositionEnd))}},{key:"startListeningToTouch",value:function(){document&&document.addEventListener&&(document.addEventListener("touchstart",this.onTouchStart,!1),document.addEventListener("touchmove",this.onTouchMove,!1),document.addEventListener("touchend",this.onTouchEnd,!1))}},{key:"stopListeningToTouch",value:function(){document&&document.removeEventListener&&(document.removeEventListener("touchstart",this.onTouchStart),document.removeEventListener("touchmove",this.onTouchMove),document.removeEventListener("touchend",this.onTouchEnd))}},{key:"renderInput",value:function(){var e=this.props,t=e.isDisabled,n=e.isSearchable,r=e.inputId,l=e.inputValue,s=e.tabIndex,o=e.form,i=this.getComponents().Input,a=this.state.inputIsHidden,c=this.commonProps,u=r||this.getElementId("input"),d={"aria-autocomplete":"list","aria-label":this.props["aria-label"],"aria-labelledby":this.props["aria-labelledby"]};return n?ee().createElement(i,Q({},c,{autoCapitalize:"none",autoComplete:"off",autoCorrect:"off",id:u,innerRef:this.getInputRef,isDisabled:t,isHidden:a,onBlur:this.onInputBlur,onChange:this.handleInputChange,onFocus:this.onInputFocus,spellCheck:"false",tabIndex:s,form:o,type:"text",value:l},d)):ee().createElement(rr,Q({id:u,innerRef:this.getInputRef,onBlur:this.onInputBlur,onChange:Wt,onFocus:this.onInputFocus,readOnly:!0,disabled:t,tabIndex:s,form:o,value:""},d))}},{key:"renderPlaceholderOrValue",value:function(){var e=this,t=this.getComponents(),n=t.MultiValue,r=t.MultiValueContainer,l=t.MultiValueLabel,s=t.MultiValueRemove,o=t.SingleValue,i=t.Placeholder,a=this.commonProps,c=this.props,u=c.controlShouldRenderValue,d=c.isDisabled,p=c.isMulti,m=c.inputValue,f=c.placeholder,h=this.state,g=h.selectValue,v=h.focusedValue,b=h.isFocused;if(!this.hasValue()||!u)return m?null:ee().createElement(i,Q({},a,{key:"placeholder",isDisabled:d,isFocused:b}),f);if(p)return g.map((function(t,o){var i=t===v;return ee().createElement(n,Q({},a,{components:{Container:r,Label:l,Remove:s},isFocused:i,isDisabled:d,key:"".concat(e.getOptionValue(t)).concat(o),index:o,removeProps:{onClick:function(){return e.removeValue(t)},onTouchEnd:function(){return e.removeValue(t)},onMouseDown:function(e){e.preventDefault(),e.stopPropagation()}},data:t}),e.formatOptionLabel(t,"value"))}));if(m)return null;var _=g[0];return ee().createElement(o,Q({},a,{data:_,isDisabled:d}),this.formatOptionLabel(_,"value"))}},{key:"renderClearIndicator",value:function(){var e=this.getComponents().ClearIndicator,t=this.commonProps,n=this.props,r=n.isDisabled,l=n.isLoading,s=this.state.isFocused;if(!this.isClearable()||!e||r||!this.hasValue()||l)return null;var o={onMouseDown:this.onClearIndicatorMouseDown,onTouchEnd:this.onClearIndicatorTouchEnd,"aria-hidden":"true"};return ee().createElement(e,Q({},t,{innerProps:o,isFocused:s}))}},{key:"renderLoadingIndicator",value:function(){var e=this.getComponents().LoadingIndicator,t=this.commonProps,n=this.props,r=n.isDisabled,l=n.isLoading,s=this.state.isFocused;return e&&l?ee().createElement(e,Q({},t,{innerProps:{"aria-hidden":"true"},isDisabled:r,isFocused:s})):null}},{key:"renderIndicatorSeparator",value:function(){var e=this.getComponents(),t=e.DropdownIndicator,n=e.IndicatorSeparator;if(!t||!n)return null;var r=this.commonProps,l=this.props.isDisabled,s=this.state.isFocused;return ee().createElement(n,Q({},r,{isDisabled:l,isFocused:s}))}},{key:"renderDropdownIndicator",value:function(){var e=this.getComponents().DropdownIndicator;if(!e)return null;var t=this.commonProps,n=this.props.isDisabled,r=this.state.isFocused,l={onMouseDown:this.onDropdownIndicatorMouseDown,onTouchEnd:this.onDropdownIndicatorTouchEnd,"aria-hidden":"true"};return ee().createElement(e,Q({},t,{innerProps:l,isDisabled:n,isFocused:r}))}},{key:"renderMenu",value:function(){var e=this,t=this.getComponents(),n=t.Group,r=t.GroupHeading,l=t.Menu,s=t.MenuList,o=t.MenuPortal,i=t.LoadingMessage,a=t.NoOptionsMessage,c=t.Option,u=this.commonProps,d=this.state.focusedOption,p=this.props,m=p.captureMenuScroll,f=p.inputValue,h=p.isLoading,g=p.loadingMessage,v=p.minMenuHeight,b=p.maxMenuHeight,_=p.menuIsOpen,y=p.menuPlacement,w=p.menuPosition,E=p.menuPortalTarget,x=p.menuShouldBlockScroll,C=p.menuShouldScrollIntoView,k=p.noOptionsMessage,S=p.onMenuScrollToTop,O=p.onMenuScrollToBottom;if(!_)return null;var I,T=function(t,n){var r=t.type,l=t.data,s=t.isDisabled,o=t.isSelected,i=t.label,a=t.value,p=d===l,m=s?void 0:function(){return e.onOptionHover(l)},f=s?void 0:function(){return e.selectOption(l)},h="".concat(e.getElementId("option"),"-").concat(n),g={id:h,onClick:f,onMouseMove:m,onMouseOver:m,tabIndex:-1};return ee().createElement(c,Q({},u,{innerProps:g,data:l,isDisabled:s,isSelected:o,key:h,label:i,type:r,value:a,isFocused:p,innerRef:p?e.getFocusedOptionRef:void 0}),e.formatOptionLabel(t.data,"menu"))};if(this.hasOptions())I=this.getCategorizedOptions().map((function(t){if("group"===t.type){var l=t.data,s=t.options,o=t.index,i="".concat(e.getElementId("group"),"-").concat(o),a="".concat(i,"-heading");return ee().createElement(n,Q({},u,{key:i,data:l,options:s,Heading:r,headingProps:{id:a,data:t.data},label:e.formatGroupLabel(t.data)}),t.options.map((function(e){return T(e,"".concat(o,"-").concat(e.index))})))}if("option"===t.type)return T(t,"".concat(t.index))}));else if(h){var P=g({inputValue:f});if(null===P)return null;I=ee().createElement(i,u,P)}else{var M=k({inputValue:f});if(null===M)return null;I=ee().createElement(a,u,M)}var D={minMenuHeight:v,maxMenuHeight:b,menuPlacement:y,menuPosition:w,menuShouldScrollIntoView:C},L=ee().createElement(pn,Q({},u,D),(function(t){var n=t.ref,r=t.placerProps,o=r.placement,i=r.maxHeight;return ee().createElement(l,Q({},u,D,{innerRef:n,innerProps:{onMouseDown:e.onMenuMouseDown,onMouseMove:e.onMenuMouseMove},isLoading:h,placement:o}),ee().createElement(hr,{captureEnabled:m,onTopArrive:S,onBottomArrive:O,lockEnabled:x},(function(t){return ee().createElement(s,Q({},u,{innerRef:function(n){e.getMenuListRef(n),t(n)},isLoading:h,maxHeight:i,focusedOption:d}),I)})))}));return E||"fixed"===w?ee().createElement(o,Q({},u,{appendTo:E,controlElement:this.controlRef,menuPlacement:y,menuPosition:w}),L):L}},{key:"renderFormField",value:function(){var e=this,t=this.props,n=t.delimiter,r=t.isDisabled,l=t.isMulti,s=t.name,o=this.state.selectValue;if(s&&!r){if(l){if(n){var i=o.map((function(t){return e.getOptionValue(t)})).join(n);return ee().createElement("input",{name:s,type:"hidden",value:i})}var a=o.length>0?o.map((function(t,n){return ee().createElement("input",{key:"i-".concat(n),name:s,type:"hidden",value:e.getOptionValue(t)})})):ee().createElement("input",{name:s,type:"hidden"});return ee().createElement("div",null,a)}var c=o[0]?this.getOptionValue(o[0]):"";return ee().createElement("input",{name:s,type:"hidden",value:c})}}},{key:"renderLiveRegion",value:function(){var e=this.commonProps,t=this.state,n=t.ariaSelection,r=t.focusedOption,l=t.focusedValue,s=t.isFocused,o=t.selectValue,i=this.getFocusableOptions();return ee().createElement(Gn,Q({},e,{ariaSelection:n,focusedOption:r,focusedValue:l,isFocused:s,selectValue:o,focusableOptions:i}))}},{key:"render",value:function(){var e=this.getComponents(),t=e.Control,n=e.IndicatorsContainer,r=e.SelectContainer,l=e.ValueContainer,s=this.props,o=s.className,i=s.id,a=s.isDisabled,c=s.menuIsOpen,u=this.state.isFocused,d=this.commonProps=this.getCommonProps();return ee().createElement(r,Q({},d,{className:o,innerProps:{id:i,onKeyDown:this.onKeyDown},isDisabled:a,isFocused:u}),this.renderLiveRegion(),ee().createElement(t,Q({},d,{innerRef:this.getControlRef,innerProps:{onMouseDown:this.onControlMouseDown,onTouchEnd:this.onControlTouchEnd},isDisabled:a,isFocused:u,menuIsOpen:c}),ee().createElement(l,Q({},d,{isDisabled:a}),this.renderPlaceholderOrValue(),this.renderInput()),ee().createElement(n,Q({},d,{isDisabled:a}),this.renderClearIndicator(),this.renderLoadingIndicator(),this.renderIndicatorSeparator(),this.renderDropdownIndicator())),this.renderMenu(),this.renderFormField())}}],[{key:"getDerivedStateFromProps",value:function(e,t){var n=t.prevProps,r=t.clearFocusValueOnUpdate,l=t.inputIsHiddenAfterUpdate,s=e.options,o=e.value,i=e.menuIsOpen,a=e.inputValue,c={};if(n&&(o!==n.value||s!==n.options||i!==n.menuIsOpen||a!==n.inputValue)){var u=Xt(o),d=i?function(e,t){return wr(yr(e,t))}(e,u):[],p=r?function(e,t){var n=e.focusedValue,r=e.selectValue.indexOf(n);if(r>-1){if(t.indexOf(n)>-1)return n;if(r-1?n:t[0]}(t,d);c={selectValue:u,focusedOption:m,focusedValue:p,clearFocusValueOnUpdate:!1}}var f=null!=l&&e!==n?{inputIsHidden:l,inputIsHiddenAfterUpdate:void 0}:{};return Ut(Ut(Ut({},c),f),{},{prevProps:e})}}]),n}(J.Component);Pr.defaultProps=br;var Mr,Dr,Lr,Rr={cacheOptions:!1,defaultOptions:!1,filterOption:null,isLoading:!1},Vr=(Mr=Pr,Lr=Dr=function(e){Bt(n,e);var t=Gt(n);function n(){var e;Vt(this,n);for(var r=arguments.length,l=new Array(r),s=0;s1?n-1:0),l=1;l"llms-search")),Y(this,"getSearchPath",(()=>this.props.searchPath)),Y(this,"getSearchUrl",(e=>wp.url.addQueryArgs(this.getSearchPath(),this.getSearchArgs(e)))),Y(this,"formatSearchResultLabel",(e=>e.id)),Y(this,"formatSearchResultValue",(e=>e.id)),Y(this,"onSearch",(0,X.debounce)(300,((e,t)=>{wp.apiFetch({path:this.getSearchUrl(e)}).then((e=>{t(this.formatSearchResults(e))}))})))}getSearchArgs(e){return Object.assign({per_page:20,search:encodeURI(e)},this.props.searchArgs)}formatSearchResults(e){return e.map((e=>({...e,label:this.formatSearchResultLabel(e),value:this.formatSearchResultValue(e)})))}render(){const{label:e,isMulti:t,isDisabled:n,onChange:r,placeholder:l,selected:s}=this.props,o=this.props.className||this.getDefaultClassName();return(0,A.createElement)(q.BaseControl,{id:(0,Fr.uniqueId)(`${o}--`),label:e},(0,A.createElement)(Ar,{ref:e=>this.selectRef=e,className:o,classNamePrefix:"llms-search",isMulti:t,isDisabled:n,value:this.formatSearchResults(s||[]),defaultOptions:s,placeholder:l,loadOptions:this.onSearch,onChange:r,styles:{control:e=>({...e,borderColor:"#8d96a0","&:hover":{...e["&:hover"],borderColor:"#8d96a0"}})},theme:e=>({...e,colors:{...e.colors,primary:"#008dbe",primary25:"#ccf2ff",primary50:"#b3ecff",primary75:"#4dd2ff"},spacing:{...e.spacing,baseUnit:2,controlHeight:28,menuGutter:4}})}))}}class Hr extends Br{constructor(){super(...arguments),Y(this,"getDefaultClassName",(()=>`llms-search--${this.props.postType.replace("llms_","")}`)),Y(this,"getSearchPath",(()=>this.props.searchPath||`/wp/v2/${this.props.postType}`)),Y(this,"formatSearchResultLabel",(e=>(0,$.sprintf)(
+// Translators: %1$s = Post title; %2$ = post id.
+(0,$._x)("%1$s (ID# %2$d)","Search result label","lifterlms"),e.title.rendered,e.id)))}}const zr=(0,U.createHigherOrderComponent)((e=>t=>{if(!z(wp.blocks.getBlockType(t.name),t.name))return(0,A.createElement)(e,t);const{attributes:{llms_visibility:n,llms_visibility_in:r},setAttributes:l}=t;if(!n||"off"===n)return(0,A.createElement)(e,t);let{llms_visibility_posts:s}=t.attributes;void 0===s&&(s="[]"),s=JSON.parse(s);const o=()=>{const e=wp.data.select("core/editor").getCurrentPost(),t=[];return-1!==["course","lesson"].indexOf(e.type)&&t.push({value:"this",label:(0,$.__)("in this course","lifterlms")}),t.push({value:"any_course",label:(0,$.__)("in any course","lifterlms")}),-1!==["llms_membership"].indexOf(e.type)&&t.push({value:"this",label:(0,$.__)("in this membership","lifterlms")}),t.push({value:"any_membership",label:(0,$.__)("in any membership","lifterlms")},{value:"any",label:(0,$.__)("in any course or membership","lifterlms")},{value:"list_all",label:(0,$.__)("in all of the selected courses or memberships","lifterlms")},{value:"list_any",label:(0,$.__)("in any of the selected courses or memberships","lifterlms")}),(0,H.applyFilters)("llms_blocks_block_visibility_in_options",t,e)},i=(e,t)=>{"select-option"===t.action?a(t.option):"remove-value"===t.action&&c(t.removedValue)},a=e=>{s.map((e=>{let{id:t}=e;return t})).includes(e.id)||s.push(e),u()},c=e=>{s.splice(s.map((e=>{let{id:t}=e;return t})).indexOf(e.id),1),u()},u=()=>{const e=s.map((e=>{const{id:t,title:n,type:r}=e,l={id:t,title:n,type:r};return(0,H.applyFilters)("llms_block_visibility_stored_post_props",l,e)}));l({llms_visibility_posts:JSON.stringify(e)})};return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(K,t,(0,A.createElement)(e,t)),(0,A.createElement)(j.InspectorControls,null,(0,A.createElement)(q.PanelBody,{title:(0,$.__)("Enrollment Visibility","lifterlms")},(0,A.createElement)(q.SelectControl,{className:"llms-visibility-select",label:(0,$.__)("Display to","lifterlms"),value:n,onChange:e=>{l({llms_visibility:e,llms_visibility_in:o()[0].value})},options:(0,H.applyFilters)("llms_block_visibility_settings_options",W)}),-1===["all","logged_in","logged_out"].indexOf(n)&&(0,A.createElement)(A.Fragment,null,(0,A.createElement)(q.SelectControl,{className:"llms-visibility-select--in",label:(d=n,"enrolled"===d?(0,$.__)("Enrolled In","lifterlms"):(0,$.__)("Not Enrolled In","lifterlms")),value:r,onChange:e=>l({llms_visibility_in:e}),options:o()}),("list_all"===r||"list_any"===r)&&(0,A.createElement)("div",null,(0,A.createElement)(Hr,{isMulti:!0,postType:"course",label:(0,$.__)("Courses","lifterlms"),placeholder:(0,$.__)("Search by course title…","lifterlms"),onChange:i,selected:s.filter((e=>"course"===e.type))}),(0,A.createElement)(Hr,{isMulti:!0,postType:"llms_membership",label:(0,$.__)("Memberships","lifterlms"),placeholder:(0,$.__)("Search by membership title…","lifterlms"),onChange:i,selected:s.filter((e=>"llms_membership"===e.type))}))))));var d}),"withInspectorControl");(0,H.addFilter)("blocks.registerBlockType","llms/visibility-attributes",(function(e,t){if(!z(e,t))return e;e.attributes||(e.attributes={});const n={llms_visibility:{default:"all",type:"string"},llms_visibility_in:{default:"",type:"string"},llms_visibility_posts:{default:"[]",type:"string"}};return Object.keys(n).forEach((t=>{var r,l,s;e.attributes=(r=e.attributes,s=n[t],r[l=t]&&r[l].default?r[l].type=s.type:r[l]=s,r)})),e})),(0,H.addFilter)("editor.BlockEdit","llms/visibility-controls",zr);const $r=window.wp.domReady;var Ur=n.n($r);const jr=window.wp.data,qr=window.wp.editor,Gr=window.wp.blocks,Wr=e=>{let t=[];return e.forEach((e=>{if("core/block"===e.name){const{getBlocks:n}=(0,jr.select)(j.store);t=t.concat(Wr(n(e.clientId)))}else e.innerBlocks.length?t=t.concat(Wr(e.innerBlocks)):t.push(e)})),t},Kr=()=>{const{getBlocks:e}=(0,jr.select)(j.store);return Wr(e())},Yr=()=>!!(window.llms&&window.llms.post&&window.llms.post.post_type)&&window.llms.post.post_type;const Xr=()=>{!function(){const{_llms_form_location:e}=(0,jr.select)(qr.store).getEditedPostAttribute("meta");["registration","account"].includes(e)&&(0,H.addFilter)("llms_block_supports_visibility","llms/form-block-visibility",(()=>!1))}(),function(){const e={"llms/form-field-user-email":["all","logged_out"],"llms/form-field-user-password":["all","logged_out"],"llms/form-field-user-login":["logged_out"]},t=Object.keys(e);(0,H.addFilter)("llms_block_visibility_settings_options","llms/form-block-visibility-options",(n=>{const{getSelectedBlock:r}=(0,jr.select)(j.store),l=r();return l&&(e=>{let{name:n,innerBlocks:r}=e;return"llms/form-field-confirm-group"===n?(0,Fr.some)(r,(e=>t.includes(e.name))):t.includes(n)})(l)?n.filter((n=>{let{value:r}=n;return(n=>{let{name:r,innerBlocks:l}=n,s=r;if("llms/form-field-confirm-group"===r){const e=l.find((e=>t.includes(e.name)));s=e?e.name:s}return e[s]||[]})(l).includes(r)})):n}))}(),function(){const{_llms_form_is_core:e}=(0,jr.select)(qr.store).getEditedPostAttribute("meta"),t=[".edit-post-layout .components-panel__body.edit-post-post-status"];"yes"===e&&t.push(".edit-post-layout button.editor-post-switch-to-draft"),(0,jr.subscribe)((()=>{setTimeout((()=>{document.querySelectorAll(t.join(",")).forEach((e=>{e.style.display="none"}))}),1)}))}(),function(){const e="llms/form-field-user-email",t="llms-forms-no-email-error-notice",{getNotices:n}=(0,jr.select)("core/notices"),{createErrorNotice:r,removeNotice:l}=(0,jr.dispatch)("core/notices");(0,jr.subscribe)((0,Fr.debounce)((()=>{const s=(0,jr.select)("core/editor").getCurrentPost(),o=Kr().map((e=>e.name));if(!s.content.includes("\x3c!-- wp:")||!o.length)return;const i=n().map((e=>e.id)).includes(t),a=document.querySelector("button.editor-post-publish-button");o.includes(e)||i?o.includes(e)&&i&&(l(t),a.disabled=!1):(r((0,$.__)("User Email is a required field.","lifterlms"),{id:t,isDismissible:!1,actions:[{label:(0,$.__)("Restore user email field?","lifterlms"),onClick:()=>{((0,jr.dispatch)("core/block-editor")||(0,jr.dispatch)("core/editor")).insertBlock((0,Gr.createBlock)(e),0)}}]}),a.disabled=!0)}),500))}()};function Qr(e){return e.reduce(((e,t)=>({...e,[t.name]:t})),{})}function Zr(e){return Object.values(e)}const Jr=Qr(window.llms.userInfoFields.map((e=>({...e,isPersisted:!0}))));function el(e,t){return{...e,[t.name]:{...t}}}function tl(e,t){return e=Zr(e).filter((e=>{let{name:n}=e;return n!==t})),Qr(e)}function nl(e,t,n){return{...e,[t]:{...e[t],...n}}}function rl(e,t,n){const r={...e[t]};return el(e=tl(e,t),{...r,name:n})}function ll(){return Jr}function sl(e){return{type:"ADD_FIELD",field:e}}function ol(e){return{type:"DELETE_FIELD",name:e}}function il(e,t){return{type:"EDIT_FIELD",name:e,edits:t}}function al(e,t){return{type:"EDIT_FIELD",name:e,edits:{clientId:t}}}function cl(e){return{type:"EDIT_FIELD",name:e,edits:{clientId:null}}}function ul(e){return{type:"RECEIVE_FIELDS",fields:e}}function dl(e,t){return{type:"RENAME_FIELD",oldName:e,newName:t}}function pl(){return{type:"RESET_FIELDS"}}function ml(e,t){let{fields:n}=e;return!!n[t]}function fl(e,t){let{fields:n}=e;return n[t]||null}function hl(e,t,n){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"global";const l="global"===r?e.fields:vl(e);return Zr(l).find((e=>e[n]===t))||null}function gl(e){let{fields:t}=e;return t}function vl(e){let{fields:t}=e;return Qr(Zr(t).filter((e=>{let{clientId:t}=e;return t})))}function bl(e,t,n){const r=fl(e,t);return!(!r||!r.clientId||r.clientId===n)}function _l(e,t){return!!hl(e,t,"clientId","local")}const yl={reducer:(0,jr.combineReducers)({fields:function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Jr,t=arguments.length>1?arguments[1]:void 0;const{type:n}=t;switch(n){case"ADD_FIELD":return el(e,t.field);case"DELETE_FIELD":return tl(e,t.name);case"EDIT_FIELD":return nl(e,t.name,t.edits);case"RECEIVE_FIELDS":return Qr(t.fields);case"RENAME_FIELD":return rl(e,t.oldName,t.newName);case"RESET_FIELDS":return ll();default:return e}}}),actions:{...e},selectors:{...t}},wl=(0,jr.createReduxStore)("llms/user-info-fields",yl);(0,jr.register)(wl);let El=[];const xl=(e,t)=>(0,Fr.differenceBy)(e,t,"clientId").filter((e=>{let{name:t}=e;return 0===t.indexOf("llms/form-")}));function Cl(){const e=Kr(),t=xl(El,e),n=xl(e,El);El=e,(e=>{e.forEach((e=>{let{attributes:t}=e;const{name:n}=t,{getField:r}=(0,jr.select)(wl),{deleteField:l,unloadField:s}=(0,jr.dispatch)(wl),o=r(n);o&&(o.isPersisted?s(n):l(n))}))})(t),setTimeout((()=>{(e=>{const{fieldExists:t}=(0,jr.select)(wl),{loadField:n,addField:r}=(0,jr.dispatch)(wl);e.forEach((e=>{let{attributes:l,clientId:s}=e;const{name:o}=l;t(o)?n(o,s):r({name:o,clientId:s,id:l.id,label:l.label,data_store:l.data_store,data_store_key:l.data_store_key})}))})(n)}),100)}function kl(){(0,jr.subscribe)(Cl)}const Sl=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(F.Path,{d:"M52.5 440.6c-9.5 7.9-22.8 9.7-34.1 4.4S0 428.4 0 416V96C0 83.6 7.2 72.3 18.4 67s24.5-3.6 34.1 4.4L224 214.3V256v41.7L52.5 440.6zM256 352V256 128 96c0-12.4 7.2-23.7 18.4-29s24.5-3.6 34.1 4.4l192 160c7.3 6.1 11.5 15.1 11.5 24.6s-4.2 18.5-11.5 24.6l-192 160c-9.5 7.9-22.8 9.7-34.1 4.4s-18.4-16.6-18.4-29V352z"})),Ol="llms/course-continue-button",Il=["course"],Tl={title:(0,$.__)("Course Continue Button","lifterlms"),icon:Sl,category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms")],edit:e=>(0,A.createElement)("div",{className:e.className},(0,A.createElement)("p",{style:{textAlign:"center"}},(0,A.createElement)(q.Button,{isPrimary:!0,isLarge:!0},(0,$.__)("Continue","lifterlms")))),save:e=>(0,A.createElement)("div",{className:e.className,style:{textAlign:"center"}},"[lifterlms_course_continue_button]")};class Pl extends A.Component{render(){const{attributes:{length:e,show_cats:t,show_difficulty:n,show_length:r,show_tags:l,show_tracks:s,title_size:o},setAttributes:i}=this.props;return(0,A.createElement)(j.InspectorControls,null,(0,A.createElement)(q.PanelBody,{title:(0,$.__)("Course Information Options","lifterlms")},(0,A.createElement)(q.SelectControl,{label:(0,$.__)("Title Headline Size","lifterlms"),value:o,onChange:e=>i({title_size:e}),help:(0,$.__)("Headline size for the information title element.","lifterlms"),options:[{value:"h1",label:"h1"},{value:"h2",label:"h2"},{value:"h3",label:"h3"},{value:"h4",label:"h4"},{value:"h5",label:"h5"},{value:"h6",label:"h6"}]}),(0,A.createElement)(q.TextControl,{label:(0,$.__)("Estimated Completion Time","lifterlms"),value:e,onChange:e=>i({length:e}),help:(0,$.__)("How many hours, days, weeks, etc… should a student expect to spend in order to complete this course.","lifterlms")}),(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Display Estimated Time","lifterlms"),checked:!!r,onChange:()=>i({show_length:!r}),help:r?(0,$.__)("Displaying estimated time","lifterlms"):(0,$.__)("Hiding estimated time","lifterlms")}),(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Display Difficulty","lifterlms"),checked:!!n,onChange:()=>i({show_difficulty:!n}),help:n?(0,$.__)("Displaying difficulty","lifterlms"):(0,$.__)("Hiding difficulty","lifterlms")}),(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Display Tracks","lifterlms"),checked:!!s,onChange:()=>i({show_tracks:!s}),help:s?(0,$.__)("Displaying tracks list","lifterlms"):(0,$.__)("Hiding tracks list","lifterlms")}),(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Display Categories","lifterlms"),checked:!!t,onChange:()=>i({show_cats:!t}),help:t?(0,$.__)("Displaying categories list","lifterlms"):(0,$.__)("Hiding categories list","lifterlms")}),(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Display Tags","lifterlms"),checked:!!l,onChange:()=>i({show_tags:!l}),help:l?(0,$.__)("Displaying tags list","lifterlms"):(0,$.__)("Hiding tags list","lifterlms")})))}}class Ml extends A.Component{constructor(){super(...arguments),Y(this,"state",{terms:!1})}getTerms(){const{currentPost:e,taxonomy:t}=this.props,n=e._links["wp:term"].filter((e=>e.taxonomy===t))[0].href;wp.apiFetch({url:wp.url.addQueryArgs(n,{per_page:-1})}).then((e=>{this.setState({terms:e})}))}componentDidUpdate(e){e.currentPost[this.props.taxonomy]!==this.props.currentPost[this.props.taxonomy]&&this.getTerms()}componentWillMount(){this.getTerms()}renderTerms(e){const t=e.length-1;return(0,A.createElement)(A.Fragment,null,e?e.map(((e,n)=>this.renderTerm(e,t===n))):(0,$.__)("Loading…","lifterlms"))}renderTerm(e,t){return(0,A.createElement)(A.Fragment,null,(0,A.createElement)("a",{href:e.link,target:"_blank",rel:"noreferrer"},e.name),t?"":", ")}render(){const{terms:e}=this.state,{taxonomyName:t}=this.props;return Array.isArray(e)&&!e.length?"":(0,A.createElement)("li",null,(0,A.createElement)("strong",null,t),": ",this.renderTerms(e))}}const Dl=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(F.Path,{d:"M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zm64 0v64h64V96H64zm384 0H192v64H448V96zM64 224v64h64V224H64zm384 0H192v64H448V224zM64 352v64h64V352H64zm384 0H192v64H448V352z"})),Ll="llms/course-information",Rl=["course"],Vl={title:(0,$.__)("Course Information","lifterlms"),icon:Dl,category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms")],attributes:{title:{type:"string",default:(0,$.__)("Course Information","lifterlms")},title_size:{type:"string",default:"h3"},length:{type:"string",default:"",source:"meta",meta:"_llms_length"},show_cats:{type:"boolean",default:!0},show_difficulty:{type:"boolean",default:!0},show_length:{type:"boolean",default:!0},show_tags:{type:"boolean",default:!0},show_tracks:{type:"boolean",default:!0}},supports:{multiple:!1},edit:e=>{const{attributes:t,setAttributes:n}=e,{length:r,show_cats:l,show_difficulty:s,show_length:o,show_tags:i,show_tracks:a,title:c,title_size:u}=t,d=wp.data.select("core/editor").getCurrentPost(),p=o||s||a||l||i;return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(Pl,{attributes:t,setAttributes:n}),(0,A.createElement)("div",{className:e.className},(0,A.createElement)(j.RichText,{tagName:u,value:c,onChange:e=>n({title:e})}),p&&(0,A.createElement)(A.Fragment,null,(0,A.createElement)("ul",null,o&&r&&(0,A.createElement)("li",null,(0,A.createElement)("strong",null,(0,$.__)("Estimated Time","lifterlms")),": ",r),s&&(0,A.createElement)(Ml,{currentPost:d,taxonomy:"course_difficulty",taxonomyName:(0,$.__)("Difficulty","lifterlms")}),a&&(0,A.createElement)(Ml,{currentPost:d,taxonomy:"course_track",taxonomyName:(0,$.__)("Tracks","lifterlms")}),l&&(0,A.createElement)(Ml,{currentPost:d,taxonomy:"course_cat",taxonomyName:(0,$.__)("Categories","lifterlms")}),i&&(0,A.createElement)(Ml,{currentPost:d,taxonomy:"course_tag",taxonomyName:(0,$.__)("Tags","lifterlms")})))))},save:()=>null},Nl=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(F.Path,{d:"M64 64c0-17.7-14.3-32-32-32S0 46.3 0 64V400c0 44.2 35.8 80 80 80H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H80c-8.8 0-16-7.2-16-16V64zm406.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L320 210.7l-57.4-57.4c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L240 221.3l57.4 57.4c12.5 12.5 32.8 12.5 45.3 0l128-128z"})),Al=["course"],Fl="llms/course-progress",Bl={title:(0,$.__)("Course Progress","lifterlms"),icon:Nl,category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms")],supports:{llms_visibility:!1},edit:e=>(0,A.createElement)("div",{className:e.className},(0,A.createElement)("div",{className:"progress-bar",value:"50",max:"100"},(0,A.createElement)("div",{className:"progress--fill"})),(0,A.createElement)("span",null,"50%")),save:()=>null,deprecated:[{save:e=>(0,A.createElement)("div",{className:e.className},"[lifterlms_course_progress]")}]},Hl=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 640 512"},(0,A.createElement)(F.Path,{d:"M192 96a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm-8 384V352h16V480c0 17.7 14.3 32 32 32s32-14.3 32-32V192h56 64 16c17.7 0 32-14.3 32-32s-14.3-32-32-32H384V64H576V256H384V224H320v48c0 26.5 21.5 48 48 48H592c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48H368c-26.5 0-48 21.5-48 48v80H243.1 177.1c-33.7 0-64.9 17.7-82.3 46.6l-58.3 97c-9.1 15.1-4.2 34.8 10.9 43.9s34.8 4.2 43.9-10.9L120 256.9V480c0 17.7 14.3 32 32 32s32-14.3 32-32z"})),zl=window.wp.serverSideRender;var $l=n.n(zl);class Ul extends A.Component{constructor(){super(...arguments),Y(this,"render",(()=>{const{name:e,attributes:t,post_id:n}=this.props;return(0,A.createElement)(A.Fragment,null,(0,A.createElement)($l(),{block:e,attributes:t,urlQueryArgs:{post_id:n}}))})),this.state={instructors:this.props.instructors}}}const jl=(0,U.compose)([(0,jr.withSelect)((e=>{const{getEditedPostAttribute:t,getCurrentPostId:n}=e("core/editor");return{post_id:n(),instructors:t("instructors")}}))])(Ul),ql="llms/instructors",Gl=["course","llms_membership"],Wl={title:(0,$.__)("Instructors","lifterlms"),icon:Hl,category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms"),(0,$.__)("Course","lifterlms"),(0,$.__)("Memebership","lifterlms")],attributes:{post_id:{type:"int",default:0}},edit:jl,save:()=>null},Kl=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 640 512"},(0,A.createElement)(F.Path,{d:"M32 64c17.7 0 32 14.3 32 32l0 320c0 17.7-14.3 32-32 32s-32-14.3-32-32V96C0 78.3 14.3 64 32 64zm214.6 73.4c12.5 12.5 12.5 32.8 0 45.3L205.3 224l229.5 0-41.4-41.4c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l96 96c12.5 12.5 12.5 32.8 0 45.3l-96 96c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L434.7 288l-229.5 0 41.4 41.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-96-96c-12.5-12.5-12.5-32.8 0-45.3l96-96c12.5-12.5 32.8-12.5 45.3 0zM640 96V416c0 17.7-14.3 32-32 32s-32-14.3-32-32V96c0-17.7 14.3-32 32-32s32 14.3 32 32z"})),Yl="llms/lesson-navigation",Xl=["lesson"],Ql={title:(0,$.__)("Lesson Navigation","lifterlms"),icon:Kl,category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms")],edit(e){const t=wp.data.select("core/editor").getCurrentPost(),{attributes:n}=e;return(0,A.createElement)(A.Fragment,null,(0,A.createElement)($l(),{block:Yl,attributes:n,urlQueryArgs:{post_id:t.id}}))},save:()=>null},Zl=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(F.Path,{d:"M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"})),Jl="llms/lesson-progression",es=["lesson"],ts={title:(0,$.__)("Lesson Progression (Mark Complete)","lifterlms"),icon:Zl,category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms")],supports:{llms_visibility:!1},edit(){const e=1*(0,jr.select)("core/editor").getCurrentPost().meta._llms_quiz;let t=!e;return t=(0,H.applyFilters)("llms.lessonProgressBlock.showMainBtn",t),(0,A.createElement)(A.Fragment,null,(0,A.createElement)("div",{className:"llms-lesson-button-wrapper"},(0,A.createElement)(A.Fragment,null,!!e&&(0,A.createElement)(q.Button,{className:"llms-prog-btn--quiz llms-button-action auto button"},(0,$.__)("Take Quiz","lifterlms")),t&&(0,A.createElement)(q.Button,{className:"llms-prog-btn--complete llms-field-button llms-button-primary auto button"},(0,$.__)("Mark Complete","lifterlms")))))},save:()=>null},ns=window.jQuery;var rs=n.n(ns);const ls=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 576 512"},(0,A.createElement)(F.Path,{d:"M64 64C28.7 64 0 92.7 0 128V384c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H64zM272 192H496c8.8 0 16 7.2 16 16s-7.2 16-16 16H272c-8.8 0-16-7.2-16-16s7.2-16 16-16zM256 304c0-8.8 7.2-16 16-16H496c8.8 0 16 7.2 16 16s-7.2 16-16 16H272c-8.8 0-16-7.2-16-16zM164 152v13.9c7.5 1.2 14.6 2.9 21.1 4.7c10.7 2.8 17 13.8 14.2 24.5s-13.8 17-24.5 14.2c-11-2.9-21.6-5-31.2-5.2c-7.9-.1-16 1.8-21.5 5c-4.8 2.8-6.2 5.6-6.2 9.3c0 1.8 .1 3.5 5.3 6.7c6.3 3.8 15.5 6.7 28.3 10.5l.7 .2c11.2 3.4 25.6 7.7 37.1 15c12.9 8.1 24.3 21.3 24.6 41.6c.3 20.9-10.5 36.1-24.8 45c-7.2 4.5-15.2 7.3-23.2 9V360c0 11-9 20-20 20s-20-9-20-20V345.4c-10.3-2.2-20-5.5-28.2-8.4l0 0 0 0c-2.1-.7-4.1-1.4-6.1-2.1c-10.5-3.5-16.1-14.8-12.6-25.3s14.8-16.1 25.3-12.6c2.5 .8 4.9 1.7 7.2 2.4c13.6 4.6 24 8.1 35.1 8.5c8.6 .3 16.5-1.6 21.4-4.7c4.1-2.5 6-5.5 5.9-10.5c0-2.9-.8-5-5.9-8.2c-6.3-4-15.4-6.9-28-10.7l-1.7-.5c-10.9-3.3-24.6-7.4-35.6-14c-12.7-7.7-24.6-20.5-24.7-40.7c-.1-21.1 11.8-35.7 25.8-43.9c6.9-4.1 14.5-6.8 22.2-8.5V152c0-11 9-20 20-20s20 9 20 20z"}));let ss=null;(0,jr.subscribe)((()=>{const{getCurrentPostLastRevisionId:e,isCurrentPostPublished:t,isSavingPost:n,isPublishingPost:r}=(0,jr.select)("core/editor");if(!t())return;const l=rs()("#llms-save-access-plans");l.length&&ss!==e()&&"disabled"!==l.attr("disabled")&&(n()||r())&&(ss=e(),l.trigger("click"))})),rs()(document).on("llms-access-plan-validation-errors",(function(){(0,jr.dispatch)("core/notices").createErrorNotice((0,$.__)("Validation errors were encountered while attempting to save your access plans.","lifterlms"),{id:"llms-access-plan-error-notice"})}));const os="llms/pricing-table",is=["course","llms_membership"],as={title:(0,$.__)("LifterLMS Pricing Table","lifterlms"),icon:ls,category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms")],attributes:{post_id:{type:"int",default:0}},edit:e=>{const{attributes:t}=e;return rs()(document).one("llms-access-plans-updated",(function(){(0,jr.dispatch)("core/editor").replaceBlock(e.clientId,(0,Gr.createBlock)(os)),setTimeout((function(){(0,jr.dispatch)("core/editor").savePost()}),500)})),(0,A.createElement)(A.Fragment,null,(0,A.createElement)($l(),{block:os,attributes:t,urlQueryArgs:{post_id:(0,jr.select)("core/editor").getCurrentPostId()}}))},save:()=>null},cs="llms/php-template",us={title:(0,$.__)("LifterLMS PHP Template","lifterlms"),category:"llms-blocks",keywords:[(0,$.__)("LifterLMS","lifterlms")],attributes:{template:{type:"string",default:""},title:{type:"string",default:""}},supports:{html:!1,multiple:!1,reusable:!1,inserter:!1},edit:function(e){const{attributes:t}=e,{template:n}=t,r=(0,j.useBlockProps)();let{title:l}=t;if(!l){const e=window.llmsBlockTemplatesL10n;l=e&&e[n]?e[n]:n}return(0,A.createElement)("div",r,(0,A.createElement)(q.Placeholder,{label:l,className:"wp-block-liftelrms-php-template__placeholder"},(0,A.createElement)("div",{className:"wp-block-liftelrms-php-template__placeholder-copy"},(0,A.createElement)("p",{className:"wp-block-liftelrms-php-template__placeholder-warning"},(0,A.createElement)("strong",null,(0,$.__)("Attention: Do not remove this block!","lifterlms"))," ",(0,$.__)("Removal will cause unintended effects on your LMS site.","lifterlms")),(0,A.createElement)("p",null,(0,$.sprintf)(/* translators: %s is the template title */
+(0,$.__)("This is an editor placeholder for the %s. On your site this will be replaced by the relevant template. You can move this placeholder around and add further blocks around it to extend the template.","lifterlms"),l)))))},save:()=>null};function ds(e){if(!e)return[e];if(e.innerBlocks.length)return ps(e.innerBlocks);if("core/block"===e.name){const{blocks:t}=(0,jr.select)("core").getEditedEntityRecord("postType","wp_block",e.attributes.ref);return ps(t)}return-1===e.name.indexOf("llms/form-field")?[]:[e]}function ps(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=[];return(0,Fr.forEach)(e,(e=>{const n=ds(e);n.length&&(t=t.concat(n))})),t}const ms=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"global";const{getFieldBy:r}=(0,jr.select)(wl);return!r(t,e,n)};if("wp_block"===Yr()){let e="";(0,jr.subscribe)((()=>{const t=(0,jr.select)("core/editor").getEditedPostContent();if(void 0===t||t===e)return;e=t;const n=t.includes("\x3c!-- wp:llms/form-field")?"yes":"no";(0,jr.dispatch)("core/editor").editPost({is_llms_field:n})}))}(0,H.addFilter)("blocks.getSaveElement","llms/core-block/save",((e,t,n)=>{if("core/block"!==t.name)return e;const{ref:r}=n;if((0,jr.select)("core").hasFinishedResolution("getEntityRecord",["postType","wp_block",r])){const e=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return Array.isArray(e)||(e=[e]),ps(e)}(function(e){let t=!1;return(0,Fr.some)((0,jr.select)("core/block-editor").getBlocks(),(n=>{const r=n.attributes.ref===e;return r&&(t=n),r})),t}(r));e.length&&setTimeout((()=>{(0,jr.dispatch)("core").editEntityRecord("postType","wp_block",n.ref,{is_llms_field:e.length>0?"yes":"no"})}))}return e}));const fs=(0,U.withInstanceId)((function(e){let{options:t,fieldType:n,instanceId:r}=e;return(0,A.createElement)(A.Fragment,null,t.map(((e,t)=>(0,A.createElement)("label",{htmlFor:`llms-${n}-${r}-${t}`,key:t,style:{display:"block",pointerEvents:"none"}},(0,A.createElement)("input",{id:`llms-${n}-${r}-${t}`,type:n,checked:"yes"===e.default,readOnly:!0})," ",e.text))))}));class hs extends A.Component{getFieldType(){const{attributes:{field:e}}=this.props;return-1!==["email","text","number","url","tel"].indexOf(e)?"input":e}render(){const{attributes:e,setAttributes:t,block:n,clientId:r}=this.props,{description:l,label:s,options:o,placeholder:i,required:a}=e,c=n.supports.llms_edit_fill,u=[];a&&u.push("llms-is-required");const d=this.getFieldType();return(0,A.createElement)(A.Fragment,null,(0,A.createElement)("div",{className:"llms-field"},"html"!==d&&(0,A.createElement)(j.RichText,{tagName:"label",className:u.join(" "),value:s,onChange:e=>{t({label:e})},allowedFormats:["bold","italic"],"aria-label":s?(0,$.__)("Field label"):(0,$.__)("Empty field label; start writing to add a label"),placeholder:(0,$.__)("Enter a label")}),"input"===d&&(0,A.createElement)("input",{onChange:e=>t({placeholder:e.target.value}),value:i,placeholder:(0,$.__)("Add optional placeholder text","lifterlms")}),"password"===d&&(0,A.createElement)("input",{disabled:"disabed",type:"password",value:"F4K3p4$50Rd"}),"textarea"===d&&(0,A.createElement)("textarea",{rows:this.props.attributes.html_attrs.rows,onChange:e=>t({placeholder:e.target.value}),value:i,placeholder:(0,$.__)("Add optional placeholder text","lifterlms")}),"select"===d&&(0,A.createElement)("select",null,(0,A.createElement)("option",null,(()=>{if(i)return i;if(!o.length)return"";let e=o[0].text;const t=o.filter((e=>"yes"===e.default));return t.length&&(e=t[0].text),e})())),(0,A.createElement)(j.RichText,{tagName:"span",value:l,onChange:e=>{t({description:e})},allowedFormats:["bold","strikethrough","link"],"aria-label":s?(0,$.__)("Optional field description"):(0,$.__)("Empty field description; start writing to add a description"),placeholder:(0,$.__)("Add optional description text"),style:{color:"#808285",fontStyle:"italic"}}),("radio"===d||"checkbox"===d)&&(0,A.createElement)(fs,{options:o,fieldType:d})),c.after&&(0,A.createElement)(q.Slot,{name:`llmsEditFill.after.${c.after}.${r}`}))}}var gs,vs=new Uint8Array(16);function bs(){if(!gs&&!(gs="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return gs(vs)}const _s=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,ys=function(e){return"string"==typeof e&&_s.test(e)};for(var ws=[],Es=0;Es<256;++Es)ws.push((Es+256).toString(16).substr(1));const xs=function(e,t,n){var r=(e=e||{}).random||(e.rng||bs)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var l=0;l<16;++l)t[n+l]=r[l];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(ws[e[t+0]]+ws[e[t+1]]+ws[e[t+2]]+ws[e[t+3]]+"-"+ws[e[t+4]]+ws[e[t+5]]+"-"+ws[e[t+6]]+ws[e[t+7]]+"-"+ws[e[t+8]]+ws[e[t+9]]+"-"+ws[e[t+10]]+ws[e[t+11]]+ws[e[t+12]]+ws[e[t+13]]+ws[e[t+14]]+ws[e[t+15]]).toLowerCase();if(!ys(n))throw TypeError("Stringified UUID is invalid");return n}(r)};class Cs extends Br{constructor(){super(...arguments),Y(this,"getDefaultClassName",(()=>"llms-search--user")),Y(this,"getSearchPath",(()=>this.props.searchPath||"/wp/v2/users")),Y(this,"formatSearchResultLabel",(e=>(0,$.sprintf)(
+// Translators: %1$s = User's name; %2$s = User's id.
+(0,$._x)("%1$s (ID# %2$d)","User search result label","lifterlms"),e.name,e.id)))}getSearchArgs(e){const t=super.getSearchArgs(e),{roles:n}=this.props;return n&&(t.roles=Array.isArray(n)?n.join(","):n),t}}const ks="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,Ss=ks?J.useLayoutEffect:J.useEffect;function Os(e,t){const n=(0,J.useRef)();return(0,J.useMemo)((()=>{const t=e(n.current);return n.current=t,t}),[...t])}function Is(){const e=(0,J.useRef)(null),t=(0,J.useCallback)((t=>{e.current=t}),[]);return[e,t]}let Ts={};function Ps(e,t){return(0,J.useMemo)((()=>{if(t)return t;const n=null==Ts[e]?0:Ts[e]+1;return Ts[e]=n,`${e}-${n}`}),[e,t])}function Ms(e){return(t,...n)=>n.reduce(((t,n)=>{const r=Object.entries(n);for(const[n,l]of r){const r=t[n];null!=r&&(t[n]=r+e*l)}return t}),{...t})}const Ds=Ms(1),Ls=Ms(-1),Rs=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return`translate3d(${t?Math.round(t):0}px, ${n?Math.round(n):0}px, 0)`}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return`scaleX(${t}) scaleY(${n})`}},Transform:{toString(e){if(e)return[Rs.Translate.toString(e),Rs.Scale.toString(e)].join(" ")}},Transition:{toString:({property:e,duration:t,easing:n})=>`${e} ${t}ms ${n}`}}),Vs={display:"none"};function Ns(e){let{id:t,value:n}=e;return ee().createElement("div",{id:t,style:Vs},n)}const As={position:"fixed",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"};function Fs(e){let{id:t,announcement:n}=e;return ee().createElement("div",{id:t,style:As,role:"status","aria-live":"assertive","aria-atomic":!0},n)}const Bs={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},Hs={onDragStart:e=>`Picked up draggable item ${e}.`,onDragOver:(e,t)=>t?`Draggable item ${e} was moved over droppable area ${t}.`:`Draggable item ${e} is no longer over a droppable area.`,onDragEnd:(e,t)=>t?`Draggable item ${e} was dropped over droppable area ${t}`:`Draggable item ${e} was dropped.`,onDragCancel:e=>`Dragging was cancelled. Draggable item ${e} was dropped.`};var zs;!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(zs||(zs={}));const $s=e=>Us(e,((e,t)=>e{const n=Xs(t,t.left,t.top),r=e.map((([e,t])=>Ks(Xs(t),n))),l=$s(r);return e[l]?e[l][0]:null};function Zs(e){return function(t,...n){return n.reduce(((t,n)=>({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x,offsetLeft:t.offsetLeft+e*n.x,offsetTop:t.offsetTop+e*n.y})),{...t})}}const Js=Zs(1);function eo(e){const t=[];return e?function e(n){return n?n instanceof Document&&null!=n.scrollingElement?(t.push(n.scrollingElement),t):!(n instanceof HTMLElement)||n instanceof SVGElement?t:(function(e){const t=window.getComputedStyle(e),n=/(auto|scroll|overlay)/;return null!=["overflow","overflowX","overflowY"].find((e=>{const r=t[e];return"string"==typeof r&&n.test(r)}))}(n)&&t.push(n),e(n.parentNode)):t}(e.parentNode):t}function to(e){return ks?e===document.scrollingElement||e instanceof Document?window:e instanceof HTMLElement?e:null:null}function no(e){return e instanceof Window?{x:e.scrollX,y:e.scrollY}:{x:e.scrollLeft,y:e.scrollTop}}var ro;function lo(e){const t={x:0,y:0},n={x:e.scrollWidth-e.clientWidth,y:e.scrollHeight-e.clientHeight};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=n.y,isRight:e.scrollLeft>=n.x,maxScroll:n,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(ro||(ro={}));const so={x:.2,y:.2};function oo(e,t,{top:n,left:r,right:l,bottom:s},o=10,i=so){const{clientHeight:a,clientWidth:c}=e,u=(d=e,ks&&d&&d===document.scrollingElement?{top:0,left:0,right:c,bottom:a,width:c,height:a}:t);var d;const{isTop:p,isBottom:m,isLeft:f,isRight:h}=lo(e),g={x:0,y:0},v={x:0,y:0},b=u.height*i.y,_=u.width*i.x;return!p&&n<=u.top+b?(g.y=ro.Backward,v.y=o*Math.abs((u.top+b-n)/b)):!m&&s>=u.bottom-b&&(g.y=ro.Forward,v.y=o*Math.abs((u.bottom-b-s)/b)),!h&&l>=u.right-_?(g.x=ro.Forward,v.x=o*Math.abs((u.right-_-l)/_)):!f&&r<=u.left+_&&(g.x=ro.Backward,v.x=o*Math.abs((u.left+_-r)/_)),{direction:g,speed:v}}function io(e){if(e===document.scrollingElement){const{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}const{top:t,left:n,right:r,bottom:l}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:l,width:e.clientWidth,height:e.clientHeight}}function ao(e){return e.reduce(((e,t)=>Ds(e,no(t))),Ws)}function co(e,t,n=Ws){if(!(e&&e instanceof HTMLElement))return n;const r={x:n.x+e.offsetLeft,y:n.y+e.offsetTop};return e.offsetParent===t?r:co(e.offsetParent,t,r)}function uo(e){const{offsetWidth:t,offsetHeight:n}=e,{x:r,y:l}=co(e,null);return{width:t,height:n,offsetTop:l,offsetLeft:r}}function po(e){if(e instanceof Window){const e=window.innerWidth,t=window.innerHeight;return{top:0,left:0,right:e,bottom:t,width:e,height:t,offsetTop:0,offsetLeft:0}}const{offsetTop:t,offsetLeft:n}=uo(e),{width:r,height:l,top:s,bottom:o,left:i,right:a}=e.getBoundingClientRect();return{width:r,height:l,top:s,bottom:o,right:a,left:i,offsetTop:t,offsetLeft:n}}function mo(e){const{width:t,height:n,offsetTop:r,offsetLeft:l}=uo(e),s=ao(eo(e)),o=r-s.y,i=l-s.x;return{width:t,height:n,top:o,bottom:o+n,right:i+t,left:i,offsetTop:r,offsetLeft:l}}function fo(e){return"top"in e}function ho(e,t=e.offsetLeft,n=e.offsetTop){return[{x:t,y:n},{x:t+e.width,y:n},{x:t,y:n+e.height},{x:t+e.width,y:n+e.height}]}const go=(e,t)=>{const n=e.map((([e,n])=>function(e,t){const n=Math.max(t.top,e.offsetTop),r=Math.max(t.left,e.offsetLeft),l=Math.min(t.left+t.width,e.offsetLeft+e.width),s=Math.min(t.top+t.height,e.offsetTop+e.height),o=l-r,i=s-n;if(re>t));return n[r]<=0?null:e[r]?e[r][0]:null};function vo(e){return e instanceof HTMLElement?e.ownerDocument:document}function bo(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:{},translate:{x:0,y:0}},droppable:{containers:{}}}}function _o(e,t){switch(t.type){case zs.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case zs.DragMove:return e.draggable.active?{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}}:e;case zs.DragEnd:case zs.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case zs.RegisterDroppable:{const{element:n}=t,{id:r}=n;return{...e,droppable:{...e.droppable,containers:{...e.droppable.containers,[r]:n}}}}case zs.SetDroppableDisabled:{const{id:n,disabled:r}=t,l=e.droppable.containers[n];return l?{...e,droppable:{...e.droppable,containers:{...e.droppable.containers,[n]:{...l,disabled:r}}}}:e}case zs.UnregisterDroppable:{const{id:n}=t;return{...e,droppable:{...e.droppable,containers:qs(n,e.droppable.containers)}}}default:return e}}const yo=(0,J.createContext)({type:null,event:null});function wo({announcements:e=Hs,hiddenTextDescribedById:t,screenReaderInstructions:n}){const{announce:r,announcement:l}=function(){const[e,t]=(0,J.useState)("");return{announce:(0,J.useCallback)((e=>{null!=e&&t(e)}),[]),announcement:e}}(),s=Ps("DndLiveRegion"),[o,i]=(0,J.useState)(!1);return(0,J.useEffect)((()=>{i(!0)}),[]),function({onDragStart:e,onDragMove:t,onDragOver:n,onDragEnd:r,onDragCancel:l}){const s=(0,J.useContext)(yo),o=(0,J.useRef)(s);(0,J.useEffect)((()=>{if(s!==o.current){const{type:i,event:a}=s;switch(i){case zs.DragStart:null==e||e(a);break;case zs.DragMove:null==t||t(a);break;case zs.DragOver:null==n||n(a);break;case zs.DragCancel:null==l||l(a);break;case zs.DragEnd:null==r||r(a)}o.current=s}}),[s,e,t,n,r,l])}((0,J.useMemo)((()=>({onDragStart({active:t}){r(e.onDragStart(t.id))},onDragMove({active:t,over:n}){e.onDragMove&&r(e.onDragMove(t.id,null==n?void 0:n.id))},onDragOver({active:t,over:n}){r(e.onDragOver(t.id,null==n?void 0:n.id))},onDragEnd({active:t,over:n}){r(e.onDragEnd(t.id,null==n?void 0:n.id))},onDragCancel({active:t}){r(e.onDragCancel(t.id))}})),[r,e])),o?(0,Ht.createPortal)(ee().createElement(ee().Fragment,null,ee().createElement(Ns,{id:t,value:n.draggable}),ee().createElement(Fs,{id:s,announcement:l})),document.body):null}var Eo,xo,Co,ko;function So(e){const t=(0,J.useRef)(e);return Ss((()=>{t.current!==e&&(t.current=e)}),[e]),t}!function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(Eo||(Eo={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(xo||(xo={})),function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(Co||(Co={})),function(e){e.Optimized="optimized"}(ko||(ko={}));const Oo=new Map;const Io={strategy:Co.WhileDragging,frequency:ko.Optimized},To=[],Po=Lo(po),Mo=Ro(po),Do=Lo(mo);function Lo(e){return function(t,n){const r=(0,J.useRef)(t);return Os((l=>t?n||!l&&t||t!==r.current?t instanceof HTMLElement&&null==t.parentNode?null:e(t):null!=l?l:null:null),[t,n])}}function Ro(e){const t=[];return function(n,r){const l=(0,J.useRef)(n);return Os((s=>n.length?r||!s&&n.length||n!==l.current?n.map((t=>e(t))):null!=s?s:t:t),[n,r])}}function Vo(e,t){return(0,J.useMemo)((()=>({sensor:e,options:null!=t?t:{}})),[e,t])}class No{constructor(e){this.target=e,this.listeners=[]}add(e,t,n){this.target.addEventListener(e,t,n),this.listeners.push({eventName:e,handler:t})}removeAll(){this.listeners.forEach((({eventName:e,handler:t})=>this.target.removeEventListener(e,t)))}}function Ao(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return"number"==typeof t?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t&&r>t.y}var Fo;!function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter"}(Fo||(Fo={}));const Bo={start:[Fo.Space,Fo.Enter],cancel:[Fo.Esc],end:[Fo.Space,Fo.Enter]},Ho=(e,{currentCoordinates:t})=>{switch(e.code){case Fo.Right:return{...t,x:t.x+25};case Fo.Left:return{...t,x:t.x-25};case Fo.Down:return{...t,y:t.y+25};case Fo.Up:return{...t,y:t.y-25}}};class zo{constructor(e){this.props=e,this.autoScrollEnabled=!1,this.coordinates=Ws;const{event:{target:t}}=e;this.props=e,this.listeners=new No(vo(t)),this.windowListeners=new No(function(e){var t;return null!=(t=vo(e).defaultView)?t:window}(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),setTimeout((()=>{this.listeners.add("keydown",this.handleKeyDown),this.windowListeners.add("resize",this.handleCancel)}))}handleStart(){const{activeNode:e,onStart:t}=this.props;if(!e.node.current)throw new Error("Active draggable node is undefined");const n=po(e.node.current),r={x:n.left,y:n.top};this.coordinates=r,t(r)}handleKeyDown(e){if(e instanceof KeyboardEvent){const{coordinates:t}=this,{active:n,context:r,options:l}=this.props,{keyboardCodes:s=Bo,coordinateGetter:o=Ho,scrollBehavior:i="smooth"}=l,{code:a}=e;if(s.end.includes(a))return void this.handleEnd(e);if(s.cancel.includes(a))return void this.handleCancel(e);const c=o(e,{active:n,context:r.current,currentCoordinates:t});if(c){const n={x:0,y:0},{scrollableAncestors:l}=r.current;for(const r of l){const l=e.code,s=Ls(c,t),{isTop:o,isRight:a,isLeft:u,isBottom:d,maxScroll:p,minScroll:m}=lo(r),f=io(r),h={x:Math.min(l===Fo.Right?f.right-f.width/2:f.right,Math.max(l===Fo.Right?f.left:f.left+f.width/2,c.x)),y:Math.min(l===Fo.Down?f.bottom-f.height/2:f.bottom,Math.max(l===Fo.Down?f.top:f.top+f.height/2,c.y))},g=l===Fo.Right&&!a||l===Fo.Left&&!u,v=l===Fo.Down&&!d||l===Fo.Up&&!o;if(g&&h.x!==c.x){if(l===Fo.Right&&r.scrollLeft+s.x<=p.x||l===Fo.Left&&r.scrollLeft+s.x>=m.x)return void r.scrollBy({left:s.x,behavior:i});n.x=l===Fo.Right?r.scrollLeft-p.x:r.scrollLeft-m.x,r.scrollBy({left:-n.x,behavior:i});break}if(v&&h.y!==c.y){if(l===Fo.Down&&r.scrollTop+s.y<=p.y||l===Fo.Up&&r.scrollTop+s.y>=m.y)return void r.scrollBy({top:s.y,behavior:i});n.y=l===Fo.Down?r.scrollTop-p.y:r.scrollTop-m.y,r.scrollBy({top:-n.y,behavior:i});break}}this.handleMove(e,Ds(c,n))}}}handleMove(e,t){const{onMove:n}=this.props;e.preventDefault(),n(t),this.coordinates=t}handleEnd(e){const{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){const{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function $o(e){return Boolean(e&&"distance"in e)}function Uo(e){return Boolean(e&&"delay"in e)}var jo;zo.activators=[{eventName:"onKeyDown",handler:(e,{keyboardCodes:t=Bo,onActivation:n})=>{const{code:r}=e.nativeEvent;return!!t.start.includes(r)&&(e.preventDefault(),null==n||n({event:e.nativeEvent}),!0)}}],function(e){e.Keydown="keydown"}(jo||(jo={}));class qo{constructor(e,t,n=function(e){return e instanceof EventTarget?e:vo(e)}(e.event.target)){this.props=e,this.events=t,this.autoScrollEnabled=!0,this.activated=!1,this.timeoutId=null;const{event:r}=e;this.props=e,this.events=t,this.ownerDocument=vo(r.target),this.listeners=new No(n),this.initialCoordinates=Ys(r),this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:t}}}=this;if(this.listeners.add(e.move.name,this.handleMove,!1),this.listeners.add(e.end.name,this.handleEnd),this.ownerDocument.addEventListener(jo.Keydown,this.handleKeydown),t){if($o(t))return;if(Uo(t))return void(this.timeoutId=setTimeout(this.handleStart,t.delay))}this.handleStart()}detach(){this.listeners.removeAll(),this.ownerDocument.removeEventListener(jo.Keydown,this.handleKeydown),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handleStart(){const{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,t(e))}handleMove(e){const{activated:t,initialCoordinates:n,props:r}=this,{onMove:l,options:{activationConstraint:s}}=r;if(!n)return;const o=Ys(e),i=Ls(n,o);if(!t&&s){if(Uo(s))return Ao(i,s.tolerance)?this.handleCancel():void 0;if($o(s))return Ao(i,s.distance)?this.handleStart():void 0}e.cancelable&&e.preventDefault(),l(o)}handleEnd(){const{onEnd:e}=this.props;this.detach(),e()}handleCancel(){const{onCancel:e}=this.props;this.detach(),e()}handleKeydown(e){e.code===Fo.Esc&&this.handleCancel()}}const Go={move:{name:"pointermove"},end:{name:"pointerup"}};class Wo extends qo{constructor(e){const{event:t}=e,n=vo(t.target);super(e,Go,n)}}Wo.activators=[{eventName:"onPointerDown",handler:({nativeEvent:e},{onActivation:t})=>!(!e.isPrimary||0!==e.button||(null==t||t({event:e}),0))}];const Ko={move:{name:"mousemove"},end:{name:"mouseup"}};var Yo;!function(e){e[e.RightClick=2]="RightClick"}(Yo||(Yo={})),class extends qo{constructor(e){super(e,Ko,vo(e.event.target))}}.activators=[{eventName:"onMouseDown",handler:({nativeEvent:e},{onActivation:t})=>e.button!==Yo.RightClick&&(null==t||t({event:e}),!0)}];const Xo={move:{name:"touchmove"},end:{name:"touchend"}};(class extends qo{constructor(e){super(e,Xo)}}).activators=[{eventName:"onTouchStart",handler:({nativeEvent:e},{onActivation:t})=>{const{touches:n}=e;return!(n.length>1||(null==t||t({event:e}),0))}}];const Qo=[{sensor:Wo,options:{}},{sensor:zo,options:{}}],Zo={current:{}},Jo=(0,J.createContext)({...Ws,scaleX:1,scaleY:1}),ei=(0,J.memo)((function({id:e,autoScroll:t=!0,announcements:n,children:r,sensors:l=Qo,collisionDetection:s=go,layoutMeasuring:o,modifiers:i,screenReaderInstructions:a=Bs,...c}){var u,d,p;const m=(0,J.useReducer)(_o,void 0,bo),[f,h]=m,[g,v]=(0,J.useState)((()=>({type:null,event:null}))),{draggable:{active:b,nodes:_,translate:y},droppable:{containers:w}}=f,E=b?_[b]:null,x=(0,J.useRef)({initial:null,translated:null}),C=(0,J.useMemo)((()=>{var e;return null!=b?{id:b,data:null!=(e=null==E?void 0:E.data)?e:Zo,rect:x}:null}),[b,E]),k=(0,J.useRef)(null),[S,O]=(0,J.useState)(null),[I,T]=(0,J.useState)(null),P=(0,J.useRef)(c),M=Ps("DndDescribedBy",e),{layoutRectMap:D,recomputeLayouts:L,willRecomputeLayouts:R}=function(e,{dragging:t,dependencies:n,config:r}){const[l,s]=(0,J.useState)(!1),{frequency:o,strategy:i}=(a=r)?{...Io,...a}:Io;var a;const c=(0,J.useRef)(e),u=(0,J.useCallback)((()=>s(!0)),[]),d=(0,J.useRef)(null),p=function(){switch(i){case Co.Always:return!1;case Co.BeforeDragging:return t;default:return!t}}(),m=Os((n=>{if(p&&!t)return Oo;if(!n||n===Oo||c.current!==e||l){for(let t of Object.values(e))t&&(t.rect.current=t.node.current?uo(t.node.current):null);return function(e){const t=new Map;if(e)for(const n of Object.values(e)){if(!n)continue;const{id:e,rect:r,disabled:l}=n;l||null==r.current||t.set(e,r.current)}return t}(e)}return n}),[e,t,p,l]);return(0,J.useEffect)((()=>{c.current=e}),[e]),(0,J.useEffect)((()=>{l&&s(!1)}),[l]),(0,J.useEffect)((function(){p||requestAnimationFrame(u)}),[t,p]),(0,J.useEffect)((function(){p||"number"!=typeof o||null!==d.current||(d.current=setTimeout((()=>{u(),d.current=null}),o))}),[o,p,u,...n]),{layoutRectMap:m,recomputeLayouts:u,willRecomputeLayouts:l}}(w,{dragging:null!=b,dependencies:[y.x,y.y],config:o}),V=function(e,t){const n=null!==t?e[t]:void 0,r=n?n.node.current:null;return Os((e=>{var n;return null===t?null:null!=(n=null!=r?r:e)?n:null}),[r,t])}(_,b),N=I?Ys(I):null,A=Do(V),F=Po(V),B=(0,J.useRef)(null),H=($=B.current,(z=A)&&$?{x:z.left-$.left,y:z.top-$.top}:Ws);var z,$;const U=(0,J.useRef)({active:null,activeNode:V,collisionRect:null,droppableRects:D,draggableNodes:_,draggingNodeRect:null,droppableContainers:w,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null,translatedRect:null}),j=function(e,t){var n,r;return e&&null!=(n=null==(r=t[e])?void 0:r.node.current)?n:null}(null!=(u=null==(d=U.current.over)?void 0:d.id)?u:null,w),q=Po(V?V.ownerDocument.defaultView:null),G=Po(V?V.parentElement:null),W=function(e){const t=(0,J.useRef)(e),n=Os((n=>e?n&&e&&t.current&&e.parentNode===t.current.parentNode?n:eo(e):To),[e]);return(0,J.useEffect)((()=>{t.current=e}),[e]),n}(b?null!=j?j:V:null),K=Mo(W),[Y,X]=Is(),Q=Po(b?Y.current:null,R),Z=null!=Q?Q:F,te=function(e,{transform:t,...n}){return(null==e?void 0:e.length)?e.reduce(((e,t)=>t({transform:e,...n})),t):t}(i,{transform:{x:y.x-H.x,y:y.y-H.y,scaleX:1,scaleY:1},active:C,over:U.current.over,activeNodeRect:F,draggingNodeRect:Z,containerNodeRect:G,overlayNodeRect:Q,scrollableAncestors:W,scrollableAncestorRects:K,windowRect:q}),ne=N?Ds(N,y):null,re=function(e){const[t,n]=(0,J.useState)(null),r=(0,J.useRef)(e),l=(0,J.useCallback)((e=>{const t=to(e.target);t&&n((e=>e?(e.set(t,no(t)),new Map(e)):null))}),[]);return(0,J.useEffect)((()=>{const t=r.current;if(e!==t){s(t);const o=e.map((e=>{const t=to(e);return t?(t.addEventListener("scroll",l,{passive:!0}),[t,no(t)]):null})).filter((e=>null!=e));n(o.length?new Map(o):null),r.current=e}return()=>{s(e),s(t)};function s(e){e.forEach((e=>{const t=to(e);null==t||t.removeEventListener("scroll",l)}))}}),[l,e]),(0,J.useMemo)((()=>e.length?t?Array.from(t.values()).reduce(((e,t)=>Ds(e,t)),Ws):ao(e):Ws),[e,t])}(W),le=Ds(te,re),se=A?Js(A,te):null,oe=se?Js(se,re):null,ie=function(e,t){var n;return e&&null!=(n=t[e])?n:null}(C&&oe?s(Array.from(D.entries()),oe):null,w),ae=(0,J.useMemo)((()=>ie&&ie.rect.current?{id:ie.id,rect:ie.rect.current,data:ie.data,disabled:ie.disabled}:null),[ie]),ce=function(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}(te,null!=(p=null==ie?void 0:ie.rect.current)?p:null,A),ue=(0,J.useCallback)(((e,{sensor:t,options:n})=>{if(!k.current)return;const r=_[k.current];if(!r)return;const l=new t({active:k.current,activeNode:r,event:e.nativeEvent,options:n,context:U,onStart(e){const t=k.current;if(!t)return;const n=_[t];if(!n)return;const{onDragStart:r}=P.current,l={active:{id:t,data:n.data,rect:x}};h({type:zs.DragStart,initialCoordinates:e,active:t}),v({type:zs.DragStart,event:l}),null==r||r(l)},onMove(e){h({type:zs.DragMove,coordinates:e})},onEnd:s(zs.DragEnd),onCancel:s(zs.DragCancel)});function s(e){return async function(){const{active:t,over:n,scrollAdjustedTranslate:r}=U.current;let l=null;if(t&&r){const{cancelDrop:s}=P.current;l={active:t,delta:r,over:n},e===zs.DragEnd&&"function"==typeof s&&await Promise.resolve(s(l))&&(e=zs.DragCancel)}if(k.current=null,h({type:e}),O(null),T(null),l){const{onDragCancel:t,onDragEnd:n}=P.current,r=e===zs.DragEnd?n:t;v({type:e,event:l}),null==r||r(l)}}}O(l),T(e.nativeEvent)}),[h,_]),de=(0,J.useCallback)(((e,t)=>(n,r)=>{const l=n.nativeEvent;null!==k.current||l.dndKit||l.defaultPrevented||!0===e(n,t.options)&&(l.dndKit={capturedBy:t.sensor},k.current=r,ue(n,t))}),[ue]),pe=function(e,t){return(0,J.useMemo)((()=>e.reduce(((e,n)=>{const{sensor:r}=n;return[...e,...r.activators.map((e=>({eventName:e.eventName,handler:t(e.handler,n)})))]}),[])),[e,t])}(l,de);Ss((()=>{P.current=c}),Object.values(c)),(0,J.useEffect)((()=>{C||(B.current=null),C&&A&&!B.current&&(B.current=A)}),[A,C]),(0,J.useEffect)((()=>{const{onDragMove:e}=P.current,{active:t,over:n}=U.current;if(!t)return;const r={active:t,delta:{x:le.x,y:le.y},over:n};v({type:zs.DragMove,event:r}),null==e||e(r)}),[le.x,le.y]),(0,J.useEffect)((()=>{const{active:e,scrollAdjustedTranslate:t}=U.current;if(!e||!k.current||!t)return;const{onDragOver:n}=P.current,r={active:e,delta:{x:t.x,y:t.y},over:ae};v({type:zs.DragOver,event:r}),null==n||n(r)}),[null==ae?void 0:ae.id]),Ss((()=>{U.current={active:C,activeNode:V,collisionRect:oe,droppableRects:D,draggableNodes:_,draggingNodeRect:Z,droppableContainers:w,over:ae,scrollableAncestors:W,scrollAdjustedTranslate:le,translatedRect:se},x.current={initial:Z,translated:se}}),[C,V,oe,_,Z,D,w,ae,W,le,se]),function({acceleration:e,activator:t=Eo.Pointer,canScroll:n,draggingRect:r,enabled:l,interval:s=5,order:o=xo.TreeOrder,pointerCoordinates:i,scrollableAncestors:a,scrollableAncestorRects:c,threshold:u}){const[d,p]=function(){const e=(0,J.useRef)(null);return[(0,J.useCallback)(((t,n)=>{e.current=setInterval(t,n)}),[]),(0,J.useCallback)((()=>{null!==e.current&&(clearInterval(e.current),e.current=null)}),[])]}(),m=(0,J.useRef)({x:1,y:1}),f=(0,J.useMemo)((()=>{switch(t){case Eo.Pointer:return i?{top:i.y,bottom:i.y,left:i.x,right:i.x}:null;case Eo.DraggableRect:return r}return null}),[t,r,i]),h=(0,J.useRef)(Ws),g=(0,J.useRef)(null),v=(0,J.useCallback)((()=>{const e=g.current;if(!e)return;const t=m.current.x*h.current.x,n=m.current.y*h.current.y;e.scrollBy(t,n)}),[]),b=(0,J.useMemo)((()=>o===xo.TreeOrder?[...a].reverse():a),[o,a]);(0,J.useEffect)((()=>{if(l&&a.length&&f){for(const t of b){if(!1===(null==n?void 0:n(t)))continue;const r=a.indexOf(t),l=c[r];if(!l)continue;const{direction:o,speed:i}=oo(t,l,f,e,u);if(i.x>0||i.y>0)return p(),g.current=t,d(v,s),m.current=i,void(h.current=o)}m.current={x:0,y:0},h.current={x:0,y:0},p()}else p()}),[e,v,n,p,l,s,JSON.stringify(f),d,a,b,c,JSON.stringify(u)])}({...function(){const e=!1===(null==S?void 0:S.autoScrollEnabled),n="object"==typeof t?!1===t.enabled:!1===t,r=!e&&!n;return"object"==typeof t?{...t,enabled:r}:{enabled:r}}(),draggingRect:se,pointerCoordinates:ne,scrollableAncestors:W,scrollableAncestorRects:K});const me=(0,J.useMemo)((()=>({active:C,activeNode:V,activeNodeRect:A,activeNodeClientRect:F,activatorEvent:I,activators:pe,ariaDescribedById:{draggable:M},overlayNode:{nodeRef:Y,rect:Q,setRef:X},containerNodeRect:G,dispatch:h,draggableNodes:_,droppableContainers:w,droppableRects:D,over:ae,recomputeLayouts:L,scrollableAncestors:W,scrollableAncestorRects:K,willRecomputeLayouts:R,windowRect:q})),[C,V,F,A,I,pe,G,Q,Y,h,_,M,w,D,ae,L,W,K,X,R,q]);return ee().createElement(yo.Provider,{value:g},ee().createElement(Gs.Provider,{value:me},ee().createElement(Jo.Provider,{value:ce},r)),ee().createElement(wo,{announcements:n,hiddenTextDescribedById:M,screenReaderInstructions:a}))})),ti=(0,J.createContext)(null),ni="button";function ri(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function li(e){return null!==e&&e>=0}const si=({layoutRects:e,activeIndex:t,overIndex:n,index:r})=>{const l=ri(e,n,t),s=e[r],o=l[r];return o&&s?{x:o.offsetLeft-s.offsetLeft,y:o.offsetTop-s.offsetTop,scaleX:o.width/s.width,scaleY:o.height/s.height}:null},oi={scaleX:1,scaleY:1},ii=({activeIndex:e,activeNodeRect:t,index:n,layoutRects:r,overIndex:l})=>{var s;const o=null!=(s=r[e])?s:t;if(!o)return null;if(n===e){const t=r[l];return t?{x:0,y:ee&&n<=l?{x:0,y:-o.height-i,...oi}:n=l?{x:0,y:o.height+i,...oi}:{x:0,y:0,...oi}},ai="Sortable",ci=ee().createContext({activeIndex:-1,containerId:ai,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:si,wasSorting:{current:!1}});function ui({children:e,id:t,items:n,strategy:r=si}){const{active:l,overlayNode:s,droppableRects:o,over:i,recomputeLayouts:a,willRecomputeLayouts:c}=(0,J.useContext)(Gs),u=Ps(ai,t),d=Boolean(null!==s.rect),p=(0,J.useMemo)((()=>n.map((e=>"string"==typeof e?e:e.id))),[n]),m=l?p.indexOf(l.id):-1,f=-1!==m,h=(0,J.useRef)(f),g=i?p.indexOf(i.id):-1,v=(0,J.useRef)(p),b=function(e,t){return e.reduce(((e,n,r)=>{const l=t.get(n);return l&&(e[r]=l),e}),Array(e.length))}(p,o),_=(y=p,w=v.current,!(y.join()===w.join()));var y,w;const E=-1!==g&&-1===m||_;Ss((()=>{_&&f&&!c&&a()}),[_,f,a,c]),(0,J.useEffect)((()=>{v.current=p}),[p]),(0,J.useEffect)((()=>{requestAnimationFrame((()=>{h.current=f}))}),[f]);const x=(0,J.useMemo)((()=>({activeIndex:m,containerId:u,disableTransforms:E,items:p,overIndex:g,useDragOverlay:d,sortedRects:b,strategy:r,wasSorting:h})),[m,u,E,p,g,b,d,r,h]);return ee().createElement(ci.Provider,{value:x},e)}const di=({isSorting:e,index:t,newIndex:n,transition:r})=>!(!r||!e&&n===t),pi={duration:200,easing:"ease"},mi="transform",fi=Rs.Transition.toString({property:mi,duration:0,easing:"linear"}),hi={roleDescription:"sortable"};function gi({animateLayoutChanges:e=di,attributes:t,disabled:n,data:r,id:l,strategy:s,transition:o=pi}){const{items:i,containerId:a,activeIndex:c,disableTransforms:u,sortedRects:d,overIndex:p,useDragOverlay:m,strategy:f,wasSorting:h}=(0,J.useContext)(ci),g=i.indexOf(l),v=(0,J.useMemo)((()=>({sortable:{containerId:a,index:g,items:i},...r})),[a,r,g,i]),{rect:b,node:_,setNodeRef:y}=function({data:e,disabled:t=!1,id:n}){const{active:r,dispatch:l,over:s}=(0,J.useContext)(Gs),o=(0,J.useRef)(null),[i,a]=Is(),c=So(e);return Ss((()=>(l({type:zs.RegisterDroppable,element:{id:n,disabled:t,node:i,rect:o,data:c}}),()=>l({type:zs.UnregisterDroppable,id:n}))),[n]),(0,J.useEffect)((()=>{l({type:zs.SetDroppableDisabled,id:n,disabled:t})}),[t]),{active:r,rect:o,isOver:(null==s?void 0:s.id)===n,node:i,over:s,setNodeRef:a}}({id:l,data:v}),{active:w,activeNodeRect:E,activatorEvent:x,attributes:C,setNodeRef:k,listeners:S,isDragging:O,over:I,transform:T}=function({id:e,data:t,disabled:n=!1,attributes:r}){const{active:l,activeNodeRect:s,activatorEvent:o,ariaDescribedById:i,draggableNodes:a,droppableRects:c,activators:u,over:d}=(0,J.useContext)(Gs),{role:p=ni,roleDescription:m="draggable",tabIndex:f=0}=null!=r?r:{},h=(null==l?void 0:l.id)===e,g=(0,J.useContext)(h?Jo:ti),[v,b]=Is(),_=function(e,t){return(0,J.useMemo)((()=>e.reduce(((e,{eventName:n,handler:r})=>(e[n]=e=>{r(e,t)},e)),{})),[e,t])}(u,e),y=So(t);return(0,J.useEffect)((()=>(a[e]={node:v,data:y},()=>{delete a[e]})),[a,e]),{active:l,activeNodeRect:s,activatorEvent:o,attributes:(0,J.useMemo)((()=>({role:p,tabIndex:f,"aria-pressed":!(!h||p!==ni)||void 0,"aria-roledescription":m,"aria-describedby":i.draggable})),[p,f,h,m,i.draggable]),droppableRects:c,isDragging:h,listeners:n?void 0:_,node:v,over:d,setNodeRef:b,transform:g}}({id:l,data:v,attributes:{...hi,...t},disabled:n}),P=function(...e){return(0,J.useMemo)((()=>t=>{e.forEach((e=>e(t)))}),e)}(y,k),M=Boolean(w),D=M&&h.current&&!u&&li(c)&&li(p),L=!m&&O,R=L&&D?T:null,V=D?null!=R?R:(null!=s?s:f)({layoutRects:d,activeNodeRect:E,activeIndex:c,overIndex:p,index:g}):null,N=li(c)&&li(p)?ri(i,c,p).indexOf(l):g,A=(0,J.useRef)(N),F=e({active:w,isDragging:O,isSorting:M,id:l,index:g,items:i,newIndex:A.current,transition:o,wasSorting:h.current}),B=function({rect:e,disabled:t,index:n,node:r}){const[l,s]=(0,J.useState)(null),o=(0,J.useRef)(n);return(0,J.useEffect)((()=>{if(!t&&n!==o.current&&r.current){const t=e.current;if(t){const e=po(r.current),n={x:t.offsetLeft-e.offsetLeft,y:t.offsetTop-e.offsetTop,scaleX:t.width/e.width,scaleY:t.height/e.height};(n.x||n.y)&&s(n)}}n!==o.current&&(o.current=n)}),[t,n,r,e]),(0,J.useEffect)((()=>{l&&requestAnimationFrame((()=>{s(null)}))}),[l]),l}({disabled:!F,index:g,node:_,rect:b});return(0,J.useEffect)((()=>{M&&(A.current=N)}),[M,N]),{active:w,attributes:C,activatorEvent:x,rect:b,index:g,isSorting:M,isDragging:O,listeners:S,node:_,overIndex:p,over:I,setNodeRef:P,setDroppableNodeRef:y,setDraggableNodeRef:k,transform:null!=B?B:V,transition:B?fi:L||!o?null:M||F?Rs.Transition.toString({...o,property:mi}):null}}const vi=[Fo.Down,Fo.Right,Fo.Up,Fo.Left],bi=(e,{context:{droppableContainers:t,translatedRect:n,scrollableAncestors:r}})=>{if(vi.includes(e.code)){if(e.preventDefault(),!n)return;const s=[];Object.entries(t).forEach((([t,r])=>{if(null==r?void 0:r.disabled)return;const l=null==r?void 0:r.node.current;if(!l)return;const o=mo(l);switch(e.code){case Fo.Down:n.top+n.height<=o.top&&s.push([t,o]);break;case Fo.Up:n.top>=o.top+o.height&&s.push([t,o]);break;case Fo.Left:n.left>=o.left+o.width&&s.push([t,o]);break;case Fo.Right:n.left+n.width<=o.left&&s.push([t,o])}}));const o=((e,t)=>{const n=ho(t,t.left,t.top),r=e.map((([e,t])=>{const r=ho(t,fo(t)?t.left:void 0,fo(t)?t.top:void 0),l=n.reduce(((e,t,n)=>e+Ks(r[n],t)),0);return Number((l/4).toFixed(4))})),l=$s(r);return e[l]?e[l][0]:null})(s,n);if(o){var l;const e=null==(l=t[o])?void 0:l.node.current;if(e){const t=eo(e).some(((e,t)=>r[t]!==e)),l=mo(e),s=t?{x:0,y:0}:{x:n.width-l.width,y:n.height-l.height};return{x:l.left-s.x,y:l.top-s.y}}}}};const _i=({transform:e})=>({...e,x:0}),yi=({transform:e,activeNodeRect:t,windowRect:n})=>t&&n?function(e,t,n){const r={...e};return t.top+e.y<=n.top?r.y=n.top-t.top:t.bottom+e.y>=n.top+n.height&&(r.y=n.top+n.height-t.bottom),t.left+e.x<=n.left?r.x=n.left-t.left:t.right+e.x>=n.left+n.width&&(r.x=n.left+n.width-t.right),r}(e,t,n):e,wi=(0,A.createElement)("svg",{width:"18",height:"18",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 18 18"},(0,A.createElement)("path",{d:"M5 4h2V2H5v2zm6-2v2h2V2h-2zm-6 8h2V8H5v2zm6 0h2V8h-2v2zm-6 6h2v-2H5v2zm6 0h2v-2h-2v2z"}));function Ei(e){let{label:t,setNodeRef:n,listeners:r}=e;return t=t||(0,$.__)("Reorder instructor","lifterlms"),(0,A.createElement)(q.Button,Q({isSmall:!0,showTooltip:!0,label:t,icon:wi,ref:n,className:"llms-drag-handle"},r))}function xi(e){let{id:t,index:n,item:r,isDragging:l,dragHandle:s,ListItem:o,itemClassName:i="",manageState:a,extraProps:c={}}=e;const{attributes:u,listeners:d,setNodeRef:p,transform:m,transition:f}=gi({id:t}),h={transform:Rs.Transform.toString(m),transition:f};return l&&m&&m.scaleX&&m.scaleY&&(m.scaleX=.9,m.scaleY=.9),l&&(i+=" llms-is-dragging"),(0,A.createElement)("div",Q({style:h,ref:s?void 0:p,className:`llms-sortable-list--item ${i}`},u,s?{}:d),(0,A.createElement)(o,{id:t,item:r,index:n,isDragging:l,setNodeRef:p,listeners:d,manageState:a,extraProps:c}))}function Ci(e){let{ListItem:t,manageState:n,items:r=[],sortableStrategy:l=ii,ctxModifiers:s=[_i,yi],dragHandle:o=!0,listClassName:i="",itemClassName:a="",extraProps:c={}}=e;const[u,d]=(0,A.useState)(!1),p=function(...e){return(0,J.useMemo)((()=>[...e].filter((e=>null!=e))),[...e])}(Vo(Wo),Vo(zo,{coordinateGetter:bi}));return(0,A.createElement)(ei,{sensors:p,collisionDetection:Qs,onDragStart:function(e){d(e.active.id)},onDragEnd:function(e){d(!1);const{active:t,over:l}=e;if(t.id!==l.id){const e=(0,Fr.findIndex)(r,{id:t.id}),s=(0,Fr.findIndex)(r,{id:l.id});n.updateItems(ri(r,e,s))}},modifiers:s},(0,A.createElement)("div",{className:`llms-sortable-list ${i}`},(0,A.createElement)(ui,{items:r,strategy:l},r.map(((e,r)=>(0,A.createElement)(xi,{id:e.id,key:e.id,index:r,item:e,isDragging:e.id===u,dragHandle:o,ListItem:t,itemClassName:a,manageState:n,extraProps:c}))))))}function ki(e){let{id:t,item:n,extraProps:r,manageState:l,listeners:s,setNodeRef:o}=e;const{showKeys:i,type:a,optionCount:c}=r,{updateItem:u,deleteItem:d}=l;return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(Ei,{label:(0,$.__)("Reorder option","lifterlms"),setNodeRef:o,listeners:s}),(0,A.createElement)(q.Tooltip,{text:(0,$.__)("Make default","lifterlms")},(0,A.createElement)("div",{className:"llms-field-opt-default-wrap"},"checkbox"===a&&(0,A.createElement)((()=>(0,A.createElement)(q.CheckboxControl,{className:"llms-field-opt-default",checked:"yes"===n.default,onChange:e=>{u(t,{...n,default:!0===e?"yes":"no"})},tabIndex:"-1"})),null),"checkbox"!==a&&(0,A.createElement)((()=>(0,A.createElement)(q.RadioControl,{className:"llms-field-opt-default",selected:n.default,onChange:e=>{u(t,{...n,default:e})},options:[{label:"",value:"yes"}],tabIndex:"-1"})),null))),(0,A.createElement)("div",{className:"llms-field-opt-text-wrap"},(0,A.createElement)(q.TextControl,{className:"llms-field-opt-text",value:n.text,onChange:e=>u(t,{...n,text:e}),placeholder:(0,$.__)("Option label","lifterlms")}),i&&(0,A.createElement)("div",{className:"llms-field-opt-db-key"},(0,A.createElement)(q.Tooltip,{text:(0,$.__)("Database key value","lifterlms")},(0,A.createElement)(q.Dashicon,{icon:"database"})),(0,A.createElement)(q.TextControl,{className:"llms-field-opt-text ",value:n.key,onChange:e=>u(t,{...n,key:e}),placeholder:(0,$.__)("Database key value","lifterlms")}))),c>1&&(0,A.createElement)("div",{className:"llms-del-field-opt-wrap"},(0,A.createElement)(q.Button,{style:{flex:1},icon:"trash",label:(0,$.__)("Delete Option","lifterlms"),onClick:()=>d(t),tabIndex:"-1",isSmall:!0})))}class Si extends A.Component{constructor(){super(...arguments),Y(this,"addOption",(()=>{const{options:e}=this.state,{length:t}=e,[n,r]=this.getUniqueKeyNumber(t+1),l={key:n,id:xs(),
+// Translators: %d = Option index in the list of options.
+text:(0,$.sprintf)((0,$.__)("Option %d","lifterlms"),r),default:"no"};e.push(l),this.updateOptions(e)})),Y(this,"getManageState",(()=>({createItem:this.addOption,deleteItem:this.removeOption,updateItem:this.updateOption,updateItems:this.updateOptions}))),Y(this,"getUniqueKeyNumber",(e=>{const t=e=>{const{options:t}=this.state;return-1===t.findIndex((t=>{let{key:n}=t;return n===e}))};
+// Translators: %d = Option index in the list of options.
+let n=(0,$.sprintf)((0,$.__)("option_%d","lifterlms"),e);for(;!t(n);)[n,e]=this.getUniqueKeyNumber(++e);return[n,e]})),Y(this,"updateOption",((e,t)=>{const{options:n}=this.state,{field:r}=this.props.attributes,l="yes"===t.default&&"checkbox"!==r,s=n.map((n=>(e===n.id?n={...n,...t}:l&&(n={...n,default:"no"}),n)));this.updateOptions(s)})),Y(this,"updateOptions",(e=>{const{setAttributes:t}=this.props;this.setState({options:e}),t({options:e.map((e=>{let{id:t,...n}=e;return n}))})})),Y(this,"removeOption",(e=>{const{options:t}=this.state,{field:n}=this.props.attributes;let r=null;if("checkbox"!==n){const n=t.find((t=>{let{id:n}=t;return n===e}));r="yes"===n.default}this.updateOptions(t.filter((t=>{let{id:n}=t;return n!==e})).map(((e,t)=>(r&&0===t&&(e={...e,default:"yes"}),e))))}));const{options:e}=this.props.attributes;this.state={showKeys:!1,options:e.map((e=>({...e,id:xs()})))}}render(){const{props:e,state:t}=this,{attributes:n}=e,{id:r,field:l}=n,{options:s,showKeys:o}=t,{length:i}=s;return(0,A.createElement)(q.BaseControl,{id:r,label:(0,$.__)("Options","lifterlms")},(0,A.createElement)(Ci,{ListItem:ki,items:s,itemClassName:"llms-field-option",manageState:this.getManageState(),extraProps:{type:l,showKeys:o,optionCount:i}}),(0,A.createElement)("div",{className:"llms-field-options--footer"},(0,A.createElement)(q.Button,{isSecondary:!0,onClick:this.addOption},(0,$.__)("Add option","lifterlms")),(0,A.createElement)(q.Button,{isTertiary:!0,onClick:()=>this.setState({showKeys:!o})},o?(0,$.__)("Hide keys","lifterlms"):(0,$.__)("Show keys","lifterlms"))))}}class Oi extends A.Component{constructor(e){super(e),this.state={validationErrors:{}}}getBlockByFieldId(e){const t=Kr().filter((t=>e===t.attributes.id));return!!t&&t[0]}getColumnsOptions(e){let t=[];return e&&(!e||e["llms/fieldGroup/fieldLayout"]&&"stacked"!==e["llms/fieldGroup/fieldLayout"])||t.push({value:12,label:(0,$.__)("100%","lifterlms")}),t=t.concat([{value:9,label:(0,$.__)("75%","lifterlms")},{value:8,label:(0,$.__)("66.66%","lifterlms")},{value:6,label:(0,$.__)("50%","lifterlms")},{value:4,label:(0,$.__)("33.33%","lifterlms")},{value:3,label:(0,$.__)("25%","lifterlms")}]),t}getMatchFieldOptions(){const{clientId:e,name:t}=this.props;return[{value:"",label:(0,$.__)("Select a field","lifterlms")}].concat(Kr().filter((n=>n.clientId!==e&&-1!==t.indexOf("llms/form-field-"))).map((e=>{const{id:t,label:n}=e.attributes;return{value:t,label:`${n} (${t})`}})))}hasInspectorSupport(){const{inspectorSupports:e}=this.props;return Object.keys(e).filter((t=>e[t])).length>=1}hasInspectorControlSupport(e){const{inspectorSupports:t}=this.props;return t[e]}canTransformToGroup(e){return!(!e||this.isInAConfirmGroup(e))&&(0,Gr.getPossibleBlockTransformations)([e]).map((e=>{let{name:t}=e;return t})).includes("llms/form-field-confirm-group")}isInAConfirmGroup(e){return!!this.getParentGroupClientId(e)}getParentGroupClientId(e){if(!e)return!1;const{clientId:t}=e,{getBlockParentsByBlockName:n}=(0,jr.select)(j.store),r=n(t,"llms/form-field-confirm-group");return!!r.length&&r[0]}getBlockSiblings(e){const t=this.getParentGroupClientId(e);if(!t)return[];const{getBlock:n}=(0,jr.select)(j.store);return n(t).innerBlocks.filter((t=>{let{clientId:n}=t;return n!==e.clientId}))}getValidationErrText(e){let t="";const n=this.state.validationErrors[e];if(n)if(this.containsInvalidCharacters(n))
+// Translators: %s = user-submitted value.
+t=(0,$.__)('The value "%s" contains invalid characters. Only letters, numbers, underscores, and hyphens are allowed.',"lifterlms");else switch(e){case"data_store_key":
+// Translators: %s = user-submitted value.
+t=(0,$.__)('The user meta key "%s" is not unique. Please choose a unique value.',"lifterlms");break;case"id":
+// Translators: %s = user-submitted value.
+t=(0,$.__)('The ID "%s" is not unique. Please choose a unique field ID.',"lifterlms");break;case"name":
+// Translators: %s = user-submitted value.
+t=(0,$.__)('The name "%s" is not unique. Please choose a globally unique field name.',"lifterlms");break;default:
+// Translators: %s = user-submitted value.
+t=(0,$.__)('The chosen value "%s" is invalid.',"lifterlms")}else t=(0,$.__)("The value cannot be blank.","lifterlms");return(0,$.sprintf)(t,n)}containsInvalidCharacters(e){return!!e.match(/[^A-Za-z0-9\-\_]/g)}setValidationError(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;this.setState({validationErrors:{...this.state.validationErrors,[e]:t}})}hasValidationErr(e){return"string"==typeof this.state.validationErrors[e]}ValidatedTextControl(e){let{parent:t,attrKey:n,label:r,help:l}=e;const{attributes:s}=t.props,o=s[n],i=t.hasValidationErr(n),a=i?"llms-invalid-control":"";return(0,A.createElement)("div",{className:a},(0,A.createElement)(q.TextControl,{label:r,help:l,value:o,onChange:e=>t.updateValueWithValidation(n,e,"name"===n?"global":"local")}),i&&(0,A.createElement)("p",{className:"llms-invalid-control--msg"},t.getValidationErrText(n)))}updateValueWithValidation(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"local";const{clientId:r,attributes:l,setAttributes:s}=this.props,{name:o}=l,i=l[e],{editField:a,renameField:c}=(0,jr.dispatch)(wl),{lockPostSaving:u,unlockPostSaving:d}=(0,jr.dispatch)(qr.store),p=`llms-${e}-validation-err-${r}-${o}`;if(t===i)return;const m=!t,f=this.containsInvalidCharacters(t),h=ms(t,e,n),g=!m&&!f&&h;if(this.setValidationError(e),d(p),!g){if(this.setValidationError(e,t),m)return;u(p)}"name"===e?(h||(t=t.slice(0,-1)),c(l.name,t)):a(l.name,{[e]:t}),s({[e]:t})}render(){if(!this.hasInspectorSupport())return"";const{attributes:e,setAttributes:t,clientId:n,context:r}=this.props,l=(0,jr.select)(j.store).getBlock(n),{required:s,placeholder:o,columns:i,isConfirmationField:a,isConfirmationControlField:c}=e,u=this.canTransformToGroup(l),d=this.isInAConfirmGroup(l);return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(j.InspectorControls,null,(0,A.createElement)(q.PanelBody,null,!a&&this.hasInspectorControlSupport("required")&&(0,A.createElement)(q.ToggleControl,{className:"llms-required-field-toggle",label:(0,$.__)("Required","lifterlms"),checked:!!s,onChange:()=>t({required:!s}),help:s?(0,$.__)("Field is required.","lifterlms"):(0,$.__)("Field is optional.","lifterlms")}),(0,A.createElement)(q.SelectControl,{className:"llms-field-width-select",label:(0,$.__)("Field Width","lifterlms"),onChange:e=>{e=parseInt(e,10),t({columns:e});const n=this.getBlockSiblings(l);if(n.length&&e+n[0].attributes.columns>12){const{updateBlockAttributes:t}=(0,jr.dispatch)(j.store);t(n[0].clientId,{columns:12-e})}},help:(0,$.__)("Determines the width of the form field.","lifterlms"),value:i,options:this.getColumnsOptions(r)}),this.hasInspectorControlSupport("options")&&(0,A.createElement)(Si,{attributes:e,setAttributes:t}),this.hasInspectorControlSupport("placeholder")&&(0,A.createElement)(q.TextControl,{label:(0,$.__)("Placeholder","lifterlms"),value:o,onChange:e=>t({placeholder:e}),help:(0,$.__)("Displays a placeholder option as the selected instead of a default value.","lifterlms")}),(u||c&&d)&&(0,A.createElement)(q.ToggleControl,{className:"llms-confirmation-field-toggle",label:(0,$.__)("Confirmation Field","lifterlms"),checked:d,onChange:()=>{const{replaceBlock:e,selectBlock:t}=(0,jr.dispatch)(j.store),{findControllerBlockIndex:r}=(0,Gr.getBlockType)("llms/form-field-confirm-group"),{getBlock:s}=(0,jr.select)(j.store);let o=n,i="llms/form-field-confirm-group",a=l,c=null;d&&(o=this.getParentGroupClientId(l),a=s(o),i=l.name);const u=(0,Gr.switchToBlockType)(a,i);if(e(o,u),d)c=u[0].clientId;else{const{innerBlocks:e}=u[0];c=e[r(e)].clientId}t(c)},help:d?(0,$.__)("A Confirmation field is active.","lifterlms"):(0,$.__)("No confirmation field.","lifterlms")}),this.hasInspectorControlSupport("customFill")&&(0,A.createElement)(q.Slot,{name:`llmsInspectorControlsFill.${this.hasInspectorControlSupport("customFill")}.${n}`})),!a&&this.hasInspectorControlSupport("storage")&&(0,A.createElement)(q.PanelBody,{title:(0,$.__)("Data Storage","lifterlms")},(0,A.createElement)(this.ValidatedTextControl,{parent:this,attrKey:"data_store_key",label:(0,$.__)("Usermeta Key","lifterlms"),help:(0,$.__)("Database field key name. Only accepts alphanumeric characters, hyphens, and underscores.","lifterlms")}))),(0,A.createElement)(j.InspectorAdvancedControls,null,!a&&this.hasInspectorControlSupport("name")&&(0,A.createElement)(this.ValidatedTextControl,{parent:this,attrKey:"name",label:(0,$.__)("Field Name","lifterlms"),help:(0,$.__)("The field's HTML name attribute.","lifterlms")}),!a&&this.hasInspectorControlSupport("id")&&(0,A.createElement)(this.ValidatedTextControl,{parent:this,attrKey:"id",label:(0,$.__)("Field ID","lifterlms"),help:(0,$.__)("The field's HTML id attribute.","lifterlms")})))}}function Ii(e){const t=function(e){const{getBlock:t,getBlockParentsByBlockName:n}=(0,jr.select)("core/block-editor");return t(n(e,function(){const{getBlockTypes:e,hasBlockSupport:t}=(0,jr.select)("core/blocks");return e().filter((e=>t(e,"llms_field_group")))}().map((e=>{let{name:t}=e;return t}))))}(e);return t&&t.innerBlocks.length?(0,Fr.find)(t.innerBlocks,(t=>t.clientId!==e)):null}function Ti(){let{setAttributes:e,currentUpdates:t,siblingClientId:n,siblingUpdates:r}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{updateBlockAttributes:l}=(0,jr.dispatch)("core/block-editor");setTimeout((()=>{(0,Fr.isEmpty)(t)||e(t),n&&!(0,Fr.isEmpty)(r)&&l(n,r)}))}function Pi(e,t){const n={};return e.required!==t.required&&(n.required=e.required),e.field!==t.field&&(n.field=e.field),{currentUpdates:{},siblingUpdates:n}}function Mi(e){const{attributes:t,block:n,setAttributes:r}=e,{fieldLayout:l}=t,{innerBlocks:s}=n;return(0,A.createElement)(q.RadioControl,{label:(0,$.__)("Field Layout","lifterlms"),selected:l,onChange:e=>function(e){let{fieldLayout:t,setAttributes:n,innerBlocks:r}=e;const{updateBlockAttributes:l}=(0,jr.dispatch)(j.store);n({fieldLayout:t});const s="columns"===t?6:12;r.forEach(((e,n)=>{let{clientId:r}=e,o=1===n;0===n&&"stacked"===t&&(o=!0),l(r,{columns:s,last_column:o})}))}({fieldLayout:e,setAttributes:r,innerBlocks:s}),options:[{value:"columns",label:(0,$.__)("Columns","lifterlms")},{value:"stacked",label:(0,$.__)("Stacked","lifterlms")}]})}const Di=e=>{const{getCurrentPostId:t}=(0,jr.select)("core/editor");return(0,Fr.snakeCase)((0,Fr.uniqueId)(`${e}_${t()}_`))},Li=e=>(0,Fr.kebabCase)(e),Ri={apiVersion:2,icon:{foreground:"currentColor"},category:"llms-user-info-fields",keywords:[(0,$.__)("LifterLMS","lifterlms"),"llms"],attributes:{},supports:{llms_visibility:!0},example:{},fillInspectorControls(e,t,n){},fillEditAfter(e,t,n){}},Vi={attributes:{description:{type:"string",__default:""},field:{type:"string",__default:"text"},required:{type:"boolean",__default:!1},label:{type:"string",__default:""},label_show_empty:{type:"string",__default:!1},match:{type:"string",__default:""},options:{type:"array",__default:[]},options_preset:{type:"string",__default:""},placeholder:{type:"string",__default:""},columns:{type:"integer",__default:12},last_column:{type:"boolean",__default:!0},name:{type:"string",__default:""},id:{type:"string",__default:""},data_store:{type:"string",__default:"usermeta"},data_store_key:{type:"string",__default:""},html_attrs:{type:"object",__default:{}},isConfirmationField:{type:"boolean",__default:!1},isConfirmationControlField:{type:"boolean",__default:!1}},supports:{llms_field_inspector:{id:!0,name:!0,options:!1,placeholder:!1,required:!0,customFill:!1,storage:!0},llms_edit_fill:{after:!1},llms_field_group:!1},edit:function(e){let{attributes:t}=e,n=!0;const{name:r}=e,l=(0,Gr.getBlockType)(r),{clientId:s,context:o,setAttributes:i}=e,a=l.supports.llms_field_inspector,c=l.supports.llms_edit_fill,{fillEditAfter:u,fillInspectorControls:d}=l,{getSelectedBlockClientId:p}=(0,jr.select)(j.store),{isDuplicate:m}=(0,jr.select)(wl),f=!!o["llms/fieldGroup/fieldLayout"],h=t.name&&m(t.name,s)&&0!==r.indexOf("llms/form-field-user-"),g=!f&&t.isConfirmationField;g&&(n=!1),t=n?((e,t,n)=>{if(Object.keys(t).forEach((n=>{const r=t[n].__default;void 0!==r&&void 0===e[n]&&(e[n]=r)})),!e.name||n&&!e.isConfirmationField){let t=Di(e.field);for(;!ms("name",t);)t=Di(e.field);e.name=t}if(!e.id||n&&!e.isConfirmationField){let t=Li(e.name);for(;!ms("id",t,"local");)t=Li((0,Fr.uniqueId)(`${e.field}-field-`));e.id=t}return(""===e.data_store_key||n&&!e.isConfirmationField)&&(e.data_store_key=e.name),e})(t,l.attributes,h):t,f&&n&&function(){let{attributes:e,clientId:t,setAttributes:n}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const r=Ii(t);let l={},s={};if(!r)return;const o=r.clientId;if(e.isConfirmationControlField||e.isConfirmationField){const t=Pi(e,r.attributes);l=(0,Fr.merge)(l,t.currentUpdates),s=(0,Fr.merge)(s,t.siblingUpdates)}Ti({setAttributes:n,currentUpdates:l,siblingClientId:o,siblingUpdates:s})}(e);const v=(0,j.useBlockProps)({className:`llms-fields llms-cols-${t.columns}`});return(0,A.useEffect)((()=>{if(l.variations&&l.variations.length&&s===p()){const e=setInterval((()=>{const n=document.querySelector(".block-editor-block-inspector .block-editor-block-variation-transforms");return n&&(n.style.display=t.isConfirmationField?"none":"inline-block",clearInterval(e)),()=>{clearInterval(e)}}),10)}})),g?(setTimeout((()=>{(0,jr.dispatch)(j.store).removeBlock(s)}),10),null):(0,A.createElement)("div",v,(0,A.createElement)(Oi,{attributes:t,clientId:s,name:r,setAttributes:i,inspectorSupports:a,context:o}),(0,A.createElement)(hs,{attributes:t,setAttributes:i,block:l,clientId:s,context:o}),a.customFill&&(0,A.createElement)(q.Fill,{name:`llmsInspectorControlsFill.${a.customFill}.${s}`},d(t,i,e)),c.after&&(0,A.createElement)(q.Fill,{name:`llmsEditFill.after.${c.after}.${s}`},u(t,i,e)))},save:function(e){const{attributes:t}=e;return t}},Ni={attributes:{fieldLayout:{type:"string",default:"columns"}},supports:{llms_field_group:!0,llms_field_inspector:!1},providesContext:{"llms/fieldGroup/fieldLayout":"fieldLayout"},llmsInnerBlocks:{template:[],allowed:[],lock:"insert"},edit:function(e){const{attributes:t,clientId:n,name:r,setAttributes:l}=e,{fieldLayout:s}=t,{getBlock:o}=(0,jr.select)(j.store),i=o(n),a=(0,Gr.getBlockType)(r),{allowed:c,template:u,lock:d}=a.llmsInnerBlocks,p=i&&i.innerBlocks.length&&"llms/form-field-confirm-group"===i.name?i.innerBlocks[a.findControllerBlockIndex(i.innerBlocks)]:null,m=p?(0,Gr.getBlockType)(p.name):null,f=m?m.supports.llms_edit_fill:{after:!1},h=a.supports.llms_field_inspector,g=a.providesContext&&a.providesContext["llms/fieldGroup/fieldLayout"];let v="columns"===s?"horizontal":"vertical";return g||(v="vertical"),(0,A.createElement)("div",(0,j.useBlockProps)(),(0,A.createElement)(j.InspectorControls,null,(0,A.createElement)(q.PanelBody,null,g&&(0,A.createElement)(Mi,Q({},e,{block:i})),h.customFill&&a.fillInspectorControls(t,l,e))),(0,A.createElement)("div",{className:"llms-field-group","data-field-layout":g?s:"stacked"},(0,A.createElement)(j.InnerBlocks,{allowedBlocks:c,template:"function"==typeof u?u({attributes:t,clientId:n,block:i,blockType:a}):u,templateLock:d,orientation:v})),f.after&&(0,A.createElement)(q.Slot,{name:`llmsEditFill.after.${f.after}.${p.clientId}`}))},save:function(){return(0,A.createElement)(j.InnerBlocks.Content,null)}},Ai=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"field";const t="field"===e?Vi:Ni;return(0,Fr.merge)({},(0,Fr.cloneDeep)(Ri),t)};function Fi(){return(0,Fr.cloneDeep)(["llms_form","wp_block"])}function Bi(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];e=(0,Fr.cloneDeep)(e);for(let t=0;t0&&void 0!==arguments[0]?arguments[0]:2,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;const n=[];for(let r=1;r<=e;r++)n.push({default:t&&t>0?"yes":"no",
+// Translators: %d = Option index in the list of options.
+text:(0,$.sprintf)((0,$.__)("Option %d","lifterlms"),r),
+// Translators: %d = Option index in the list of options.
+key:(0,$.sprintf)((0,$.__)("option_%d","lifterlms"),r)}),t--;return n}const zi=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"},(0,A.createElement)(q.Path,{d:"M32 288c-17.7 0-32 14.3-32 32s14.3 32 32 32l384 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 288zm0-128c-17.7 0-32 14.3-32 32s14.3 32 32 32l384 0c17.7 0 32-14.3 32-32s-14.3-32-32-32L32 160z"})),$i="llms/form-field-confirm-group",Ui=Fi(),ji=!0;function qi(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const{id:t}=e;let{match:n}=e;return t&&!n&&(n=`${t}_confirm`),{...e,match:n,columns:6,last_column:!1,isConfirmationControlField:!0,llms_visibility:"off"}}function Gi(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{id:t,name:n,match:r}=e;return t&&!r&&(r=t,t=`${t}_confirm`,n=`${n}_confirm`),{...e,id:t,name:n,match:r,label:e.label?
+// Translators: %s label of the controller field.
+(0,$.sprintf)((0,$.__)("Confirm %s","lifterlms"),e.label):"",columns:6,last_column:!0,data_store:!1,data_store_key:!1,isConfirmationField:!0,llms_visibility:"off"}}function Wi(e){const{unloadField:t}=(0,jr.dispatch)(wl);t(e)}const Ki=["llms/form-field-text","llms/form-field-user-email","llms/form-field-user-login","llms/form-field-user-password"],Yi={from:[],to:[]};Ki.forEach((e=>{Yi.from.push({type:"block",blocks:[e],transform:t=>{Wi(t.name);const{llms_visibility:n}=t,r=qi(t),l=Gi(t),s=[(0,Gr.createBlock)(e,r),(0,Gr.createBlock)("llms/form-field-text",l)];return(0,Gr.createBlock)($i,{llms_visibility:n},(0,$.isRTL)()?s.reverse():s)}}),Yi.to.push({type:"block",blocks:[e],isMatch:()=>{const{getSelectedBlock:t}=(0,jr.select)(j.store),{innerBlocks:n}=t(),r=n[Xi(n)],{name:l}=r||{};return l===e},transform:(e,t)=>{const{llms_visibility:n}=e,r=t[Xi(t)],{name:l,attributes:s}=r;return Wi(s.name),(0,Gr.createBlock)(l,{...s,columns:12,last_column:!0,isConfirmationControlField:!1,match:"",llms_visibility:n})}})}));const Xi=e=>e.findIndex((e=>{let{attributes:t}=e;return t.isConfirmationControlField})),Qi=Bi(Ai("group"),{title:(0,$.__)("Input Confirmation Group","lifterlms"),description:(0,$.__)("Adds a required confirmation field to an input field.","lifterlms"),icon:zi,category:"llms-custom-fields",transforms:Yi,fillInspectorControls:(e,t,n)=>{const{clientId:r}=n;return(0,A.createElement)(q.Button,{isDestructive:!0,onClick:()=>function(e){const{getBlock:t}=(0,jr.select)(j.store),{replaceBlock:n}=(0,jr.dispatch)(j.store),r=t(e),{innerBlocks:l}=r,{llms_visibility:s}=r.attributes,{name:o,attributes:i}=l[Xi(l)];Wi(i.name),n(e,(0,Gr.createBlock)(o,{...i,columns:12,last_column:!0,isConfirmationControlField:!1,match:"",llms_visibility:s}))}(r)},(0,$.__)("Remove confirmation field","lifterlms"))},findControllerBlockIndex:Xi,supports:{llms_field_inspector:{customFill:"confirmGroupAdditionalControls"},inserter:!1},llmsInnerBlocks:{allowed:Ki,template:e=>{let{block:t}=e,n=null;return t&&t.innerBlocks.length||(n=[["llms/form-field-text",qi()],["llms/form-field-text",Gi()]]),n}}}),Zi=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"},(0,A.createElement)(F.Path,{d:"M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H384c8.8 0 16-7.2 16-16V96c0-8.8-7.2-16-16-16H64zM0 96C0 60.7 28.7 32 64 32H384c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM337 209L209 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L303 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"})),Ji="llms/form-field-checkboxes",ea=Fi(),ta=!1,na=Bi(Ai(),{title:(0,$.__)("Checkboxes","lifterlms"),description:(0,$.__)("A single checkbox toggle or a group of multiple checkboxes.","lifterlms"),icon:Zi,category:"llms-custom-fields",supports:{llms_field_inspector:{options:!0}},attributes:{field:{__default:"checkbox"},options:{__default:Hi(2,0)}},transforms:{from:[{type:"block",blocks:["llms/form-field-radio","llms/form-field-select"],transform:e=>(0,Gr.createBlock)(Ji,{...e,field:na.attributes.field.__default})}]}}),ra=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(F.Path,{d:"M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm256-96a96 96 0 1 1 0 192 96 96 0 1 1 0-192z"})),la="llms/form-field-radio",sa=Fi(),oa=!1,ia=Bi(na,{title:(0,$.__)("Radio","lifterlms"),description:(0,$.__)("A group of radio inputs which can be populated with any number of options.","lifterlms"),icon:ra,attributes:{field:{__default:"radio"},options:{__default:Hi(2,1)}},transforms:{from:[{type:"block",blocks:["llms/form-field-checkboxes","llms/form-field-select"],transform:e=>(0,Gr.createBlock)(la,{...e,field:ia.attributes.field.__default})}]}}),aa=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"},(0,A.createElement)(q.Path,{d:"M384 432c8.8 0 16-7.2 16-16l0-320c0-8.8-7.2-16-16-16L64 80c-8.8 0-16 7.2-16 16l0 320c0 8.8 7.2 16 16 16l320 0zm64-16c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96C0 60.7 28.7 32 64 32l320 0c35.3 0 64 28.7 64 64l0 320zM224 352c-6.7 0-13-2.8-17.6-7.7l-104-112c-6.5-7-8.2-17.2-4.4-25.9s12.5-14.4 22-14.4l208 0c9.5 0 18.2 5.7 22 14.4s2.1 18.9-4.4 25.9l-104 112c-4.5 4.9-10.9 7.7-17.6 7.7z"})),ca="llms/form-field-select",ua=Fi(),da=!1,pa=Bi(ia,{title:(0,$.__)("Dropdown","lifterlms"),description:(0,$.__)("A select field which can be populated with any number of options.","lifterlms"),icon:aa,attributes:{field:{__default:"select"}},supports:{llms_field_inspector:{placeholder:!0}},transforms:{from:[{type:"block",blocks:["llms/form-field-checkboxes","llms/form-field-radio"],transform:e=>(0,Gr.createBlock)(ca,{...e,field:pa.attributes.field.__default})}]}}),ma=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(q.Path,{d:"M448 0h-384c-8.832 0-16 7.168-16 16v96c0 8.832 7.168 16 16 16h16c8.832 0 16-7.168 16-16l32-48h96v384l-80 32c-8.832 0-16 7.152-16 16s7.168 16 16 16h224c8.848 0 16-7.152 16-16s-7.152-16-16-16l-80-32v-384h96l32 48c0 8.832 7.152 16 16 16h16c8.848 0 16-7.168 16-16v-96c0-8.832-7.152-16-16-16z"})),fa=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"},(0,A.createElement)(F.Path,{d:"M181.3 32.4c17.4 2.9 29.2 19.4 26.3 36.8L197.8 128h95.1l11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3s29.2 19.4 26.3 36.8L357.8 128H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H347.1L325.8 320H384c17.7 0 32 14.3 32 32s-14.3 32-32 32H315.1l-11.5 69.3c-2.9 17.4-19.4 29.2-36.8 26.3s-29.2-19.4-26.3-36.8l9.8-58.7H155.1l-11.5 69.3c-2.9 17.4-19.4 29.2-36.8 26.3s-29.2-19.4-26.3-36.8L90.2 384H32c-17.7 0-32-14.3-32-32s14.3-32 32-32h68.9l21.3-128H64c-17.7 0-32-14.3-32-32s14.3-32 32-32h68.9l11.5-69.3c2.9-17.4 19.4-29.2 36.8-26.3zM187.1 192L165.8 320h95.1l21.3-128H187.1z"})),ha=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(q.Path,{d:"M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"})),ga=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"},(0,A.createElement)(F.Path,{d:"M144 144v48H304V144c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192V144C80 64.5 144.5 0 224 0s144 64.5 144 144v48h16c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64H80z"})),va=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(F.Path,{d:"M164.9 24.6c-7.7-18.6-28-28.5-47.4-23.2l-88 24C12.1 30.2 0 46 0 64C0 311.4 200.6 512 448 512c18 0 33.8-12.1 38.6-29.5l24-88c5.3-19.4-4.6-39.7-23.2-47.4l-96-40c-16.3-6.8-35.2-2.1-46.3 11.6L304.7 368C234.3 334.7 177.3 277.7 144 207.3L193.3 167c13.7-11.2 18.4-30 11.6-46.3l-40-96z"})),ba=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 640 512"},(0,A.createElement)(q.Path,{d:"M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"})),_a=Ai(),ya="llms/form-field-text",wa=Fi(),Ea=!0,xa=[{name:"text",title:(0,$.__)("Text","lifterlms"),description:(0,$.__)("An input field which accepts any form of text.","lifterlms"),isDefault:!0,icon:ma},{name:"email",title:(0,$.__)("Email","lifterlms"),description:(0,$.__)("A text input field which only accepts an email address.","lifterlms"),icon:ha},{name:"password",title:(0,$.__)("Password","lifterlms"),description:(0,$.__)("User password confirmation field.","lifterlms"),icon:ga,scope:[]},{name:"number",title:(0,$.__)("Number","lifterlms"),description:(0,$.__)("An input field which only accepts numeric input.","lifterlms"),icon:fa,attributes:{html_attrs:{min:"",max:""}}},{name:"tel",title:(0,$.__)("Phone Number","lifterlms"),description:(0,$.__)("An input field which only accepts phone numbers.","lifterlms"),icon:va},{name:"url",title:(0,$.__)("Website Address / URL","lifterlms"),description:(0,$.__)("An input field which only accepts a website address or URL.","lifterlms"),icon:ba}];xa.forEach((e=>{e.scope=e.scope||["block","inserter","transform"],window.llmsBlocks.variationIconCanBeObject&&(e.icon={..._a.icon,src:e.icon}),e.attributes||(e.attributes={}),e.attributes.field=e.name,e.isActive=(e,t)=>e.field===t.field}));const Ca=Bi(_a,{title:(0,$.__)("Text","lifterlms"),description:(0,$.__)("A simple text input field.","lifterlms"),icon:ma,usesContext:["llms/fieldGroup/fieldLayout"],supports:{inserter:!1,llms_field_inspector:{customFill:"fieldTextAdditionalControls"}},variations:xa,fillInspectorControls:(e,t)=>{if(e.isConfirmationField||"number"!==e.field)return;const{html_attrs:n}=e,{min:r,max:l}=n;return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(q.TextControl,{label:(0,$.__)("Minimum Value","lifterlms"),help:(0,$.__)("Specify the minimum allowed value. Leave blank for no minimum.","lifterlms"),value:r,type:"number",onChange:e=>t({html_attrs:{...n,min:e}})}),(0,A.createElement)(q.TextControl,{label:(0,$.__)("Maximum Value","lifterlms"),help:(0,$.__)("Specify the maximum allowed value. Leave blank for no maximum.","lifterlms"),value:l,type:"number",onChange:e=>t({html_attrs:{...n,max:e}})}))}}),ka=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"},(0,A.createElement)(q.Path,{d:"M192 32h64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H384l0 352c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-352H288V448c0 17.7-14.3 32-32 32s-32-14.3-32-32V352H192c-88.4 0-160-71.6-160-160s71.6-160 160-160z"})),Sa="llms/form-field-textarea",Oa=!1,Ia=Bi(Ca,{title:(0,$.__)("Textarea","lifterlms"),description:(0,$.__)("A text field accepting multiple lines of user information.","lifterlms"),icon:ka,category:"llms-custom-fields",supports:{inserter:!0,llms_field_inspector:{customFill:"fieldTextarea"}},attributes:{field:{__default:"textarea"},html_attrs:{__default:{rows:4}}},fillInspectorControls:(e,t)=>{const{html_attrs:n}=e,{rows:r}=n;return(0,A.createElement)(q.TextControl,{label:(0,$.__)("Rows","lifterlms"),help:(0,$.__)("Specify the number of text rows for the textarea input.","lifterlms"),value:r,type:"number",onChange:e=>t({html_attrs:{...n,rows:e}}),min:"2",step:"1"})},transforms:{from:[{type:"block",blocks:["llms/form-field-text"],transform:e=>(0,Gr.createBlock)(Sa,{...e,html_attrs:{...e.html_attrs,rows:4},field:"textarea"})}],to:[{type:"block",blocks:["llms/form-field-text"],transform:e=>(0,Gr.createBlock)("llms/form-field-text",{...e,field:"text"})}]}},["transforms","variations"]),Ta=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 576 512"},(0,A.createElement)(F.Path,{d:"M64 64C28.7 64 0 92.7 0 128v64c0 8.8 7.4 15.7 15.7 18.6C34.5 217.1 48 235 48 256s-13.5 38.9-32.3 45.4C7.4 304.3 0 311.2 0 320v64c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V320c0-8.8-7.4-15.7-15.7-18.6C541.5 294.9 528 277 528 256s13.5-38.9 32.3-45.4c8.3-2.9 15.7-9.8 15.7-18.6V128c0-35.3-28.7-64-64-64H64zm64 112l0 160c0 8.8 7.2 16 16 16H432c8.8 0 16-7.2 16-16V176c0-8.8-7.2-16-16-16H144c-8.8 0-16 7.2-16 16zM96 160c0-17.7 14.3-32 32-32H448c17.7 0 32 14.3 32 32V352c0 17.7-14.3 32-32 32H128c-17.7 0-32-14.3-32-32V160z"})),Pa="llms/form-field-redeem-voucher",Ma=!0,Da=Bi(Ca,{title:(0,$.__)("Voucher Code Redemption","lifterlms"),description:(0,$.__)("Allows user to redeem a voucher code during account registration.","lifterlms"),icon:Ta,supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,storage:!1,customFill:"redeemVoucher"}},attributes:{id:{__default:"llms_voucher"},field:{__default:"text"},label:{__default:(0,$.__)("Have a voucher?","lifterlms")},name:{__default:"llms_voucher"},placeholder:{__default:(0,$.__)("Voucher Code","lifterlms")},data_store:{__default:!1},data_store_key:{__default:!1},toggleable:{__default:!1}},fillInspectorControls:(e,t)=>{const{toggleable:n,required:r}=e;return r?null:(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Toggleable","lifterlms"),checked:!!n,onChange:()=>t({toggleable:!n}),help:n?(0,$.__)("Field is revealed when the toggle is clicked.","lifterlms"):(0,$.__)("Field is always visible.","lifterlms")})}},["transforms","variations"]),La=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 384 512"},(0,A.createElement)(F.Path,{d:"M192 0c-41.8 0-77.4 26.7-90.5 64H64C28.7 64 0 92.7 0 128V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V128c0-35.3-28.7-64-64-64H282.5C269.4 26.7 233.8 0 192 0zm0 64a32 32 0 1 1 0 64 32 32 0 1 1 0-64zM128 256a64 64 0 1 1 128 0 64 64 0 1 1 -128 0zM80 432c0-44.2 35.8-80 80-80h64c44.2 0 80 35.8 80 80c0 8.8-7.2 16-16 16H96c-8.8 0-16-7.2-16-16z"})),Ra="llms/form-field-user-display-name",Va=!0,Na=Bi(Ca,{title:(0,$.__)("User Display Name","lifterlms"),description:(0,$.__)("Allows a user to choose how their name will be displayed publicly on the site.","lifterlms"),icon:La,supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1}},attributes:{id:{__default:"display_name"},field:{__default:"text"},label:{__default:(0,$.__)("Display Name","lifterlms")},name:{__default:"display_name"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"display_name"}}},["transforms","variations"]),Aa=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(F.Path,{d:"M399 384.2C376.9 345.8 335.4 320 288 320H224c-47.4 0-88.9 25.8-111 64.2c35.2 39.2 86.2 63.8 143 63.8s107.8-24.7 143-63.8zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm256 16a72 72 0 1 0 0-144 72 72 0 1 0 0 144z"})),Fa="llms/form-field-user-login",Ba=!0,Ha=Bi(Ca,{title:(0,$.__)("User Login","lifterlms"),description:(0,$.__)("Field used to collect a user's account username. If this field is omitted a username will be automatically generated based off their email address. Users can always login using either their email address or username.","lifterlms"),icon:Aa,supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1}},attributes:{id:{__default:"user_login"},field:{__default:"text"},label:{__default:(0,$.__)("Username","lifterlms")},name:{__default:"user_login"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"user_login"},llms_visibility:{default:"logged_out"}}},["transforms","variations"]),za="llms/form-field-user-email",$a=!0,Ua=Bi(Ca,{title:(0,$.__)("User Email","lifterlms"),description:(0,$.__)("A special field used to collect a user's account email address.","lifterlms"),icon:ha,supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1}},attributes:{id:{__default:"email_address"},field:{__default:"email"},label:{__default:(0,$.__)("Email Address","lifterlms")},name:{__default:"email_address"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"user_email"}}},["transforms","variations"]),ja=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 448 512"},(0,A.createElement)(F.Path,{d:"M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"})),qa="llms/form-field-user-first-name",Ga=!0,Wa=Bi(Ca,{title:(0,$.__)("First Name","lifterlms"),description:(0,$.__)("A special field used to collect a user's first name.","lifterlms"),icon:ja,supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,storage:!1}},attributes:{id:{__default:"first_name"},field:{__default:"text"},label:{__default:(0,$.__)("First Name","lifterlms")},name:{__default:"first_name"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"first_name"}},parent:["llms/form-field-user-name"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),Ka="llms/form-field-user-last-name",Ya=!0,Xa=Bi(Wa,{title:(0,$.__)("Last Name","lifterlms"),description:(0,$.__)("A special field used to collect a user's last name.","lifterlms"),attributes:{id:{__default:"last_name"},label:{__default:(0,$.__)("Last Name","lifterlms")},name:{__default:"last_name"},data_store_key:{__default:"last_name"}}}),Qa="llms/form-field-user-name",Za=Fi(),Ja=!0,ec=Bi(Ai("group"),{title:(0,$.__)("User name","lifterlms"),description:(0,$.__)("A special field used to collect a user's first and last name.","lifterlms"),icon:ja,supports:{inserter:!0,multiple:!1},llmsInnerBlocks:{allowed:["llms/form-field-user-first-name","llms/form-field-user-last-name"],template:[["llms/form-field-user-first-name",{columns:6,last_column:!1}],["llms/form-field-user-last-name",{columns:6,last_column:!0}]]}}),tc="llms/form-field-user-password",nc=!0;function rc(e,t){return{html_attrs:{...e,minlength:t}}}const lc=Bi(Ca,{title:(0,$.__)("User Password","lifterlms"),description:(0,$.__)("A special field used to collect a user's account password.","lifterlms"),icon:ga,supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!1,storage:!1,customFill:"userPassAdditionalControls"},llms_edit_fill:{after:"userPassStrengthMeter"}},attributes:{id:{__default:"password"},field:{__default:"password"},label:{__default:(0,$.__)("Password","lifterlms")},name:{__default:"password"},required:{__default:!0},data_store:{__default:"users"},data_store_key:{__default:"user_pass"},meter:{type:"boolean",__default:!0},meter_description:{type:"string",__default:(0,$.__)("A strong password is required with at least 8 characters. To make it stronger, use both upper and lower case letters, numbers, and symbols.","lifterlms")},min_strength:{type:"string",__default:"strong"},html_attrs:{__default:{minlength:8}}},fillEditAfter:(e,t)=>{const{meter:n,meter_description:r}=e;return n?(0,A.createElement)(A.Fragment,null,(0,A.createElement)("div",{className:"llms-pwd-meter"},(0,A.createElement)("div",null,(0,$.__)("Very Weak","lifterlms"))),(0,A.createElement)(j.RichText,{style:{marginTop:0},tagName:"p",value:r,onChange:e=>t({meter_description:e}),allowedFormats:["core/bold","core/italic"],"aria-label":r?(0,$.__)("Password strength meter description","lifterlms"):(0,$.__)("Empty Password strength meter description; start writing to add a label"),placeholder:(0,$.__)("Enter a description for the password strength meter","lifterlms")})):null},fillInspectorControls:(e,t)=>{const{isConfirmationControlField:n,isConfirmationField:r,meter:l,min_strength:s,html_attrs:o}=e,{minlength:i}=o;if(r)return;return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Password strength meter","lifterlms"),help:l?(0,$.__)("Password strength meter is enabled.","lifterlms"):(0,$.__)("Password strength meter is disabled.","lifterlms"),checked:l,onChange:()=>t({meter:!l})}),l&&(0,A.createElement)(q.SelectControl,{label:(0,$.__)("Minimum Password Strength","lifterlms"),value:s,onChange:e=>t({min_strength:e}),options:[{value:"strong",label:(0,$.__)("Strong","lifterlms")},{value:"medium",label:(0,$.__)("Medium","lifterlms")},{value:"weak",label:(0,$.__)("Weak","lifterlms")}]}),(0,A.createElement)(q.TextControl,{label:(0,$.__)("Minimum Password Length","lifterlms"),value:i,type:"number",min:"6",onChange:e=>(e=>{t(rc(o,e)),n&&function(e){const{getSelectedBlockClientId:t}=(0,jr.select)(j.store),{updateBlockAttributes:n}=(0,jr.dispatch)(j.store),r=Ii(t()),{attributes:l,clientId:s}=r,{html_attrs:o}=l;n(s,rc(o,e))}(e)})(1*e)}))}},["transforms","variations"]),sc=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 576 512"},(0,A.createElement)(q.Path,{d:"M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm80 256h64c44.2 0 80 35.8 80 80c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16c0-44.2 35.8-80 80-80zm-32-96a64 64 0 1 1 128 0 64 64 0 1 1 -128 0zm256-32H496c8.8 0 16 7.2 16 16s-7.2 16-16 16H368c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H496c8.8 0 16 7.2 16 16s-7.2 16-16 16H368c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64H496c8.8 0 16 7.2 16 16s-7.2 16-16 16H368c-8.8 0-16-7.2-16-16s7.2-16 16-16z"})),oc="llms/form-field-user-address",ic=Fi(),ac=!0,cc=Bi(Ai("group"),{title:(0,$.__)("User Address","lifterlms"),description:(0,$.__)("A group of fields used to collect a user's full address.","lifterlms"),icon:sc,supports:{inserter:!0,multiple:!1},llmsInnerBlocks:{allowed:["llms/form-field-user-address-street","llms/form-field-user-address-city","llms/form-field-user-address-country","llms/form-field-user-address-region"],template:[["llms/form-field-user-address-street"],["llms/form-field-user-address-city"],["llms/form-field-user-address-country"],["llms/form-field-user-address-region"]]}},["providesContext"]),uc="llms/form-field-user-address-street",dc=Fi(),pc=!0,mc=Bi(Ai("group"),{title:(0,$.__)("User Street Address","lifterlms"),description:(0,$.__)("Collect a user's street address.","lifterlms"),icon:sc,supports:{multiple:!1},llmsInnerBlocks:{allowed:["llms/form-field-user-address-street-primary","llms/form-field-user-address-street-secondary"],template:[["llms/form-field-user-address-street-primary",{columns:8,last_column:!1}],["llms/form-field-user-address-street-secondary",{columns:4,last_column:!0}]]},parent:["llms/form-field-user-name"]}),fc=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 576 512"},(0,A.createElement)(q.Path,{d:"M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"})),hc="llms/form-field-user-address-street-primary",gc=!0,vc=Bi(Ca,{title:(0,$.__)("User Street Address","lifterlms"),description:(0,$.__)("A special field used to collect a user's street address.","lifterlms"),icon:fc,supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,storage:!1}},attributes:{id:{__default:"llms_billing_address_1"},label:{__default:(0,$.__)("Address","lifterlms")},name:{__default:"llms_billing_address_1"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_address_1"}},parent:["llms/form-field-user-address-street"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),bc="llms/form-field-user-address-street-secondary",_c=!0,yc=Bi(vc,{title:(0,$.__)("User Street Address Additional Information","lifterlms"),description:(0,$.__)("A special field used to collect a user's street address.","lifterlms"),attributes:{id:{__default:"llms_billing_address_2"},label:{__default:""},placeholder:{__default:(0,$.__)("Apartment, suite, etc…","lifterlms")},name:{__default:"llms_billing_address_2"},required:{__default:!1},data_store_key:{__default:"llms_billing_address_2"},label_show_empty:{__default:!0}},usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),wc=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 576 512"},(0,A.createElement)(q.Path,{d:"M408 120c0 54.6-73.1 151.9-105.2 192c-7.7 9.6-22 9.6-29.6 0C241.1 271.9 168 174.6 168 120C168 53.7 221.7 0 288 0s120 53.7 120 120zm8 80.4c3.5-6.9 6.7-13.8 9.6-20.6c.5-1.2 1-2.5 1.5-3.7l116-46.4C558.9 123.4 576 135 576 152V422.8c0 9.8-6 18.6-15.1 22.3L416 503V200.4zM137.6 138.3c2.4 14.1 7.2 28.3 12.8 41.5c2.9 6.8 6.1 13.7 9.6 20.6V451.8L32.9 502.7C17.1 509 0 497.4 0 480.4V209.6c0-9.8 6-18.6 15.1-22.3l122.6-49zM327.8 332c13.9-17.4 35.7-45.7 56.2-77V504.3L192 449.4V255c20.5 31.3 42.3 59.6 56.2 77c20.5 25.6 59.1 25.6 79.6 0zM288 152a40 40 0 1 0 0-80 40 40 0 1 0 0 80z"})),Ec="llms/form-field-user-address-city",xc=!0,Cc=Bi(Ca,{title:(0,$.__)("User City","lifterlms"),description:(0,$.__)("A special field used to collect a user's billing city.","lifterlms"),icon:wc,supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,match:!1,storage:!1}},attributes:{id:{__default:"llms_billing_city"},label:{__default:(0,$.__)("City","lifterlms")},name:{__default:"llms_billing_city"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_city"}},parent:["llms/form-field-user-address"]},["transforms","variations"]),kc=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 512 512"},(0,A.createElement)(q.Path,{d:"M57.7 193l9.4 16.4c8.3 14.5 21.9 25.2 38 29.8L163 255.7c17.2 4.9 29 20.6 29 38.5v39.9c0 11 6.2 21 16 25.9s16 14.9 16 25.9v39c0 15.6 14.9 26.9 29.9 22.6c16.1-4.6 28.6-17.5 32.7-33.8l2.8-11.2c4.2-16.9 15.2-31.4 30.3-40l8.1-4.6c15-8.5 24.2-24.5 24.2-41.7v-8.3c0-12.7-5.1-24.9-14.1-33.9l-3.9-3.9c-9-9-21.2-14.1-33.9-14.1H257c-11.1 0-22.1-2.9-31.8-8.4l-34.5-19.7c-4.3-2.5-7.6-6.5-9.2-11.2c-3.2-9.6 1.1-20 10.2-24.5l5.9-3c6.6-3.3 14.3-3.9 21.3-1.5l23.2 7.7c8.2 2.7 17.2-.4 21.9-7.5c4.7-7 4.2-16.3-1.2-22.8l-13.6-16.3c-10-12-9.9-29.5 .3-41.3l15.7-18.3c8.8-10.3 10.2-25 3.5-36.7l-2.4-4.2c-3.5-.2-6.9-.3-10.4-.3C163.1 48 84.4 108.9 57.7 193zM464 256c0-36.8-9.6-71.4-26.4-101.5L412 164.8c-15.7 6.3-23.8 23.8-18.5 39.8l16.9 50.7c3.5 10.4 12 18.3 22.6 20.9l29.1 7.3c1.2-9 1.8-18.2 1.8-27.5zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"})),Sc="llms/form-field-user-address-country",Oc=!0,Ic=Bi(pa,{title:(0,$.__)("User Country","lifterlms"),description:(0,$.__)("A special field used to collect a user's billing country.","lifterlms"),icon:kc,supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,match:!1,storage:!1,options:!1}},attributes:{id:{__default:"llms_billing_country"},label:{__default:(0,$.__)("Country","lifterlms")},name:{__default:"llms_billing_country"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_country"},options_preset:{__default:"countries"},placeholder:{__default:(0,$.__)("Select a Country","lifterlms")}},parent:["llms/form-field-user-address"]},["transforms"]),Tc="llms/form-field-user-address-region",Pc=Fi(),Mc=!0,Dc=Bi(Ai("group"),{title:(0,$.__)("User Street Address","lifterlms"),description:(0,$.__)("Collect a user's street address.","lifterlms"),icon:{src:sc},supports:{multiple:!1},parent:["llms/form-field-user-name"],llmsInnerBlocks:{allowed:["llms/form-field-user-address-state","llms/form-field-user-address-postal-code"],template:[["llms/form-field-user-address-state",{columns:6,last_column:!1}],["llms/form-field-user-address-postal-code",{columns:6,last_column:!0}]]}}),Lc=(0,A.createElement)(F.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 384 512"},(0,A.createElement)(F.Path,{d:"M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"})),Rc="llms/form-field-user-address-state",Vc=!0,Nc=Bi(pa,{title:(0,$.__)("User Country","lifterlms"),description:(0,$.__)("A special field used to collect a user's billing country.","lifterlms"),icon:Lc,supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,storage:!1,options:!1}},attributes:{id:{__default:"llms_billing_state"},label:{__default:(0,$.__)("State / Region","lifterlms")},name:{__default:"llms_billing_state"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_state"},options_preset:{__default:"states"},placeholder:{__default:(0,$.__)("Select a State / Region","lifterlms")}},parent:["llms/form-field-user-address-region"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms"]),Ac=(0,A.createElement)(q.SVG,{className:"llms-block-icon",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 320 512"},(0,A.createElement)(q.Path,{d:"M16 144a144 144 0 1 1 288 0A144 144 0 1 1 16 144zM160 80c8.8 0 16-7.2 16-16s-7.2-16-16-16c-53 0-96 43-96 96c0 8.8 7.2 16 16 16s16-7.2 16-16c0-35.3 28.7-64 64-64zM128 480V317.1c10.4 1.9 21.1 2.9 32 2.9s21.6-1 32-2.9V480c0 17.7-14.3 32-32 32s-32-14.3-32-32z"})),Fc="llms/form-field-user-address-postal-code",Bc=!0,Hc=Bi(Ca,{title:(0,$.__)("User Postal Code","lifterlms"),description:(0,$.__)("A special field used to collect a user's postal or zip code.","lifterlms"),icon:Ac,supports:{multiple:!1,llms_field_inspector:{id:!1,name:!1,required:!0,match:!1,storage:!1}},attributes:{id:{__default:"llms_billing_zip"},label:{__default:(0,$.__)("Postal / Zip Code","lifterlms")},name:{__default:"llms_billing_zip"},required:{__default:!0},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_billing_zip"}},parent:["llms/form-field-user-address-region"],usesContext:["llms/fieldGroup/fieldLayout"]},["transforms","variations"]),zc="llms/form-field-user-phone",$c=!0,Uc=Bi(Ca,{title:(0,$.__)("User Phone","lifterlms"),description:(0,$.__)("A field used to collect a user's phone number.","lifterlms"),icon:va,supports:{inserter:!0,multiple:!1,llms_field_inspector:{id:!1,name:!1,storage:!1}},attributes:{id:{__default:"llms_phone"},field:{__default:"tel"},label:{__default:(0,$.__)("Phone Number","lifterlms")},name:{__default:"llms_phone"},data_store:{__default:"usermeta"},data_store_key:{__default:"llms_phone"}}},["transforms","variations"]),jc=()=>{const e=(0,H.applyFilters)("llms.formBlocksSafelist",["core/block","core/paragraph","core/heading","core/image","core/html","core/column","core/columns","core/group","core/separator","core/spacer"]),{getCurrentPost:t}=(0,jr.select)(qr.store),{meta:n={}}=t(),{_llms_form_location:r}=n;(0,Gr.getBlockTypes)().forEach((t=>{let{name:n}=t;(t=>-1===e.indexOf(t)&&(0===t.indexOf("llms/form-field-redeem-voucher")?"registration"!==r:0===t.indexOf("llms/form-field-user-login")?"account"===r:-1===t.indexOf("llms/form-field")))(n)&&(0,Gr.unregisterBlockType)(n)}))};Ur()((()=>{const{getCurrentPost:e}=(0,jr.select)(qr.store);let t=!1;const n=(0,jr.subscribe)((()=>{const r=e();if(!1===t&&0!==Object.keys(r).length){t=!0,n();const{type:e,is_llms_field:l}=r;"llms_form"===e?(Xr(),jc(),kl()):"wp_block"===e&&"yes"===l&&(jc(),kl())}}))}));const qc=window.wp.plugins,Gc=window.wp.editPost,Wc=(0,H.applyFilters)("llms_blocks_post_visibility_options",[{value:"catalog_search",label:(0,$.__)("Visible","lifterlms"),info:(0,$.__)("Visible in the catalog and search results.","lifterlms")},{value:"catalog",label:(0,$.__)("Catalog only","lifterlms"),info:(0,$.__)("Only visible in the catalog.","lifterlms")},{value:"search",label:(0,$.__)("Search only","lifterlms"),info:(0,$.__)("Only visible in search results.","lifterlms")},{value:"hidden",label:(0,$.__)("Hidden","lifterlms"),info:(0,$.__)("Hidden from catalog and search results.","lifterlms")}]),Kc=(0,jr.withSelect)((e=>({visibility:e("core/editor").getEditedPostAttribute("visibility")})))((function(e){let{visibility:t}=e;return Wc.find((e=>{let{value:n}=e;return n===t})).label}));class Yc extends A.Component{render(){const{onUpdateVisibility:e,instanceId:t,visibility:n}=this.props;return(0,A.createElement)(Gc.PluginPostStatusInfo,{className:"llms-post-visibility"},(0,A.createElement)("span",null,(0,$.__)("Catalog & Search Visibility","lifterlms")),(0,A.createElement)("div",null,(0,A.createElement)(q.Dropdown,{className:"llms-post-visibility-dropdown",contentClassName:"llms-post-visibility-content edit-post-post-visibility__dialog",renderToggle:e=>{let{isOpen:t,onToggle:n}=e;return(0,A.createElement)(q.Button,{onClick:n,"aria-expanded":t,isLink:!0},(0,A.createElement)(Kc,null))},renderContent:()=>(0,A.createElement)("fieldset",{key:"visibility-selector",className:"editor-post-visibility__dialog-fieldset"},(0,A.createElement)("legend",{className:"editor-post-visibility__dialog-legend"},(0,$.__)("Catalog Visibility","lifterlms")),Wc.map((r=>{let{value:l,label:s,info:o}=r;return(0,A.createElement)("div",{key:l,className:"editor-post-visibility__choice"},(0,A.createElement)("input",{type:"radio",name:`llms-editor-post-visibility__setting-${t}`,value:l,onChange:()=>e(l),checked:l===n,id:`editor-post-${l}-${t}`,"aria-describedby":`editor-post-${l}-${t}-description`,className:"editor-post-visibility__dialog-radio"}),(0,A.createElement)("label",{htmlFor:`editor-post-${l}-${t}`,className:"editor-post-visibility__dialog-label"},s),(0,A.createElement)("p",{id:`llms-editor-post-${l}-${t}-description`,className:"editor-post-visibility__dialog-info"},o))})))})))}}const Xc=(0,U.compose)([(0,jr.withSelect)((e=>{const{getCurrentPostType:t,getEditedPostAttribute:n}=e("core/editor");return{postType:t(),visibility:n("visibility")}})),(0,jr.withDispatch)((e=>{const{editPost:t}=e("core/editor");return{onUpdateVisibility(e){t({visibility:e})}}})),(0,U.ifCondition)((e=>{let{postType:t}=e;return-1!==["course","llms_membership"].indexOf(t)})),U.withInstanceId])(Yc);(0,qc.registerPlugin)("llms-post-visibility",{render:Xc});const Qc=window.wp.richText;function Zc(e){let{text:t,onSuccess:n}=e;const r=void 0!==U.useCopyToClipboard;return(0,A.createElement)(q.Tooltip,{text:(0,$.__)("Click to copy.","lifterlms")},r&&(0,A.createElement)((()=>{const e=(0,U.useCopyToClipboard)(t,n);return(0,A.createElement)(q.Button,{isLink:!0,ref:e},t)}),null),!r&&(0,A.createElement)((()=>(0,A.createElement)(q.ClipboardButton,{isLink:!0,text:t,onCopy:n},t)),null))}function Jc(e){let{closeModal:t,isActive:n,onChange:r,searchQuery:l,value:s,defaultValue:o}=e,{userInfoFields:i}=window.llms;l&&(i=i.filter((e=>function(e,t){let{label:n,name:r,id:l,data_store_key:s}=t;const o=[n,r,l,s],i=e.toLowerCase();return o.some((e=>e.toLowerCase().includes(i)))}(l,e))));const a=!i.length;a&&i.push({data_store_key:l,label:(0,$.__)("Custom User Information","lifterlms"),id:"custom",name:l});const c=(0,H.applyFilters)("llms/userInfoShortcodes/exclude",["password"]);return i=i.filter((e=>{let{id:t}=e;return!c.includes(t)})),(0,A.createElement)(A.Fragment,null,a&&(0,A.createElement)("p",{className:"llms-error"},(0,$.__)("No fields found matching your search but you can use the shortcode below if the meta information exists in the database.","lifterlms")),(0,A.createElement)("table",{className:"llms-table zebra"},(0,A.createElement)("thead",null,(0,A.createElement)("tr",null,(0,A.createElement)("th",null,(0,$.__)("Name","lifterlms")),(0,A.createElement)("th",null,(0,$.__)("Shortcode","lifterlms")),(0,A.createElement)("th",null,(0,$.__)("Insert","lifterlms")))),(0,A.createElement)("tbody",null,i.map((e=>function(e,t,n,r,l,s){let{label:o,name:i,data_store_key:a}=e;const c=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return`[llms-user ${e}${t?` or="${t}"`:""}]`}(a,s);return(0,A.createElement)("tr",{key:i},(0,A.createElement)("td",null,o),(0,A.createElement)("td",null,(0,A.createElement)(Zc,{text:c,onSuccess:n})),(0,A.createElement)("td",null,(0,A.createElement)(q.Button,{isSecondary:!0,isSmall:!0,onClick:()=>{const e=(0,Qc.create)({html:`${c} `});n(),r(t?(0,Qc.replace)(l,/\[user .+?\]/,e):(0,Qc.insert)(l,e))}},(0,$.__)("Insert","lifterlms"))))}(e,n,t,r,s,o))))))}(0,Qc.registerFormatType)("llms/user-info-shortcodes",{title:(0,$.__)("LifterLMS User Information Shortcodes","lifterlms"),tagName:"span",className:"llms-user-sc-wrap",edit:function(e){const[t,n]=(0,A.useState)(!1),[r,l]=(0,A.useState)(""),[s,o]=(0,A.useState)(""),i=()=>n(!1),{value:a,onChange:c,isActive:u}=e;return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(j.RichTextToolbarButton,{icon:(0,A.createElement)(B,null),title:(0,$.__)("Shortcodes","lifterlms"),onClick:()=>n(!0)}),t&&(0,A.createElement)(q.Modal,{className:"llms-shortcodes-modal",title:(0,$.__)("LifterLMS User Information Shortcodes","lifterlms"),onRequestClose:i},(0,A.createElement)("div",{className:"llms-shortcodes-modal--main"},(0,A.createElement)("aside",null,(0,A.createElement)(q.TextControl,{type:"search",label:(0,$.__)("Filter by label, key, or ID…","lifterlms"),onChange:e=>l(e)}),(0,A.createElement)(q.TextControl,{label:(0,$.__)("Default value","lifterlms"),onChange:e=>o(e),help:(0,$.__)("Optional text displayed when no information exists or the user is logged out.","lifterlms")})),(0,A.createElement)("section",null,(0,A.createElement)(Jc,{closeModal:i,isActive:u,onChange:c,searchQuery:r,value:a,defaultValue:s})))))}});const eu=window.wp.url;function tu(e){const{id:t,item:n,index:r,setNodeRef:l,listeners:s,manageState:o}=e,{visibility:i,name:a,label:c}=n,{updateItem:u,deleteItem:d}=o,p="visible"===i,m=0===r,[f,h]=(0,A.useState)(!1);return(0,A.createElement)(A.Fragment,null,(0,A.createElement)("div",{className:"llms-instructor--header"},(0,A.createElement)("section",null,(0,A.createElement)("strong",null,a),(0,A.createElement)("small",null,"(#",t,")")),(0,A.createElement)("aside",null,m&&(0,A.createElement)(q.Tooltip,{text:(0,$.__)("Primary Instructor","lifterlms")},(0,A.createElement)(q.Dashicon,{icon:"star-filled"})),(0,A.createElement)(Ei,{label:(0,$.__)("Reorder instructor","lifterlms"),setNodeRef:l,listeners:s}),(0,A.createElement)(q.Button,{isSmall:!0,showTooltip:!0,label:(0,$.__)("Edit instructor","lifterlms"),icon:f?"arrow-up-alt2":"arrow-down-alt2",onClick:()=>h(!f)}))),f&&(0,A.createElement)("div",{className:"llms-instructor--settings"},(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Visibility","lifterlms"),help:p?(0,$.__)("Instructor is visible on frontend","lifterlms"):(0,$.__)("Instructor is hidden on frontend","lifterlms"),checked:p,onChange:e=>u(t,{visibility:e?"visible":"hidden"})}),p&&(0,A.createElement)(q.TextControl,{label:(0,$.__)("Label","lifterlms"),value:c,onChange:e=>u(t,{label:e})}),(0,A.createElement)(q.Button,{isSecondary:!0,iconPosition:"right",href:(0,eu.addQueryArgs)("/wp-admin/user-edit.php",{user_id:t}),target:"_blank",rel:"noreferrer",style:{marginRight:"5px"}},(0,$.__)("Edit","lifterlms"),(0,A.createElement)(q.Dashicon,{icon:"external"})),!m&&(0,A.createElement)(q.Button,{isDestructive:!0,onClick:()=>d(n)},(0,$.__)("Remove","lifterlms"))))}function nu(e){let{instructors:t,roles:n,addInstructor:r}=e;return(0,A.createElement)(Cs,{roles:n,placeholder:(0,$.__)("Search…","lifterlms"),searchArgs:{exclude:t.map((e=>{let{id:t}=e;return t}))},onChange:r})}class ru extends A.Component{constructor(){super(...arguments),Y(this,"onSearchChange",(e=>{this.setState({search:e})})),Y(this,"updateInstructor",((e,t)=>{const{instructors:n}=this.state,r=n.map((n=>(e===n.id&&(n={...n,...t}),n)));this.updateInstructors(r)})),Y(this,"updateInstructors",(e=>{this.setState({instructors:e}),this.props.updateInstructors(e)})),Y(this,"addInstructor",(e=>{let{id:t,name:n}=e;const{instructors:r}=this.state;r.push({...this.getInstructorDefaults(),id:t,name:n}),this.updateInstructors(r)})),Y(this,"removeInstructor",(e=>{let{instructors:t}=this.state;t=t.filter((t=>{let{id:n}=t;return e.id!==n})),this.updateInstructors(t)})),Y(this,"render",(()=>(0,A.createElement)(q.PanelBody,{title:(0,$.__)("Instructors","lifterlms")},(0,A.createElement)(nu,{roles:this.getRoles(),instructors:this.state.instructors,addInstructor:this.addInstructor}),(0,A.createElement)(Ci,{ListItem:tu,items:this.state.instructors,itemClassName:"llms-instructor",manageState:{createItem:this.addInstructor,deleteItem:this.removeInstructor,updateItem:this.updateInstructor,updateItems:this.updateInstructors}}))));let{instructors:e}=this.props;e="string"==typeof e?JSON.parse(e):e,this.state={instructors:e||[],search:""}}getRoles(){return(0,H.applyFilters)("llms_instructor_roles",["administrator","lms_manager","instructor","instructors_assistant"])}getInstructorDefaults(){return(0,H.applyFilters)("llms_instructor_defaults",{label:(0,$.__)("Author","lifterlms"),visibility:"visible"})}}const lu=(0,jr.withSelect)((e=>{const{getEditedPostAttribute:t}=e("core/editor");return{instructors:t("instructors")}})),su=(0,jr.withDispatch)((e=>{const{editPost:t}=e("core/editor");return{updateInstructors(e){t({instructors:JSON.stringify(e)})}}})),ou=(0,U.compose)([lu,su])(ru),iu=window.wp.notices,{Fill:au,Slot:cu}=(0,q.createSlotFill)("LLMSFormDocSettings"),uu=e=>{let{children:t}=e;return(0,A.createElement)(au,null,t)};uu.Slot=cu,window.llms.plugins=window.llms.plugins||{},window.llms.plugins.LLMSFormDocSettings=uu;const du=uu;class pu extends A.Component{constructor(){super(...arguments),Y(this,"render",(()=>{if(void 0===Gc.PluginDocumentSettingPanel)return null;if("llms_form"!==(0,jr.select)(qr.store).getCurrentPostType())return null;const{location:e,link:t,showTitle:n,freeApTitle:r,setFormMetas:l}=this.props,{formLocations:s}=window.llms,o=s[e];function i(e){(0,jr.dispatch)(j.store).replaceBlocks((0,jr.select)(j.store).getBlocks().map((e=>e.clientId)),(0,Gr.parse)(e))}function a(){const e="llms-form-restore-default",t=(0,jr.select)(qr.store).getEditedPostAttribute("content"),{createSuccessNotice:n,removeNotice:r}=(0,jr.dispatch)(iu.store),{resetFields:l}=(0,jr.dispatch)(wl);l(),i(o.template),n((0,$.__)("The form has been restored to the default template.","lifterlms"),{id:e,actions:[{label:(0,$.__)("Undo","lifterlms"),onClick:()=>{l(),i(t),r(e)}}]})}return""===n&&l({_llms_form_show_title:"yes"}),(0,A.createElement)(A.Fragment,null,(0,A.createElement)(Gc.PluginDocumentSettingPanel,{className:"llms-forms-doc-settings",name:"llms-forms-doc-settings",title:(0,$.__)("Form Settings","lifterlms"),opened:!0},(0,A.createElement)(du.Slot,null,(s=>(0,A.createElement)(A.Fragment,null,(0,A.createElement)(q.PanelRow,null,(0,A.createElement)("strong",null,(0,$.__)("Location","lifterlms")),!t&&(0,A.createElement)("strong",null,o.name),t&&(0,A.createElement)(q.ExternalLink,{href:t},o.name)),(0,A.createElement)("p",{style:{marginTop:"5px"}},(0,A.createElement)("em",null,o.description)),s,(0,A.createElement)("br",null),(0,A.createElement)(q.ToggleControl,{label:(0,$.__)("Display Form Title","lifterlms"),checked:"yes"===n,help:"yes"===n?(0,$.__)("Displaying form title.","lifterlms"):(0,$.__)("Not displaying form title.","lifterlms"),onChange:e=>l({_llms_form_show_title:e?"yes":"no"})}),"checkout"===e&&"yes"===n&&(0,A.createElement)(q.TextControl,{label:(0,$.__)("Free Access Plan Form Title","lifterlms"),value:r,onChange:e=>l({_llms_form_title_free_access_plans:e}),help:(0,$.__)("The form title to be shown for free access plans.","lifterlms")}),(0,A.createElement)("br",null),(0,A.createElement)(q.PanelRow,null,(0,A.createElement)(q.Button,{isDestructive:!0,onClick:a},(0,$.__)("Revert to Default","lifterlms"))),(0,A.createElement)("p",{style:{marginTop:"5px"}},(0,A.createElement)("em",null,(0,$.__)("Replace the existing content of the form with the original default content.","lifterlms"))))))))}))}}const mu=(0,jr.withSelect)((e=>{const{getCurrentPost:t,getCurrentPostType:n,getEditedPostAttribute:r}=e(qr.store);if("llms_form"!==n())return{};const l=r("meta");return{link:t().link,location:l._llms_form_location,showTitle:l._llms_form_show_title,freeApTitle:l._llms_form_title_free_access_plans}})),fu=(0,jr.withDispatch)((e=>{const{editPost:t}=e("core/editor");return{setFormMetas(e){t({meta:e})}}})),hu=(0,U.compose)([mu,fu])(pu),gu=()=>{const e=(0,A.useRef)(null);(0,A.useEffect)((()=>{const t=document.querySelectorAll(".llms-builder-launcher")[0];if(!t)return null;for(;e.current.firstChild;)e.current.removeChild(e.current.firstChild);const n=t.cloneNode(!0);e.current.appendChild(n)}),[]);const t="llms-course-builder-panel";return(0,A.createElement)(A.Fragment,null,(0,A.createElement)(q.PanelBody,{title:(0,$.__)("Course Builder","lifterlms"),className:t,opened:!0,onToggle:()=>{document.getElementsByClassName(t)[0].classList.toggle("llms-course-builder-panel--close")}},(0,A.createElement)("div",{ref:e})))},vu="llms-launch-course-builder-top-button",bu=()=>{var e,t;let n=document.getElementsByClassName("edit-post-header-toolbar__left")[0];if(n||(n=document.getElementsByClassName("editor-document-tools__left")[0]),!n)return;const r=null!==(e=null!==(t=window?.llmsBlocks?.courseId)&&void 0!==t?t:(0,jr.select)("core/editor")?.getCurrentPostId())&&void 0!==e?e:0;setTimeout((()=>{if(document.getElementById(vu))return;const e=document.createElement("a");e.id=vu,e.href=window.llms.admin_url+"admin.php?page=llms-course-builder&course_id="+r,e.className="llms-button-primary",e.style.marginLeft="16px",e.innerHTML=(0,$.__)("Launch Course Builder","lifterlms"),n.appendChild(e)}),1)};(0,qc.registerPlugin)("post-status-info-test",{render:()=>{var e,t,n;const r=null!==(e=(0,jr.select)("core/editor")?.getCurrentPostType())&&void 0!==e?e:"";if(!["course","lesson"].includes(r))return null;const l=null!==(t=null!==(n=window?.llmsBlocks?.courseId)&&void 0!==n?n:(0,jr.select)("core/editor")?.getCurrentPostId())&&void 0!==t?t:0;return(0,A.createElement)(Gc.PluginPostStatusInfo,{className:"llms-launch-course-builder"},(0,A.createElement)(q.Button,{href:window.llms.admin_url+"admin.php?page=llms-course-builder&course_id="+l,className:"llms-button-primary"},(0,$.__)("Launch Course Builder","lifterlms")))}}),(0,qc.registerPlugin)("llms",{render:()=>{const e=(0,jr.select)("core/editor").getCurrentPostType();return["course","lesson","llms_membership"].includes(e)?(["course","lesson"].includes(e)&&(0,jr.subscribe)(bu),(0,A.createElement)(A.Fragment,null,(0,A.createElement)(Gc.PluginSidebarMoreMenuItem,{target:"llms-sidebar",icon:(0,A.createElement)(B,null)},"LifterLMS"),(0,A.createElement)(Gc.PluginSidebar,{name:"llms-sidebar",title:"LifterLMS"},["course","lesson"].includes(e)&&(0,A.createElement)(gu,null),["course","llms_membership"].includes(e)&&(0,A.createElement)(ou,null)))):null},icon:(0,A.createElement)(B,null)}),(0,qc.registerPlugin)("llms-forms-doc-settings",{render:hu,icon:""}),(()=>{const e=Yr(),t=[r,l,s,o,i,a,c,u];Object.keys(N).forEach((e=>{N[e].composed&&t.push(N[e])})),["llms_form","wp_block"].includes(e)&&(0,H.doAction)("llms_form_fields_ready",N),t.forEach((t=>{const{name:n,postTypes:r,settings:l}=t;r&&-1===r.indexOf(e)||(0,Gr.registerBlockType)(n,l)}))})();const{components:_u={}}=window.llms;window.llms.components={..._u,...d},wp.blocks.updateCategory("llms-blocks",{icon:B})})()})();
\ No newline at end of file
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-information-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-information-block.php
new file mode 100644
index 0000000000..c00f0d42a7
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-information-block.php
@@ -0,0 +1,183 @@
+ __( 'Course Information', 'lifterlms' ),
+ 'title_size' => 'h3',
+ 'show_length' => true,
+ 'show_difficulty' => true,
+ 'show_tracks' => true,
+ 'show_cats' => true,
+ 'show_tags' => true,
+ )
+ );
+
+ $show_wrappers = false;
+
+ if ( $attributes['show_length'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_length', 10 );
+ }
+
+ if ( $attributes['show_difficulty'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_difficulty', 20 );
+ }
+
+ if ( $attributes['show_tracks'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_course_tracks', 25 );
+ }
+
+ if ( $attributes['show_cats'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_course_categories', 30 );
+ }
+
+ if ( $attributes['show_tags'] ) {
+ $show_wrappers = true;
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_course_tags', 35 );
+ }
+
+ if ( $show_wrappers ) {
+
+ $this->title = $attributes['title'];
+ $this->title_size = $attributes['title_size'];
+
+ add_filter( 'llms_course_meta_info_title', array( $this, 'filter_title' ) );
+ add_filter( 'llms_course_meta_info_title_size', array( $this, 'filter_title_size' ) );
+
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_meta_wrapper_start', 5 );
+ add_action( $this->get_render_hook(), 'lifterlms_template_single_meta_wrapper_end', 50 );
+
+ }
+
+ }
+
+ /**
+ * Filters the title of the course information headline per block settings.
+ *
+ * @param string $title default title.
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function filter_title( $title ) {
+ return $this->title;
+ }
+
+ /**
+ * Filters the title headline element size of the course information headline per block settings.
+ *
+ * @param string $size default size.
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function filter_title_size( $size ) {
+ return $this->title_size;
+ }
+
+ /**
+ * Register meta attributes stub.
+ *
+ * Called after registering the block type.
+ *
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function register_meta() {
+
+ register_meta(
+ 'post',
+ '_llms_length',
+ array(
+ 'object_subtype' => 'course',
+ 'sanitize_callback' => 'sanitize_text_field',
+ 'auth_callback' => array( $this, 'meta_auth_callback' ),
+ 'type' => 'string',
+ 'single' => true,
+ 'show_in_rest' => true,
+ )
+ );
+
+ }
+
+ /**
+ * Meta field update authorization callback.
+ *
+ * @param bool $allowed Is the update allowed.
+ * @param string $meta_key Meta keyname.
+ * @param int $object_id WP Object ID (post,comment,etc)...
+ * @param int $user_id WP User ID.
+ * @param string $cap requested capability.
+ * @param array $caps user capabilities.
+ * @return bool
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function meta_auth_callback( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ) {
+ return true;
+ }
+
+}
+
+return new LLMS_Blocks_Course_Information_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-progress-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-progress-block.php
new file mode 100644
index 0000000000..31541749b5
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-progress-block.php
@@ -0,0 +1,92 @@
+get_render_hook(), array( $this, 'output' ), 10 );
+
+ }
+
+ /**
+ * Output the course progress bar
+ *
+ * @since 1.9.0
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @return void
+ */
+ public function output( $attributes = array() ) {
+
+ $block_content = '';
+ $progress = do_shortcode( '[lifterlms_course_progress check_enrollment=1]' );
+ $class = empty( $attributes['className'] ) ? '' : $attributes['className'];
+
+ if ( $progress ) {
+ $block_content = sprintf(
+ '%4$s
',
+ $this->vendor,
+ $this->id,
+ // Take into account the custom class attribute.
+ empty( $attributes['className'] ) ? '' : ' ' . esc_attr( $attributes['className'] ),
+ $progress
+ );
+ }
+
+ /**
+ * Filters the block html
+ *
+ * @since 1.9.0
+ *
+ * @param string $block_content The block's html.
+ * @param array $attributes The block's array of attributes.
+ * @param LLMS_Blocks_Course_Progress_Block $block This block object.
+ */
+ $block_content = apply_filters( 'llms_blocks_render_course_progress_block', $block_content, $attributes, $this );
+
+ if ( $block_content ) {
+ echo $block_content;
+ }
+
+ }
+}
+
+return new LLMS_Blocks_Course_Progress_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-syllabus-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-syllabus-block.php
new file mode 100644
index 0000000000..2d2b5251ba
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-course-syllabus-block.php
@@ -0,0 +1,176 @@
+get_render_hook(), 'lifterlms_template_single_syllabus', 10 );
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 1.0.0
+ * @deprecated 2.5.0
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ llms_deprecated_function( __METHOD__, '2.5.0' );
+
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'course_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Retrieve the ID/Name of the block.
+ *
+ * @since 1.0.0
+ * @deprecated 2.5.0
+ *
+ * @return string
+ */
+ public function get_block_id() {
+ llms_deprecated_function( __METHOD__, '2.5.0' );
+
+ return sprintf( '%1$s/%2$s', $this->vendor, $this->id );
+ }
+
+ /**
+ * Output a message when no HTML was rendered.
+ *
+ * @since 1.0.0
+ * @since 1.8.0 Don't output empty render messages on the frontend.
+ * @deprecated 2.5.0
+ *
+ * @return string
+ */
+ public function get_empty_render_message() {
+ llms_deprecated_function( __METHOD__, '2.5.0' );
+
+ if ( ! is_admin() ) {
+ return '';
+ }
+
+ return __( 'No HTML was returned.', 'lifterlms' );
+ }
+
+ /**
+ * Retrieve a string which can be used to render the block.
+ *
+ * @since 1.0.0
+ * @deprecated 2.5.0
+ *
+ * @return string
+ */
+ public function get_render_hook() {
+ llms_deprecated_function( __METHOD__, '2.5.0' );
+
+ return sprintf( '%1$s_%2$s_block_render', $this->vendor, $this->id );
+ }
+
+ /**
+ * Removed hooks stub.
+ *
+ * Extending classes can use this class to remove hooks attached to the render function action.
+ *
+ * @since 1.0.0
+ * @deprecated 2.5.0
+ *
+ * @return void
+ */
+ public function remove_hooks() {
+ llms_deprecated_function( __METHOD__, '2.5.0' );
+ }
+
+ /**
+ * Renders the block type output for given attributes.
+ *
+ * @since 1.0.0
+ * @deprecated 2.5.0
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @param string $content Optional. Block content. Default empty string.
+ * @return string
+ */
+ public function render_callback( $attributes = array(), $content = '' ) {
+ llms_deprecated_function( __METHOD__, '2.5.0' );
+
+ $this->add_hooks( $attributes, $content );
+
+ ob_start();
+ do_action( $this->get_render_hook(), $attributes, $content );
+ $ret = ob_get_clean();
+
+ $this->remove_hooks();
+
+ if ( ! $ret ) {
+ $ret = $this->get_empty_render_message();
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Register meta attributes stub.
+ *
+ * Called after registering the block type.
+ *
+ * @since 1.0.0
+ * @deprecated 2.5.0
+ *
+ * @return void
+ */
+ public function register_meta() {
+ llms_deprecated_function( __METHOD__, '2.5.0' );
+ }
+}
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-instructors-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-instructors-block.php
new file mode 100644
index 0000000000..04aed6ec84
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-instructors-block.php
@@ -0,0 +1,101 @@
+get_render_hook(), $func, 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 1.0.0
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Output a message when no HTML was rendered
+ *
+ * @since 1.0.0
+ * @since 1.8.0 Fixed spelling error.
+ *
+ * @return string
+ */
+ public function get_empty_render_message() {
+ return __( 'No visible instructors were found.', 'lifterlms' );
+ }
+
+}
+
+return new LLMS_Blocks_Instructors_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-navigation-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-navigation-block.php
new file mode 100644
index 0000000000..3a8b287593
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-navigation-block.php
@@ -0,0 +1,71 @@
+get_render_hook(), 'lifterlms_template_lesson_navigation', 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @return array
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+}
+
+return new LLMS_Blocks_Lesson_Navigation_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-progression-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-progression-block.php
new file mode 100644
index 0000000000..de07ea34d6
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-lesson-progression-block.php
@@ -0,0 +1,120 @@
+get_render_hook(), 'lifterlms_template_complete_lesson_link', 10 );
+
+ }
+
+ /**
+ * Output a message when no HTML was rendered
+ *
+ * @since 1.7.0
+ * @since 2.0.0 Ensure the queried object is an `LLMS_Lesson` before checking if it's free.
+ *
+ * @return string
+ */
+ public function get_empty_render_message() {
+ $lesson = llms_get_post( get_the_ID() );
+ if ( $lesson && is_a( $lesson, 'LLMS_Lesson' ) && $lesson->is_free() ) {
+ return '';
+ }
+ return parent::get_empty_render_message();
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 1.0.0
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Register meta attributes.
+ *
+ * Called after registering the block type.
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function register_meta() {
+
+ register_meta(
+ 'post',
+ '_llms_quiz',
+ array(
+ 'object_subtype' => 'lesson',
+ 'sanitize_callback' => 'absint',
+ 'auth_callback' => '__return_true',
+ 'type' => 'string',
+ 'single' => true,
+ 'show_in_rest' => true,
+ )
+ );
+
+ }
+
+}
+
+return new LLMS_Blocks_Lesson_Progression_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-php-template-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-php-template-block.php
new file mode 100644
index 0000000000..901c38c3ef
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-php-template-block.php
@@ -0,0 +1,144 @@
+ 'loop-main',
+ 'archive-llms_membership' => 'loop-main',
+ 'taxonomy-course_cat' => 'loop-main',
+ 'taxonomy-course_difficulty' => 'loop-main',
+ 'taxonomy-course_tag' => 'loop-main',
+ 'taxonomy-course_track' => 'loop-main',
+ 'taxonomy-membership_cat' => 'loop-main',
+ 'taxonomy-membership_tag' => 'loop-main',
+ 'single-certificate' => 'content-certificate',
+ 'single-no-access' => 'content-no-access',
+ );
+
+ /**
+ * Add actions attached to the render function action.
+ *
+ * @since 2.3.0
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @param string $content Optional. Block content. Default empty string.
+ * @return void
+ */
+ public function add_hooks( $attributes = array(), $content = '' ) {
+
+ add_action( $this->get_render_hook(), array( $this, 'output' ), 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes.
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 2.3.0
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array(
+ 'template' => array(
+ 'type' => 'string',
+ 'default' => '',
+ ),
+ 'title' => array(
+ 'type' => 'string',
+ 'default' => '',
+ ),
+ );
+ }
+
+ /**
+ * Output the template.
+ *
+ * @since 2.3.0
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @return void
+ */
+ public function output( $attributes = array() ) {
+
+ if ( empty( $attributes['template'] ) ) {
+ return;
+ }
+
+ /**
+ * Filters the php templates that can be render via this block.
+ *
+ * @since 2.3.0
+ *
+ * @param array $templates Templates map, where the keys are the template attribute value and the values are the php file names (w/o extension).
+ */
+ $templates = apply_filters( 'llms_blocks_php_templates_block', $this->templates );
+
+ if ( ! array_key_exists( $attributes['template'], $templates ) ) {
+ return;
+ }
+
+ ob_start();
+
+ llms_get_template( "{$templates[$attributes['template']]}.php" );
+
+ $block_content = ob_get_clean();
+
+ /**
+ * Filters the block html.
+ *
+ * @since 2.3.0
+ *
+ * @param string $block_content The block's html.
+ * @param array $attributes The block's array of attributes.
+ * @param array $template The template file basename to be rendered.
+ * @param LLMS_Blocks_PHP_Template_Block $block This block object.
+ */
+ $block_content = apply_filters( 'llms_blocks_render_php_template_block', $block_content, $attributes, $templates[ $attributes['template'] ], $this );
+
+ if ( $block_content ) {
+ echo $block_content;
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks_PHP_Template_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-pricing-table-block.php b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-pricing-table-block.php
new file mode 100644
index 0000000000..a47d848c10
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/class-llms-blocks-pricing-table-block.php
@@ -0,0 +1,129 @@
+get_render_hook(), array( $this, 'output' ), 10 );
+
+ }
+
+ /**
+ * Retrieve custom block attributes
+ *
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @since 1.0.0
+ * @since 1.3.6 Unknown.
+ *
+ * @return array
+ */
+ public function get_attributes() {
+ return array_merge(
+ parent::get_attributes(),
+ array(
+ 'post_id' => array(
+ 'type' => 'int',
+ 'default' => 0,
+ ),
+ )
+ );
+ }
+
+ /**
+ * Output the pricing table.
+ *
+ * @since 1.0.0
+ * @since 1.3.7 Unknown.
+ * @since 1.9.0 Added `llms_blocks_render_pricing_table_block` filter.
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @return void
+ */
+ public function output( $attributes = array() ) {
+
+ ob_start();
+ if ( 'edit' === filter_input( INPUT_GET, 'context' ) ) {
+ $id = filter_input( INPUT_GET, 'post_id', FILTER_SANITIZE_NUMBER_INT );
+ if ( $id ) {
+ $product = new LLMS_Product( $id );
+ if ( ! $product->get_access_plans() ) {
+ echo '' . __( 'No access plans found.', 'lifterlms' ) . '
';
+ }
+ }
+
+ // force display of the table on the admin panel.
+ add_filter( 'llms_product_pricing_table_enrollment_status', '__return_false' );
+ add_filter( 'llms_product_is_purchasable', '__return_true' );
+
+ }
+
+ lifterlms_template_pricing_table( $attributes['post_id'] );
+
+ $block_content = ob_get_clean();
+
+ /**
+ * Filters the block html
+ *
+ * @since 1.9.0
+ *
+ * @param string $block_content The block's html.
+ * @param array $attributes The block's array of attributes.
+ * @param LLMS_Blocks_Pricing_Table_Block $block This block object.
+ */
+ $block_content = apply_filters( 'llms_blocks_render_pricing_table_block', $block_content, $attributes, $this );
+
+ remove_filter( 'llms_product_pricing_table_enrollment_status', '__return_false' );
+ remove_filter( 'llms_product_is_purchasable', '__return_true' );
+
+ if ( $block_content ) {
+ echo $block_content;
+ }
+
+ }
+}
+
+return new LLMS_Blocks_Pricing_Table_Block();
diff --git a/libraries/lifterlms-blocks/includes/blocks/index.php b/libraries/lifterlms-blocks/includes/blocks/index.php
new file mode 100644
index 0000000000..82e2315c6b
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/blocks/index.php
@@ -0,0 +1,2 @@
+is_dynamic ) {
+
+ register_block_type(
+ $this->get_block_id(),
+ array(
+ 'attributes' => $this->get_attributes(),
+ 'render_callback' => array( $this, 'render_callback' ),
+ )
+ );
+
+ }
+
+ $this->register_meta();
+
+ }
+
+ /**
+ * Add hooks stub.
+ * Extending classes can use this class to add hooks attached to the render function action.
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @param string $content Optional. Block content. Default empty string.
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function add_hooks( $attributes = array(), $content = '' ) {}
+
+ /**
+ * Retrieve custom block attributes.
+ * Necessary to override when creating ServerSideRender blocks.
+ *
+ * @return array
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_attributes() {
+ return LLMS_Blocks_Visibility::get_attributes();
+ }
+
+ /**
+ * Retrieve the ID/Name of the block.
+ *
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_block_id() {
+ return sprintf( '%1$s/%2$s', $this->vendor, $this->id );
+ }
+
+ /**
+ * Output a message when no HTML was rendered
+ *
+ * @since 1.0.0
+ * @since 1.8.0 Don't output empty render messages on the frontend.
+ *
+ * @return string
+ */
+ public function get_empty_render_message() {
+ if ( ! is_admin() ) {
+ return '';
+ }
+ return __( 'No HTML was returned.', 'lifterlms' );
+ }
+
+ /**
+ * Retrieve a string which can be used to render the block.
+ *
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function get_render_hook() {
+ return sprintf( '%1$s_%2$s_block_render', $this->vendor, $this->id );
+ }
+
+ /**
+ * Removed hooks stub.
+ * Extending classes can use this class to remove hooks attached to the render function action.
+ *
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function remove_hooks() {}
+
+ /**
+ * Renders the block type output for given attributes.
+ *
+ * @param array $attributes Optional. Block attributes. Default empty array.
+ * @param string $content Optional. Block content. Default empty string.
+ * @return string
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function render_callback( $attributes = array(), $content = '' ) {
+
+ $this->add_hooks( $attributes, $content );
+
+ ob_start();
+ do_action( $this->get_render_hook(), $attributes, $content );
+ $ret = ob_get_clean();
+
+ $this->remove_hooks();
+
+ if ( ! $ret ) {
+ $ret = $this->get_empty_render_message();
+ }
+
+ return $ret;
+
+ }
+
+ /**
+ * Register meta attributes stub.
+ *
+ * Called after registering the block type.
+ *
+ * @return void
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function register_meta() {}
+
+}
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-assets.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-assets.php
new file mode 100644
index 0000000000..a48bd0df6d
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-assets.php
@@ -0,0 +1,238 @@
+assets = new LLMS_Assets(
+ 'llms-blocks',
+ array(
+ // Base defaults shared by all asset types.
+ 'base' => array(
+ 'base_file' => LLMS_BLOCKS_PLUGIN_FILE,
+ 'base_url' => LLMS_BLOCKS_PLUGIN_DIR_URL,
+ 'version' => LLMS_BLOCKS_VERSION,
+ 'suffix' => '', // Only minified files are distributed.
+ ),
+ // Script specific defaults.
+ 'script' => array(
+ 'translate' => true, // All scripts in the blocks plugin are translated.
+ ),
+ )
+ );
+
+ // Define plugin assets.
+ $this->define();
+ $this->define_bc();
+
+ // Enqueue editor assets.
+ add_action( 'enqueue_block_editor_assets', array( $this, 'editor_assets' ), 5 );
+
+ }
+
+ /**
+ * Define block plugin assets.
+ *
+ * @since 1.10.0
+ *
+ * @return void
+ */
+ private function define() {
+
+ $asset = include LLMS_BLOCKS_PLUGIN_DIR . '/assets/js/llms-blocks.asset.php';
+
+ $this->assets->define(
+ 'scripts',
+ array(
+ 'llms-blocks-editor' => array(
+ 'dependencies' => $asset['dependencies'],
+ 'file_name' => 'llms-blocks',
+ 'version' => $asset['version'],
+ ),
+ )
+ );
+
+ $this->assets->define(
+ 'styles',
+ array(
+ 'llms-blocks-editor' => array(
+ 'dependencies' => array( 'wp-edit-blocks' ),
+ 'file_name' => 'llms-blocks',
+ 'version' => $asset['version'],
+ ),
+ )
+ );
+
+ }
+
+ /**
+ * Define backwards compatibility assets
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ protected function define_bc() {
+
+ if ( ! $this->use_bc_assets() ) {
+ return;
+ }
+
+ $asset = include LLMS_BLOCKS_PLUGIN_DIR . '/assets/js/llms-blocks-backwards-compat.asset.php';
+
+ $this->assets->define(
+ 'scripts',
+ array(
+ 'llms-blocks-editor-bc' => array(
+ 'dependencies' => $asset['dependencies'],
+ 'file_name' => 'llms-blocks-backwards-compat',
+ 'version' => $asset['version'],
+ ),
+ )
+ );
+
+ }
+
+ /**
+ * Enqueue block editor assets.
+ *
+ * @since 1.0.0
+ * @since 1.4.1 Fix double slash in asset path.
+ * @since 1.8.0 Update asset paths and improve script dependencies.
+ * @since 1.10.0 Use `LLMS_Assets` class methods for asset enqueues.
+ * @since 2.0.0 Maybe load backwards compatibility script.
+ * @since 2.2.0 Only load assets on post screens.
+ * @since 2.3.0 Also load assets on site editor screen.
+ * @since 2.4.3 Added script localization.
+ * @since 2.5.0 Add courseId to script localization.
+ *
+ * @return void
+ */
+ public function editor_assets() {
+
+ $screen = get_current_screen();
+ if ( $screen && ! in_array( $screen->base, array( 'post', 'site-editor' ), true ) ) {
+ return;
+ }
+
+ if ( $this->use_bc_assets() ) {
+ $this->assets->enqueue_script( 'llms-blocks-editor-bc' );
+ }
+
+ $this->assets->enqueue_script( 'llms-blocks-editor' );
+ $this->assets->enqueue_style( 'llms-blocks-editor' );
+
+ wp_localize_script(
+ 'llms-blocks-editor',
+ 'llmsBlocks',
+ array(
+ 'variationIconCanBeObject' => self::can_variation_transform_icon_be_an_object(),
+ 'courseId' => self::get_course_id(),
+ )
+ );
+
+ }
+
+ /**
+ * Determines if WP Core backwards compatibility scripts should defined & be loaded.
+ *
+ * @since 2.0.0
+ *
+ * @return boolean
+ */
+ private function use_bc_assets() {
+ return ( ! LLMS_Forms::instance()->are_requirements_met() &&
+ /**
+ * Filter allowing opt-out of block editor backwards compatibility scripts.
+ *
+ * @since 2.0.0
+ *
+ * @example
+ * ```
+ * // Disable backwards compatibility scripts.
+ * add_filter( 'llms_blocks_load_bc_scripts', '__return_false' );
+ * ```
+ *
+ * @param boolean $load_scripts Whether or not to load the scripts.
+ */
+ apply_filters( 'llms_blocks_load_bc_scripts', true )
+ );
+ }
+
+ /**
+ * Can a variation transform icon be an object.
+ *
+ * @link https://github.com/gocodebox/lifterlms-blocks/issues/170
+ *
+ * @since 2.4.3
+ *
+ * @return bool
+ */
+ private static function can_variation_transform_icon_be_an_object(): bool {
+ global $wp_version;
+
+ return version_compare( $wp_version, '6.0-src', '<' ) && ! defined( 'GUTENBERG_VERSION' )
+ || ( defined( 'GUTENBERG_VERSION' ) && version_compare( GUTENBERG_VERSION, '13.0', '<' ) );
+ }
+
+ /**
+ * Returns the current course or lesson's parent course ID.
+ *
+ * @since 2.5.0
+ *
+ * @return int
+ */
+ private static function get_course_id(): int {
+ $post_type = get_post_type();
+ $post_id = get_the_ID() ?? 0;
+
+ if ( 'lesson' === $post_type ) {
+ $parent = llms_get_post_parent_course( $post_id );
+
+ if ( $parent ) {
+ $post_id = $parent->get( 'id' );
+ }
+ }
+
+ return $post_id;
+ }
+
+}
+
+return new LLMS_Blocks_Assets();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-migrate.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-migrate.php
new file mode 100644
index 0000000000..db8f5ccac9
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-migrate.php
@@ -0,0 +1,433 @@
+update_post_content( $post->ID, $post->post_content . "\r\r" . $this->get_template( $post->post_type ) ) ) {
+ // Save migration state.
+ $this->update_migration_status( $post->ID );
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Don't remove core template actions when a sales page is enabled and the page is restricted.
+
+ * @since 1.2.0
+ * @since 1.3.1 Unknown.
+ *
+ * @param bool $ret Default migration status.
+ * @param int $post_id WP_Post ID.
+ * @return bool
+ */
+ public static function check_sales_page( $ret, $post_id ) {
+
+ $page_restricted = llms_page_restricted( $post_id );
+ if ( $page_restricted['is_restricted'] ) {
+ $sales_page = get_post_meta( $post_id, '_llms_sales_page_content_type', true );
+ if ( '' === $sales_page || 'content' === $sales_page ) {
+ $ret = false;
+ }
+ }
+
+ return $ret;
+
+ }
+
+ /**
+ * Get an array of post types which can be migrated.
+ *
+ * @since 1.3.3
+ * @since 1.7.0 Memberships are migrateable.
+ *
+ * @return array
+ */
+ public function get_migrateable_post_types() {
+ /**
+ * Filters the post types that can be migrated
+ *
+ * @since 1.3.3
+ *
+ * @param string[] $post_types An array of string representing the post types that can be migrated.
+ */
+ return apply_filters( 'llms_blocks_migrateable_post_types', array( 'course', 'lesson', 'llms_membership' ) );
+ }
+
+ /**
+ * Retrieve a WP_Query for posts which have already been migrated.
+ *
+ * @since 1.4.0
+ *
+ * @param array $args Optional query arguments to pass to WP_Query.
+ * @return WP_Query
+ */
+ public function get_migrated_posts( $args = array() ) {
+
+ return new WP_Query(
+ wp_parse_args(
+ $args,
+ array(
+ 'post_type' => $this->get_migrateable_post_types(),
+ 'meta_query' => array(
+ array(
+ 'key' => '_llms_blocks_migrated',
+ 'value' => 'yes',
+ ),
+ ),
+ )
+ )
+ );
+
+ }
+
+ /**
+ * Retrieve the block template by post type.
+ *
+ * @since 1.0.0
+ * @since 1.7.0 Add membership template.
+ * @since 1.8.0 Updated course progress shortcode and added the `$merge_deprecated_versions` param.
+ * @since 1.9.1 Fix course progress block.
+ *
+ * @param string $post_type WP post type.
+ * @param boolean $merge_deprecated_versions Optional. Whether or not getting the deprecated blocks merged, useful when removing templates. Default `false`.
+ * @return string
+ */
+ private function get_template( $post_type, $merge_deprecated_versions = false ) {
+
+ if ( 'course' === $post_type ) {
+ ob_start();
+
+ ?>
+
+
+
+
+
+
+
+
+
+[lifterlms_course_progress check_enrollment=1]
+[lifterlms_course_progress]
+
+
+
+
+[lifterlms_course_continue_button]
+
+
+
+
+
+
+
+
+
+ should_migrate_post( $post->ID ) ) {
+ return;
+ }
+
+ if ( 'llms_membership' === $post->post_type ) {
+ if ( has_block( 'llms/pricing-table', $post->post_content ) ) {
+ $this->update_migration_status( $post->ID );
+ return;
+ }
+ } elseif ( has_blocks( $post->post_content ) ) {
+ $this->update_migration_status( $post->ID );
+ return;
+ }
+
+ $this->add_template_to_post( $post );
+
+ // Reload.
+ wp_safe_redirect(
+ add_query_arg(
+ array(
+ 'post' => $post->ID,
+ 'action' => 'edit',
+ ),
+ admin_url( 'post.php' )
+ )
+ );
+ exit;
+
+ }
+
+ /**
+ * Remove post type templates and any LifterLMS Blocks from a given post.
+ *
+ * @since 1.4.0
+ * @since 1.8.0 Get all post type's template with deprecated blocks versions merged.
+ *
+ * @param WP_Post $post Post object.
+ * @return bool
+ */
+ private function remove_template_from_post( $post ) {
+
+ $template = $this->get_template( $post->post_type, true );
+ if ( ! $template ) {
+ return;
+ }
+
+ // explicitly remove template pieces.
+ $parts = array_filter( array_map( 'trim', explode( "\n", $template ) ) );
+ $content = str_replace( $parts, '', $post->post_content );
+
+ // replace any remaining LLMS blocks not found in the template (also grabs any openers that have block settings JSON).
+ $content = trim( preg_replace( '##', '', $content ) );
+
+ if ( $this->update_post_content( $post->ID, $content ) ) {
+ $this->update_migration_status( $post->ID, 'no' );
+ return true;
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Removes core template action hooks from posts which have been migrated to the block editor
+ *
+ * @since 1.3.2 Unknown.
+ *
+ * @return void
+ * @since 1.1.0
+ */
+ public function remove_template_hooks() {
+
+ if ( ! llms_blocks_is_post_migrated( get_the_ID() ) ) {
+ return;
+ }
+
+ // Course Information.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_meta_wrapper_start', 5 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_length', 10 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_difficulty', 20 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_tracks', 25 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_categories', 30 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_tags', 35 );
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_meta_wrapper_end', 50 );
+
+ // Remove Course Progress.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_course_progress', 60 );
+
+ // Course Syllabus.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_single_syllabus', 90 );
+
+ // Instructors.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_course_author', 40 );
+
+ // Lesson Navigation.
+ remove_action( 'lifterlms_single_lesson_after_summary', 'lifterlms_template_lesson_navigation', 20 );
+
+ // Lesson Progression.
+ remove_action( 'lifterlms_single_lesson_after_summary', 'lifterlms_template_complete_lesson_link', 10 );
+
+ // Pricing Table.
+ remove_action( 'lifterlms_single_course_after_summary', 'lifterlms_template_pricing_table', 60 );
+ remove_action( 'lifterlms_single_membership_after_summary', 'lifterlms_template_pricing_table', 10 );
+
+ }
+
+ /**
+ * Determine if a post should be migrated.
+ *
+ * @since 1.3.3
+ *
+ * @param int $post_id WP_Post ID.
+ * @return bool
+ */
+ public function should_migrate_post( $post_id ) {
+
+ $ret = true;
+
+ // Not a valid post type.
+ if ( ! in_array( get_post_type( $post_id ), $this->get_migrateable_post_types(), true ) ) {
+
+ $ret = false;
+
+ // Classic is enabled, don't migrate.
+ } elseif ( llms_blocks_is_classic_enabled_for_post( $post_id ) ) {
+
+ $ret = false;
+
+ // Already Migrated.
+ } elseif ( llms_parse_bool( get_post_meta( $post_id, '_llms_blocks_migrated', true ) ) ) {
+
+ $ret = false;
+
+ }
+
+ /**
+ * Filters whether or not a post should be migrated
+ *
+ * @since 1.3.3
+ *
+ * @param bool $migrate Whether or not a post should be migrated.
+ * @param int $post_id WP_Post ID.
+ */
+ return apply_filters( 'llms_blocks_should_migrate_post', $ret, $post_id );
+
+ }
+
+ /**
+ * Unmigrates migrated posts.
+ *
+ * @since 1.4.0
+ *
+ * @return void.
+ */
+ public function unmigrate_posts() {
+
+ $posts = $this->get_migrated_posts( array( 'posts_per_page' => 250 ) ); // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
+
+ if ( $posts->posts ) {
+ foreach ( $posts->posts as $post ) {
+ $this->remove_template_from_post( $post );
+ }
+ }
+
+ }
+
+ /**
+ * Update post meta data to signal status of the editor migration.
+ *
+ * @since 1.1.0
+ *
+ * @param int $post_id WP_Post ID.
+ * @param string $status Yes or no.
+ * @return void
+ */
+ private function update_migration_status( $post_id, $status = 'yes' ) {
+ update_post_meta( $post_id, '_llms_blocks_migrated', $status );
+ }
+
+ /**
+ * Update the post content for a given post.
+ *
+ * @since 1.4.0
+ *
+ * @param int $id WP_Post ID.
+ * @param string $content Post content to update.
+ * @return bool
+ */
+ private function update_post_content( $id, $content ) {
+
+ global $wpdb;
+ $update = $wpdb->update(
+ $wpdb->posts,
+ array(
+ 'post_content' => $content,
+ ),
+ array(
+ 'ID' => $id,
+ ),
+ array( '%s' ),
+ array( '%d' )
+ ); // db no-cache okay.
+
+ return false === $update ? false : true;
+
+ }
+
+}
+
+global $llms_blocks_migrate;
+$llms_blocks_migrate = new LLMS_Blocks_Migrate();
+return $llms_blocks_migrate;
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-page-builders.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-page-builders.php
new file mode 100644
index 0000000000..a635193b2c
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-page-builders.php
@@ -0,0 +1,133 @@
+instructors()->get_instructors( false );
+ foreach ( $ret as &$instructor ) {
+ $name = '';
+ $student = llms_get_student( $instructor['id'] );
+ if ( $student ) {
+ $name = $student->get_name();
+ }
+ $instructor['name'] = $name;
+ }
+ }
+ return $ret;
+
+ }
+
+ /**
+ * Automatically sets instructor data when a new course/membership is created.
+ *
+ * @since 1.6.0
+ *
+ * @link https://developer.wordpress.org/reference/hooks/save_post_post-post_type/
+ *
+ * @param int $post_id WP_Post ID.
+ * @param WP_Post $post Post object.
+ * @param bool $update Whether the save is an update (`true`) or a creation (`false`).
+ * @return bool
+ */
+ public function maybe_set_default_instructor( $post_id, $post, $update ) {
+
+ if ( $update || ! $post->post_author ) {
+ return false;
+ }
+
+ $obj = llms_get_post( $post );
+ if ( ! $obj || ! is_a( $obj, 'LLMS_Post_Model' ) || ! in_array( $obj->get( 'type' ), $this->post_types, true ) ) {
+ return false;
+ }
+
+ remove_action( 'save_post_course', array( $this, 'maybe_set_instructors' ), 50, 3 );
+ $obj->instructors()->set_instructors( array( array( 'id' => $post->post_author ) ) );
+
+ return true;
+
+ }
+
+ /**
+ * Update instructor information for a given object.
+ *
+ * @since 1.0.0
+ * @since 1.7.1 Decode JSON prior to saving.
+ * @since 2.4.0 Fix access to non-existing variable when current user canno edit the course/membership.
+ *
+ * @param string $value Instructor data to add to the object (JSON).
+ * @param WP_Post $object WP_Post object.
+ * @param string $key Name of the field.
+ * @return null|WP_Error
+ */
+ public function update_callback( $value, $object, $key ) {
+
+ if ( ! current_user_can( 'edit_post', $object->ID ) ) {
+ return new WP_Error(
+ 'rest_cannot_update',
+ __( 'Sorry, you are not allowed to edit the object instructors.', 'lifterlms' ),
+ array(
+ 'key' => $key,
+ 'status' => rest_authorization_required_code(),
+ )
+ );
+ }
+
+ $value = json_decode( $value, true );
+
+ $obj = llms_get_post( $object );
+ if ( $obj ) {
+ $obj->instructors()->set_instructors( $value );
+ }
+
+ return null;
+ }
+
+ /**
+ * Register custom meta fields.
+ *
+ * @since 1.0.0
+ * @since 1.6.0 Use `$this->post_types` for loop.
+ * @since 1.7.1 Don't define a schema.
+ *
+ * @return void
+ */
+ public function register_meta() {
+
+ foreach ( $this->post_types as $post_type ) {
+
+ register_rest_field(
+ $post_type,
+ 'instructors',
+ array(
+ 'get_callback' => array( $this, 'get_callback' ),
+ 'update_callback' => array( $this, 'update_callback' ),
+ 'schema' => null,
+ )
+ );
+
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks_Post_Instructors();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-post-types.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-types.php
new file mode 100644
index 0000000000..4fe0c64dab
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-types.php
@@ -0,0 +1,155 @@
+ __( 'Add a short description of your course visible to all visitors...', 'lifterlms' ),
+ ),
+ ),
+ array( 'llms/course-information' ),
+ array( 'llms/instructors' ),
+ array( 'llms/pricing-table' ),
+ array( 'llms/course-progress' ),
+ array( 'llms/course-continue-button' ),
+ array( 'llms/course-syllabus' ),
+ );
+
+ return $post_type;
+
+ }
+
+ /**
+ * Add an editor template for memberships.
+ *
+ * @since 1.7.0
+ * @since 1.11.0 Add instructors block.
+ *
+ * @param array $post_type Post type registration data.
+ * @return array
+ */
+ public function add_membership_template( $post_type ) {
+
+ $post_type['template'] = array(
+ array(
+ 'core/paragraph',
+ array(
+ 'placeholder' => __( 'Add a short description of your membership visible to all visitors...', 'lifterlms' ),
+ ),
+ ),
+ array( 'llms/instructors' ),
+ array( 'llms/pricing-table' ),
+ );
+
+ return $post_type;
+
+ }
+
+ /**
+ * Add an editor template for lessons
+ *
+ * @param array $post_type post type data.
+ * @return array
+ * @since 1.0.0
+ * @version 1.0.0
+ */
+ public function add_lesson_template( $post_type ) {
+
+ $post_type['template'] = array(
+ array(
+ 'core/paragraph',
+ array(
+ 'placeholder' => __( 'Add your lesson content...', 'lifterlms' ),
+ ),
+ ),
+ array( 'llms/lesson-progression' ),
+ array( 'llms/lesson-navigation' ),
+ );
+
+ return $post_type;
+
+ }
+
+}
+
+return new LLMS_Blocks_Post_Types();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-post-visibility.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-visibility.php
new file mode 100644
index 0000000000..71b8e70353
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-post-visibility.php
@@ -0,0 +1,134 @@
+get_catalog_visibility();
+ }
+ return $ret;
+
+ }
+
+ /**
+ * Update visibility information for a given object.
+ *
+ * @param string $value new visibility status value.
+ * @param WP_Post $object WP_Post object.
+ * @param string $key name of the field.
+ * @return null|WP_Error
+ * @since 1.3.0
+ * @version 1.3.0
+ */
+ public function update_callback( $value, $object, $key ) {
+
+ if ( ! current_user_can( 'edit_post', $object->ID ) ) {
+ return new WP_Error(
+ 'rest_cannot_update',
+ __( 'Sorry, you are not allowed to edit the object visibility.', 'lifterlms' ),
+ array(
+ 'key' => $name,
+ 'status' => rest_authorization_required_code(),
+ )
+ );
+ }
+
+ $obj = new LLMS_Product( $object->ID );
+ if ( $obj ) {
+ $obj->set_catalog_visibility( $value );
+ }
+
+ return null;
+ }
+
+ /**
+ * Register custom meta fields.
+ *
+ * @return void
+ * @since 1.3.0
+ * @version 1.3.0
+ */
+ public function register_meta() {
+
+ foreach ( array( 'course', 'llms_membership' ) as $post_type ) {
+
+ register_rest_field(
+ $post_type,
+ 'visibility',
+ array(
+ 'get_callback' => array( $this, 'get_callback' ),
+ 'update_callback' => array( $this, 'update_callback' ),
+ 'schema' => array(
+ 'description' => __( 'Post visibility.', 'lifterlms' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'properties' => array(),
+ 'arg_options' => array(
+ 'sanitize_callback' => null,
+ 'validate_callback' => null,
+ ),
+ ),
+ )
+ );
+
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks_Post_Visibility();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-reusable.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-reusable.php
new file mode 100644
index 0000000000..3a39b7e6e6
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-reusable.php
@@ -0,0 +1,200 @@
+ID, '_is_llms_field', $value ) ? true : false;
+ }
+
+ /**
+ * Register custom rest fields
+ *
+ * @since 2.0.0
+ *
+ * @return void
+ */
+ public function rest_register_fields() {
+
+ register_rest_field(
+ 'wp_block',
+ 'is_llms_field',
+ array(
+ 'get_callback' => array( $this, 'rest_callback_get' ),
+ 'update_callback' => array( $this, 'rest_callback_update' ),
+ )
+ );
+
+ }
+
+ /**
+ * Modify the rest request query used to list reusable blocks within the block editor
+ *
+ * Ensures that reusable blocks containing LifterLMS Form Fields can only be inserted/viewed
+ * in the context that we allow them to be used within.
+ *
+ * + When viewing a `wp_block` post, all reusable blocks should be displayed.
+ * + When viewing an `llms_form` post, only blocks that specify `is_llms_field` as 'yes' can be displayed.
+ * + When viewing any other post, any post with `is_llms_field` of 'yes' is excluded.
+ *
+ * @since 2.0.0
+ *
+ * @see [Reference]
+ * @link [URL]
+ *
+ * @param arrays $args WP_Query arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ public function mod_wp_block_query( $args, $request ) {
+
+ $referer = $request->get_header( 'referer' );
+ $screen = empty( $referer ) ? false : $this->get_screen_from_referer( $referer );
+
+ // We don't care about the screen or it's a reusable block screen.
+ if ( empty( $screen ) || 'wp_block' === $screen ) {
+ return $args;
+ }
+
+ // Add a meta query if it doesn't already exist.
+ if ( empty( $args['meta_query'] ) ) {
+ $args['meta_query'] = array(
+ 'relation' => 'AND',
+ );
+ }
+
+ // Forms should show only blocks with forms and everything else should exclude blocks with forms.
+ $include_fields = 'llms_form' === $screen;
+ $args['meta_query'][] = $this->get_meta_query( $include_fields );
+
+ return $args;
+
+ }
+
+ /**
+ * Retrieve a meta query array depending on the post type of the referring rest request
+ *
+ * @since 2.0.0
+ *
+ * @param boolean $include_fields Whether or not to include form fields.
+ * @return array
+ */
+ private function get_meta_query( $include_fields ) {
+
+ // Default query when including fields.
+ $meta_query = array(
+ 'key' => '_is_llms_field',
+ 'value' => 'yes',
+ );
+
+ // Excluding fields.
+ if ( ! $include_fields ) {
+
+ $meta_query = array(
+ 'relation' => 'OR',
+ wp_parse_args(
+ array(
+ 'compare' => '!=',
+ ),
+ $meta_query
+ ),
+ array(
+ 'key' => '_is_llms_field',
+ 'compare' => 'NOT EXISTS',
+ ),
+ );
+ }
+
+ return $meta_query;
+
+ }
+
+ /**
+ * Determine the screen where a reusable blocks rest query originated
+ *
+ * The screen name will either be "widgets" or the WP_Post name of a registered WP_Post type.
+ *
+ * For any other screen we return `false` because we don't care about it.
+ *
+ * @since 2.0.0
+ * @since 2.3.1 Don't pass `null` to `basename()`.
+ *
+ * @param string $referer Referring URL for the REST request.
+ * @return string|boolean Returns the screen name or `false` if we don't care about the screen.
+ */
+ private function get_screen_from_referer( $referer ) {
+
+ // Blockified widgets screen.
+ $url_path = wp_parse_url( $referer, PHP_URL_PATH );
+ if ( $url_path && 'widgets.php' === basename( $url_path ) ) {
+ return 'widgets';
+ }
+
+ $query_args = array();
+ wp_parse_str( wp_parse_url( $referer, PHP_URL_QUERY ), $query_args );
+
+ // Something else.
+ if ( empty( $query_args['post'] ) ) {
+ return false;
+ }
+
+ // Block editor for a WP_Post.
+ return get_post_type( $query_args['post'] );
+
+ }
+
+}
+
+return new LLMS_Blocks_Reusable();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-status-tools.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-status-tools.php
new file mode 100644
index 0000000000..66c486f1cb
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-status-tools.php
@@ -0,0 +1,88 @@
+get_migrated_posts();
+
+ if ( $posts->found_posts ) {
+
+ $desc = __( 'Removes block editor code from all courses and lessons which were migrated to the block editor during an upgrade to WordPress 5.0 or later. If you installed the Classic Editor plugin after upgrading and see duplicate content items (such as the course syllabus or lesson mark complete button) this tool will remove the duplicates.', 'lifterlms' );
+ $desc .= ' ';
+ // Translators: %d = number of affected courses/lessons.
+ $desc .= sprintf( __( 'Currently %d courses and/or lessons are affected.', 'lifterlms' ), $posts->found_posts );
+
+ $tools['blocks-unmigrate'] = array(
+ 'description' => $desc,
+ 'label' => __( 'Remove LifterLMS Block Code', 'lifterlms' ),
+ 'text' => __( 'Remove Block Code', 'lifterlms' ),
+ );
+
+ }
+
+ return $tools;
+
+ }
+
+ /**
+ * Run tool actions on tool page form submission.
+ *
+ * @since 1.4.0
+ *
+ * @param string $tool ID of the tool being run.
+ * @return void
+ */
+ public function maybe_toggle_mode( $tool ) {
+
+ if ( 'blocks-unmigrate' !== $tool ) {
+ return;
+ }
+
+ do_action( 'llms_blocks_unmigrate_posts' );
+
+ }
+
+}
+
+return new LLMS_Blocks_Status_Tools();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks-visibility.php b/libraries/lifterlms-blocks/includes/class-llms-blocks-visibility.php
new file mode 100644
index 0000000000..6ccf56222c
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks-visibility.php
@@ -0,0 +1,266 @@
+ array(
+ 'default' => 'all',
+ 'type' => 'string',
+ ),
+ 'llms_visibility_in' => array(
+ 'default' => '',
+ 'type' => 'string',
+ ),
+ 'llms_visibility_posts' => array(
+ 'default' => '[]',
+ 'type' => 'string',
+ ),
+ );
+ }
+
+ /**
+ * Get the number of enrollments for a user by post type.
+ *
+ * @since 1.0.0
+ *
+ * @param int $uid WP_User ID.
+ * @param string $type Post type.
+ * @return int
+ */
+ private function get_enrollment_count_by_type( $uid, $type ) {
+
+ $found = 0;
+ $student = llms_get_student( $uid );
+
+ $type = str_replace( 'any_', '', $type );
+
+ if ( 'course' === $type || 'membership' === $type ) {
+ $enrollments = $student->get_enrollments( $type, array( 'limit' => 1 ) );
+ $found = $enrollments['found'];
+ } elseif ( 'any' === $type ) {
+ $found = $this->get_enrollment_count_by_type( $uid, 'course' );
+ if ( ! $found ) {
+ $found = $this->get_enrollment_count_by_type( $uid, 'membership' );
+ }
+ }
+
+ return $found;
+
+ }
+
+ /**
+ * Parse post ids from block visibility in attributes.
+ *
+ * @since 1.0.0
+ *
+ * @param array $attrs Block attributes.
+ * @return array
+ */
+ private function get_post_ids_from_block_attributes( $attrs ) {
+
+ $ids = array();
+ if ( 'this' === $attrs['llms_visibility_in'] ) {
+ $ids[] = get_the_ID();
+ } elseif ( ! empty( $attrs['llms_visibility_posts'] ) ) {
+ $ids = wp_list_pluck( json_decode( $attrs['llms_visibility_posts'] ), 'id' );
+ }
+
+ return $ids;
+
+ }
+
+ /**
+ * Filter block output.
+ *
+ * @since 1.0.0
+ * @since 1.6.0 Add logic for `logged_in` and `logged_out` block visibility options.
+ * @since 2.0.0 Added a conditional prior to checking the block's visibility attributes.
+ * @since 2.4.2 Set the `user_login` field block's visibility to its default 'logged_out' if not set.
+ *
+ * @param string $content Block inner content.
+ * @param array $block Block data array.
+ * @return string
+ */
+ public function maybe_filter_block( $content, $block ) {
+
+ // Allow conditionally filtering the block based on external context.
+ if ( ! $this->should_filter_block( $block ) ) {
+ return $content;
+ }
+
+ // Set the `user_login` field block's visibility to its default 'logged_out' if not set.
+ // The WordPress serializer `getCommentAttributes()` function removes the attribute before being
+ // serialized into `post_content` if the attribute can have only one value and it's the default.
+ if ( 'llms/form-field-user-login' === $block['blockName'] && empty( $block['attrs']['llms_visibility'] ) ) {
+ $block['attrs']['llms_visibility'] = 'logged_out';
+ }
+
+ // No attributes or no llms visibility settings (visible to "all").
+ if ( empty( $block['attrs'] ) || empty( $block['attrs']['llms_visibility'] ) ) {
+ return $content;
+ }
+
+ $uid = get_current_user_id();
+
+ // Show only to logged in users.
+ if ( 'logged_in' === $block['attrs']['llms_visibility'] && ! $uid ) {
+
+ $content = '';
+
+ // Show only to logged out users.
+ } elseif ( 'logged_out' === $block['attrs']['llms_visibility'] && $uid ) {
+ $content = '';
+
+ // Enrolled checks.
+ } elseif ( 'enrolled' === $block['attrs']['llms_visibility'] && ! empty( $block['attrs']['llms_visibility_in'] ) ) {
+
+ // Don't have to run any further checks if we don't have a user.
+ if ( ! $uid ) {
+
+ $content = '';
+
+ // Checks for the "any" conditions.
+ } elseif ( in_array( $block['attrs']['llms_visibility_in'], array( 'any', 'any_course', 'any_membership' ), true ) ) {
+
+ $found = $this->get_enrollment_count_by_type( $uid, $block['attrs']['llms_visibility_in'] );
+ if ( ! $found ) {
+ $content = '';
+ }
+
+ // Checks for specifics.
+ } elseif ( in_array( $block['attrs']['llms_visibility_in'], array( 'this', 'list_all', 'list_any' ), true ) ) {
+
+ $relation = 'list_any' === $block['attrs']['llms_visibility_in'] ? 'any' : 'all'; // "this" becomes an "all" relationship
+ if ( ! llms_is_user_enrolled( $uid, $this->get_post_ids_from_block_attributes( $block['attrs'] ), $relation ) ) {
+ $content = '';
+ }
+ }
+
+ // Not-Enrolled checks.
+ } elseif ( 'not_enrolled' === $block['attrs']['llms_visibility'] && ! empty( $block['attrs']['llms_visibility_in'] ) ) {
+
+ // Only need to check logged in users.
+ if ( $uid ) {
+
+ // Checks for the "any" conditions.
+ if ( in_array( $block['attrs']['llms_visibility_in'], array( 'any', 'any_course', 'any_membership' ), true ) ) {
+
+ $found = $this->get_enrollment_count_by_type( $uid, $block['attrs']['llms_visibility_in'] );
+ if ( $found ) {
+ $content = '';
+ }
+
+ // Checks for specifics.
+ } elseif ( in_array( $block['attrs']['llms_visibility_in'], array( 'this', 'list_all', 'list_any' ), true ) ) {
+
+ $relation = 'list_any' === $block['attrs']['llms_visibility_in'] ? 'any' : 'all'; // "this" becomes an "all" relationship
+ if ( llms_is_user_enrolled( $uid, $this->get_post_ids_from_block_attributes( $block['attrs'] ), $relation ) ) {
+ $content = '';
+ }
+ }
+ }
+ }
+
+ /**
+ * Filters a blocks content after it has been run through visibility attribute filters
+ *
+ * @since 1.0.0
+ *
+ * @param string $content The HTML content for a block. May be an empty string if the block should be invisible to the current user.
+ * @param array $block Block data array.
+ */
+ return apply_filters( 'llms_blocks_visibility_render_block', $content, $block );
+
+ }
+
+ /**
+ * Determine whether or not a block's rendering should be modified by block-level visibility settings
+ *
+ * This method does not determine whether or not the block will be rendered, it only determines whether
+ * or not we should check if it should be rendered.
+ *
+ * This method is primarily used to ensure that LifterLMS core dynamic blocks (pricing table, course syllabus, etc...)
+ * are *always* displayed to creators when editing content within the block editor. This parses data from a block-renderer
+ * WP Core API request.
+ *
+ * @since 2.0.0
+ * @since 2.3.1 Don't use deprecated `FILTER_SANITIZE_STRING`.
+ *
+ * @link https://developer.wordpress.org/rest-api/reference/rendered-blocks/
+ *
+ * @param array $block Block data array.
+ * @return boolean If `true`, block filters should be checked, other wise they will be skipped.
+ */
+ private function should_filter_block( $block ) {
+
+ // Always filter unless explicitly told not to.
+ $should_filter = true;
+
+ if ( llms_is_rest() ) {
+
+ $context = llms_filter_input( INPUT_GET, 'context' );
+ $post_id = llms_filter_input( INPUT_GET, 'post_id', FILTER_SANITIZE_NUMBER_INT );
+
+ // Always render blocks when a valid user is requesting the block in the edit context.
+ if ( 'edit' === $context && $post_id && current_user_can( 'edit_post', $post_id ) ) {
+ $should_filter = false;
+ }
+ }
+
+ /**
+ * Filters whether or not a block's rendering should be modified by block-level visibility settings
+ *
+ * This filter does not determine whether or not the block will be rendered, it only determines whether
+ * or not we should check if it should be rendered.
+ *
+ * @since 2.0.0
+ *
+ * @param boolean $should_filter Whether or not to apply visibility filters.
+ * @param array $block Block data array.
+ */
+ return apply_filters( 'llms_blocks_visibility_should_filter_block', $should_filter, $block );
+
+ }
+
+}
+
+return new LLMS_Blocks_Visibility();
diff --git a/libraries/lifterlms-blocks/includes/class-llms-blocks.php b/libraries/lifterlms-blocks/includes/class-llms-blocks.php
new file mode 100644
index 0000000000..2cc8f2a051
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/class-llms-blocks.php
@@ -0,0 +1,262 @@
+ 'llms-blocks',
+ 'title' => __( 'LifterLMS Blocks', 'lifterlms' ),
+ );
+
+ array_unshift(
+ $categories,
+ array(
+ 'slug' => 'llms-custom-fields',
+ 'title' => __( 'Custom User Information', 'lifterlms' ),
+ )
+ );
+
+ array_unshift(
+ $categories,
+ array(
+ 'slug' => 'llms-user-info-fields',
+ 'title' => __( 'User Information', 'lifterlms' ),
+ )
+ );
+
+ return $categories;
+ }
+
+
+ /**
+ * Print dynamic block information as a JS variable.
+ *
+ * Allows us to ensure we only add visibility attributes to static blocks.
+ * Prevents an issue causing rest api validation issues during attribute validation
+ * because it's impossible to register custom attributes on a block.
+ *
+ * @link https://github.com/gocodebox/lifterlms-blocks/issues/30
+ *
+ * @since 1.5.1
+ * @since 2.0.0 Since WordPress 5.8 blocks are available in widgets and customizer screen too.
+ *
+ * @return void
+ */
+ public function admin_print_scripts() {
+
+ $screen = get_current_screen();
+ if ( ! $screen || ( empty( $screen->is_block_editor ) && 'customize' !== $screen->base ) ) {
+ return;
+ }
+
+ echo '';
+
+ }
+
+ /**
+ * Retrieve a list of dynamic block names registered with WordPress (excluding LifterLMS blocks).
+ *
+ * @since 1.5.1
+ *
+ * @return array
+ */
+ private function get_dynamic_block_names() {
+ $blocks = array();
+ foreach ( get_dynamic_block_names() as $name ) {
+ if ( 0 !== strpos( $name, 'llms/' ) ) {
+ $blocks[] = $name;
+ }
+ }
+ return apply_filters( 'llms_blocks_get_dynamic_block_names', $blocks );
+ }
+
+ /**
+ * Include all files.
+ *
+ * @since 2.0.0
+ * @since 2.3.0 Include php template block file.
+ *
+ * @return void
+ */
+ private function includes() {
+
+ // Functions.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/functions-llms-blocks.php';
+
+ // Classes.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-assets.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-abstract-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-migrate.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-page-builders.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-post-instructors.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-post-types.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-post-visibility.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-reusable.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-status-tools.php';
+
+ // Block Visibility Component.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/class-llms-blocks-visibility.php';
+
+ // Dynamic Blocks.
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-course-information-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-course-syllabus-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-course-progress-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-instructors-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-lesson-navigation-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-lesson-progression-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-pricing-table-block.php';
+ require_once LLMS_BLOCKS_PLUGIN_DIR . '/includes/blocks/class-llms-blocks-php-template-block.php';
+
+ }
+
+ /**
+ * Register all blocks & components.
+ *
+ * @since 1.0.0
+ * @since 1.4.0 Add status tools class.
+ * @since 1.9.0 Added course progress block class.
+ * @since 2.0.0 Return early if LifterLMS isn't installed, move file inclusion to `$this->includes()`,
+ * and moved actions and filters from the constructor.
+ * @since 2.2.1 Handle '-src' in WordPress version numbers.
+ * @since 2.5.0 Updated minimum LifterLMS core version to 7.2.0.
+ *
+ * @return void
+ */
+ public function init() {
+
+ if ( ! function_exists( 'llms' ) || ! version_compare( self::MIN_CORE_VERSION, llms()->version, '<=' ) ) {
+ return;
+ }
+
+ $this->includes();
+
+ add_action( 'add_meta_boxes', array( $this, 'remove_metaboxes' ), 999, 2 );
+
+ global $wp_version;
+ $filter = version_compare( $wp_version, '5.8-src', '>=' ) ? 'block_categories_all' : 'block_categories';
+
+ add_filter( $filter, array( $this, 'add_block_category' ) );
+ add_action( 'admin_print_scripts', array( $this, 'admin_print_scripts' ), 15 );
+
+ /**
+ * When loaded as a library included by the LifterLMS core localization is handled by the LifterLMS core.
+ *
+ * When the plugin is loaded by itself as a plugin, we must localize it independently.
+ */
+ if ( ! defined( 'LLMS_BLOCKS_LIB' ) || ! LLMS_BLOCKS_LIB ) {
+ add_action( 'init', array( $this, 'load_textdomain' ), 0 );
+ }
+
+ }
+
+ /**
+ * Load l10n files.
+ *
+ * This method is only used when the plugin is loaded as a standalone plugin (for development purposes),
+ * otherwise (when loaded as a library from within the LifterLMS core plugin) the localization
+ * strings are included into the LifterLMS Core plugin's po/mo files and are localized by the LifterLMS
+ * core plugin.
+ *
+ * Files can be found in the following order (The first loaded file takes priority):
+ * 1. WP_LANG_DIR/lifterlms/lifterlms-blocks-LOCALE.mo
+ * 2. WP_LANG_DIR/plugins/lifterlms-blocks-LOCALE.mo
+ * 3. WP_CONTENT_DIR/plugins/lifterlms-blocks/i18n/lifterlms-blocks-LOCALE.mo
+ *
+ * Note: The function `load_plugin_textdomain()` is not used because the same textdomain as the LifterLMS core
+ * is used for this plugin but the file is named `lifterlms-blocks` in order to allow using a separate language
+ * file for each codebase.
+ *
+ * @since 1.10.0
+ *
+ * @return void
+ */
+ public function load_textdomain() {
+
+ // load locale.
+ $locale = apply_filters( 'plugin_locale', get_locale(), 'lifterlms' );
+
+ // Load from the LifterLMS "safe" directory if it exists.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/lifterlms/lifterlms-blocks-' . $locale . '.mo' );
+
+ // Load from the default plugins language file directory.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/plugins/lifterlms-blocks-' . $locale . '.mo' );
+
+ // Load from the plugin's language file directory.
+ load_textdomain( 'lifterlms', LLMS_BLOCKS_PLUGIN_DIR . '/i18n/lifterlms-blocks-' . $locale . '.mo' );
+
+ }
+
+ /**
+ * Remove deprecated core metaboxes.
+ *
+ * @since 1.0.0
+ * @since 1.3.0 Updated.
+ *
+ * @param string $post_type WP post type of the current post.
+ * @param string $post WP_Post.
+ * @return void
+ */
+ public function remove_metaboxes( $post_type, $post ) {
+
+ if ( ! llms_blocks_is_classic_enabled_for_post( $post ) ) {
+
+ remove_meta_box( 'llms-instructors', 'course', 'normal' );
+ remove_meta_box( 'llms-instructors', 'llms_membership', 'normal' );
+
+ }
+
+ }
+
+}
+
+return new LLMS_Blocks();
diff --git a/libraries/lifterlms-blocks/includes/functions-llms-blocks.php b/libraries/lifterlms-blocks/includes/functions-llms-blocks.php
new file mode 100644
index 0000000000..9102605455
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/functions-llms-blocks.php
@@ -0,0 +1,75 @@
+ID, 'classic-editor-remember', true ) );
+ }
+
+ // Uses same editor for all posts.
+ } else {
+
+ $ret = ( 'classic' === get_option( 'classic-editor-replace', 'classic' ) );
+
+ }
+ }
+
+ return apply_filters( 'llms_blocks_is_classic_enabled_for_post', $ret, $post );
+
+}
+
+/**
+ * Determine if a post is migrated
+ *
+ * @param mixed $post WP_Post or WP_Post ID.
+ * @return boolean
+ * @since 1.3.1
+ * @version 1.3.1
+ */
+function llms_blocks_is_post_migrated( $post ) {
+
+ $post_id = null;
+ $ret = false;
+
+ $post = get_post( $post );
+ if ( $post ) {
+
+ $post_id = $post->ID;
+
+ // Classic editor is being used for this post.
+ if ( llms_blocks_is_classic_enabled_for_post( $post_id ) ) {
+ $ret = false;
+ } else {
+ $ret = llms_parse_bool( get_post_meta( $post_id, '_llms_blocks_migrated', true ) );
+ }
+ }
+
+ return apply_filters( 'llms_blocks_is_post_migrated', $ret, $post_id );
+
+}
diff --git a/libraries/lifterlms-blocks/includes/index.php b/libraries/lifterlms-blocks/includes/index.php
new file mode 100644
index 0000000000..82e2315c6b
--- /dev/null
+++ b/libraries/lifterlms-blocks/includes/index.php
@@ -0,0 +1,2 @@
+chaining = true;
+ $this->$command( $args, $assoc_args );
+ $this->chaining = false;
+ }
+
+ /**
+ * Retrieve an LLMS_Add_On object for a given add-on by it's slug.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug An add-on slug. Must be prefixed.
+ * @param bool|WP_Error|string $err If truthy, will return `null` and use log to the console using a WP_CLI method as defined by $err_type.
+ * Pass `true` to output a default error message.
+ * Pass a WP_Error object or string to use as the error.
+ * @param string $err_type Method to pass `$err` to when an error is encountered. Default `\WP_CLI::error()`.
+ * Use `\WP_CLI::warning()` or `\WP_CLI::log()` where appropriate.
+ * @return LLMS_Add_On|boolean|null Returns an add-on object if the add-on can be located or `false` if not found.
+ * Returns `null` when an error is encountered and `$err` is a truthy.
+ */
+ protected function get_addon( $slug, $err = false, $err_type = 'error' ) {
+
+ $addon = llms_get_add_on( $this->prefix_slug( $slug ), 'slug' );
+ $exists = ! empty( $addon->get( 'id' ) );
+
+ if ( ! $exists && $err ) {
+ $err = is_bool( $err ) ? sprintf( 'Invalid slug: %s.', $slug ) : $err;
+ return \WP_CLI::$err_type( $err );
+ }
+
+ return ! $exists ? false : $addon;
+ }
+
+ /**
+ * Prefix an add-on slug with `lifterlms-` if it's not already present.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @return string
+ */
+ protected function prefix_slug( $slug ) {
+ if ( 0 !== strpos( $slug, 'lifterlms-' ) ) {
+ $slug = "lifterlms-{$slug}";
+ }
+ return $slug;
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Activate.php b/libraries/lifterlms-cli/src/Commands/AddOn/Activate.php
new file mode 100644
index 0000000000..a7973ba0dc
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Activate.php
@@ -0,0 +1,101 @@
+...]
+ * : The slug of one or more LifterLMS add-on to install.
+ *
+ * [--all]
+ * : If set, all of the LifterLMS add-ons installed on the site will be activated.
+ *
+ * ## EXAMPLES
+ *
+ * # Activate the LifterLMS Groups add-on.
+ * $ wp llms addon activate lifterlms-groups
+ *
+ * # Activate an add-on without using the `lifterlms-` prefix.
+ * $ wp llms addon activate advanced-videos
+ *
+ * # Activate multiple LifterLMS add-ons.
+ * $ wp llms addon activate lifterlms-groups lifterlms-assignments lifterlms-pdfs
+ *
+ * # Activate all installed LifterLMS add-ons.
+ * $ wp llms addon activate --all
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function activate( $args, $assoc_args ) {
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'inactive', false );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to activate.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'activate_one' );
+ if ( ! $this->chaining ) {
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'activate', count( $args ), $results['successes'], $results['errors'] );
+ }
+
+ }
+
+ /**
+ * Loop callback function for activate()
+ *
+ * Ensures add-on can be activated and actually activates the add-on.
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Fixed unmerged placeholder in warning message when add-on is not installed.
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function activate_one( $slug, $addon, $assoc_args ) {
+
+ if ( $addon->is_active() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is already active.', $slug ) );
+ }
+
+ if ( ! $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%1$s" is not installed. Run \'wp llms addon install %s\' to install it.', $slug ) );
+ }
+
+ $res = $addon->activate();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ \WP_CLI::log( $res );
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/ChannelSet.php b/libraries/lifterlms-cli/src/Commands/AddOn/ChannelSet.php
new file mode 100644
index 0000000000..256c6fe07f
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/ChannelSet.php
@@ -0,0 +1,61 @@
+
+ * : The slug of the add-on.
+ *
+ * []
+ * : The update channel to subscribe to.
+ * ---
+ * default: 'stable'
+ * options:
+ * - stable
+ * - beta
+ * ---
+ *
+ * ## EXAMPLES
+ *
+ * # Subscribe the Groups add-on to the beta channel.
+ * $ wp llms addon channel-set lifterlms-groups stable
+ *
+ * # Subscribe to the stable channel.
+ * $ wp llms addon channel-set lifterlms-groups stable
+ *
+ * @subcommand channel-set
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Updated success message.
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @return null
+ */
+ public function channel_set( $args ) {
+
+ $addon = $this->get_addon( $args[0], true );
+ $addon->subscribe_to_channel( $args[1] );
+ return \WP_CLI::success( sprintf( 'Subscribed to the %s channel.', $args[1] ) );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Deactivate.php b/libraries/lifterlms-cli/src/Commands/AddOn/Deactivate.php
new file mode 100644
index 0000000000..117e898e4b
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Deactivate.php
@@ -0,0 +1,111 @@
+...]
+ * : The slug of one or more add-on to deactivate.
+ *
+ * [--uninstall]
+ * : Uninstall the add-ons after deactivation.
+ *
+ * [--all]
+ * : If set, all of the plugin add-ons installed on the site will be activated.
+ *
+ * ## EXAMPLES
+ *
+ * # Deactivate the LifterLMS Groups add-on.
+ * $ wp llms addon deactivate lifterlms-groups
+ *
+ * # Deactivate an add-on without using the `lifterlms-` prefix.
+ * $ wp llms addon deactivate advanced-videos
+ *
+ * # Deactivate multiple LifterLMS add-ons.
+ * $ wp llms addon deactivate lifterlms-groups lifterlms-assignments lifterlms-pdfs
+ *
+ * # Deactivate all installed LifterLMS add-ons.
+ * $ wp llms addon deactivate --all
+ *
+ * # Deactivate and uninstall the LifterLMS Groups add-on.
+ * $ wp llms addon deactivate lifterlms-groups --uninstall
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Completion messages use says "deactivate(d)" in favor of "activate(d)".
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function deactivate( $args, $assoc_args ) {
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'active', false, 'plugin' );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to deactivate.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'deactivate_one' );
+ if ( ! $this->chaining ) {
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'deactivate', count( $args ), $results['successes'], $results['errors'] );
+ }
+
+ }
+
+ /**
+ * Loop callback function for deactivate()
+ *
+ * Ensures add-on can be deactivated and actually deactivates (and maybe uninstalls) the add-on.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function deactivate_one( $slug, $addon, $assoc_args ) {
+
+ if ( ! $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%1$s" is not installed.', $slug ) );
+ }
+
+ if ( ! $addon->is_active() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is already deactivated.', $slug ) );
+ }
+
+ $res = $addon->deactivate();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ if ( ! empty( $assoc_args['uninstall'] ) ) {
+ $this->chain_command( 'uninstall', array( $slug ) );
+ }
+
+ \WP_CLI::log( $res );
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Enumerate.php b/libraries/lifterlms-cli/src/Commands/AddOn/Enumerate.php
new file mode 100644
index 0000000000..d98d9eed07
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Enumerate.php
@@ -0,0 +1,127 @@
+=]
+ * : Filter results based on the value of a field.
+ *
+ * [--field=]
+ * : Prints the value of a single field for each add-on.
+ *
+ * [--fields=]
+ * : Limit the output to only the specified fields. Use "all" to display all available fields.
+ *
+ * [--format=]
+ * : Render output in a particular format.
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - csv
+ * - count
+ * - json
+ * - yaml
+ * ---
+ *
+ * ## AVAILABLE FIELDS
+ *
+ * These fields will be displayed by default for each add-on:
+ *
+ * * name
+ * * status
+ * * update
+ * * version
+ *
+ * These fields are optionally available:
+ *
+ * * update_version
+ * * license
+ * * title
+ * * channel
+ * * type
+ * * file
+ *
+ * ## EXAMPLES
+ *
+ * # List all add-ons.
+ * $ wp llms addon list
+ *
+ * # List all add-ons in JSON format.
+ * $ wp llms addon list --format=json
+ *
+ * # List all add-ons by name only.
+ * $ wp llms addon list --field=name
+ *
+ * # List all add-ons with all available fields.
+ * $ wp llms addon list --fields=all
+ *
+ * # List all add-ons with a custom fields list.
+ * $ wp llms addon list --fields=title,status,version
+ *
+ * # List currently activated add-ons.
+ * $ wp llms addon list --status=active
+ *
+ * # List all theme add-ons.
+ * $ wp llms addon list --type=theme
+ *
+ * # List all add-ons with available updates.
+ * $ wp llms addon list --update=available
+ *
+ * # List all add-ons licensed on the site.
+ * $ wp llms addon list --license=active
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function list( $args, $assoc_args ) {
+
+ $fields = array( 'name', 'status', 'update', 'version' );
+ $all_fields = array_merge( $fields, array( 'update_version', 'license', 'title', 'channel', 'type', 'file' ) );
+
+ // Determine if there's a user filter submitted through`--=`.
+ $filter_field = array_values( array_intersect( $all_fields, array_keys( $assoc_args ) ) );
+
+ $list = $this->get_filtered_items( $assoc_args, ! empty( $filter_field ) ? $filter_field[0] : '' );
+
+ if ( ! empty( $assoc_args['fields'] ) && 'all' === $assoc_args['fields'] ) {
+ $assoc_args['fields'] = $all_fields;
+ }
+
+ $formatter = new Formatter( $assoc_args, $fields );
+ return $formatter->display_items( $list );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Get.php b/libraries/lifterlms-cli/src/Commands/AddOn/Get.php
new file mode 100644
index 0000000000..0ea53099ee
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Get.php
@@ -0,0 +1,120 @@
+
+ * : The slug of the add-on to get information about.
+ *
+ * ## OPTIONS
+ *
+ * [--field=]
+ * : Retrieve a single piece of information about the add-on.
+ *
+ * [--fields=]
+ * : Limit the output to only the specified fields. Use "all" to display all available fields.
+ *
+ * [--format=]
+ * : Render output in a particular format.
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - csv
+ * - json
+ * - yaml
+ * ---
+ *
+ * ## AVAILABLE FIELDS
+ *
+ * These fields will be displayed by default for each add-on:
+ *
+ * * name
+ * * title
+ * * version
+ * * description
+ * * status
+ *
+ * These fields are optionally available:
+ *
+ * * update
+ * * update_version
+ * * license
+ * * title
+ * * channel
+ * * type
+ * * file
+ * * permalink
+ * * changelog
+ * * documentation
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function get( $args, $assoc_args ) {
+
+ $addon = $this->get_addon( $args[0], true );
+ $fields = array( 'name', 'title', 'version', 'description', 'status' );
+ $all_fields = array_merge( $fields, array( 'update', 'update_version', 'license', 'title', 'channel', 'type', 'file', 'permalink', 'changelog', 'documentation' ) );
+
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ $assoc_args['fields'] = 'all' === $assoc_args['fields'] ? $all_fields : $assoc_args['fields'];
+ } else {
+ $assoc_args['fields'] = $fields;
+ }
+
+ // Get formatted item.
+ $item = $this->format_item( $addon );
+
+ // Put the keys in the order defined by input args.
+ $item = array_merge( array_flip( $assoc_args['fields'] ), $item );
+
+ // Pass the item as an array and all fields for proper formatting when --field= is passed.
+ $list = array( $item );
+ $format_fields = $all_fields;
+
+ // Format when displaying multiple fields.
+ if ( empty( $assoc_args['field'] ) ) {
+
+ $list = array();
+ foreach ( $item as $Field => $Value ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+ if ( ! in_array( $Field, $assoc_args['fields'], true ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.VariableNotSnakeCase
+ continue;
+ }
+ $list[] = compact( 'Field', 'Value' );
+ }
+ $format_fields = array( 'Field', 'Value' );
+ unset( $assoc_args['fields'] );
+
+ }
+
+ $formatter = new Formatter( $assoc_args, $format_fields );
+ return $formatter->display_items( $list );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Install.php b/libraries/lifterlms-cli/src/Commands/AddOn/Install.php
new file mode 100644
index 0000000000..91892b1df0
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Install.php
@@ -0,0 +1,106 @@
+...]
+ * : The slug of one or more add-on to install.
+ *
+ * [--key=]
+ * : If set, will attempt to activate and use the provided license key.
+ *
+ * [--activate]
+ * : If set, the add-on(s) will be activated immediately after install.
+ *
+ * [--all]
+ * : If set, all of the add-ons available to the site will be installed.
+ * All existing license keys stored on the site will be queried for the list of available add-ons.
+ *
+ * [--type=]
+ * : When using '--all', determines the type of add-on to be installed.
+ * ---
+ * default: 'all'
+ * options:
+ * - all
+ * - plugin
+ * - theme
+ * ---
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function install( $args, $assoc_args ) {
+
+ // If a key is provided, activate it first.
+ if ( ! empty( $assoc_args['key'] ) ) {
+ \WP_CLI::runcommand( "llms license activate {$assoc_args['key']}" );
+ }
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'uninstalled', true, $assoc_args['type'] );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to install.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'install_one' );
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'install', count( $args ), $results['successes'], $results['errors'] );
+
+ }
+
+ /**
+ * Loop callback function for install()
+ *
+ * Ensures add-on can be installed and actually installs (and maybe activates) the add-on.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function install_one( $slug, $addon, $assoc_args ) {
+
+ if ( $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is already installed.', $slug ) );
+ }
+
+ \WP_CLI::log( sprintf( 'Installing add-on: %s...', $addon->get( 'title' ) ) );
+ $res = $addon->install();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ \WP_CLI::log( $res );
+ if ( ! empty( $assoc_args['activate'] ) ) {
+ $this->chain_command( 'activate', array( $slug ) );
+ }
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Main.php b/libraries/lifterlms-cli/src/Commands/AddOn/Main.php
new file mode 100644
index 0000000000..b5beab0b95
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Main.php
@@ -0,0 +1,202 @@
+ $addon->get( 'slug' ),
+ 'description' => $addon->get( 'description' ),
+ 'status' => $addon->get_status(),
+ 'license' => str_replace( 'license_', '', $addon->get_license_status() ),
+ 'update' => $addon->has_available_update() ? 'available' : 'none',
+ 'version' => $addon->is_installed() ? $addon->get_installed_version() : 'N/A',
+ 'update_version' => $addon->get( 'version' ),
+ 'title' => $addon->get( 'title' ),
+ 'channel' => $addon->get_channel_subscription(),
+ 'type' => $addon->get( 'type' ),
+ 'file' => $addon->get( 'update_file' ),
+ 'permalink' => $addon->get( 'permalink' ),
+ 'changelog' => $addon->get( 'changelog' ),
+ 'documentation' => $addon->get( 'documentation' ),
+ );
+
+ return $formatted;
+ }
+
+ /**
+ * Retrieve an array of available add-on slugs based on the supplied query criteria.
+ *
+ * This function passes data to `wp llms addon list` with specific filters and returns an associative
+ * array of add-on slugs from that list.
+ *
+ * This is used, mostly, to generate a list of available addons for various commands which provide an `--all` flag/option.
+ *
+ * @since 0.0.1
+ *
+ * @param string $status Add-on status, passed as the `--status` option to `llms addon list`.
+ * @param bool $check_license Whether or not the add-on should be licensed. This is used to determine what is installable / upgradeable.
+ * @param string $type Add-on type. Accepts 'all' (default), 'plugin' or 'theme'.
+ * @return string[] Array of add-on slugs meeting the specified filters.
+ */
+ private function get_available_addons( $status, $check_license, $type = 'all' ) {
+
+ $list = \WP_CLI::runcommand(
+ "llms addon list --format=json --status={$status} --fields=name,license,type",
+ array(
+ 'return' => true,
+ )
+ );
+ $list = array_filter(
+ json_decode( $list, true ),
+ function( $item ) use ( $check_license, $type ) {
+ return ( ( $check_license && 'active' === $item['license'] ) || ! $check_license ) && ( 'all' === $type || $type === $item['type'] );
+ }
+ );
+
+ return wp_list_pluck( $list, 'name' );
+
+ }
+
+ /**
+ * Retrieves an optionally filtered list of add-ons for use in the `list` command.
+ *
+ * @since 0.0.1
+ *
+ * @param array $assoc_args Associative array of command options.
+ * @param string $filter_field The optional name of the field to filter results by.
+ * @return array[] Array of add-on items.
+ */
+ private function get_filtered_items( $assoc_args, $filter_field = '' ) {
+
+ $addons = llms_get_add_ons();
+
+ $list = array_filter(
+ $addons['items'],
+ function( $item ) {
+ return // Skip anything without a slug.
+ ! empty( $item['slug'] ) &&
+ // Skip the LifterLMS core.
+ 'lifterlms' !== $item['slug'] &&
+ // Skip third party add-ons.
+ ! in_array( 'third-party', array_keys( $item['categories'] ), true );
+ }
+ );
+
+ // Format remaining items.
+ $list = array_map( array( $this, 'format_item' ), $list );
+
+ // Filter by field value.
+ if ( $filter_field ) {
+ $field_val = $assoc_args[ $filter_field ];
+ $list = array_filter(
+ $list,
+ function( $item ) use ( $filter_field, $field_val ) {
+ return $item[ $filter_field ] === $field_val;
+ }
+ );
+ }
+
+ // Alpha sort the list by slug.
+ usort(
+ $list,
+ function( $a, $b ) {
+ return strcmp( $a['name'], $b['name'] );
+ }
+ );
+
+ return $list;
+
+ }
+
+ /**
+ * Reusable loop function for handling commands which accept one or more slugs as the commands first argument
+ *
+ * @since 0.0.1
+ *
+ * @param string[] $slugs Array of add-on slugs, with or without the `lifterlms-` prefix.
+ * @param array $assoc_args Associative array of command options from the original command.
+ * @param string $callback Name of the method to use for handling a single add-on for the given command.
+ * The callback should accept three arguments:
+ * + @type string $slug Add-on slug for the current item.
+ * + @type LLMS_Add_On $addon Add-on object for the current item.
+ * + @type array $assoc_args Array of arguments from the initial command.
+ * The callback should return a truthy to signal success and
+ * a falsy to signal an error.
+ * @return array {
+ * Associative arrays containing details on the errors and successes encountered during the loop.
+ *
+ * @type int $errors Number of errors encountered in the loop.
+ * @type int $successes Number of success encountered in the loop.
+ * }
+ */
+ private function loop( $slugs, $assoc_args, $callback ) {
+
+ $successes = 0;
+ $errors = 0;
+
+ foreach ( $slugs as $slug ) {
+
+ if ( empty( $slug ) ) {
+ \WP_CLI::warning( 'Ignoring ambiguous empty slug value.' );
+ continue;
+ }
+
+ $addon = $this->get_addon( $slug, true, 'warning' );
+ if ( empty( $addon ) ) {
+ $errors++;
+ continue;
+ }
+
+ if ( ! $this->$callback( $slug, $addon, $assoc_args ) ) {
+ $errors++;
+ continue;
+ }
+
+ $successes++;
+
+ }
+
+ return compact( 'errors', 'successes' );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Uninstall.php b/libraries/lifterlms-cli/src/Commands/AddOn/Uninstall.php
new file mode 100644
index 0000000000..101a92202e
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Uninstall.php
@@ -0,0 +1,104 @@
+...]
+ * : The slug of one or more add-on to install.
+ *
+ * [--deactivate]
+ * : If set, the plugin add-on(s) will be deactivated prior to uninstalling. Default behavior is to warn and skip if the plugin is active.
+ * Themes cannot be deactivated, another theme must be activated and then an add-on theme can be uninstalled.
+ *
+ * [--all]
+ * : If set, all of the add-ons available to the site will be uninstalled.
+ *
+ * [--type=]
+ * : When using '--all', determines the type of add-on to be uninstalled.
+ * ---
+ * default: 'all'
+ * options:
+ * - all
+ * - plugin
+ * - theme
+ * ---
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function uninstall( $args, $assoc_args ) {
+
+ if ( ! empty( $assoc_args['all'] ) ) {
+ $args = $this->get_available_addons( 'inactive', false, $assoc_args['type'] );
+ if ( empty( $args ) ) {
+ return \WP_CLI::warning( 'No add-ons to uninstall.' );
+ }
+ }
+
+ $results = $this->loop( $args, $assoc_args, 'uninstall_one' );
+ if ( ! $this->chaining ) {
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'uninstall', count( $args ), $results['successes'], $results['errors'] );
+ }
+
+ }
+
+ /**
+ * Loop callback function for uninstall()
+ *
+ * Ensures add-on can be uninstalled and actually installs (and maybe deactivates) the add-on.
+ *
+ * @since 0.0.1
+ *
+ * @param string $slug Add-on slug.
+ * @param LLMS_Add_On $addon Add-on object.
+ * @param array $assoc_args Associative array of command options.
+ * @return null|true Returns `null` if an error is encountered and `true` on success.
+ */
+ private function uninstall_one( $slug, $addon, $assoc_args ) {
+
+ if ( ! $addon->is_installed() ) {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is not installed.', $slug ) );
+ }
+
+ if ( $addon->is_active() ) {
+ if ( ! empty( $assoc_args['deactivate'] ) ) {
+ $this->chain_command( 'deactivate', array( $slug ) );
+ } else {
+ return \WP_CLI::warning( sprintf( 'Add-on "%s" is active.', $slug ) );
+ }
+ }
+
+ $res = $addon->uninstall();
+ if ( is_wp_error( $res ) ) {
+ return \WP_CLI::warning( $res );
+ }
+
+ \WP_CLI::log( $res );
+
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/AddOn/Update.php b/libraries/lifterlms-cli/src/Commands/AddOn/Update.php
new file mode 100644
index 0000000000..b179dbdcee
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/AddOn/Update.php
@@ -0,0 +1,165 @@
+...]
+ * : The slug of one or more add-on to update.
+ *
+ * [--exclude]
+ * : A comma-separated list of add-on slugs which should be excluded from updating.
+ *
+ * [--all]
+ * : If set, all of the add-ons available to the site will be uninstalled.
+ *
+ * [--type=]
+ * : When using '--all', determines the type of add-on to be uninstalled.
+ * ---
+ * default: 'all'
+ * options:
+ * - all
+ * - plugin
+ * - theme
+ * ---
+ *
+ * [--format=]
+ * : Render output in a particular format.
+ * ---
+ * default: table
+ * options:
+ * - table
+ * - csv
+ * - json
+ * - yaml
+ * ---
+ *
+ * [--dry-run]
+ * : Preview which plugins would be updated.
+ *
+ * @since 0.0.1
+ *
+ * @param array $include List of add-on slugs to be updated.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function update( $include, $assoc_args ) {
+
+ $include = array_map( array( $this, 'prefix_slug' ), $include );
+
+ $fields = array( 'name', 'status', 'version', 'update_version' );
+
+ $exclude = ! empty( $assoc_args['exclude'] ) ? array_map( array( $this, 'prefix_slug' ), explode( ',', $assoc_args['exclude'] ) ) : array();
+
+ // Retrieve all available updates and we'll filter it down.
+ $list = \WP_CLI::runcommand(
+ "llms addon list --format=json {$fieldopt}--update=available --fields=name,status,version,update_version",
+ array(
+ 'return' => true,
+ )
+ );
+ $list = array_filter(
+ json_decode( $list, true ),
+ function( $item ) use ( $include, $exclude ) {
+ // Add-on is active and an update is available.
+ return // Add-on is installed.
+ in_array( $item['status'], array( 'active', 'inactive' ), true ) &&
+ // Not excluded.
+ ! in_array( $item['name'], $exclude, true ) &&
+ // No add-ons specified or the add-on is in the specified list.
+ ( empty( $include ) || in_array( $item['name'], $include, true ) );
+ }
+ );
+
+ // WP-CLI `wp plugin update` shows a string when displaying table and no output for other formats.
+ if ( empty( $list ) ) {
+ if ( 'table' === $assoc_args['format'] ) {
+ return \WP_CLI::log( 'No add-on updates available.' );
+ }
+ return;
+ }
+
+ /**
+ * The WP Core upgrader pulls information from the site transient.
+ * If the update check cron or a manual visit to an update screen on the admin panel
+ * hasn't recently occurred the transient won't be set and we'll know there's an update
+ * but the transient will not and the upgrader won't be able to upgrade.
+ *
+ * So we'll force a redundant check to take place here to ensure that we can upgrade.
+ */
+ wp_update_plugins();
+ wp_update_themes();
+
+ if ( empty( $assoc_args['dry-run'] ) ) {
+
+ $fields = array( 'name', 'status', 'old_version', 'new_version' );
+
+ $errors = 0;
+ $successes = 0;
+ foreach ( $list as &$item ) {
+
+ if ( $this->update_one( $item ) ) {
+ $successes++;
+ } else {
+ $errors++;
+ }
+ }
+
+ \WP_CLI\Utils\report_batch_operation_results( 'add-on', 'update', count( $list ), $successes, $errors );
+
+ }
+
+ $formatter = new Formatter( $assoc_args, $fields );
+ return $formatter->display_items( $list );
+
+ }
+
+
+ /**
+ * Update a single add-on
+ *
+ * @since 0.0.1
+ *
+ * @param array $item Associative array of add-on data.
+ * @return boolean Returns `false` when an error is encountered and `true` otherwise.
+ */
+ private function update_one( &$item ) {
+
+ $addon = $this->get_addon( $item['name'] );
+
+ \WP_CLI::log( sprintf( 'Updating add-on: %s...', $addon->get( 'title' ) ) );
+ $res = $addon->update();
+ if ( is_wp_error( $res ) ) {
+ \WP_CLI::warning( $res );
+ return false;
+ }
+
+ $item['old_version'] = $item['version'];
+ $item['new_version'] = $item['update_version'];
+
+ \WP_CLI::log( $res );
+ return true;
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/License.php b/libraries/lifterlms-cli/src/Commands/License.php
new file mode 100644
index 0000000000..650ec36dc9
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/License.php
@@ -0,0 +1,105 @@
+]
+ * : The license key to be activated.
+ *
+ * @since 0.0.1
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @return null
+ */
+ public function activate( $args ) {
+
+ $res = \LLMS_Helper_Keys::activate_keys( $args[0] );
+ if ( ! empty( $res['data']['errors'] ) ) {
+ return \WP_CLI::error( $res['data']['errors'][0] );
+ } elseif ( ! empty( $res['data']['activations'] ) ) {
+ \LLMS_Helper_Keys::add_license_key( $res['data']['activations'][0] );
+ return \WP_CLI::success( sprintf( 'License key "%s" has been activated on this site.', $args[0] ) );
+ }
+
+ return \WP_CLI::error( 'An unknown error was encountered.' );
+
+ }
+
+ /**
+ * Deactivate a license key.
+ *
+ * ## OPTIONS
+ *
+ * []
+ * : The license key to be deactivated.
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Use a strict comparison when checking response status.
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @return null
+ */
+ public function deactivate( $args ) {
+
+ $res = \LLMS_Helper_Keys::deactivate_keys( array( $args[0] ) );
+ if ( ! empty( $res['data']['errors'] ) ) {
+ return \WP_CLI::error( $res['data']['errors'][0] );
+ } elseif ( ! empty( $res['data']['deactivations'] ) ) {
+ \LLMS_Helper_Keys::remove_license_key( $args[0] );
+ return \WP_CLI::success( sprintf( 'License key "%s" has been deactivated from this site.', $args[0] ) );
+ } elseif ( ! empty( $res['data']['status'] ) && 200 === absint( $res['data']['status'] ) ) {
+ return \WP_CLI::error( sprintf( 'License key "%s" was not active on this site.', $args[0] ) );
+ }
+
+ return \WP_CLI::error( 'An unknown error was encountered.' );
+
+ }
+
+ /**
+ * List activated license keys.
+ *
+ * ## OPTIONS
+ *
+ * []
+ * : The license key to be deactivated.
+ *
+ * @since 0.0.1
+ *
+ * @return null
+ */
+ public function list() {
+
+ $list = array_keys( llms_helper_options()->get_license_keys() );
+
+ if ( 0 === count( $list ) ) {
+ return \WP_CLI::warning( 'No license keys found on this site.' );
+ }
+
+ foreach ( $list as $key ) {
+ \WP_CLI::log( $key );
+ }
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/Restful/Command.php b/libraries/lifterlms-cli/src/Commands/Restful/Command.php
new file mode 100644
index 0000000000..7204a6d949
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/Restful/Command.php
@@ -0,0 +1,670 @@
+name = $name;
+ $parsed_args = preg_match_all( '#\([^\)]+\)#', $route, $matches );
+ $this->resource_identifier = ! empty( $matches[0] ) ? array_pop( $matches[0] ) : null;
+ $this->route = rtrim( $route );
+ $this->schema = $schema;
+ }
+
+ /**
+ * Create a new item.
+ *
+ * @subcommand create
+ */
+ public function create_item( $args, $assoc_args ) {
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_base_route(), $assoc_args );
+ if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) {
+ \WP_CLI::line( $body['id'] );
+ } else {
+ \WP_CLI::success( "Created {$this->name} {$body['id']}." );
+ }
+ }
+
+ /**
+ * Generate some items.
+ *
+ * @subcommand generate
+ */
+ public function generate_items( $args, $assoc_args ) {
+
+ $count = $assoc_args['count'];
+ unset( $assoc_args['count'] );
+ $format = $assoc_args['format'];
+ unset( $assoc_args['format'] );
+
+ $notify = false;
+ if ( 'progress' === $format ) {
+ $notify = \WP_CLI\Utils\make_progress_bar( 'Generating items', $count );
+ }
+
+ for ( $i = 0; $i < $count; $i++ ) {
+
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_base_route(), $assoc_args );
+
+ if ( 'progress' === $format ) {
+ $notify->tick();
+ } elseif ( 'ids' === $format ) {
+ echo $body['id'];
+ if ( $i < $count - 1 ) {
+ echo ' ';
+ }
+ }
+ }
+
+ if ( 'progress' === $format ) {
+ $notify->finish();
+ }
+ }
+
+ /**
+ * Delete an existing item.
+ *
+ * @subcommand delete
+ */
+ public function delete_item( $args, $assoc_args ) {
+ list( $status, $body ) = $this->do_request( 'DELETE', $this->get_filled_route( $args ), $assoc_args );
+ $id = isset( $body['previous'] ) ? $body['previous']['id'] : $body['id'];
+ if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) {
+ \WP_CLI::line( $id );
+ } else {
+ if ( empty( $assoc_args['force'] ) ) {
+ \WP_CLI::success( "Trashed {$this->name} {$id}." );
+ } else {
+ \WP_CLI::success( "Deleted {$this->name} {$id}." );
+ }
+ }
+ }
+
+ /**
+ * Get a single item.
+ *
+ * @subcommand get
+ */
+ public function get_item( $args, $assoc_args ) {
+ list( $status, $body, $headers ) = $this->do_request( 'GET', $this->get_filled_route( $args ), $assoc_args );
+
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ $body = self::limit_item_to_fields( $body, $fields );
+ }
+
+ if ( 'headers' === $assoc_args['format'] ) {
+ echo json_encode( $headers );
+ } elseif ( 'body' === $assoc_args['format'] ) {
+ echo json_encode( $body );
+ } elseif ( 'envelope' === $assoc_args['format'] ) {
+ echo json_encode(
+ array(
+ 'body' => $body,
+ 'headers' => $headers,
+ 'status' => $status,
+ 'api_url' => $this->api_url,
+ )
+ );
+ } else {
+ $formatter = $this->get_formatter( $assoc_args );
+ $formatter->display_item( $body );
+ }
+ }
+
+ /**
+ * List all items.
+ *
+ * @subcommand list
+ */
+ public function list_items( $args, $assoc_args ) {
+ if ( ! empty( $assoc_args['format'] ) && 'count' === $assoc_args['format'] ) {
+ $method = 'HEAD';
+ } else {
+ $method = 'GET';
+ }
+ list( $status, $body, $headers ) = $this->do_request( $method, $this->get_base_route(), $assoc_args );
+ if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
+ $items = array_column( $body, 'id' );
+ } else {
+ $items = $body;
+ }
+
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ foreach ( $items as $key => $item ) {
+ $items[ $key ] = self::limit_item_to_fields( $item, $fields );
+ }
+ }
+
+ if ( ! empty( $assoc_args['format'] ) && 'count' === $assoc_args['format'] ) {
+ echo (int) $headers['X-WP-Total'];
+ } elseif ( 'headers' === $assoc_args['format'] ) {
+ echo json_encode( $headers );
+ } elseif ( 'body' === $assoc_args['format'] ) {
+ echo json_encode( $body );
+ } elseif ( 'envelope' === $assoc_args['format'] ) {
+ echo json_encode(
+ array(
+ 'body' => $body,
+ 'headers' => $headers,
+ 'status' => $status,
+ 'api_url' => $this->api_url,
+ )
+ );
+ } else {
+ $formatter = $this->get_formatter( $assoc_args );
+ $formatter->display_items( $items );
+ }
+ }
+
+ /**
+ * Compare items between environments.
+ *
+ *
+ * : Alias for the WordPress site to compare to.
+ *
+ * []
+ * : Limit comparison to a specific resource, instead of the collection.
+ *
+ * [--fields=]
+ * : Limit comparison to specific fields.
+ *
+ * @subcommand diff
+ */
+ public function diff_items( $args, $assoc_args ) {
+
+ list( $alias ) = $args;
+ if ( ! array_key_exists( $alias, \WP_CLI::get_runner()->aliases ) ) {
+ \WP_CLI::error( "Alias '{$alias}' not found." );
+ }
+ $resource = isset( $args[1] ) ? $args[1] : null;
+ $fields = \WP_CLI\Utils\get_flag_value( $assoc_args, 'fields', null );
+
+ list( $from_status, $from_body, $from_headers ) = $this->do_request( 'GET', $this->get_base_route(), array() );
+
+ $php_bin = \WP_CLI::get_php_binary();
+ $script_path = $GLOBALS['argv'][0];
+ $other_args = implode( ' ', array_map( 'escapeshellarg', array( $alias, 'rest', $this->name, 'list' ) ) );
+ $other_assoc_args = \WP_CLI\Utils\assoc_args_to_str( array( 'format' => 'envelope' ) );
+ $full_command = "{$php_bin} {$script_path} {$other_args} {$other_assoc_args}";
+ $process = \WP_CLI\Process::create(
+ $full_command,
+ null,
+ array(
+ 'HOME' => getenv( 'HOME' ),
+ 'WP_CLI_PACKAGES_DIR' => getenv( 'WP_CLI_PACKAGES_DIR' ),
+ 'WP_CLI_CONFIG_PATH' => getenv( 'WP_CLI_CONFIG_PATH' ),
+ )
+ );
+ $result = $process->run();
+ $response = json_decode( $result->stdout, true );
+ $to_headers = $response['headers'];
+ $to_body = $response['body'];
+ $to_api_url = $response['api_url'];
+
+ if ( ! is_null( $resource ) ) {
+ $field = is_numeric( $resource ) ? 'id' : 'slug';
+ $callback = function( $value ) use ( $field, $resource ) {
+ if ( isset( $value[ $field ] ) && $resource == $value[ $field ] ) {
+ return true;
+ }
+ return false;
+ };
+ foreach ( array( 'to_body', 'from_body' ) as $response_type ) {
+ $$response_type = array_filter( $$response_type, $callback );
+ }
+ }
+
+ $display_items = array();
+ do {
+ $from_item = $to_item = array();
+ if ( ! empty( $from_body ) ) {
+ $from_item = array_shift( $from_body );
+ if ( ! empty( $to_body ) && ! empty( $from_item['slug'] ) ) {
+ foreach ( $to_body as $i => $item ) {
+ if ( ! empty( $item['slug'] ) && $item['slug'] === $from_item['slug'] ) {
+ $to_item = $item;
+ unset( $to_body[ $i ] );
+ break;
+ }
+ }
+ }
+ } elseif ( ! empty( $to_body ) ) {
+ $to_item = array_shift( $to_body );
+ }
+
+ if ( ! empty( $to_item ) ) {
+ foreach ( array( 'to_item', 'from_item' ) as $item ) {
+ if ( isset( $$item['_links'] ) ) {
+ unset( $$item['_links'] );
+ }
+ }
+ $display_items[] = array(
+ 'from' => self::limit_item_to_fields( $from_item, $fields ),
+ 'to' => self::limit_item_to_fields( $to_item, $fields ),
+ );
+ }
+ } while ( count( $from_body ) || count( $to_body ) );
+
+ \WP_CLI::line( \cli\Colors::colorize( "%R(-) {$this->api_url} %G(+) {$to_api_url}%n" ) );
+ foreach ( $display_items as $display_item ) {
+ $this->show_difference(
+ $this->name,
+ array(
+ 'from' => $display_item['from'],
+ 'to' => $display_item['to'],
+ )
+ );
+ }
+ }
+
+ /**
+ * Update an existing item.
+ *
+ * @subcommand update
+ */
+ public function update_item( $args, $assoc_args ) {
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_filled_route( $args ), $assoc_args );
+ if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'porcelain' ) ) {
+ \WP_CLI::line( $body['id'] );
+ } else {
+ \WP_CLI::success( "Updated {$this->name} {$body['id']}." );
+ }
+ }
+
+ /**
+ * Open an existing item in the editor
+ *
+ * @subcommand edit
+ */
+ public function edit_item( $args, $assoc_args ) {
+ $assoc_args['context'] = 'edit';
+ list( $status, $options_body ) = $this->do_request( 'OPTIONS', $this->get_filled_route( $args ), $assoc_args );
+ if ( empty( $options_body['schema'] ) ) {
+ \WP_CLI::error( 'Cannot edit - no schema found for resource.' );
+ }
+ $schema = $options_body['schema'];
+ list( $status, $resource_fields ) = $this->do_request( 'GET', $this->get_filled_route( $args ), $assoc_args );
+ $editable_fields = array();
+ foreach ( $resource_fields as $key => $value ) {
+ if ( ! isset( $schema['properties'][ $key ] ) || ! empty( $schema['properties'][ $key ]['readonly'] ) ) {
+ continue;
+ }
+ $properties = $schema['properties'][ $key ];
+ if ( isset( $properties['properties'] ) ) {
+ $parent_key = $key;
+ $properties = $properties['properties'];
+ foreach ( $value as $key => $value ) {
+ if ( isset( $properties[ $key ] ) && empty( $properties[ $key ]['readonly'] ) ) {
+ if ( ! isset( $editable_fields[ $parent_key ] ) ) {
+ $editable_fields[ $parent_key ] = array();
+ }
+ $editable_fields[ $parent_key ][ $key ] = $value;
+ }
+ }
+ continue;
+ }
+ if ( empty( $properties['readonly'] ) ) {
+ $editable_fields[ $key ] = $value;
+ }
+ }
+ if ( empty( $editable_fields ) ) {
+ \WP_CLI::error( 'Cannot edit - no editable fields found on schema.' );
+ }
+ $ret = \WP_CLI\Utils\launch_editor_for_input( \Spyc::YAMLDump( $editable_fields ), sprintf( 'Editing %s %s', $schema['title'], $args[0] ) );
+ if ( false === $ret ) {
+ \WP_CLI::warning( 'No edits made.' );
+ } else {
+ list( $status, $body ) = $this->do_request( 'POST', $this->get_filled_route( $args ), \Spyc::YAMLLoadString( $ret ) );
+ \WP_CLI::success( "Updated {$schema['title']} {$args[0]}." );
+ }
+ }
+
+ /**
+ * Do a REST Request
+ *
+ * @param string $method
+ */
+ private function do_request( $method, $route, $assoc_args ) {
+ if ( 'internal' === $this->scope ) {
+ if ( ! defined( 'REST_REQUEST' ) ) {
+ define( 'REST_REQUEST', true );
+ }
+ $request = new \WP_REST_Request( $method, $route );
+ if ( in_array( $method, array( 'POST', 'PUT' ) ) ) {
+ $request->set_body_params( $assoc_args );
+ } else {
+ foreach ( $assoc_args as $key => $value ) {
+ $request->set_param( $key, $value );
+ }
+ }
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
+ $original_queries = is_array( $GLOBALS['wpdb']->queries ) ? array_keys( $GLOBALS['wpdb']->queries ) : array();
+ }
+ $response = rest_do_request( $request );
+ if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) {
+ $performed_queries = array();
+ foreach ( (array) $GLOBALS['wpdb']->queries as $key => $query ) {
+ if ( in_array( $key, $original_queries ) ) {
+ continue;
+ }
+ $performed_queries[] = $query;
+ }
+ usort(
+ $performed_queries,
+ function( $a, $b ) {
+ if ( $a[1] === $b[1] ) {
+ return 0;
+ }
+ return ( $a[1] > $b[1] ) ? -1 : 1;
+ }
+ );
+
+ $query_count = count( $performed_queries );
+ $query_total_time = 0;
+ foreach ( $performed_queries as $query ) {
+ $query_total_time += $query[1];
+ }
+ $slow_query_message = '';
+ if ( $performed_queries && 'rest' === \WP_CLI::get_config( 'debug' ) ) {
+ $slow_query_message .= '. Ordered by slowness, the queries are:' . PHP_EOL;
+ foreach ( $performed_queries as $i => $query ) {
+ $i++;
+ $bits = explode( ', ', $query[2] );
+ $backtrace = implode( ', ', array_slice( $bits, 13 ) );
+ $seconds = round( $query[1], 6 );
+ $slow_query_message .= <<as_error() ) {
+ \WP_CLI::error( $error );
+ }
+ return array( $response->get_status(), $response->get_data(), $response->get_headers() );
+ } elseif ( 'http' === $this->scope ) {
+ $headers = array();
+ if ( ! empty( $this->auth ) && 'basic' === $this->auth['type'] ) {
+ $headers['Authorization'] = 'Basic ' . base64_encode( $this->auth['username'] . ':' . $this->auth['password'] );
+ }
+ if ( 'OPTIONS' === $method ) {
+ $method = 'GET';
+ $assoc_args['_method'] = 'OPTIONS';
+ }
+ $response = \WP_CLI\Utils\http_request( $method, rtrim( $this->api_url, '/' ) . $route, $assoc_args, $headers );
+ $body = json_decode( $response->body, true );
+ if ( $response->status_code >= 400 ) {
+ if ( ! empty( $body['message'] ) ) {
+ \WP_CLI::error( $body['message'] . ' ' . json_encode( array( 'status' => $response->status_code ) ) );
+ } else {
+ switch ( $response->status_code ) {
+ case 404:
+ \WP_CLI::error( "No {$this->name} found." );
+ break;
+ default:
+ \WP_CLI::error( 'Could not complete request.' );
+ break;
+ }
+ }
+ }
+ return array( $response->status_code, json_decode( $response->body, true ), $response->headers->getAll() );
+ }
+ \WP_CLI::error( 'Invalid scope for REST command.' );
+ }
+
+ /**
+ * Get Formatter object based on supplied parameters.
+ *
+ * @param array $assoc_args Parameters passed to command. Determines formatting.
+ * @return \WP_CLI\Formatter
+ */
+ protected function get_formatter( &$assoc_args ) {
+ if ( ! empty( $assoc_args['fields'] ) ) {
+ if ( is_string( $assoc_args['fields'] ) ) {
+ $fields = explode( ',', $assoc_args['fields'] );
+ } else {
+ $fields = $assoc_args['fields'];
+ }
+ } else {
+ if ( ! empty( $assoc_args['context'] ) ) {
+ $fields = $this->get_context_fields( $assoc_args['context'] );
+ } else {
+ $fields = $this->get_context_fields( 'view' );
+ }
+ }
+ return new \WP_CLI\Formatter( $assoc_args, $fields );
+ }
+
+ /**
+ * Get a list of fields present in a given context
+ *
+ * @param string $context
+ * @return array
+ */
+ private function get_context_fields( $context ) {
+ $fields = array();
+ foreach ( $this->schema['properties'] as $key => $args ) {
+ if ( empty( $args['context'] ) || in_array( $context, $args['context'] ) ) {
+ $fields[] = $key;
+ }
+ }
+ return $fields;
+ }
+
+ /**
+ * Get the base route for this resource
+ *
+ * @return string
+ */
+ private function get_base_route() {
+ return substr( $this->route, 0, strlen( $this->route ) - strlen( $this->resource_identifier ) );
+ }
+
+ /**
+ * Fill the route based on provided $args
+ */
+ private function get_filled_route( $args ) {
+ return rtrim( $this->get_base_route(), '/' ) . '/' . $args[0];
+ }
+
+ /**
+ * Visually depict the difference between "dictated" and "current"
+ *
+ * @param array
+ */
+ private function show_difference( $slug, $difference ) {
+ $this->output_nesting_level = 0;
+ $this->nested_line( $slug . ': ' );
+ $this->recursively_show_difference( $difference['to'], $difference['from'] );
+ $this->output_nesting_level = 0;
+ }
+
+ /**
+ * Recursively output the difference between "dictated" and "current"
+ */
+ private function recursively_show_difference( $dictated, $current = null ) {
+
+ $this->output_nesting_level++;
+
+ if ( $this->is_assoc_array( $dictated ) ) {
+
+ foreach ( $dictated as $key => $value ) {
+
+ if ( $this->is_assoc_array( $value ) || is_array( $value ) ) {
+
+ $new_current = isset( $current[ $key ] ) ? $current[ $key ] : null;
+ if ( $new_current ) {
+ $this->nested_line( $key . ': ' );
+ } else {
+ $this->add_line( $key . ': ' );
+ }
+
+ $this->recursively_show_difference( $value, $new_current );
+
+ } elseif ( is_string( $value ) ) {
+
+ $pre = $key . ': ';
+
+ if ( isset( $current[ $key ] ) && $current[ $key ] !== $value ) {
+
+ $this->remove_line( $pre . $current[ $key ] );
+ $this->add_line( $pre . $value );
+
+ } elseif ( ! isset( $current[ $key ] ) ) {
+
+ $this->add_line( $pre . $value );
+
+ }
+ }
+ }
+ } elseif ( is_array( $dictated ) ) {
+
+ foreach ( $dictated as $value ) {
+
+ if ( ! $current
+ || ! in_array( $value, $current ) ) {
+ $this->add_line( '- ' . $value );
+ }
+ }
+ } elseif ( is_string( $value ) ) {
+
+ $pre = $key . ': ';
+
+ if ( isset( $current[ $key ] ) && $current[ $key ] !== $value ) {
+
+ $this->remove_line( $pre . $current[ $key ] );
+ $this->add_line( $pre . $value );
+
+ } elseif ( ! isset( $current[ $key ] ) ) {
+
+ $this->add_line( $pre . $value );
+
+ } else {
+
+ $this->nested_line( $pre );
+
+ }
+ }
+
+ $this->output_nesting_level--;
+
+ }
+
+ /**
+ * Output a line to be added
+ *
+ * @param string
+ */
+ private function add_line( $line ) {
+ $this->nested_line( $line, 'add' );
+ }
+
+ /**
+ * Output a line to be removed
+ *
+ * @param string
+ */
+ private function remove_line( $line ) {
+ $this->nested_line( $line, 'remove' );
+ }
+
+ /**
+ * Output a line that's appropriately nested
+ */
+ private function nested_line( $line, $change = false ) {
+
+ if ( 'add' == $change ) {
+ $color = '%G';
+ $label = '+ ';
+ } elseif ( 'remove' == $change ) {
+ $color = '%R';
+ $label = '- ';
+ } else {
+ $color = false;
+ $label = false;
+ }
+
+ $spaces = ( $this->output_nesting_level * 2 ) + 2;
+ if ( $color && $label ) {
+ $line = \cli\Colors::colorize( "{$color}{$label}" ) . $line . \cli\Colors::colorize( '%n' );
+ $spaces = $spaces - 2;
+ }
+ \WP_CLI::line( str_pad( ' ', $spaces ) . $line );
+ }
+
+ /**
+ * Whether or not this is an associative array
+ *
+ * @param array
+ * @return bool
+ */
+ private function is_assoc_array( $array ) {
+
+ if ( ! is_array( $array ) ) {
+ return false;
+ }
+
+ return array_keys( $array ) !== range( 0, count( $array ) - 1 );
+ }
+
+ /**
+ * Reduce an item to specific fields.
+ *
+ * @param array $item
+ * @param array $fields
+ * @return array
+ */
+ private static function limit_item_to_fields( $item, $fields ) {
+ if ( empty( $fields ) ) {
+ return $item;
+ }
+ if ( is_string( $fields ) ) {
+ $fields = explode( ',', $fields );
+ }
+ foreach ( $item as $i => $field ) {
+ if ( ! in_array( $i, $fields ) ) {
+ unset( $item[ $i ] );
+ }
+ }
+ return $item;
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/Restful/Runner.php b/libraries/lifterlms-cli/src/Commands/Restful/Runner.php
new file mode 100644
index 0000000000..a6d2a3f739
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/Restful/Runner.php
@@ -0,0 +1,391 @@
+set_param( 'context', 'help' );
+
+ $response = $wp_rest_server->dispatch( $request );
+ $response_data = $response->get_data();
+ if ( empty( $response_data ) ) {
+ return;
+ }
+
+ foreach ( $response_data['routes'] as $route => $route_data ) {
+
+ // Skip non LifterLMS routes.
+ if ( 0 !== strpos( $route, '/llms/' ) ) {
+ continue;
+ }
+
+ if ( empty( $route_data['schema']['title'] ) ) {
+ \WP_CLI::debug( "No schema title found for {$route}, skipping LifterLMS CLI REST command registration.", 'lifterlms' );
+ continue;
+ }
+
+ $name = $route_data['schema']['title'];
+ $rest_command = new Command( $name, $route, $route_data['schema'] );
+ self::register_route_commands( $rest_command, $route, $route_data );
+
+ }
+
+ }
+
+
+ private static function get_command_root_desc( $resource ) {
+ $resource = str_replace( array( '-', 'students', 'api' ), array( ' ', 'student', 'API' ), $resource );
+ if ( 's' !== substr( $resource, -1 ) ) {
+ $resource .= 's';
+ }
+ return sprintf( 'Manage %s.', $resource );
+ }
+
+ private static function get_command_short_desc( $command, $resource ) {
+
+ $before = '';
+ $after = '';
+
+
+ switch ( $command ) {
+ case 'create':
+ $before = 'Creates a new';
+ break;
+
+ case 'delete':
+ $before = 'Deletes an existing';
+ break;
+
+ case 'diff':
+ $before = 'Compare';
+ $resource = self::pluralize_resource( $resource );
+ $after = 'between environments';
+ break;
+
+ case 'edit':
+ $before = 'Launches system editor to edit the';
+ $after = 'content';
+ break;
+
+ case 'generate':
+ $before = 'Generates some';
+ $resource = self::pluralize_resource( $resource );
+ break;
+
+ case 'get':
+ $before = 'Gets details about a';
+ break;
+
+ case 'list':
+ $before = 'Gets a list of ';
+ $resource = self::pluralize_resource( $resource );
+ break;
+
+ case 'update':
+ $before = 'Updates an existing';
+ break;
+ }
+
+ return trim( implode( ' ', array( $before, $resource, $after ) ) ) . '.';
+ }
+
+ private static function pluralize_resource( $resource ) {
+
+ switch ( $resource ) {
+ default:
+ $resource .= 's';
+ }
+
+ return $resource;
+ }
+
+ private static function get_supported_commands( $route, $route_data ) {
+
+ $supported_commands = array();
+ foreach ( $route_data['endpoints'] as $endpoint ) {
+
+ $parsed_args = preg_match_all( '#\([^\)]+\)#', $route, $matches );
+ $resource_id = ! empty( $matches[0] ) ? array_pop( $matches[0] ) : null;
+ $trimmed_route = rtrim( $route );
+ $is_singular = $resource_id === substr( $trimmed_route, - strlen( $resource_id ) );
+
+ // List a collection
+ if ( array( 'GET' ) == $endpoint['methods']
+ && ! $is_singular ) {
+ $supported_commands['list'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Create a specific resource
+ if ( array( 'POST' ) == $endpoint['methods']
+ && ! $is_singular ) {
+ $supported_commands['create'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Get a specific resource
+ if ( array( 'GET' ) == $endpoint['methods']
+ && $is_singular ) {
+ $supported_commands['get'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Update a specific resource
+ if ( in_array( 'POST', $endpoint['methods'] )
+ && $is_singular ) {
+ $supported_commands['update'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+
+ // Delete a specific resource
+ if ( array( 'DELETE' ) == $endpoint['methods']
+ && $is_singular ) {
+ $supported_commands['delete'] = ! empty( $endpoint['args'] ) ? $endpoint['args'] : array();
+ }
+ }
+
+ return $supported_commands;
+
+ }
+
+ public static function before_invoke_command() {
+
+ /**
+ * If `--user` was passed the user will already be set, otherwise there won't be a user.
+ *
+ * It is "safe" to assume that someone using the CLI has admin access and we'll set the current
+ * user to be the first admin we find in the DB that has the `manage_options` cap.
+ */
+ if ( ! get_current_user_id() ) {
+ $user = \LLMS_Install::get_can_install_user_id();
+ if ( $user ) {
+ wp_set_current_user( $user );
+ }
+ }
+
+ if ( \WP_CLI::get_config( 'debug' ) && ! defined( 'SAVEQUERIES' ) ) {
+ define( 'SAVEQUERIES', true );
+ }
+
+ }
+
+ /**
+ * Register WP-CLI commands for all endpoints on a route
+ *
+ * @param string
+ * @param array $endpoints
+ */
+ private static function register_route_commands( $rest_command, $route, $route_data ) {
+
+ $resource = str_replace( array( 'llms_', '_' ), array( '', '-' ), $route_data['schema']['title'] );
+ $parent = "llms {$resource}";
+
+ $supported_commands = self::get_supported_commands( $route, $route_data );
+ foreach ( $supported_commands as $command => $endpoint_args ) {
+
+ $synopsis = array();
+ if ( in_array( $command, array( 'delete', 'get', 'update' ) ) ) {
+ $synopsis[] = array(
+ 'name' => 'id',
+ 'type' => 'positional',
+ 'description' => 'The id for the resource.',
+ 'optional' => false,
+ );
+ }
+
+ foreach ( $endpoint_args as $name => $args ) {
+ $arg_reg = array(
+ 'name' => $name,
+ 'type' => 'assoc',
+ 'description' => ! empty( $args['description'] ) ? $args['description'] : '',
+ 'optional' => empty( $args['required'] ) ? true : false,
+ );
+ foreach ( array( 'enum', 'default' ) as $key ) {
+ if ( isset( $args[ $key ] ) ) {
+ $new_key = 'enum' === $key ? 'options' : $key;
+ $arg_reg[ $new_key ] = $args[ $key ];
+ }
+ }
+ $synopsis[] = $arg_reg;
+ }
+
+ if ( in_array( $command, array( 'list', 'get' ) ) ) {
+ $synopsis[] = array(
+ 'name' => 'fields',
+ 'type' => 'assoc',
+ 'description' => 'Limit response to specific fields. Defaults to all fields.',
+ 'optional' => true,
+ );
+ $synopsis[] = array(
+ 'name' => 'field',
+ 'type' => 'assoc',
+ 'description' => 'Get the value of an individual field.',
+ 'optional' => true,
+ );
+ $synopsis[] = array(
+ 'name' => 'format',
+ 'type' => 'assoc',
+ 'description' => 'Render response in a particular format.',
+ 'optional' => true,
+ 'default' => 'table',
+ 'options' => array(
+ 'table',
+ 'json',
+ 'csv',
+ 'ids',
+ 'yaml',
+ 'count',
+ 'headers',
+ 'body',
+ 'envelope',
+ ),
+ );
+ }
+
+ if ( in_array( $command, array( 'create', 'update', 'delete' ) ) ) {
+ $synopsis[] = array(
+ 'name' => 'porcelain',
+ 'type' => 'flag',
+ 'description' => 'Output just the id when the operation is successful.',
+ 'optional' => true,
+ );
+ }
+
+ $methods = array(
+ 'list' => 'list_items',
+ 'create' => 'create_item',
+ 'delete' => 'delete_item',
+ 'get' => 'get_item',
+ 'update' => 'update_item',
+ );
+
+ // Add the root command, eg: wp llms course.
+ \WP_CLI::add_command(
+ "{$parent}",
+ $rest_command,
+ array(
+ 'shortdesc' => self::get_command_root_desc( $resource ),
+ )
+ );
+
+ // Register main subcommands, eg: wp llms course create, wp llms course delete, etc...
+ \WP_CLI::add_command(
+ "{$parent} {$command}",
+ array( $rest_command, $methods[ $command ] ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( $command, $resource ),
+ 'synopsis' => $synopsis,
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+
+ // If listing is supported, add the diff command.
+ if ( 'list' === $command ) {
+ \WP_CLI::add_command(
+ "{$parent} diff",
+ array( $rest_command, 'diff_items' ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( 'diff', $resource ),
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+ }
+
+ // If creation is supported, add the generate command.
+ if ( 'create' === $command ) {
+ \WP_CLI::add_command(
+ "{$parent} generate",
+ array( $rest_command, 'generate_items' ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( 'generate', $resource ),
+ 'synopsis' => self::get_generate_command_synopsis( $synopsis ),
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+ }
+
+
+ // If updating and getting is supported, add the edit command.
+ if ( 'update' === $command && array_key_exists( 'get', $supported_commands ) ) {
+ $synopsis = array();
+ $synopsis[] = array(
+ 'name' => 'id',
+ 'type' => 'positional',
+ 'description' => 'The id for the resource.',
+ 'optional' => false,
+ );
+ \WP_CLI::add_command(
+ "{$parent} edit",
+ array( $rest_command, 'edit_item' ),
+ array(
+ 'shortdesc' => self::get_command_short_desc( 'edit', $resource ),
+ 'synopsis' => $synopsis,
+ 'before_invoke' => array( __CLASS__, 'before_invoke_command' ),
+ )
+ );
+ }
+ }
+ }
+
+ private static function get_generate_command_synopsis( $create_synopsis ) {
+
+ $generate_synopsis = array(
+ array(
+ 'name' => 'count',
+ 'type' => 'assoc',
+ 'description' => 'Number of items to generate.',
+ 'optional' => true,
+ 'default' => 10,
+ ),
+ array(
+ 'name' => 'format',
+ 'type' => 'assoc',
+ 'description' => 'Render generation in specific format.',
+ 'optional' => true,
+ 'default' => 'progress',
+ 'options' => array(
+ 'progress',
+ 'ids',
+ ),
+ ),
+ );
+
+ return array_merge( $generate_synopsis, $create_synopsis );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Commands/Root.php b/libraries/lifterlms-cli/src/Commands/Root.php
new file mode 100644
index 0000000000..0da54b4a48
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Commands/Root.php
@@ -0,0 +1,83 @@
+]
+ * : The slug of the LifterLMS plugin or theme. Default: lifterlms.
+ *
+ * ## EXAMPLES
+ *
+ * # Show the LifterLMS core plugin version
+ * wp llms version
+ *
+ * # Show the LifterLMS core plugin version
+ * wp llms version core
+ *
+ * # Show an add-on version without the "lifterlms-" prefix.
+ * wp llms version groups
+ *
+ * # Show an add-on version with the "lifterlms-" prefix.
+ * wp llms version lifterlms-assignments
+ *
+ * @since 0.0.1
+ * @since 0.0.2 Remove `--db` option. This will be implemented in a separate command.
+ *
+ * @param array $args Indexed array of positional command arguments.
+ * @param array $assoc_args Associative array of command options.
+ * @return null
+ */
+ public function version( $args, $assoc_args ) {
+
+ $slug = empty( $args[0] ) ? 'core' : $args[0];
+ if ( in_array( $slug, array( 'core', 'lifterlms' ), true ) ) {
+ return \WP_CLI::log( llms()->version );
+ }
+
+ $addon = $this->get_addon( $slug );
+ if ( empty( $addon ) ) {
+ return \WP_CLI::error( 'Invalid slug.' );
+ }
+
+ if ( $addon->is_installed() ) {
+ return \WP_CLI::log( $addon->get_installed_version() );
+ }
+
+ return \WP_CLI::error(
+ sprintf(
+ "The requested add-on is not installed. Run 'wp llms addon install %s.' to install it.",
+ $args[0]
+ )
+ );
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/Main.php b/libraries/lifterlms-cli/src/Main.php
new file mode 100644
index 0000000000..4cd19b55e3
--- /dev/null
+++ b/libraries/lifterlms-cli/src/Main.php
@@ -0,0 +1,140 @@
+version );
+ }
+
+ // Get started (after REST).
+ add_action( 'plugins_loaded', array( $this, 'init' ) );
+
+ }
+
+ /**
+ * Add all LifterLMS CLI commands
+ *
+ * This includes a separate file so that commands can be included on their own
+ * when generating documentation.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ public function commands() {
+ require_once LLMS_CLI_PLUGIN_DIR . 'src/commands.php';
+ }
+
+ /**
+ * Register WP_CLI hooks
+ *
+ * Loads all commands and sets up license and addon commands to be aborted
+ * if the LifterLMS Helper is not present.
+ *
+ * @since 0.0.1
+ *
+ * @return void
+ */
+ private function hooks() {
+
+ \WP_CLI::add_hook( 'after_wp_load', array( $this, 'commands' ) );
+
+ // If the Helper doesn't exist abort command addition.
+ if ( ! class_exists( 'LifterLMS_Helper' ) ) {
+ $helper_commands = array(
+ 'license',
+ 'addon install',
+ 'addon uninstall',
+ 'addon activate',
+ 'addon deactivate',
+ 'addon update',
+ );
+ foreach ( $helper_commands as $command ) {
+ \WP_CLI::add_hook(
+ "before_add_command:llms {$command}",
+ function( CommandAddition $command_addition ) {
+ $command_addition->abort( 'The LifterLMS Helper is required to use this command.' );
+ }
+ );
+ }
+ }
+
+ }
+ /**
+ * Include all required files and classes
+ *
+ * @since [version
+ *
+ * @return void
+ */
+ public function init() {
+
+ // Only load if we have the minimum LifterLMS version installed & activated.
+ if ( function_exists( 'llms' ) && version_compare( '5.0.0', llms()->version, '<=' ) ) {
+
+ $this->hooks();
+
+ }
+
+ }
+
+}
diff --git a/libraries/lifterlms-cli/src/commands.php b/libraries/lifterlms-cli/src/commands.php
new file mode 100644
index 0000000000..e97b5f5d89
--- /dev/null
+++ b/libraries/lifterlms-cli/src/commands.php
@@ -0,0 +1,42 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier
+ * @author Jordi Boggiano
+ * @see https://www.php-fig.org/psr/psr-0/
+ * @see https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+ /** @var ?string */
+ private $vendorDir;
+
+ // PSR-4
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixLengthsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixDirsPsr4 = array();
+ /**
+ * @var array[]
+ * @psalm-var array
+ */
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ /**
+ * @var array[]
+ * @psalm-var array>
+ */
+ private $prefixesPsr0 = array();
+ /**
+ * @var array[]
+ * @psalm-var array
+ */
+ private $fallbackDirsPsr0 = array();
+
+ /** @var bool */
+ private $useIncludePath = false;
+
+ /**
+ * @var string[]
+ * @psalm-var array
+ */
+ private $classMap = array();
+
+ /** @var bool */
+ private $classMapAuthoritative = false;
+
+ /**
+ * @var bool[]
+ * @psalm-var array
+ */
+ private $missingClasses = array();
+
+ /** @var ?string */
+ private $apcuPrefix;
+
+ /**
+ * @var self[]
+ */
+ private static $registeredLoaders = array();
+
+ /**
+ * @param ?string $vendorDir
+ */
+ public function __construct($vendorDir = null)
+ {
+ $this->vendorDir = $vendorDir;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+ }
+
+ return array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array>
+ */
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array
+ */
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return array
+ */
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ /**
+ * @return string[] Array of classname => path
+ * @psalm-var array
+ */
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param string[] $classMap Class to filename map
+ * @psalm-param array $classMap
+ *
+ * @return void
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @return void
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param string[]|string $paths The PSR-0 base directories
+ *
+ * @return void
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param string[]|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ *
+ * @return void
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ *
+ * @return void
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ *
+ * @return void
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+ *
+ * @param string|null $apcuPrefix
+ *
+ * @return void
+ */
+ public function setApcuPrefix($apcuPrefix)
+ {
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+ }
+
+ /**
+ * The APCu prefix in use, or null if APCu caching is not enabled.
+ *
+ * @return string|null
+ */
+ public function getApcuPrefix()
+ {
+ return $this->apcuPrefix;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ *
+ * @return void
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+ if (null === $this->vendorDir) {
+ return;
+ }
+
+ if ($prepend) {
+ self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+ } else {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ self::$registeredLoaders[$this->vendorDir] = $this;
+ }
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ *
+ * @return void
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+
+ if (null !== $this->vendorDir) {
+ unset(self::$registeredLoaders[$this->vendorDir]);
+ }
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return true|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+
+ return null;
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+ return false;
+ }
+ if (null !== $this->apcuPrefix) {
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+ if ($hit) {
+ return $file;
+ }
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if (false === $file && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if (null !== $this->apcuPrefix) {
+ apcu_add($this->apcuPrefix.$class, $file);
+ }
+
+ if (false === $file) {
+ // Remember that this class does not exist.
+ $this->missingClasses[$class] = true;
+ }
+
+ return $file;
+ }
+
+ /**
+ * Returns the currently registered loaders indexed by their corresponding vendor directories.
+ *
+ * @return self[]
+ */
+ public static function getRegisteredLoaders()
+ {
+ return self::$registeredLoaders;
+ }
+
+ /**
+ * @param string $class
+ * @param string $ext
+ * @return string|false
+ */
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ $subPath = $class;
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
+ $subPath = substr($subPath, 0, $lastPos);
+ $search = $subPath . '\\';
+ if (isset($this->prefixDirsPsr4[$search])) {
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
+ if (file_exists($file = $dir . $pathEnd)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+
+ return false;
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ *
+ * @param string $file
+ * @return void
+ * @private
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/InstalledVersions.php b/libraries/lifterlms-cli/vendor/composer/InstalledVersions.php
new file mode 100644
index 0000000000..d50e0c9fcc
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/InstalledVersions.php
@@ -0,0 +1,350 @@
+
+ * Jordi Boggiano
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ */
+class InstalledVersions
+{
+ /**
+ * @var mixed[]|null
+ * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null
+ */
+ private static $installed;
+
+ /**
+ * @var bool|null
+ */
+ private static $canGetVendors;
+
+ /**
+ * @var array[]
+ * @psalm-var array}>
+ */
+ private static $installedByVendor = array();
+
+ /**
+ * Returns a list of all package names which are present, either by being installed, replaced or provided
+ *
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackages()
+ {
+ $packages = array();
+ foreach (self::getInstalled() as $installed) {
+ $packages[] = array_keys($installed['versions']);
+ }
+
+ if (1 === \count($packages)) {
+ return $packages[0];
+ }
+
+ return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+ }
+
+ /**
+ * Returns a list of all package names with a specific type e.g. 'library'
+ *
+ * @param string $type
+ * @return string[]
+ * @psalm-return list
+ */
+ public static function getInstalledPackagesByType($type)
+ {
+ $packagesByType = array();
+
+ foreach (self::getInstalled() as $installed) {
+ foreach ($installed['versions'] as $name => $package) {
+ if (isset($package['type']) && $package['type'] === $type) {
+ $packagesByType[] = $name;
+ }
+ }
+ }
+
+ return $packagesByType;
+ }
+
+ /**
+ * Checks whether the given package is installed
+ *
+ * This also returns true if the package name is provided or replaced by another package
+ *
+ * @param string $packageName
+ * @param bool $includeDevRequirements
+ * @return bool
+ */
+ public static function isInstalled($packageName, $includeDevRequirements = true)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (isset($installed['versions'][$packageName])) {
+ return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether the given package satisfies a version constraint
+ *
+ * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+ *
+ * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+ *
+ * @param VersionParser $parser Install composer/semver to have access to this class and functionality
+ * @param string $packageName
+ * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+ * @return bool
+ */
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
+ {
+ $constraint = $parser->parseConstraints($constraint);
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+ return $provided->matches($constraint);
+ }
+
+ /**
+ * Returns a version constraint representing all the range(s) which are installed for a given package
+ *
+ * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+ * whether a given version of a package is installed, and not just whether it exists
+ *
+ * @param string $packageName
+ * @return string Version constraint usable with composer/semver
+ */
+ public static function getVersionRanges($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ $ranges = array();
+ if (isset($installed['versions'][$packageName]['pretty_version'])) {
+ $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+ }
+ if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+ }
+ if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+ }
+ if (array_key_exists('provided', $installed['versions'][$packageName])) {
+ $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+ }
+
+ return implode(' || ', $ranges);
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+ */
+ public static function getPrettyVersion($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['pretty_version'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+ */
+ public static function getReference($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ if (!isset($installed['versions'][$packageName]['reference'])) {
+ return null;
+ }
+
+ return $installed['versions'][$packageName]['reference'];
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @param string $packageName
+ * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+ */
+ public static function getInstallPath($packageName)
+ {
+ foreach (self::getInstalled() as $installed) {
+ if (!isset($installed['versions'][$packageName])) {
+ continue;
+ }
+
+ return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+ }
+
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+ }
+
+ /**
+ * @return array
+ * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
+ */
+ public static function getRootPackage()
+ {
+ $installed = self::getInstalled();
+
+ return $installed[0]['root'];
+ }
+
+ /**
+ * Returns the raw installed.php data for custom implementations
+ *
+ * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+ * @return array[]
+ * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}
+ */
+ public static function getRawData()
+ {
+ @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = include __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+
+ return self::$installed;
+ }
+
+ /**
+ * Returns the raw data of all installed.php which are currently loaded for custom implementations
+ *
+ * @return array[]
+ * @psalm-return list}>
+ */
+ public static function getAllRawData()
+ {
+ return self::getInstalled();
+ }
+
+ /**
+ * Lets you reload the static array from another file
+ *
+ * This is only useful for complex integrations in which a project needs to use
+ * this class but then also needs to execute another project's autoloader in process,
+ * and wants to ensure both projects have access to their version of installed.php.
+ *
+ * A typical case would be PHPUnit, where it would need to make sure it reads all
+ * the data it needs from this class, then call reload() with
+ * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+ * the project in which it runs can then also use this class safely, without
+ * interference between PHPUnit's dependencies and the project's dependencies.
+ *
+ * @param array[] $data A vendor/composer/installed.php data set
+ * @return void
+ *
+ * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data
+ */
+ public static function reload($data)
+ {
+ self::$installed = $data;
+ self::$installedByVendor = array();
+ }
+
+ /**
+ * @return array[]
+ * @psalm-return list}>
+ */
+ private static function getInstalled()
+ {
+ if (null === self::$canGetVendors) {
+ self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+ }
+
+ $installed = array();
+
+ if (self::$canGetVendors) {
+ foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+ if (isset(self::$installedByVendor[$vendorDir])) {
+ $installed[] = self::$installedByVendor[$vendorDir];
+ } elseif (is_file($vendorDir.'/composer/installed.php')) {
+ $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+ if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+ self::$installed = $installed[count($installed) - 1];
+ }
+ }
+ }
+ }
+
+ if (null === self::$installed) {
+ // only require the installed.php file if this file is loaded from its dumped location,
+ // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+ if (substr(__DIR__, -8, 1) !== 'C') {
+ self::$installed = require __DIR__ . '/installed.php';
+ } else {
+ self::$installed = array();
+ }
+ }
+ $installed[] = self::$installed;
+
+ return $installed;
+ }
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/LICENSE b/libraries/lifterlms-cli/vendor/composer/LICENSE
new file mode 100644
index 0000000000..f27399a042
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/LICENSE
@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_classmap.php b/libraries/lifterlms-cli/vendor/composer/autoload_classmap.php
new file mode 100644
index 0000000000..b26f1b13b1
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_classmap.php
@@ -0,0 +1,10 @@
+ $vendorDir . '/composer/InstalledVersions.php',
+);
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_namespaces.php b/libraries/lifterlms-cli/vendor/composer/autoload_namespaces.php
new file mode 100644
index 0000000000..b7fc0125db
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,9 @@
+ array($baseDir . '/src'),
+);
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_real.php b/libraries/lifterlms-cli/vendor/composer/autoload_real.php
new file mode 100644
index 0000000000..78fa4409f3
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_real.php
@@ -0,0 +1,57 @@
+= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+ if ($useStaticLoader) {
+ require __DIR__ . '/autoload_static.php';
+
+ call_user_func(\Composer\Autoload\ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::getInitializer($loader));
+ } else {
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+ }
+
+ $loader->register(true);
+
+ return $loader;
+ }
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/autoload_static.php b/libraries/lifterlms-cli/vendor/composer/autoload_static.php
new file mode 100644
index 0000000000..2a3b404912
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/autoload_static.php
@@ -0,0 +1,36 @@
+
+ array (
+ 'LifterLMS\\CLI\\' => 14,
+ ),
+ );
+
+ public static $prefixDirsPsr4 = array (
+ 'LifterLMS\\CLI\\' =>
+ array (
+ 0 => __DIR__ . '/../..' . '/src',
+ ),
+ );
+
+ public static $classMap = array (
+ 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ );
+
+ public static function getInitializer(ClassLoader $loader)
+ {
+ return \Closure::bind(function () use ($loader) {
+ $loader->prefixLengthsPsr4 = ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::$prefixLengthsPsr4;
+ $loader->prefixDirsPsr4 = ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::$prefixDirsPsr4;
+ $loader->classMap = ComposerStaticInitaa475372d1afb7f112bf50e9b8859e3a::$classMap;
+
+ }, null, ClassLoader::class);
+ }
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/installed.json b/libraries/lifterlms-cli/vendor/composer/installed.json
new file mode 100644
index 0000000000..f20a6c47c6
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/installed.json
@@ -0,0 +1,5 @@
+{
+ "packages": [],
+ "dev": false,
+ "dev-package-names": []
+}
diff --git a/libraries/lifterlms-cli/vendor/composer/installed.php b/libraries/lifterlms-cli/vendor/composer/installed.php
new file mode 100644
index 0000000000..252bd7959e
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/installed.php
@@ -0,0 +1,23 @@
+ array(
+ 'pretty_version' => 'dev-trunk',
+ 'version' => 'dev-trunk',
+ 'type' => 'wordpress-plugin',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'reference' => '82418fb524fe978ee668d33ff54bfd5d6e125cb8',
+ 'name' => 'lifterlms/lifterlms-cli',
+ 'dev' => false,
+ ),
+ 'versions' => array(
+ 'lifterlms/lifterlms-cli' => array(
+ 'pretty_version' => 'dev-trunk',
+ 'version' => 'dev-trunk',
+ 'type' => 'wordpress-plugin',
+ 'install_path' => __DIR__ . '/../../',
+ 'aliases' => array(),
+ 'reference' => '82418fb524fe978ee668d33ff54bfd5d6e125cb8',
+ 'dev_requirement' => false,
+ ),
+ ),
+);
diff --git a/libraries/lifterlms-cli/vendor/composer/platform_check.php b/libraries/lifterlms-cli/vendor/composer/platform_check.php
new file mode 100644
index 0000000000..92370c5a0c
--- /dev/null
+++ b/libraries/lifterlms-cli/vendor/composer/platform_check.php
@@ -0,0 +1,26 @@
+= 70300)) {
+ $issues[] = 'Your Composer dependencies require a PHP version ">= 7.3.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+ if (!headers_sent()) {
+ header('HTTP/1.1 500 Internal Server Error');
+ }
+ if (!ini_get('display_errors')) {
+ if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+ fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+ } elseif (!headers_sent()) {
+ echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+ }
+ }
+ trigger_error(
+ 'Composer detected issues in your platform: ' . implode(' ', $issues),
+ E_USER_ERROR
+ );
+}
diff --git a/libraries/lifterlms-helper/CHANGELOG.md b/libraries/lifterlms-helper/CHANGELOG.md
new file mode 100644
index 0000000000..a8b8d38899
--- /dev/null
+++ b/libraries/lifterlms-helper/CHANGELOG.md
@@ -0,0 +1,251 @@
+LifterLMS Helper Changelog
+==========================
+
+v3.5.1 - 2024-04-16
+-------------------
+
+##### New Features
+
++ Obfuscates license keys. [#47](https://github.com/gocodebox/lifterlms-helper#47)
+
+
+v3.5.0 - 2023-02-28
+-------------------
+
+##### Updates and Enhancements
+
++ Updated the appearance of the license dropdown.
+
+##### Bug Fixes
+
++ Fixed incorrect HTML code for single Add On displayed on the LifterLMS > Add-ons & more screen.
+
+##### Developer Notes
+
++ Added new paramater `$force`, `false` by default, to the static method `LLMS_Helper_Keys::activate_keys()`. It'll allow to force a remote call instead of using ccached results.
+
+##### Performance Improvements
+
++ Cache results from the activate keys calls to the LifterLMS license API. This prevents issues where sites are pinging the license server too often, specifically if they are "cloned" sites.
+
+
+v3.4.2 - 2022-04-01
+-------------------
+
+##### Bug Fixes
+
++ Fixed an issue where adding new license keys with an end-of-line symbol after the last key would result in an invalid license key error.
++ Fixed an issue that caused PHP warnings in the "Plugins -> Add New" page because the `plugin` property was missing. [#36](https://github.com/gocodebox/lifterlms-helper/issues/36)
+
+
+v3.4.1 - 2021-08-17
+-------------------
+
++ Fixed undefined index error encountered when programmatically deactivating a key that was not previously activated on the site.
+
+
+v3.4.0 - 2021-08-04
+-------------------
+
+##### Localization updates
+
++ Only runs localization functions when loaded as an independent plugin.
++ Replace the textdoman 'lifterlms-helper' with 'lifterlms'.
+
+##### Updates
+
++ Use `llms_helper()` in favor of deprecated `LLMS_Helper()` in various locations.
+
+##### Bugfix
+
++ Don't attempt to run migrations from versions less than 3.0.0 during first run when loaded as a library.
+
+
+v3.3.1 - 2021-07-26
+-------------------
+
++ Load `llms_helper()->upgrader` WP_CLI context in preparation for forthcoming the `lifterlms-cli`.
+
+
+v3.3.0 - 2021-06-14
+-------------------
+
++ This plugin is now included by default via the LifterLMS core in versions 5.0+. Installing this plugin directly will use the plugin version instead of the version included with the core. Direct installation is likely only required for development purposes when using LifterLMS 5.0+.
++ The main function `llms_helper()` is declared conditionally when the class `LifterLMS_Helper` is not yet declared.
++ Added a constant `LLMS_HELPER_DISABLE` which allows disabling of the plugin.
++ Distribution release zips now include a `composer.json` file to allow for installation via composer.
+
+
+v3.2.1 - 2021-06-03
+-------------------
+
+##### Updates
+
++ Flush cached update and add-on data when adding or removing license keys and when changing channel subscription for a package.
++ Enable updating to beta versions of packages that don't require a license when no license is present.
+
+
+v3.2.0 - 2020-12-02
+-------------------
+
+##### Updates
+
++ Moved the class `LifterLMS_Helper` class to its own file from `lifterlms-helper.php`.
++ Use `self::$instance` in favor of `self::$_instance`.
++ Use `llms()` in favor of deprecated `LLMS()`.
++ Use `llms_filter_input()` to access `$_POST` data in various places.
++ Use strict comparison for `in_array()`.
+
+##### Bug fixes
+
++ Fixed usage of incorrect textdomain in various places.
+
+##### Deprecations
+
++ Replaced usage of protected class property `$instance` in favor of `$_instance` in various singleton classes.
++ Function `LLMS_Helper()` is deprecated in favor of `llms_helper()`.
++ File `includes/model-llms-helper-add-on.php` is deprecated, use `includes/models/class-llms-helper-add-on.php` instead.
+
+
+v3.1.0 - 2020-05-22
+-------------------
+
++ Load changelogs from the make.lifterlms.com release notes archive in favor of from static html files.
++ Remove reliance on `file_get_contents()` causing errors on servers without access to the function.
+
+
+v3.0.2 - 2018-08-29
+-------------------
+
++ Fixed fatal errors encountered as a result of failed API calls
++ Fixed broken links output on the plugins update screen when an add-on is unlicensed and has an update available
++ Fixed issue causing non-beta versions of the LifterLMS core to be served from LifterLMS.com instead of from WordPress.org
+
+
+v3.0.1 - 2018-08-02
+-------------------
+
++ Fixed an issue causing key migration to run on the frontend resulting in a fatal error related to missing admin-only functions
++ Fixed an issue causing multiple submitted keys to not work properly on certain environments
++ Fixed issue causing installation script to make an activation API call even when no keys exist
++ Improved installation script message to only display a migration message when keys are actually migrated
+
+
+v3.0.0 - 2018-08-01
+-------------------
+
++ **This is nearly a complete rewrite of the codebase. Things have moved but no features have been removed.**
++ Requires LifterLMS version 3.22.0 or later
++ License key activation is now on a per-site basis as opposed to a per product basis. This means that if you have a license key for a bundle you don't have to enter the key for each add-on, you enter the key only once and it will activate ALL the add-ons.
++ The "Licenses" tab has been removed and your add-ons and licenses are now managed via LifterLMS -> Add-ons & More
++ A migration script exists to move license keys from previous versions of the helper to this version. After upgrading check LifterLMS -> Add-ons & More to ensure your keys were successfully migrated.
++ You can now install add-ons through the this plugin without having to download and install them manually. Enter your license key(s) and select the add-ons you wish to install to have them installed automatically. You can bulk install as well.
++ You can now subscribe to beta channels of LifterLMS and any LifterLMS add-ons. Visit the LifterLMS -> Status -> Betas screen to subscribe to betas. Always use betas at your own risk, by nature they're unstable!
++ Uses the LifterLMS.com v3 REST api for all API calls
++ Added RTL language support
++ Added i18n support
++ Removed and replaced various functions
++ Fixes many bugs and almost certainly introduces some new ones
+
+
+v2.5.1 - 2017-11-08
+-------------------
+
++ Fix issue causing false activations which cannot be deactivated due to blank activation keys
+
+
+v2.5.0 - 2017-07-18
+-------------------
+
++ Allow add-ons to be bulk deactivated
++ Integrates with LifterLMS site clone detection in order to automatically activate plugins on your new URL when cloning to staging / production.
++ Following clone detection if activation fails the plugin will no longer show the add-ons as activated (since they're not activated on the new URL)
++ Minor admin-panel performance improvements
++ Now uses minified JS and CSS assets
++ Now fully translateable!
+
+
+v2.4.3 - 2017-02-09
+-------------------
+
++ Handle undefined errors during post plugin install from zip file
+
+
+v2.4.2 - 2017-01-20
+-------------------
+
++ Handle failed api calls gracefully
+
+
+v2.4.1 - 2016-12-30
+-------------------
+
++ Cache add-on list prior to filtering
+
+
+v2.4.0 - 2016-12-20
+-------------------
+
++ Added a unified Helper sceen accessible via LifterLMS -> Settings -> Helper
++ Activate multiple addons simultaneously via one API call
++ Site deactivation now deactivates from remote activation server in addition to local deactivation
++ Upgraded database key handling prevents accidental duplicate activation attempts
++ Fixed several undefined index warnings
++ Normalized option fields keys
+
+
+v2.3.1 - 2016-10-12
+-------------------
+
++ Fixes issue with theme upgrade post install not working resulting in themes existing in the wrong directory after an upgrade
+
+
+v2.3.0 - 2016-10-10
+-------------------
+
++ Significantly upgrades the speed of version checks. Previously checked each LifterLMS Add-on separately, now makes one API call to retreive versions of all installed LifterLMS Add-ons.
++ Adds support for the Universe Bundle which is one key associated with multiple products
+
+
+v2.2.0 - 2016-07-06
+-------------------
+
++ After updates, clear cached update data so the upgrade doesn't still appear as pending
++ After changing license keys, clear cahced data so the next upgrade attempt will not fail again (unless it's still supposed to fail)
++ After updating the currently active theme, correctly reactivate the theme
+
+
+v2.1.0 - 2016-06-14
+-------------------
+
++ Prevent hijacking the LifterLMS Core lightbox data when attempting to view update details on the plugin update screen.
++ Added [Parsedown](https://github.com/erusev/parsedown) to render Markdown style changelogs into HTML when viewing extension changelogs in the the lightbox on plugin update screens.
+
+
+v2.0.0 - 2016-04-08
+-------------------
+
++ Includes theme-related APIs for serving updates for themes
++ Better error reporting and handling
++ A few very exciting performance enhancements
+
+
+v1.0.2 - 2016-03-07
+-------------------
+
++ Fixed an undefined variable which produced a php warning when `WP_DEBUG` was enabled
++ Resolved an issue that caused the LifterLMS Helper to hijack the "details" and related plugin screens that display inside a lightbox in the plugins admin page.
++ Added a .editorconfig file
++ Added changelog file
+
+
+v1.0.1 - 2016-02-11
+-------------------
+
++ Actual public release
+
+
+v1.0.0 - 2016-02-10
+-------------------
+
++ Initial public release
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper-rtl.css b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.css
new file mode 100644
index 0000000000..eaf51ceb9a
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.css
@@ -0,0 +1,91 @@
+.wrap.lifterlms-addons .llms-licenses {
+ display: block;
+ position: relative;
+ vertical-align: middle;
+ z-index: 1;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-button-primary {
+ width: 100%;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-license-header {
+ margin: 0 0 5px 0;
+}
+.wrap.lifterlms-addons .llms-licenses label {
+ display: block;
+ margin-top: 20px;
+}
+.wrap.lifterlms-addons .llms-licenses label:first-child {
+ margin-top: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys {
+ list-style-type: none;
+ margin: 15px 0;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li {
+ margin: 0 0 10px;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked + span {
+ color: #e5554e;
+ font-style: italic;
+}
+.wrap.lifterlms-addons .llms-licenses .fa-chevron-down {
+ margin-right: 10px;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field {
+ background: #fff;
+ border: 1px solid #ddd;
+ display: none;
+ margin-right: 0;
+ position: absolute;
+ right: -1px;
+ padding: 20px;
+ top: calc( 100% - 2px );
+ width: 90%;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea {
+ display: inline-block;
+ height: 86px;
+ font-size: 14px;
+ font-family: monospace;
+ line-height: 1.8;
+ margin: 5px 0;
+ padding: 5px 10px;
+ resize: none;
+ vertical-align: middle;
+ width: 100%;
+}
+@media only screen and (min-width: 782px) {
+ .wrap.lifterlms-addons .llms-licenses {
+ display: inline-block;
+ margin-right: 20px;
+ }
+ .wrap.lifterlms-addons .llms-licenses .llms-button-primary {
+ width: auto;
+ }
+ .wrap.lifterlms-addons .llms-licenses .llms-key-field {
+ width: 340px;
+ }
+}
+
+@media only screen and (min-width: 800px) {
+ .llms-status--betas .llms-beta-main {
+ display: flex;
+ }
+ .llms-status--betas .llms-beta-table {
+ flex: 2;
+ }
+ .llms-status--betas .llms-beta-aside {
+ flex: 1;
+ margin-left: 20px;
+ }
+}
+.llms-status--betas .llms-beta-aside {
+ background: #fef7f7;
+ border: 1px solid #e5554e;
+ padding: 20px;
+}
+.llms-status--betas .llms-beta-aside h1 {
+ padding-top: 0;
+}
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper-rtl.min.css b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.min.css
new file mode 100644
index 0000000000..d860570f1f
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper-rtl.min.css
@@ -0,0 +1 @@
+.wrap.lifterlms-addons .llms-licenses{display:block;position:relative;vertical-align:middle;z-index:1}.wrap.lifterlms-addons .llms-licenses .llms-button-primary{width:100%}.wrap.lifterlms-addons .llms-licenses .llms-license-header{margin:0 0 5px 0}.wrap.lifterlms-addons .llms-licenses label{display:block;margin-top:20px}.wrap.lifterlms-addons .llms-licenses label:first-child{margin-top:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys{list-style-type:none;margin:15px 0;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li{margin:0 0 10px;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked+span{color:#e5554e;font-style:italic}.wrap.lifterlms-addons .llms-licenses .fa-chevron-down{margin-right:10px}.wrap.lifterlms-addons .llms-licenses .llms-key-field{background:#fff;border:1px solid #ddd;display:none;margin-right:0;position:absolute;right:-1px;padding:20px;top:calc( 100% - 2px );width:90%}.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea{display:inline-block;height:86px;font-size:14px;font-family:monospace;line-height:1.8;margin:5px 0;padding:5px 10px;resize:none;vertical-align:middle;width:100%}@media only screen and (min-width: 782px){.wrap.lifterlms-addons .llms-licenses{display:inline-block;margin-right:20px}.wrap.lifterlms-addons .llms-licenses .llms-button-primary{width:auto}.wrap.lifterlms-addons .llms-licenses .llms-key-field{width:340px}}@media only screen and (min-width: 800px){.llms-status--betas .llms-beta-main{display:flex}.llms-status--betas .llms-beta-table{flex:2}.llms-status--betas .llms-beta-aside{flex:1;margin-left:20px}}.llms-status--betas .llms-beta-aside{background:#fef7f7;border:1px solid #e5554e;padding:20px}.llms-status--betas .llms-beta-aside h1{padding-top:0}
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.css b/libraries/lifterlms-helper/assets/css/llms-helper.css
new file mode 100644
index 0000000000..577497fc75
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.css
@@ -0,0 +1,93 @@
+.wrap.lifterlms-addons .llms-licenses {
+ display: block;
+ position: relative;
+ vertical-align: middle;
+ z-index: 1;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-button-primary {
+ width: 100%;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-license-header {
+ margin: 0 0 5px 0;
+}
+.wrap.lifterlms-addons .llms-licenses label {
+ display: block;
+ margin-top: 20px;
+}
+.wrap.lifterlms-addons .llms-licenses label:first-child {
+ margin-top: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys {
+ list-style-type: none;
+ margin: 15px 0;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li {
+ margin: 0 0 10px;
+ padding: 0;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked + span {
+ color: #e5554e;
+ font-style: italic;
+}
+.wrap.lifterlms-addons .llms-licenses .fa-chevron-down {
+ margin-left: 10px;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field {
+ background: #fff;
+ border: 1px solid #ddd;
+ display: none;
+ margin-left: 0;
+ position: absolute;
+ left: -1px;
+ padding: 20px;
+ top: calc( 100% - 2px );
+ width: 90%;
+}
+.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea {
+ display: inline-block;
+ height: 86px;
+ font-size: 14px;
+ font-family: monospace;
+ line-height: 1.8;
+ margin: 5px 0;
+ padding: 5px 10px;
+ resize: none;
+ vertical-align: middle;
+ width: 100%;
+}
+@media only screen and (min-width: 782px) {
+ .wrap.lifterlms-addons .llms-licenses {
+ display: inline-block;
+ margin-left: 20px;
+ }
+ .wrap.lifterlms-addons .llms-licenses .llms-button-primary {
+ width: auto;
+ }
+ .wrap.lifterlms-addons .llms-licenses .llms-key-field {
+ width: 340px;
+ }
+}
+
+@media only screen and (min-width: 800px) {
+ .llms-status--betas .llms-beta-main {
+ display: flex;
+ }
+ .llms-status--betas .llms-beta-table {
+ flex: 2;
+ }
+ .llms-status--betas .llms-beta-aside {
+ flex: 1;
+ margin-right: 20px;
+ }
+}
+.llms-status--betas .llms-beta-aside {
+ background: #fef7f7;
+ border: 1px solid #e5554e;
+ padding: 20px;
+}
+.llms-status--betas .llms-beta-aside h1 {
+ padding-top: 0;
+}
+
+/*# sourceMappingURL=llms-helper.css.map */
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.css.map b/libraries/lifterlms-helper/assets/css/llms-helper.css.map
new file mode 100644
index 0000000000..13dfa0f031
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../scss/llms-helper.scss"],"names":[],"mappings":"AAKC;EACC;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;;AAGD;EACC;EACA;;AACA;EAAgB;;AAGjB;EACC;EACA;EACA;;AACA;EACC;EACA;;AACA;EACC,OAjCO;EAkCP;;AAKH;EAAmB;;AAEnB;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAOH;EACC;IACC;IACA;;EAEA;IACC;;EAGD;IACC;;;;AASH;EAEC;IACC;;EAED;IACC;;EAED;IACC;IACA;;;AAKF;EACC;EACA;EACA;;AAEA;EACC","file":"llms-helper.css"}
\ No newline at end of file
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.min.css b/libraries/lifterlms-helper/assets/css/llms-helper.min.css
new file mode 100644
index 0000000000..7233ede9bd
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.min.css
@@ -0,0 +1 @@
+.wrap.lifterlms-addons .llms-licenses{display:block;position:relative;vertical-align:middle;z-index:1}.wrap.lifterlms-addons .llms-licenses .llms-button-primary{width:100%}.wrap.lifterlms-addons .llms-licenses .llms-license-header{margin:0 0 5px 0}.wrap.lifterlms-addons .llms-licenses label{display:block;margin-top:20px}.wrap.lifterlms-addons .llms-licenses label:first-child{margin-top:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys{list-style-type:none;margin:15px 0;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li{margin:0 0 10px;padding:0}.wrap.lifterlms-addons .llms-licenses .llms-active-keys li input:checked+span{color:#e5554e;font-style:italic}.wrap.lifterlms-addons .llms-licenses .fa-chevron-down{margin-left:10px}.wrap.lifterlms-addons .llms-licenses .llms-key-field{background:#fff;border:1px solid #ddd;display:none;margin-left:0;position:absolute;left:-1px;padding:20px;top:calc( 100% - 2px );width:90%}.wrap.lifterlms-addons .llms-licenses .llms-key-field textarea{display:inline-block;height:86px;font-size:14px;font-family:monospace;line-height:1.8;margin:5px 0;padding:5px 10px;resize:none;vertical-align:middle;width:100%}@media only screen and (min-width: 782px){.wrap.lifterlms-addons .llms-licenses{display:inline-block;margin-left:20px}.wrap.lifterlms-addons .llms-licenses .llms-button-primary{width:auto}.wrap.lifterlms-addons .llms-licenses .llms-key-field{width:340px}}@media only screen and (min-width: 800px){.llms-status--betas .llms-beta-main{display:flex}.llms-status--betas .llms-beta-table{flex:2}.llms-status--betas .llms-beta-aside{flex:1;margin-right:20px}}.llms-status--betas .llms-beta-aside{background:#fef7f7;border:1px solid #e5554e;padding:20px}.llms-status--betas .llms-beta-aside h1{padding-top:0}/*# sourceMappingURL=llms-helper.min.css.map */
diff --git a/libraries/lifterlms-helper/assets/css/llms-helper.min.css.map b/libraries/lifterlms-helper/assets/css/llms-helper.min.css.map
new file mode 100644
index 0000000000..c0ce142341
--- /dev/null
+++ b/libraries/lifterlms-helper/assets/css/llms-helper.min.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["../scss/llms-helper.scss"],"names":[],"mappings":"AAKC,sCACC,cACA,kBACA,sBACA,UAEA,2DACC,WAGD,2DACC,iBAGD,4CACC,cACA,gBACA,qEAGD,wDACC,qBACA,cACA,UACA,2DACC,gBACA,UACA,8EACC,MAjCO,QAkCP,kBAKH,wEAEA,sDACC,gBACA,sBACA,aACA,cACA,kBACA,UACA,aACA,uBACA,UAEA,+DACC,qBACA,YACA,eACA,sBACA,gBACA,aACA,iBACA,YACA,sBACA,WAOH,0CACC,sCACC,qBACA,iBAEA,2DACC,WAGD,sDACC,aASH,0CAEC,oCACC,aAED,qCACC,OAED,qCACC,OACA,mBAKF,qCACC,mBACA,yBACA,aAEA,wCACC","file":"llms-helper.min.css"}
\ No newline at end of file
diff --git a/libraries/lifterlms-helper/class-lifterlms-helper.php b/libraries/lifterlms-helper/class-lifterlms-helper.php
new file mode 100644
index 0000000000..09703ee7b3
--- /dev/null
+++ b/libraries/lifterlms-helper/class-lifterlms-helper.php
@@ -0,0 +1,210 @@
+upgrader().
+ *
+ * @var null|LLMS_Helper_Upgrader
+ */
+ private $upgrader = null;
+
+ /**
+ * Retrieve the main Instance of LifterLMS_Helper
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Use `self::$instance` in favor of `self::$_instance`.
+ *
+ * @return LifterLMS_Helper
+ */
+ public static function instance() {
+ if ( is_null( self::$instance ) ) {
+ self::$instance = new self();
+ }
+ return self::$instance;
+ }
+
+ /**
+ * Constructor, get things started!
+ *
+ * @since 1.0.0
+ * @since 3.4.0 Only localize when loaded as an independent plugin.
+ *
+ * @return void
+ */
+ private function __construct() {
+
+ // Define class constants.
+ $this->define_constants();
+
+ /**
+ * When loaded as a library included by the LifterLMS core localization is handled by the LifterLMS core.
+ *
+ * When the plugin is loaded by itself as a plugin, we must localize it independently.
+ */
+ if ( ! defined( 'LLMS_HELPER_LIB' ) || ! LLMS_HELPER_LIB ) {
+ add_action( 'init', array( $this, 'load_textdomain' ), 0 );
+ }
+
+ add_action( 'plugins_loaded', array( $this, 'init' ) );
+ }
+
+ /**
+ * Inititalize the Plugin
+ *
+ * @since 1.0.0
+ * @since 3.0.0 Unknown.
+ * @since 3.2.0 Use `llms()` in favor of deprecated `LLMS()`.
+ * @since 3.3.1 Load the upgrader instance in WP_CLI context.
+ *
+ * @return void
+ */
+ public function init() {
+
+ // Only load if we have the minimum LifterLMS version installed & activated.
+ if ( function_exists( 'llms' ) && version_compare( '3.22.0', llms()->version, '<=' ) ) {
+
+ $this->includes();
+ $this->crons();
+
+ if ( is_admin() || ( defined( 'WP_CLI' ) && WP_CLI ) ) {
+ $this->upgrader = LLMS_Helper_Upgrader::instance();
+ }
+ }
+ }
+
+ /**
+ * Schedule and handle cron functions
+ *
+ * @since 3.0.0
+ *
+ * @return void
+ */
+ private function crons() {
+
+ add_action( 'llms_helper_check_license_keys', array( 'LLMS_Helper_Keys', 'check_keys' ) );
+
+ if ( ! wp_next_scheduled( 'llms_helper_check_license_keys' ) ) {
+ wp_schedule_event( time(), 'daily', 'llms_helper_check_license_keys' );
+ }
+ }
+
+ /**
+ * Define constants for plugin
+ *
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ private function define_constants() {
+
+ if ( ! defined( 'LLMS_HELPER_VERSION' ) ) {
+ define( 'LLMS_HELPER_VERSION', $this->version );
+ }
+ }
+
+ /**
+ * Include all clasess required by the plugin
+ *
+ * @since 1.0.0
+ * @since 3.0.0 Include new files.
+ *
+ * @return void
+ */
+ private function includes() {
+
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-admin-add-ons.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-assets.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-betas.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-cloned.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-install.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-keys.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-options.php';
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/class-llms-helper-upgrader.php';
+
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/models/class-llms-helper-add-on.php';
+
+ require_once LLMS_HELPER_PLUGIN_DIR . 'includes/functions-llms-helper.php';
+ }
+
+ /**
+ * Load l10n files.
+ *
+ * This method is only used when the plugin is loaded as a standalone plugin (for development purposes),
+ * otherwise (when loaded as a library from within the LifterLMS core plugin) the localization
+ * strings are included into the LifterLMS Core plugin's po/mo files and are localized by the LifterLMS
+ * core plugin.
+ *
+ * Files can be found in the following order (The first loaded file takes priority):
+ * 1. WP_LANG_DIR/lifterlms/lifterlms-rest-LOCALE.mo
+ * 2. WP_LANG_DIR/plugins/lifterlms-rest-LOCALE.mo
+ * 3. WP_CONTENT_DIR/plugins/lifterlms-rest/i18n/lifterlms-rest-LOCALE.mo
+ *
+ * Note: The function `load_plugin_textdomain()` is not used because the same textdomain as the LifterLMS core
+ * is used for this plugin but the file is named `lifterlms-rest` in order to allow using a separate language
+ * file for each codebase.
+ *
+ * @since 2.5.0
+ * @since 3.4.0 Updated to the core textdomain.
+ *
+ * @return void
+ */
+ public function load_textdomain() {
+
+ // Load locale.
+ $locale = apply_filters( 'plugin_locale', get_locale(), 'lifterlms' );
+
+ // Load from the LifterLMS "safe" directory if it exists.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/lifterlms/lifterlms-helper-' . $locale . '.mo' );
+
+ // Load from the default plugins language file directory.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/plugins/lifterlms-helper-' . $locale . '.mo' );
+
+ // Load from the plugin's language file directory.
+ load_textdomain( 'lifterlms', LLMS_HELPER_PLUGIN_DIR . '/i18n/lifterlms-helper-' . $locale . '.mo' );
+ }
+
+ /**
+ * Return the singleton instance of the LLMS_Helper_Upgader
+ *
+ * @since 3.0.0
+ *
+ * @return LLMS_Helper_Upgrader
+ */
+ public function upgrader() {
+ return $this->upgrader;
+ }
+}
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-admin-add-ons.php b/libraries/lifterlms-helper/includes/class-llms-helper-admin-add-ons.php
new file mode 100644
index 0000000000..8cf809b37b
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-admin-add-ons.php
@@ -0,0 +1,407 @@
+has_keys() to retrieve the value.
+ *
+ * @var bool
+ */
+ private $has_keys = null;
+
+ /**
+ * Constructor
+ *
+ * @since 3.0.0
+ */
+ public function __construct() {
+
+ add_action( 'admin_init', array( $this, 'handle_actions' ) );
+
+ // Output navigation items.
+ add_action( 'lifterlms_before_addons_nav', array( $this, 'output_navigation_items' ) );
+
+ // Output the license manager interface button / dropdown.
+ add_action( 'llms_addons_page_after_title', array( $this, 'output_license_manager' ) );
+
+ // Filter current section default.
+ add_filter( 'llms_admin_add_ons_get_current_section', array( $this, 'filter_get_current_section' ) );
+
+ // Filter the content display for a section.
+ add_filter( 'llms_admin_add_ons_get_current_section_default_content', array( $this, 'filter_get_current_section_content' ), 10, 2 );
+
+ // Add install & update actions to the list of available management actions powered by the bulk actions functions in core.
+ add_filter( 'llms_admin_add_ons_manage_actions', array( $this, 'filter_manage_actions' ) );
+
+ // Output html for helper-powered actions (install & update).
+ add_action( 'llms_add_ons_single_item_actions', array( $this, 'output_single_install_action' ), 5, 2 );
+ add_action( 'llms_add_ons_single_item_after_actions', array( $this, 'output_single_update_action' ), 5, 2 );
+
+ add_filter( 'llms_admin_addon_features_exclude_ids', array( $this, 'filter_feature_exclude_ids' ) );
+ }
+
+ /**
+ * Change the default section from "All" to "Mine" but only if license keys have been saved
+ *
+ * @since 3.0.0
+ *
+ * @param string $section Section slug.
+ * @return string
+ */
+ public function filter_get_current_section( $section ) {
+
+ if ( 'all' === $section && empty( $_GET['section'] ) && $this->has_keys() ) {
+ return 'mine';
+ }
+
+ return $section;
+ }
+
+ /**
+ * Add "mine" tab content
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ *
+ * @param array $content Default items to display.
+ * @param string $section Current tab slug.
+ * @return array
+ */
+ public function filter_get_current_section_content( $content, $section ) {
+
+ if ( 'mine' === $section ) {
+ $mine = llms_helper_get_available_add_ons();
+ $addons = llms_get_add_ons();
+ if ( ! is_wp_error( $addons ) && isset( $addons['items'] ) ) {
+ foreach ( $addons['items'] as $item ) {
+ if ( in_array( $item['id'], $mine ) ) {
+ $content[] = $item;
+ }
+ }
+ }
+ }
+
+ return $content;
+ }
+
+ /**
+ * Exclude IDs for all add-ons that are currently available on the site
+ *
+ * @since 3.0.0
+ *
+ * @param array $ids Existing product ids to exclude.
+ * @return array
+ */
+ public function filter_feature_exclude_ids( $ids ) {
+ return array_unique( array_merge( $ids, llms_helper_get_available_add_ons( false ) ) );
+ }
+
+ /**
+ * Add installatino & update actions to the list of available management actions
+ *
+ * @since 3.0.0
+ *
+ * @param array $actions List of available actions, the action should correspond to a method in the LLMS_Helper_Add_On class.
+ * @return array
+ */
+ public function filter_manage_actions( $actions ) {
+ return array_merge( array( 'install', 'update' ), $actions );
+ }
+
+ /**
+ * Handle form submission actions
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Let the LifterLMS Core output flashed notices
+ * @since 3.2.1 Flush cached addon and package update data when adding or removing keys.
+ *
+ * @return void
+ */
+ public function handle_actions() {
+
+ // License key addition & removal.
+ if ( ! llms_verify_nonce( '_llms_manage_keys_nonce', 'llms_manage_keys' ) ) {
+ return;
+ }
+
+ $flush = false;
+
+ if ( isset( $_POST['llms_activate_keys'] ) && ! empty( $_POST['llms_add_keys'] ) ) {
+
+ $flush = true;
+ $this->handle_activations();
+
+ } elseif ( isset( $_POST['llms_deactivate_keys'] ) && ! empty( $_POST['llms_remove_keys'] ) ) {
+
+ $flush = true;
+ $this->handle_deactivations();
+
+ }
+
+ if ( $flush ) {
+ llms_helper_flush_cache();
+ }
+ }
+
+ /**
+ * Activate license keys with LifterLMS.com api
+ *
+ * Output errors / successes & saves successful keys to the db.
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Don't access $_POST directly.
+ * @since 3.4.0 Use core textdomain.
+ * @since 3.5.0 Passing force parameter to activate_keys() method.
+ *
+ * @return void
+ */
+ private function handle_activations() {
+
+ $res = LLMS_Helper_Keys::activate_keys( llms_filter_input( INPUT_POST, 'llms_add_keys', FILTER_SANITIZE_STRING ), true );
+
+ if ( is_wp_error( $res ) ) {
+ LLMS_Admin_Notices::flash_notice( $res->get_error_message(), 'error' );
+ return;
+ }
+
+ $data = $res['data'];
+ if ( isset( $data['errors'] ) ) {
+ foreach ( $data['errors'] as $error ) {
+ LLMS_Admin_Notices::flash_notice( make_clickable( $error ), 'error' );
+ }
+ }
+
+ if ( isset( $data['activations'] ) ) {
+ foreach ( $data['activations'] as $activation ) {
+ LLMS_Helper_Keys::add_license_key( $activation );
+ // Translators: %s = License key.
+ LLMS_Admin_Notices::flash_notice( sprintf( __( '"%s" has been saved!', 'lifterlms' ), $activation['license_key'] ), 'success' );
+ }
+ }
+ }
+
+ /**
+ * Deactivate license keys with LifterLMS.com api
+ *
+ * Output errors / successes & removes keys from the db.
+ *
+ * @since 3.0.0
+ * @since 3.2.0 Don't access $_POST directly.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return void
+ */
+ private function handle_deactivations() {
+
+ $obfuscated_keys = llms_filter_input( INPUT_POST, 'llms_remove_keys', FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY );
+ $keys = array();
+
+ // De-obfuscate the keys before sending to deactivation server or removing from the site.
+ $my_keys = llms_helper_options()->get_license_keys();
+ foreach ( $my_keys as $key ) {
+ foreach ( $obfuscated_keys as $obfuscated_key ) {
+ if ( llms_obfuscate_license_key( $key['license_key'] ) === $obfuscated_key ) {
+ $keys[] = $key['license_key'];
+ }
+ }
+ }
+
+ $res = LLMS_Helper_Keys::deactivate_keys( $keys );
+
+ if ( is_wp_error( $res ) ) {
+ LLMS_Admin_Notices::flash_notice( $res->get_error_message(), 'error' );
+ return;
+ }
+
+ foreach ( $keys as $key ) {
+ LLMS_Helper_Keys::remove_license_key( $key );
+ /* Translators: %s = License Key */
+ LLMS_Admin_Notices::flash_notice( sprintf( __( 'License key "%s" was removed from this site.', 'lifterlms' ), llms_obfuscate_license_key( $key ) ), 'info' );
+ }
+
+ if ( isset( $data['errors'] ) ) {
+ foreach ( $data['errors'] as $error ) {
+ LLMS_Admin_Notices::flash_notice( make_clickable( $error ), 'error' );
+ }
+ }
+ }
+
+ /**
+ * Determine if the current site has active license keys
+ *
+ * @since 3.0.0
+ *
+ * @return bool
+ */
+ public function has_keys() {
+
+ if ( is_null( $this->has_keys ) ) {
+ $this->has_keys = ( count( llms_helper_options()->get_license_keys() ) );
+ }
+
+ return $this->has_keys;
+ }
+
+ /**
+ * Output the HTML for the license manager area
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return void
+ */
+ public function output_license_manager() {
+
+ $my_keys = llms_helper_options()->get_license_keys();
+ if ( $my_keys ) {
+ wp_enqueue_style( 'plugin-install' );
+ wp_enqueue_script( 'plugin-install' );
+ add_thickbox();
+ }
+
+ ?>
+
+
+ is_installable() && ! $addon->is_installed() && ( ! $addon->requires_license() || $addon->is_licensed() ) ) {
+ ?>
+
+
+
+
+
+
+
+
+
+ is_installable() && $addon->is_installed() && ( ! $addon->requires_license() || $addon->is_licensed() ) && $addon->has_available_update() ) {
+ ?>
+
+
+
+
+
+
+
+
+
+ has_keys() ) {
+ return;
+ }
+
+ ?>
+
+
+
+ id && isset( $_GET['tab'] ) && 'betas' === $_GET['tab'] ) {
+ $load = true;
+ } elseif ( 'lifterlms_page_llms-add-ons' === $screen->id ) {
+ $load = true;
+ }
+
+ if ( ! $load ) {
+ return;
+ }
+
+ wp_register_style( 'llms-helper', LLMS_HELPER_PLUGIN_URL . 'assets/css/llms-helper' . LLMS_ASSETS_SUFFIX . '.css', array(), LLMS_HELPER_VERSION );
+ wp_enqueue_style( 'llms-helper' );
+
+ wp_style_add_data( 'llms-sl', 'rtl', 'replace' );
+ wp_style_add_data( 'llms-sl', 'suffix', LLMS_ASSETS_SUFFIX );
+ }
+}
+return new LLMS_Helper_Assets();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-betas.php b/libraries/lifterlms-helper/includes/class-llms-helper-betas.php
new file mode 100644
index 0000000000..45765d701d
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-betas.php
@@ -0,0 +1,107 @@
+ $channel ) {
+
+ $addon = llms_get_add_on( $id );
+ if ( 'channel' !== $addon->get_channel_subscription() ) {
+ $addon->subscribe_to_channel( sanitize_text_field( $channel ) );
+ $new_subscription = true;
+ }
+ }
+
+ // When a channel subscription changes also flush caches so we'll get the most recent add-on data immediately and allow upgrading immediately from wp core update screens.
+ if ( $new_subscription ) {
+ llms_helper_flush_cache();
+ }
+
+ return $subs;
+ }
+
+ /**
+ * Output content for the beta testing screen
+ *
+ * @since 3.0.0
+ *
+ * @param string $curr_tab Current status screen tab.
+ * @return void
+ */
+ public function output_tab( $curr_tab ) {
+
+ if ( 'betas' !== $curr_tab ) {
+ return;
+ }
+
+ $addons = llms_helper_get_available_add_ons();
+ array_unshift( $addons, 'lifterlms-com-lifterlms', 'lifterlms-com-lifterlms-helper' );
+ include 'views/beta-testing.php';
+ }
+}
+return new LLMS_Helper_Betas();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-cloned.php b/libraries/lifterlms-helper/includes/class-llms-helper-cloned.php
new file mode 100644
index 0000000000..5d9e4f45c7
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-cloned.php
@@ -0,0 +1,65 @@
+get_license_keys();
+
+ if ( ! $keys ) {
+ return;
+ }
+
+ $res = LLMS_Helper_Keys::activate_keys( array_keys( $keys ) );
+
+ if ( ! is_wp_error( $res ) ) {
+
+ $data = $res['data'];
+ if ( isset( $data['activations'] ) ) {
+ foreach ( $data['activations'] as $activation ) {
+ LLMS_Helper_Keys::add_license_key( $activation );
+ }
+ }
+ }
+ }
+}
+
+return new LLMS_Helper_Cloned();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-install.php b/libraries/lifterlms-helper/includes/class-llms-helper-install.php
new file mode 100644
index 0000000000..9c9f0f7a7b
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-install.php
@@ -0,0 +1,172 @@
+version ) {
+
+ self::install();
+
+ /**
+ * Action run after the helper library is updated.
+ *
+ * @since 3.0.0
+ */
+ do_action( 'llms_helper_updated' );
+
+ }
+ }
+
+ /**
+ * Core install function
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Skip migration when loaded as a library.
+ *
+ * @return void
+ */
+ public static function install() {
+
+ if ( ! is_blog_installed() ) {
+ return;
+ }
+
+ do_action( 'llms_helper_before_install' );
+
+ if ( ( ! defined( 'LLMS_HELPER_LIB' ) || ! LLMS_HELPER_LIB ) && ! get_option( 'llms_helper_version', '' ) ) {
+ self::_migrate_300();
+ }
+
+ self::update_version();
+
+ do_action( 'llms_helper_after_install' );
+ }
+
+ /**
+ * Update the LifterLMS version record to the latest version
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use llms_helper() in favor of deprecated LLMS_Helper().
+ *
+ * @param string $version version number.
+ * @return void
+ */
+ public static function update_version( $version = null ) {
+ delete_option( 'llms_helper_version' );
+ add_option( 'llms_helper_version', is_null( $version ) ? llms_helper()->version : $version );
+ }
+
+ /**
+ * Migrate to version 3.0.0
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return void
+ */
+ private static function _migrate_300() {
+
+ $text = '' . __( 'Welcome to the LifterLMS Helper', 'lifterlms' ) . '
';
+ $text .= '' . __( 'This plugin allows your website to interact with your subscriptions at LifterLMS.com to ensure your add-ons stay up to date.', 'lifterlms' ) . '
';
+ // Translators: %1$s = Opening anchor tag; %2$s = closing anchor tag.
+ $text .= '' . sprintf( __( 'You can activate your add-ons from the %1$sAdd-Ons & More%2$s screen.', 'lifterlms' ), '', ' ' ) . '
';
+
+ $keys = array();
+ $addons = llms_get_add_ons();
+ if ( ! is_wp_error( $addons ) && isset( $addons['items'] ) ) {
+ foreach ( $addons['items'] as $addon ) {
+
+ $addon = llms_get_add_on( $addon );
+
+ if ( ! $addon->is_installable() ) {
+ continue;
+ }
+
+ $option_name = sprintf( '%s_activation_key', $addon->get( 'slug' ) );
+
+ $key = get_option( $option_name );
+ if ( $key ) {
+ $keys[] = get_option( $option_name );
+ }
+
+ delete_option( $option_name );
+ delete_option( sprintf( '%s_update_key', $addon->get( 'slug' ) ) );
+
+ }
+ }
+
+ if ( $keys ) {
+
+ $res = LLMS_Helper_Keys::activate_keys( $keys );
+
+ if ( ! is_wp_error( $res ) ) {
+
+ $data = $res['data'];
+ if ( isset( $data['activations'] ) ) {
+
+ // Translators: %d = Number of keys that have been migrated.
+ $text .= '' . sprintf( _n( '%d license has been automatically migrated from the previous version of the LifterLMS Helper', '%d licenses have been automatically migrated from the previous version of the LifterLMS Helper.', count( $data['activations'] ), 'lifterlms' ), count( $data['activations'] ) ) . ':
';
+
+ foreach ( $data['activations'] as $activation ) {
+ LLMS_Helper_Keys::add_license_key( $activation );
+ $text .= '' . $activation['license_key'] . '
';
+ }
+ }
+ }
+ }
+
+ LLMS_Admin_Notices::flash_notice( $text, 'info' );
+
+ // Clean up legacy options.
+ $remove = array(
+ 'lifterlms_stripe_activation_key',
+ 'lifterlms_paypal_activation_key',
+ 'lifterlms_gravityforms_activation_key',
+ 'lifterlms_mailchimp_activation_key',
+ 'llms_helper_key_migration',
+ );
+
+ foreach ( $remove as $opt ) {
+ delete_option( $opt );
+ }
+ }
+}
+
+LLMS_Helper_Install::init();
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-keys.php b/libraries/lifterlms-helper/includes/class-llms-helper-keys.php
new file mode 100644
index 0000000000..67325a1020
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-keys.php
@@ -0,0 +1,249 @@
+ $keys,
+ 'url' => get_site_url(),
+ );
+
+ // Check for a cached result based on the keys and url input.
+ $cache_hash = md5( wp_json_encode( $data ) );
+ if ( $force ) {
+ // Delete cache if forcing a remote check.
+ delete_site_transient( 'llms_helper_keys_activation_response_' . $cache_hash );
+ } else {
+ // Use the cached result if present.
+ $cached_req_result = get_site_transient( 'llms_helper_keys_activation_response_' . $cache_hash );
+ if ( ! empty( $cached_req_result ) ) {
+ return $cached_req_result;
+ }
+ }
+
+ $req = new LLMS_Dot_Com_API( '/license/activate', $data );
+ set_site_transient( 'llms_helper_keys_activation_response_' . $cache_hash, $req->get_result(), HOUR_IN_SECONDS );
+
+ return $req->get_result();
+ }
+
+ /**
+ * Add a single license key
+ *
+ * @since 3.0.0
+ *
+ * @param string $activation_data Array of activation details from api call.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public static function add_license_key( $activation_data ) {
+
+ $keys = llms_helper_options()->get_license_keys();
+ $keys[ $activation_data['license_key'] ] = array(
+ 'product_id' => $activation_data['id'],
+ 'status' => 1,
+ 'license_key' => $activation_data['license_key'],
+ 'update_key' => $activation_data['update_key'],
+ 'addons' => $activation_data['addons'],
+ );
+
+ return llms_helper_options()->set_license_keys( $keys );
+ }
+
+ /**
+ * Check all saved keys to ensure they're still active
+ *
+ * Outputs warnings if the key has expired or the status has changed remotely.
+ *
+ * Runs on daily cron (`llms_check_license_keys`).
+ *
+ * Only make api calls to check once / week.
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @param bool $force Ignore the once/week setting and force a check.
+ * @return void
+ */
+ public static function check_keys( $force = false ) {
+
+ // Don't trigger during AJAX Requests.
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ return;
+ }
+
+ // Don't proceed if we don't have any keys to check.
+ $keys = llms_helper_options()->get_license_keys();
+ if ( ! $keys ) {
+ return;
+ }
+
+ if ( ! $force ) {
+ // Only check keys once a week.
+ $last_send = llms_helper_options()->get_last_keys_cron_check();
+ if ( $last_send > apply_filters( 'llms_check_license_keys_interval', strtotime( '-1 week' ) ) ) {
+ return;
+ }
+ }
+
+ // Record check time.
+ llms_helper_options()->set_last_keys_cron_check( time() );
+
+ $data = array(
+ 'keys' => array(),
+ 'url' => get_site_url(),
+ );
+
+ foreach ( $keys as $key ) {
+ $data['keys'][ $key['license_key'] ] = $key['update_key'];
+ }
+
+ $req = new LLMS_Dot_Com_API( '/license/status', $data );
+ if ( ! $req->is_error() ) {
+
+ $res = $req->get_result();
+ include_once LLMS_PLUGIN_DIR . 'includes/admin/class.llms.admin.notices.php';
+
+ /* Translators: %s = License Key */
+ $msg = __( 'The license "%s" is no longer valid and was deactivated. Please visit your account dashboard at https://lifterlms.com/my-account for more information.', 'lifterlms' );
+
+ // Output error responses.
+ if ( isset( $res['data']['errors'] ) ) {
+ foreach ( array_keys( $res['data']['errors'] ) as $key ) {
+ self::remove_license_key( $key );
+ LLMS_Admin_Notices::add_notice(
+ 'key_check_' . sanitize_text_field( $key ),
+ make_clickable( sprintf( $msg, $key ) ),
+ array(
+ 'type' => 'error',
+ 'dismiss_for_days' => 0,
+ )
+ );
+ }
+ }
+
+ // Check status of keys, if the status has changed remove it locally.
+ if ( isset( $res['data']['keys'] ) ) {
+ foreach ( $res['data']['keys'] as $key => $data ) {
+
+ if ( $data['status'] ) {
+ continue;
+ }
+
+ self::remove_license_key( $key );
+ LLMS_Admin_Notices::add_notice(
+ 'key_check_' . sanitize_text_field( $key ),
+ make_clickable( sprintf( $msg, $key ) ),
+ array(
+ 'type' => 'error',
+ 'dismiss_for_days' => 0,
+ )
+ );
+
+ }
+ }
+ }
+ }
+
+ /**
+ * Deactivate LifterLMS API keys with remote server
+ *
+ * @since 3.0.0
+ * @since 3.4.1 Ensure key exists before attempting to deactivate it.
+ * @since 3.5.0 Deleting any cached activation result.
+ *
+ * @param array $keys Array of keys.
+ * @return array
+ */
+ public static function deactivate_keys( $keys ) {
+
+ $keys = array_map( 'sanitize_text_field', $keys );
+ $keys = array_map( 'trim', $keys );
+
+ $data = array(
+ 'keys' => array(),
+ 'url' => get_site_url(),
+ );
+
+ // Delete any cached activation result.
+ $cache_hash = md5( wp_json_encode( $data ) );
+ delete_site_transient( 'llms_helper_keys_activation_response_' . $cache_hash );
+
+ $saved = llms_helper_options()->get_license_keys();
+ foreach ( $keys as $key ) {
+ if ( isset( $saved[ $key ] ) && $saved[ $key ]['update_key'] ) {
+ $data['keys'][ $key ] = $saved[ $key ]['update_key'];
+ }
+ }
+
+ $req = new LLMS_Dot_Com_API( '/license/deactivate', $data );
+ return $req->get_result();
+ }
+
+ /**
+ * Retrieve stored information about a key by the license key
+ *
+ * @since 3.3.1
+ *
+ * @param string $key License key.
+ * @return array|false Associative array of license key information. Returns `false` if the provided license key was not found.
+ */
+ public static function get( $key ) {
+
+ $saved = llms_helper_options()->get_license_keys();
+ return isset( $saved[ $key ] ) ? $saved[ $key ] : false;
+ }
+
+ /**
+ * Remove a single license key
+ *
+ * @since 3.0.0
+ *
+ * @param string $key License key.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public static function remove_license_key( $key ) {
+ $keys = llms_helper_options()->get_license_keys();
+ if ( isset( $keys[ $key ] ) ) {
+ unset( $keys[ $key ] );
+ }
+ return llms_helper_options()->set_license_keys( $keys );
+ }
+}
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-options.php b/libraries/lifterlms-helper/includes/class-llms-helper-options.php
new file mode 100644
index 0000000000..5d471f9388
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-options.php
@@ -0,0 +1,158 @@
+get_options();
+
+ if ( isset( $options[ $key ] ) ) {
+ return $options[ $key ];
+ }
+
+ return $default;
+ }
+
+ /**
+ * Retrieve all upgrader options array
+ *
+ * @since 3.0.0
+ *
+ * @return array
+ */
+ private function get_options() {
+ return get_option( 'llms_helper_options', array() );
+ }
+
+ /**
+ * Update the value of an option
+ *
+ * @since 3.0.0
+ *
+ * @param string $key Option name.
+ * @param mixed $val Option value.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ private function set_option( $key, $val ) {
+
+ $options = $this->get_options();
+ $options[ $key ] = $val;
+ return update_option( 'llms_helper_options', $options, false );
+ }
+
+ /**
+ * Get info about addon channel subscriptions
+ *
+ * @since 3.0.0
+ *
+ * @return array
+ */
+ public function get_channels() {
+ return $this->get_option( 'channels', array() );
+ }
+
+ /**
+ * Set info about addon channel subscriptions
+ *
+ * @since 3.0.0
+ *
+ * @param array $channels Array of channel information.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public function set_channels( $channels ) {
+ return $this->set_option( 'channels', $channels );
+ }
+
+ /**
+ * Retrieve a timestamp for the last time the keys check cron was run
+ *
+ * @since 3.0.0
+ *
+ * @return int
+ */
+ public function get_last_keys_cron_check() {
+ return $this->get_option( 'last_keys_cron_check', 0 );
+ }
+
+ /**
+ * Set the last cron check time
+ *
+ * @since 3.0.0
+ *
+ * @param int $time Timestamp.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public function set_last_keys_cron_check( $time ) {
+ return $this->set_option( 'last_keys_cron_check', $time );
+ }
+
+ /**
+ * Retrieve saved license key data
+ *
+ * @since 3.0.0
+ *
+ * @return array
+ */
+ public function get_license_keys() {
+ return $this->get_option( 'license_keys', array() );
+ }
+
+ /**
+ * Update saved license key data
+ *
+ * @since 3.0.0
+ *
+ * @param array $keys Key data to save.
+ * @return boolean True if option value has changed, false if not or if update failed.
+ */
+ public function set_license_keys( $keys ) {
+ return $this->set_option( 'license_keys', $keys );
+ }
+}
diff --git a/libraries/lifterlms-helper/includes/class-llms-helper-upgrader.php b/libraries/lifterlms-helper/includes/class-llms-helper-upgrader.php
new file mode 100644
index 0000000000..b5a950a8f5
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/class-llms-helper-upgrader.php
@@ -0,0 +1,504 @@
+is_installable() ) {
+ return new WP_Error( 'not_installable', __( 'Add-on cannot be installable.', 'lifterlms' ) );
+ }
+
+ // Make sure it's not already installed.
+ if ( 'install' === $action && $addon->is_installed() ) {
+ // Translators: %s = Add-on name.
+ return new WP_Error( 'installed', sprintf( __( '%s is already installed', 'lifterlms' ), $addon->get( 'title' ) ) );
+ }
+
+ // Get download info via llms.com api.
+ $dl_info = $addon->get_download_info();
+ if ( is_wp_error( $dl_info ) ) {
+ return $dl_info;
+ }
+ if ( ! isset( $dl_info['data']['url'] ) ) {
+ return new WP_Error( 'no_url', __( 'An error occured while attempting to retrieve add-on download information. Please try again.', 'lifterlms' ) );
+ }
+
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+ WP_Filesystem();
+
+ $skin = new Automatic_Upgrader_Skin();
+
+ if ( 'plugin' === $addon->get_type() ) {
+
+ $upgrader = new Plugin_Upgrader( $skin );
+
+ } elseif ( 'theme' === $addon->get_type() ) {
+
+ $upgrader = new Theme_Upgrader( $skin );
+
+ } else {
+
+ return new WP_Error( 'inconceivable', __( 'The requested action is not possible.', 'lifterlms' ) );
+
+ }
+
+ if ( 'install' === $action ) {
+ remove_filter( 'upgrader_package_options', array( $this, 'upgrader_package_options' ) );
+ $result = $upgrader->install( $dl_info['data']['url'] );
+ add_filter( 'upgrader_package_options', array( $this, 'upgrader_package_options' ) );
+ } elseif ( 'update' === $action ) {
+ $result = $upgrader->upgrade( $addon->get( 'update_file' ) );
+ }
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ } elseif ( is_wp_error( $skin->result ) ) {
+ return $skin->result;
+ } elseif ( is_null( $result ) ) {
+ return new WP_Error( 'filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.', 'lifterlms' ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Output additional information on plugins update screen when updates are available for an unlicensed addon
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @param array $plugin_data Array of plugin data.
+ * @param array $res Response data.
+ * @return void
+ */
+ public function in_plugin_update_message( $plugin_data, $res ) {
+
+ if ( empty( $plugin_data['package'] ) ) {
+
+ echo '';
+
+ echo '';
+ _e( 'Your LifterLMS add-on is currently unlicensed and cannot be updated!', 'lifterlms' );
+ echo '
';
+
+ echo '';
+ // Translators: %1$s = Opening anchor tag; %2$s = Closing anchor tag.
+ printf( __( 'If you already have a license, you can activate it on the %1$sadd-ons management screen%2$s.', 'lifterlms' ), '', ' ' );
+ echo '
';
+
+ echo '';
+ // Translators: %s = URI to licensing FAQ.
+ printf( __( 'Learn more about LifterLMS add-on licensing at %s.', 'lifterlms' ), make_clickable( 'https://lifterlms.com/docs/lifterlms-helper/' ) );
+ echo '
';
+
+ }
+ }
+
+ /**
+ * Filter API calls to get plugin information and replace it with data from LifterLMS.com API for our addons
+ *
+ * @since 3.0.0
+ *
+ * @param bool $response False (denotes API call should be made to wp.org for plugin info).
+ * @param string $action Name of the API action.
+ * @param obj $args Additional API call args.
+ * @return false|obj
+ */
+ public function plugins_api( $response, $action = '', $args = null ) {
+
+ if ( 'plugin_information' !== $action ) {
+ return $response;
+ }
+
+ if ( empty( $args->slug ) ) {
+ return $response;
+ }
+
+ $core = false;
+
+ if ( 'lifterlms' === $args->slug ) {
+ remove_filter( 'plugins_api', array( $this, 'plugins_api' ), 10, 3 );
+ $args->slug = 'lifterlms-com-lifterlms';
+ $core = true;
+ }
+
+ if ( 0 !== strpos( $args->slug, 'lifterlms-com-' ) ) {
+ return $response;
+ }
+
+ $response = $this->set_plugins_api( $args->slug, true );
+
+ if ( $core ) {
+ add_filter( 'plugins_api', array( $this, 'plugins_api' ), 10, 3 );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Handle setting the site transient for plugin updates
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ *
+ * @param obj $value Transient value.
+ * @return obj
+ */
+ public function pre_set_site_transient_update_things( $value ) {
+
+ if ( empty( $value ) ) {
+ return $value;
+ }
+
+ $which = current_filter();
+ if ( 'pre_set_site_transient_update_plugins' === $which ) {
+ $type = 'plugin';
+ } elseif ( 'pre_set_site_transient_update_themes' === $which ) {
+ $type = 'theme';
+ } else {
+ return $value;
+ }
+
+ $all_products = llms_get_add_ons( false );
+ if ( is_wp_error( $all_products ) || ! isset( $all_products['items'] ) ) {
+ return $value;
+ }
+
+ foreach ( $all_products['items'] as $addon_data ) {
+
+ $addon = llms_get_add_on( $addon_data );
+
+ if ( ! $addon->is_installable() || ! $addon->is_installed() ) {
+ continue;
+ }
+
+ if ( $type !== $addon->get_type() ) {
+ continue;
+ }
+
+ $file = $addon->get( 'update_file' );
+
+ if ( 'plugin' === $type ) {
+
+ if ( 'lifterlms-com-lifterlms' === $addon->get( 'id' ) ) {
+ if ( 'stable' === $addon->get_channel_subscription() || ! $addon->get( 'version_beta' ) ) {
+ continue;
+ }
+ }
+
+ $item = (object) $this->set_plugins_api( $addon->get( 'id' ), false );
+
+ } elseif ( 'theme' === $type ) {
+
+ $item = array(
+ 'theme' => $file,
+ 'new_version' => $addon->get_latest_version(),
+ 'url' => $addon->get_permalink(),
+ 'package' => true,
+ );
+ }
+
+ if ( $addon->has_available_update() ) {
+
+ $value->response[ $file ] = $item;
+ unset( $value->no_update[ $file ] );
+
+ } else {
+
+ $value->no_update[ $file ] = $item;
+ unset( $value->response[ $file ] );
+
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Setup an object of addon data for use when requesting plugin information normally acquired from wp.org.
+ *
+ * @since 3.0.0
+ * @since 3.2.1 Set package to `true` for add-ons which don't require a license.
+ * @since 3.4.2 Added a `plugin` property to the returned plugin object,
+ * which is required by `WP_Plugin_Install_List_Table::prepare_items()`.
+ *
+ * @param string $id Addon id.
+ * @param bool $include_sections Whether or not to include additional sections like the description and changelog.
+ * @return object
+ */
+ private function set_plugins_api( $id, $include_sections = true ) {
+
+ $addon = llms_get_add_on( $id );
+
+ if ( 'lifterlms-com-lifterlms' === $id && false !== strpos( $addon->get_latest_version(), 'beta' ) ) {
+
+ require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
+ $item = plugins_api(
+ 'plugin_information',
+ array(
+ 'slug' => 'lifterlms',
+ 'fields' => array(
+ 'banners' => true,
+ 'icons' => true,
+ ),
+ )
+ );
+ $item->version = $addon->get_latest_version();
+ $item->new_version = $addon->get_latest_version();
+ $item->package = true;
+
+ unset( $item->versions );
+
+ $item->sections['changelog'] = $this->get_changelog_for_api( $addon );
+
+ return $item;
+
+ }
+
+ $item = array(
+ 'name' => $addon->get( 'title' ),
+ 'slug' => $id,
+ 'plugin' => $addon->get( 'update_file' ),
+ 'version' => $addon->get_latest_version(),
+ 'new_version' => $addon->get_latest_version(),
+ 'author' => '' . $addon->get( 'author' )['name'] . ' ',
+ 'author_profile' => $addon->get( 'author' )['link'],
+ 'requires' => $addon->get( 'version_wp' ),
+ 'tested' => '',
+ 'requires_php' => $addon->get( 'version_php' ),
+ 'compatibility' => '',
+ 'homepage' => $addon->get( 'permalink' ),
+ 'download_link' => '',
+ 'package' => ( $addon->is_licensed() || ! $addon->requires_license() ),
+ 'banners' => array(
+ 'low' => $addon->get( 'image' ),
+ ),
+ );
+
+ if ( $include_sections ) {
+
+ $item['sections'] = array(
+ 'description' => $addon->get( 'description' ),
+ 'changelog' => $this->get_changelog_for_api( $addon ),
+ );
+
+ }
+
+ return (object) $item;
+ }
+
+ /**
+ * Retrieve the changelog for an addon
+ *
+ * Attempts to retrieve changelog HTML from the make blog.
+ *
+ * If the add-on's changelog is empty or a static html file, returns an error
+ * with a link to the release notes category on the make blog.
+ *
+ * @since 3.0.0
+ * @since 3.1.0 Retrieve changelog from the make blog in favor of legacy static html changelogs.
+ * @since 3.2.0 Fix usage of incorrect textdomain.
+ *
+ * @param LLMS_Add_On $addon Add-on object.
+ * @return string
+ */
+ private function get_changelog_for_api( $addon ) {
+
+ $src = $addon->get( 'changelog' );
+ $split = array_filter( explode( '/', $src ) );
+ $tag = end( $split );
+
+ $logs = false;
+ if ( ! empty( $tag ) && false === strpos( $tag, '.html' ) ) {
+ $logs = $this->get_changelog_html( $tag, $src );
+ }
+
+ // Translators: %s = URL for the changelog website.
+ return $logs ? $logs : make_clickable( sprintf( __( 'There was an error retrieving the changelog. Try visiting %s for recent changelogs.', 'lifterlms' ), 'https://make.lifterlms.com/category/release-notes/' ) );
+ }
+
+ /**
+ * Retrieve changelog information from the make blog
+ *
+ * Retrieves the most recent 10 changelog entries for the add-on, formats the returned information
+ * into a format suitable to display within the thickbox, adds a link to the full changelog,
+ * and returns the html string.
+ *
+ * If an error is encountered, returns an empty string.
+ *
+ * @since 3.1.0
+ * @since 3.2.0 Fix usage of incorrect textdomain.
+ *
+ * @param string $tag Tag slug for the add-on on the blog.
+ * @param string $url Full URL to the changelog entries for the add-on.
+ * @return string
+ */
+ private function get_changelog_html( $tag, $url ) {
+
+ $ret = '';
+ $req = wp_remote_get( add_query_arg( 'slug', $tag, 'https://make.lifterlms.com/wp-json/wp/v2/tags' ) );
+ $body = json_decode( wp_remote_retrieve_body( $req ), true );
+
+ if ( ! empty( $body ) && ! empty( $body[0]['_links']['wp:post_type'][0]['href'] ) ) {
+
+ $logs_url = $body[0]['_links']['wp:post_type'][0]['href'];
+ $logs_req = wp_remote_get( $logs_url );
+ $logs = json_decode( wp_remote_retrieve_body( $logs_req ), true );
+
+ if ( ! empty( $logs ) && is_array( $logs ) ) {
+ foreach ( $logs as $log ) {
+ $ts = strtotime( $log['date_gmt'] );
+ $date = function_exists( 'wp_date' ) ? wp_date( 'Y-m-d', $ts ) : gmdate( 'Y-m-d', $ts );
+ $split = array_filter( explode( ' ', $log['title']['rendered'] ) );
+ $ver = end( $split );
+ // Translators: %1$s - Version number; %2$s - Release date.
+ $ret .= '
' . sprintf( __( 'Version %1$s - %2$s', 'lifterlms' ), sanitize_text_field( wp_strip_all_tags( trim( $ver ) ) ), $date ) . ' ';
+ $ret .= strip_tags( $log['content']['rendered'], '' );
+ }
+ }
+
+ $ret .= ' ';
+ // Translators: %s = URL to the full changelog.
+ $ret .= ' ' . make_clickable( sprintf( __( 'View the full changelog at %s.', 'lifterlms' ), $url ) ) . '
';
+
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Get a real package download url for a LifterLMS add-on
+ *
+ * This is called immediately prior to package upgrades.
+ *
+ * @since 3.0.0
+ * @since 3.0.2 Unknown.
+ * @since 3.2.1 Correctly process addons which do not require a license (e.g. free products).
+ *
+ * @param array $options Package option data.
+ * @return array
+ */
+ public function upgrader_package_options( $options ) {
+
+ if ( ! isset( $options['hook_extra'] ) ) {
+ return $options;
+ }
+
+ if ( isset( $options['hook_extra']['plugin'] ) ) {
+ $file = $options['hook_extra']['plugin'];
+ } elseif ( isset( $options['hook_extra']['theme'] ) ) {
+ $file = $options['hook_extra']['theme'];
+ } else {
+ return $options;
+ }
+
+ $addon = llms_get_add_on( $file, 'update_file' );
+ if ( ! $addon || ! $addon->is_installable() || ( $addon->requires_license() && ! $addon->is_licensed() ) ) {
+ return $options;
+ }
+
+ $info = $addon->get_download_info();
+ if ( is_wp_error( $info ) || ! isset( $info['data'] ) || ! isset( $info['data']['url'] ) ) {
+ return $options;
+ }
+
+ if ( true === $options['package'] ) {
+ $options['package'] = $info['data']['url'];
+ }
+
+ return $options;
+ }
+}
diff --git a/libraries/lifterlms-helper/includes/functions-llms-helper.php b/libraries/lifterlms-helper/includes/functions-llms-helper.php
new file mode 100644
index 0000000000..436625bafd
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/functions-llms-helper.php
@@ -0,0 +1,70 @@
+get_license_keys() as $key ) {
+ if ( 1 == $key['status'] ) {
+ $ids = array_merge( $ids, $key['addons'] );
+ }
+ if ( false === $installable_only ) {
+ $ids[] = $key['product_id'];
+ }
+ }
+
+ return array_unique( $ids );
+}
+
+/**
+ * Deletes transient data related to plugin and theme updates
+ *
+ * @since 3.2.1
+ *
+ * @return void
+ */
+function llms_helper_flush_cache() {
+
+ delete_transient( 'llms_products_api_result' );
+ delete_site_transient( 'update_plugins' );
+ delete_site_transient( 'update_themes' );
+}
diff --git a/libraries/lifterlms-helper/includes/model-llms-helper-add-on.php b/libraries/lifterlms-helper/includes/model-llms-helper-add-on.php
new file mode 100644
index 0000000000..a45a23ab46
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/model-llms-helper-add-on.php
@@ -0,0 +1,18 @@
+requires_license();
+
+ $id = $this->get( 'id' );
+ foreach ( llms_helper_options()->get_license_keys() as $data ) {
+ /**
+ * 1. If license is not required, return the first license found.
+ * 2. If the addon matches the licensed product
+ * 3. If the addon is included in the licensed bundle product.
+ */
+ if ( ! $requires_license || $id === $data['product_id'] || in_array( $id, $data['addons'], true ) ) {
+ return $data;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Retrieve the update channel for the addon
+ *
+ * @since 3.0.0
+ *
+ * @return string
+ */
+ public function get_channel_subscription() {
+ $channels = llms_helper_options()->get_channels();
+ return isset( $channels[ $this->get( 'id' ) ] ) ? $channels[ $this->get( 'id' ) ] : 'stable';
+ }
+
+ /**
+ * Retrieve download information for an add-on
+ *
+ * @since 3.0.0
+ * @since 3.2.1 Allow getting download info for add-ons which do not require licenses.
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return WP_Error|array
+ */
+ public function get_download_info() {
+
+ $key = $this->find_license();
+
+ if ( $this->requires_license() && ! $key ) {
+ return new WP_Error( 'no_license', __( 'Unable to locate a license key for the selected add-on.', 'lifterlms' ) );
+ }
+
+ $args = array(
+ 'url' => get_site_url(),
+ 'add_on_slug' => $this->get( 'slug' ),
+ 'channel' => $this->get_channel_subscription(),
+ );
+
+ if ( $key ) {
+ $args['license_key'] = $key['license_key'];
+ $args['update_key'] = $key['update_key'];
+ }
+
+ $req = new LLMS_Dot_Com_API(
+ '/license/download',
+ $args
+ );
+
+ $data = $req->get_result();
+
+ if ( $req->is_error() ) {
+ return $data;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Translate strings
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @param string $string Untranslated string / key.
+ * @return string
+ */
+ public function get_l10n( $string ) {
+
+ $strings = array(
+
+ 'active' => __( 'Active', 'lifterlms' ),
+ 'inactive' => __( 'Inactive', 'lifterlms' ),
+
+ 'installed' => __( 'Installed', 'lifterlms' ),
+ 'uninstalled' => __( 'Not Installed', 'lifterlms' ),
+
+ 'activate' => __( 'Activate', 'lifterlms' ),
+ 'deactivate' => __( 'Deactivate', 'lifterlms' ),
+ 'install' => __( 'Install', 'lifterlms' ),
+
+ 'none' => __( 'N/A', 'lifterlms' ),
+
+ 'license_active' => __( 'Licensed', 'lifterlms' ),
+ 'license_inactive' => __( 'Unlicensed', 'lifterlms' ),
+
+ );
+
+ return $strings[ $string ];
+ }
+
+ /**
+ * Determine the status of an addon's license
+ *
+ * @since 3.0.0
+ * @since 3.2.1 Use `requires_license()` instead of checking `has_license` prop directly.
+ *
+ * @param bool $translate If true, returns the translated string for on-screen display.
+ * @return string
+ */
+ public function get_license_status( $translate = false ) {
+
+ if ( ! $this->requires_license() ) {
+ $ret = 'none';
+ } else {
+ $ret = $this->is_licensed() ? 'license_active' : 'license_inactive';
+ }
+
+ return $translate ? $this->get_l10n( $ret ) : $ret;
+ }
+
+ /**
+ * Install the add-on via LifterLMS.com
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return string|WP_Error
+ */
+ public function install() {
+
+ $ret = LLMS_Helper()->upgrader()->install_addon( $this );
+
+ if ( true === $ret ) {
+
+ /* Translators: %s = Add-on name */
+ return sprintf( __( '%s was successfully installed.', 'lifterlms' ), $this->get( 'title' ) );
+
+ } elseif ( is_wp_error( $ret ) ) {
+
+ return $ret;
+
+ }
+
+ /* Translators: %s = Add-on name */
+ return new WP_Error( 'activation', sprintf( __( 'Could not install %s.', 'lifterlms' ), $this->get( 'title' ) ) );
+ }
+
+ /**
+ * Determines if the add-on is licensed
+ *
+ * @since 3.0.0
+ *
+ * @return bool
+ */
+ public function is_licensed() {
+ return ( false !== $this->find_license() );
+ }
+
+ /**
+ * Determines if the add-on requires a license
+ *
+ * @since 3.2.1
+ *
+ * @return bool
+ */
+ public function requires_license() {
+ return llms_parse_bool( $this->get( 'has_license' ) );
+ }
+
+ /**
+ * Update the addons update channel subscription
+ *
+ * @since 3.0.0
+ *
+ * @param string $channel Channel name [stable|beta].
+ * @return boolean
+ */
+ public function subscribe_to_channel( $channel = 'stable' ) {
+
+ $channels = llms_helper_options()->get_channels();
+ $channels[ $this->get( 'id' ) ] = $channel;
+ return llms_helper_options()->set_channels( $channels );
+ }
+
+ /**
+ * Install the add-on via LifterLMS.com
+ *
+ * @since 3.0.0
+ * @since 3.4.0 Use core textdomain.
+ *
+ * @return string|WP_Error
+ */
+ public function update() {
+
+ $ret = LLMS_Helper()->upgrader()->install_addon( $this, 'update' );
+
+ if ( true === $ret ) {
+
+ /* Translators: %s = Add-on name */
+ return sprintf( __( '%s was successfully updated.', 'lifterlms' ), $this->get( 'title' ) );
+
+ } elseif ( is_wp_error( $ret ) ) {
+
+ return $ret;
+
+ }
+
+ /* Translators: %s = Add-on name */
+ return new WP_Error( 'activation', sprintf( __( 'Could not update %s.', 'lifterlms' ), $this->get( 'title' ) ) );
+ }
+}
diff --git a/libraries/lifterlms-helper/includes/models/index.php b/libraries/lifterlms-helper/includes/models/index.php
new file mode 100644
index 0000000000..ff2b6071fd
--- /dev/null
+++ b/libraries/lifterlms-helper/includes/models/index.php
@@ -0,0 +1 @@
+
+
+
+ for LifterLMS or any available add-ons will allow you to automatically update to the latest beta release for the given plugin or theme.', 'lifterlms' ); ?>
+
+
+
+
+
+
+
+
+ ', '
' );
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ get( 'title' ); ?>
+
+
+ get_channel_subscription() ); ?>>
+ get_channel_subscription() ); ?>>
+
+
+ get_installed_version(); ?>
+ get( 'version_beta' ) ? $addon->get( 'version_beta' ) : __( 'N/A', 'lifterlms' ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+version );
+ }
+
+ /**
+ * When loaded as a library included by the LifterLMS core localization is handled by the LifterLMS core.
+ *
+ * When the plugin is loaded by itself as a plugin, we must localize it independently.
+ */
+ if ( ! defined( 'LLMS_REST_API_LIB' ) || ! LLMS_REST_API_LIB ) {
+ add_action( 'init', array( $this, 'load_textdomain' ), 0 );
+ }
+
+ // Authentication needs to run early to handle basic auth.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-authentication.php';
+
+ // Load everything else.
+ add_action( 'plugins_loaded', array( $this, 'init' ), 10 );
+
+ }
+
+ /**
+ * Include files and instantiate classes.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.4 Load authentication early.
+ *
+ * @return void
+ */
+ public function includes() {
+
+ // Abstracts.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/abstracts/class-llms-rest-database-resource.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/abstracts/class-llms-rest-webhook-data.php';
+
+ // Functions.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/llms-rest-functions.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/server/llms-rest-server-functions.php';
+
+ // Models.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/models/class-llms-rest-api-key.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/models/class-llms-rest-webhook.php';
+
+ // Classes.
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-api-keys.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-api-keys-query.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-capabilities.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-install.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-webhooks.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/class-llms-rest-webhooks-query.php';
+
+ // Include admin classes.
+ if ( is_admin() ) {
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/admin/class-llms-rest-admin-settings.php';
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/admin/class-llms-rest-admin-form-controller.php';
+ }
+
+ add_action( 'rest_api_init', array( $this, 'rest_api_includes' ), 5 );
+ add_action( 'rest_api_init', array( $this, 'rest_api_controllers_init' ), 10 );
+
+ }
+
+ /**
+ * Retrieve an instance of the API Keys management singleton.
+ *
+ * @example $keys = LLMS_REST_API()->keys();
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return LLMS_REST_API_Keys
+ */
+ public function keys() {
+ return LLMS_REST_API_Keys::instance();
+ }
+
+ /**
+ * Include REST api specific files.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Include memberships controller class file.
+ * @since 1.0.0-beta.18 Include access plans controller class file.
+ *
+ * @return void
+ */
+ public function rest_api_includes() {
+
+ $includes = array(
+
+ // Abstracts first.
+ 'abstracts/class-llms-rest-controller-stubs',
+ 'abstracts/class-llms-rest-controller',
+ 'abstracts/class-llms-rest-users-controller',
+ 'abstracts/class-llms-rest-posts-controller',
+
+ // Functions.
+ 'server/llms-rest-server-functions',
+
+ // Controllers.
+ 'server/class-llms-rest-api-keys-controller',
+ 'server/class-llms-rest-access-plans-controller',
+ 'server/class-llms-rest-courses-controller',
+ 'server/class-llms-rest-sections-controller',
+ 'server/class-llms-rest-lessons-controller',
+ 'server/class-llms-rest-memberships-controller',
+ 'server/class-llms-rest-enrollments-controller',
+ 'server/class-llms-rest-instructors-controller',
+ 'server/class-llms-rest-students-controller',
+ 'server/class-llms-rest-students-progress-controller',
+ 'server/class-llms-rest-webhooks-controller',
+
+ );
+
+ foreach ( $includes as $include ) {
+ include_once LLMS_REST_API_PLUGIN_DIR . 'includes/' . $include . '.php';
+ }
+ }
+
+ /**
+ * Instantiate REST api Controllers.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Init memberships controller.
+ * @since 1.0.0-beta.18 Init access plans controller.
+ *
+ * @return void
+ */
+ public function rest_api_controllers_init() {
+
+ $controllers = array(
+ 'LLMS_REST_API_Keys_Controller',
+ 'LLMS_REST_Courses_Controller',
+ 'LLMS_REST_Sections_Controller',
+ 'LLMS_REST_Lessons_Controller',
+ 'LLMS_REST_Memberships_Controller',
+ 'LLMS_REST_Instructors_Controller',
+ 'LLMS_REST_Students_Controller',
+ 'LLMS_REST_Students_Progress_Controller',
+ 'LLMS_REST_Enrollments_Controller',
+ 'LLMS_REST_Webhooks_Controller',
+ 'LLMS_REST_Access_Plans_Controller',
+ );
+
+ foreach ( $controllers as $controller ) {
+ $controller_instance = new $controller();
+ $controller_instance->register_routes();
+ }
+
+ }
+
+ /**
+ * Include all required files and classes.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.6 Load webhooks actions at init 1 instead of init 10.
+ * @since 1.0.0-beta.8 Load webhooks actions a little bit later: at init 6 instead of init 10,
+ * just after all the db tables are created (init 5),
+ * to avoid PHP warnings on first plugin activation.
+ * @since 1.0.0-beta.22 Bump minimum required version to 6.0.0-alpha.1.
+ * Use `llms()` in favor of deprecated `LLMS()`.
+ * @since 1.0.0-beta.25 Perform some db clean-up on user deletion.
+ * Bump minimum required version to 6.5.0.
+ * @since 1.0.0-beta.26 Bump minimum required version to 7.0.2.
+ *
+ * @return void
+ */
+ public function init() {
+
+ // Only load if we have the minimum LifterLMS version installed & activated.
+ if ( ! function_exists( 'llms' ) || version_compare( '7.0.2', llms()->version, '>' ) ) {
+ return;
+ }
+
+ // Load includes.
+ $this->includes();
+
+ add_action( 'init', array( $this->webhooks(), 'load' ), 6 );
+ add_action( 'deleted_user', array( $this, 'on_user_deletion' ) );
+
+ }
+
+ /**
+ * When a user is deleted in WordPress, delete corresponding LifterLMS REST API data.
+ *
+ * @since 1.0.0-beta.25
+ *
+ * @param int $user_id The ID of the just deleted WP_User.
+ * @return void
+ */
+ public function on_user_deletion( $user_id ) {
+
+ global $wpdb;
+
+ // Delete user's API keys.
+ $wpdb->delete(
+ "{$wpdb->prefix}lifterlms_api_keys",
+ array(
+ 'user_id' => $user_id,
+ ),
+ array( '%d' )
+ );// db-cache ok.
+ }
+
+ /**
+ * Load l10n files.
+ *
+ * This method is only used when the plugin is loaded as a standalone plugin (for development purposes),
+ * otherwise (when loaded as a library from within the LifterLMS core plugin) the localization
+ * strings are included into the LifterLMS Core plugin's po/mo files and are localized by the LifterLMS
+ * core plugin.
+ *
+ * Files can be found in the following order (The first loaded file takes priority):
+ * 1. WP_LANG_DIR/lifterlms/lifterlms-rest-LOCALE.mo
+ * 2. WP_LANG_DIR/plugins/lifterlms-rest-LOCALE.mo
+ * 3. WP_CONTENT_DIR/plugins/lifterlms-rest/i18n/lifterlms-rest-LOCALE.mo
+ *
+ * Note: The function `load_plugin_textdomain()` is not used because the same textdomain as the LifterLMS core
+ * is used for this plugin but the file is named `lifterlms-rest` in order to allow using a separate language
+ * file for each codebase.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.17 Fixed the name of the MO loaded from the safe directory: `lifterlms-{$locale}.mo` to `lifterlms-rest-{$locale}.mo`.
+ * Fixed double slash typo in plugin textdomain path argument.
+ * Fixed issue causing language files to not load properly.
+ *
+ * @return void
+ */
+ public function load_textdomain() {
+
+ // Load locale.
+ $locale = apply_filters( 'plugin_locale', get_locale(), 'lifterlms' );
+
+ // Load from the LifterLMS "safe" directory if it exists.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/lifterlms/lifterlms-rest-' . $locale . '.mo' );
+
+ // Load from the default plugins language file directory.
+ load_textdomain( 'lifterlms', WP_LANG_DIR . '/plugins/lifterlms-rest-' . $locale . '.mo' );
+
+ // Load from the plugin's language file directory.
+ load_textdomain( 'lifterlms', LLMS_REST_API_PLUGIN_DIR . '/i18n/lifterlms-rest-' . $locale . '.mo' );
+
+ }
+
+ /**
+ * Retrieve an instance of the webhooks management singleton.
+ *
+ * @example $webhooks = LLMS_REST_API()->webhooks();
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return LLMS_REST_Webhooks
+ */
+ public function webhooks() {
+ return LLMS_REST_Webhooks::instance();
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller-stubs.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller-stubs.php
new file mode 100644
index 0000000000..14cf05ac3d
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller-stubs.php
@@ -0,0 +1,277 @@
+get_object().
+ */
+ protected function create_object( $prepared, $request ) {
+
+ // @todo: add version to message.
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::create_object', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return $this->get_object( $this->get_object_id( $prepared ) );
+
+ }
+
+ /**
+ * Retrieve an ID from the object
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $object Item object.
+ * @return int
+ */
+ protected function get_object_id( $object ) {
+ if ( is_object( $object ) && ! empty( $object->id ) ) {
+ return $object->id;
+ } elseif ( is_array( $object ) && ! empty( $object['id'] ) ) {
+ return $object['id'];
+ } elseif ( method_exists( $object, 'get_id' ) ) {
+ return $object->get_id();
+ } elseif ( method_exists( $object, 'get' ) ) {
+ return $object->get( 'id' );
+ }
+
+ // @todo: add version to message.
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_object_id', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return 0;
+
+ }
+
+ /**
+ * Retrieve a query object based on arguments from a `get_items()` (collection) request.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return object
+ */
+ protected function get_objects_query( $prepared, $request ) {
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_objects_query', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return new WP_Query( $prepared );
+
+ }
+
+ /**
+ * Retrieve an array of objects from the result of $this->get_objects_query().
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $query Objects query result.
+ * @return obj[]
+ */
+ protected function get_objects_from_query( $query ) {
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_objects_from_query', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return array();
+
+ }
+
+ /**
+ * Retrieve pagination information from an objects query.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $query Objects query result.
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array {
+ * Array of pagination information.
+ *
+ * @type int $current_page Current page number.
+ * @type int $total_results Total number of results.
+ * @type int $total_pages Total number of results pages.
+ * }
+ */
+ protected function get_pagination_data_from_query( $query, $prepared, $request ) {
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::get_pagination_data_from_query', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return array(
+ 'current_page' => 1,
+ 'total_results' => 1,
+ 'total_pages' => 1,
+ );
+
+ }
+
+ /**
+ * Prepares data of a single object for response.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param obj $object Raw object from database.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_object_data_for_response( $object, $request ) {
+
+ $data = $this->prepare_object_for_response( $object, $request );
+ $response_fields = $this->get_fields_for_response( $request );
+ // Include meta data registered via `register_meta()`.
+ if ( rest_is_field_included( 'meta', $response_fields ) ) {
+ $data['meta'] = $this->meta->get_value( $this->get_object_id( $object ), $request );
+ // Exclude disallowed meta.
+ $data['meta'] = $this->exclude_disallowed_meta_fields( $data['meta'] );
+ }
+
+ // Include custom REST fields registered via `register_rest_field()`.
+ $data = $this->add_additional_fields_to_object( $data, $request );
+
+ $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+ $data = $this->filter_response_by_context( $data, $context );
+
+ return $data;
+
+ }
+
+ /**
+ * Prepare an object for response.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Conditionally throw `_doing_it_wrong()`.
+ * @since 1.0.0-beta.27 Exclude additional fields added via `register_rest_field`.
+ *
+ * @param object $object Raw object from database.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_object_for_response( $object, $request ) {
+
+ if ( ! method_exists( $object, 'get' ) ) {
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::prepare_object_for_response', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+ }
+
+ $prepared = array();
+ $map = array_flip( $this->map_schema_to_database() );
+ $fields = $this->get_fields_for_response( $request );
+ $additional_fields = array_keys( $this->get_additional_fields() );
+
+ foreach ( $map as $db_key => $schema_key ) {
+ if ( in_array( $schema_key, $fields, true ) && ! in_array( $schema_key, $additional_fields, true ) ) {
+ $prepared[ $schema_key ] = $object->get( $db_key );
+ }
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Update the object in the database with prepared data.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return obj Object Instance of object from $this->get_object().
+ */
+ protected function update_object( $prepared, $request ) {
+
+ // @todo: add version to message.
+
+ // Translators: %s = method name.
+ _doing_it_wrong( 'LLMS_REST_Controller::update_object', sprintf( __( "Method '%s' must be overridden.", 'lifterlms' ), __METHOD__ ), '1.0.0-beta.1' );
+
+ // For example.
+ return $this->get_object( $prepared['id'] );
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller.php
new file mode 100644
index 0000000000..eb4fa15fe2
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-controller.php
@@ -0,0 +1,1069 @@
+get_item_schema();
+
+ $item = $this->prepare_item_for_database( $request );
+ // Exclude additional fields registered via `register_rest_field()`.
+ $item = array_diff_key( $item, $this->get_additional_fields() );
+ $object = $this->create_object( $item, $request );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $this->object_inserted( $object, $request, $schema, true );
+
+ // Registered via `register_meta()`.
+ $meta_update = $this->update_meta( $object, $request, $schema );
+ if ( is_wp_error( $meta_update ) ) {
+ return $meta_update;
+ }
+
+ // Registered via `register_rest_field()`.
+ $fields_update = $this->update_additional_fields_for_object( $object, $request );
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
+ }
+
+ $this->object_completely_inserted( $object, $request, $schema, true );
+
+ $request->set_param( 'context', 'edit' );
+
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ $response->set_status( 201 );
+ $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $this->get_object_id( $object ) ) ) );
+
+ return $response;
+
+ }
+
+ /**
+ * Called right after a resource is inserted (created/updated).
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param object $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ protected function object_inserted( $object, $request, $schema, $creating ) {
+
+ $type = $this->get_object_type();
+ /**
+ * Fires after a single llms resource is created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$type`, refers to the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param object $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_insert_{$type}", $object, $request, $schema, $creating );
+ }
+
+ /**
+ * Called right after a resource is completely inserted (created/updated).
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param LLMS_Post $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ protected function object_completely_inserted( $object, $request, $schema, $creating ) {
+
+ $type = $this->get_object_type();
+ /**
+ * Fires after a single llms resource is completely created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$type`, refers to the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param object $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_after_insert_{$type}", $object, $request, $schema, $creating );
+ }
+
+ /**
+ * Delete the item.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response|WP_Error
+ */
+ public function delete_item( $request ) {
+
+ $object = $this->get_object( $request['id'], false );
+
+ // We don't return 404s for items that are not found.
+ if ( ! is_wp_error( $object ) ) {
+
+ // If there was an error deleting the object return the error. If the error is that the object doesn't exist return 204 below!
+ $del = $this->delete_object( $object, $request );
+ if ( is_wp_error( $del ) ) {
+ return $del;
+ }
+ }
+
+ $response = rest_ensure_response( null );
+ $response->set_status( 204 );
+
+ return $response;
+
+ }
+
+ /**
+ * Retrieves the query params for the objects collection.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.12 Added `search_columns` collection param for searchable resources.
+ *
+ * @return array Collection parameters.
+ */
+ public function get_collection_params() {
+
+ $query_params = parent::get_collection_params();
+
+ $query_params['context']['default'] = 'view';
+
+ // We're not currently implementing searching for all of our controllers.
+ if ( empty( $this->is_searchable ) ) {
+ unset( $query_params['search'] );
+ } elseif ( ! empty( $this->search_columns_mapping ) ) {
+
+ $search_columns = array_keys( $this->search_columns_mapping );
+
+ $query_params['search_columns'] = array(
+ 'description' => __( 'Column names to be searched. Accepts a single column or a comma separated list of columns.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => $search_columns,
+ ),
+ 'default' => $search_columns,
+ );
+ }
+
+ // page and per_page params are already specified in WP_Rest_Controller->get_collection_params().
+ $query_params['order'] = array(
+ 'description' => __( 'Order sort attribute ascending or descending.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'asc',
+ 'enum' => array( 'asc', 'desc' ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ $query_params['orderby'] = array(
+ 'description' => __( 'Sort collection by object attribute.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => $this->orderby_properties[0],
+ 'enum' => $this->orderby_properties,
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ $query_params['include'] = array(
+ 'description' => __( 'Limit results to a list of ids. Accepts a single id or a comma separated list of ids.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'integer',
+ ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ $query_params['exclude'] = array(
+ 'description' => __( 'Exclude a list of ids from results. Accepts a single id or a comma separated list of ids.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'integer',
+ ),
+ 'validate_callback' => 'rest_validate_request_arg',
+ );
+
+ return $query_params;
+ }
+
+ /**
+ * Get a single item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.27 Don't call `rest_ensure_response()` twice, already called in `$this->prepare_item_for_response()`.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|WP_REST_Response
+ */
+ public function get_item( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ return $response;
+
+ }
+
+ /**
+ * Retrieves all items.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Fix an issue displaying a last page for lists with 0 possible results.
+ * @since 1.0.0-beta.7 Broken into several methods so to improve abstraction.
+ * @since 1.0.0-beta.12 Return early if `prepare_collection_query_args()` is a `WP_Error`.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function get_items( $request ) {
+
+ $prepared = $this->prepare_collection_query_args( $request );
+ if ( is_wp_error( $prepared ) ) {
+ return $prepared;
+ }
+
+ $query = $this->get_objects_query( $prepared, $request );
+ $pagination = $this->get_pagination_data_from_query( $query, $prepared, $request );
+
+ // Out-of-bounds, run the query again on page one to get a proper total count.
+ if ( $pagination['total_results'] < 1 ) {
+
+ $prepared_for_total_count = $this->prepare_args_for_total_count_query( $prepared, $request );
+ $count_query = $this->get_objects_query( $prepared_for_total_count, $request );
+ $count_results = $this->get_pagination_data_from_query( $count_query, $prepared_for_total_count, $request );
+
+ $pagination['total_results'] = $count_results['total_results'];
+ }
+
+ if ( $pagination['current_page'] > $pagination['total_pages'] && $pagination['total_results'] > 0 ) {
+ return llms_rest_bad_request_error( __( 'The page number requested is larger than the number of pages available.', 'lifterlms' ) );
+ }
+
+ $objects = $this->get_objects_from_query( $query );
+ $items = $this->prepare_collection_items_for_response( $objects, $request );
+
+ $response = rest_ensure_response( $items );
+ $response = $this->add_header_pagination( $response, $pagination, $request );
+
+ return $response;
+
+ }
+
+ /**
+ * Format query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.7
+ * @since 1.0.0-beta.12 Prepare args for search and call collection params to query args map method.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function prepare_collection_query_args( $request ) {
+
+ // Prepare all set args.
+ $registered = $this->get_collection_params();
+ $prepared = array();
+
+ foreach ( $registered as $key => $value ) {
+ if ( isset( $request[ $key ] ) ) {
+ $prepared[ $key ] = $request[ $key ];
+ }
+ }
+
+ $prepared = $this->prepare_collection_query_search_args( $prepared, $request );
+ if ( is_wp_error( $prepared ) ) {
+ return $prepared;
+ }
+
+ $prepared = $this->map_params_to_query_args( $prepared, $registered, $request );
+
+ return $prepared;
+
+ }
+
+ /**
+ * Map schema to query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.12
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param array $registered Registered collection params.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function map_params_to_query_args( $prepared, $registered, $request ) {
+ return $prepared;
+ }
+
+ /**
+ * Format search query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.12
+ * @since 1.0.0-beta.21 Return an error if requesting a list ordered by 'relevance' without providing a search string.
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array|WP_Error
+ */
+ protected function prepare_collection_query_search_args( $prepared, $request ) {
+
+ // Search?
+ if ( ! empty( $prepared['search'] ) ) {
+
+ if ( ! empty( $this->search_columns_mapping ) ) {
+
+ if ( empty( $prepared['search_columns'] ) ) {
+ return llms_rest_bad_request_error( __( 'You must provide a valid set of columns to search into.', 'lifterlms' ) );
+ }
+
+ // Filter search columns by context.
+ $search_columns = array_keys( $this->filter_response_by_context( array_flip( $prepared['search_columns'] ), $request['context'] ) );
+
+ // Check if one of more unallowed search columns have been provided as request query params (not merged with defaults).
+ if ( ! empty( $request->get_query_params()['search_columns'] ) ) {
+
+ $forbidden_columns = array_diff( $prepared['search_columns'], $search_columns );
+
+ if ( ! empty( $forbidden_columns ) ) {
+ return llms_rest_authorization_required_error(
+ sprintf(
+ // Translators: %1$s comma separated list of search columns.
+ __( 'You are not allowed to search into the provided column(s): %1$s', 'lifterlms' ),
+ implode( ',', $forbidden_columns )
+ )
+ );
+ }
+ }
+
+ $prepared['search_columns'] = array();
+
+ // Map our search columns into query compatible ones.
+ foreach ( $search_columns as $search_column ) {
+ if ( isset( $this->search_columns_mapping[ $search_column ] ) ) {
+ $prepared['search_columns'][] = $this->search_columns_mapping[ $search_column ];
+ }
+ }
+
+ if ( empty( $prepared['search_columns'] ) ) {
+ return llms_rest_bad_request_error( __( 'You must provide a valid set of columns to search into.', 'lifterlms' ) );
+ }
+ }
+
+ $prepared['search'] = '*' . $prepared['search'] . '*';
+
+ } else {
+
+ // Ensure a search string is set in case the orderby is set to 'relevance'.
+ if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] ) {
+ return llms_rest_bad_request_error(
+ __( 'You need to define a search term to order by relevance.', 'lifterlms' )
+ );
+ }
+ }
+
+ return $prepared;
+ }
+
+ /**
+ * Prepare query args for total count query.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $args Array of query args.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_args_for_total_count_query( $args, $request ) {
+ // Run the query again without pagination to get a proper total count.
+ unset( $args['paged'], $args['page'] );
+ return $args;
+ }
+
+ /**
+ * Prepare collection items for response.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $objects Array of objects to be prepared for response.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_collection_items_for_response( $objects, $request ) {
+
+ $items = array();
+
+ foreach ( $objects as $object ) {
+ $object = $this->get_object( $object, false );
+
+ if ( ! $this->check_read_object_permissions( $object ) ) {
+ continue;
+ }
+
+ $item = $this->prepare_item_for_response( $object, $request );
+ if ( ! is_wp_error( $item ) ) {
+ $items[] = $this->prepare_response_for_collection( $item );
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Add pagination info and links to the response header.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param WP_REST_Response $response Current response being served.
+ * @param array $pagination Pagination array.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response
+ */
+ protected function add_header_pagination( $response, $pagination, $request ) {
+
+ $response->header( 'X-WP-Total', $pagination['total_results'] );
+ $response->header( 'X-WP-TotalPages', $pagination['total_pages'] );
+
+ $base = add_query_arg( urlencode_deep( $request->get_query_params() ), rest_url( $request->get_route() ) );
+
+ // First page link.
+ if ( 1 !== $pagination['current_page'] ) {
+ $first_link = add_query_arg( 'page', 1, $base );
+ $response->link_header( 'first', $first_link );
+ }
+
+ // Previous page link.
+ if ( $pagination['current_page'] > 1 ) {
+ $prev_page = $pagination['current_page'] - 1;
+ if ( $prev_page > $pagination['total_pages'] ) {
+ $prev_page = $pagination['total_pages'];
+ }
+ $prev_link = add_query_arg( 'page', $prev_page, $base );
+ $response->link_header( 'prev', $prev_link );
+ }
+
+ // Next page link.
+ if ( $pagination['total_pages'] > $pagination['current_page'] ) {
+ $next_link = add_query_arg( 'page', $pagination['current_page'] + 1, $base );
+ $response->link_header( 'next', $next_link );
+ }
+
+ // Last page link.
+ if ( $pagination['total_pages'] && $pagination['total_pages'] !== $pagination['current_page'] ) {
+ $last_link = add_query_arg( 'page', $pagination['total_pages'], $base );
+ $response->link_header( 'last', $last_link );
+ }
+
+ return $response;
+
+ }
+
+ /**
+ * Retrieves the query params for retrieving a single resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_get_item_params() {
+
+ return array(
+ 'context' => $this->get_context_param(
+ array(
+ 'default' => 'view',
+ )
+ ),
+ );
+
+ }
+
+ /**
+ * Retrieve arguments for deleting a resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_delete_item_args() {
+ return array();
+ }
+
+ /**
+ * Map request keys to database keys for insertion.
+ *
+ * Array keys are the request fields (as defined in the schema) and
+ * array values are the database fields.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ protected function map_schema_to_database() {
+
+ $schema = $this->get_item_schema();
+ $keys = array_keys( $schema['properties'] );
+ return array_combine( $keys, $keys );
+
+ }
+
+ /**
+ * Get the LLMS REST resource schema, conforming to JSON Schema.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @return array
+ */
+ public function get_item_schema() {
+
+ if ( isset( $this->schema ) ) {
+ // Additional fields are not cached in the schema, @see https://core.trac.wordpress.org/ticket/47871#comment:5.
+ return $this->add_additional_fields_schema( $this->schema );
+ }
+
+ $schema = $this->get_item_schema_base();
+
+ // Add custom fields registered via `register_meta()`.
+ $schema = $this->add_meta_fields_schema( $schema );
+
+ // Allow the schema to be filtered.
+ $schema = $this->filter_item_schema( $schema );
+
+ /**
+ * Set `$this->schema` here so that we can exclude additional fields
+ * already covered by the base, filtered, schema.
+ *
+ * Additional fields are added through the call to add_additional_fields_schema() below,
+ * which will call {@see LLMS_REST_Controller::get_additional_fields()}, which requires
+ * `$this->schema` to be set.
+ */
+ $this->schema = $schema;
+
+ /**
+ * Adds the schema from additional fields (registered via `register_rest_field()`) to the schema array.
+ * Note: WordPress core doesn't cache the additional fields in the schema, see https://core.trac.wordpress.org/ticket/47871#comment:5
+ */
+ return $this->add_additional_fields_schema( $this->schema );
+
+ }
+
+ /**
+ * Add custom fields registered via `register_meta()`.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param array $schema The resource item schema.
+ * @return array
+ */
+ protected function add_meta_fields_schema( $schema ) {
+
+ if ( ! empty( $this->meta ) ) {
+ $schema['properties']['meta'] = $this->meta->get_field_schema();
+ $schema['properties']['meta']['properties'] = $this->exclude_disallowed_meta_fields(
+ $schema['properties']['meta']['properties'],
+ $schema
+ );
+ }
+
+ return $schema;
+
+ }
+
+ /**
+ * Retrieves all of the registered additional fields for a given object-type.
+ *
+ * Overrides wp core `get_additional_fields()` to allow excluding fields.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param string $object_type The object type.
+ * @return array Registered additional fields (if any), empty array if none or if the object type could
+ * not be inferred.
+ */
+ protected function get_additional_fields( $object_type = null ) {
+
+ // We require the `$this->schema['properties']` to be set in order to exclude additional fields already covered by our schema definition.
+ if ( ! isset( $this->schema['properties'] ) ) {
+ return parent::get_additional_fields( $object_type );
+ }
+
+ $additional_fields = parent::get_additional_fields( $object_type );
+ if ( ! empty( $additional_fields ) ) {
+ /**
+ * Filters the disallowed additional fields.
+ *
+ * The dynamic portion of the hook name, `$object_type`, refers the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param string[] $disallowed_additional_fields Additional rest field names to skip (added via `register_rest_field()`).
+ */
+ $disallowed_fields = apply_filters( "llms_rest_{$object_type}_disallowed_additional_fields", $this->disallowed_additional_fields );
+
+ /**
+ * Exclude:
+ * - disallowed fields defined in the instance's property `disallowed_additional_fields`.
+ * - additional fields already covered in the schema.
+ *
+ * This is meant to run only once, because otherwise we have no way
+ * to determine whether the property comes from the original schema
+ * definition, or has been added via `register_rest_field()`.
+ */
+ $additional_fields = array_diff_key(
+ $additional_fields,
+ array_flip( array_keys( $this->schema['properties'] ) ),
+ array_flip( $disallowed_fields )
+ );
+
+ }
+
+ return $additional_fields;
+
+ }
+
+ /**
+ * Exclude disallowed meta fields.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param array $meta Array of meta fields.
+ * @param array $schema The resource item schema. Falls back on the defined schema if not supplied.
+ * @return array
+ */
+ protected function exclude_disallowed_meta_fields( $meta, $schema = array() ) {
+
+ $schema = empty( $schema ) ? $this->schema : $schema;
+
+ if ( empty( $schema ) || empty( $meta ) ) {
+ return $meta;
+ }
+
+ $object_type = $this->get_object_type( $schema );
+
+ /**
+ * Filters the disallowed meta fields.
+ *
+ * The dynamic portion of the hook name, `$object_type`, refers the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param string[] $disallowed_meta_fields Meta field names to skip (added via `register_meta()`).
+ */
+ $disallowed_meta_fields = apply_filters( "llms_rest_{$object_type}_disallowed_meta_fields", $this->disallowed_meta_fields );
+
+ $meta = array_diff_key(
+ $meta,
+ array_flip( $disallowed_meta_fields )
+ );
+
+ return $meta;
+
+ }
+
+ /**
+ * Get the LLMS REST resource schema base properties, conforming to JSON Schema.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @return array
+ */
+ protected function get_item_schema_base() {
+ return array();
+ }
+
+ /**
+ * Filter the item schema.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param array $schema The resource item schema.
+ * @return array
+ */
+ protected function filter_item_schema( $schema ) {
+
+ $object_type = $this->get_object_type( $schema );
+ $unfiltered_schema = $schema;
+
+ /**
+ * Filters the item schema.
+ *
+ * The dynamic portion of the hook name, `$object_type`, refers the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param array $schema The item schema.
+ */
+ $schema = apply_filters( "llms_rest_{$object_type}_item_schema", $schema );
+
+ /**
+ * Filters whether to allow filtering the item schema to add fields.
+ *
+ * The dynamic portion of the hook name, `$object_type`, refers the object type this controller is responsible for managing.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param bool $allow Whether to allow filtering the item schema to add fields.
+ * @param array $schema The item schema.
+ */
+ if ( ! apply_filters( "llms_rest_allow_filtering_{$object_type}_item_schema_to_add_fields", false, $schema ) ) {
+ // Emit a _doing_it_wrong warning if user tries to add new properties using this filter.
+ $new_fields = array_diff_key( $schema['properties'], $unfiltered_schema['properties'] );
+ if ( count( $new_fields ) > 0 ) {
+ _doing_it_wrong(
+ "llms_rest_{$object_type}_item_schema",
+ sprintf(
+ /* translators: %s: register_rest_field */
+ __( 'Please use %s to add new schema properties.', 'lifterlms' ),
+ 'register_rest_field()'
+ ),
+ '[version]'
+ );
+ }
+ }
+
+ return $schema;
+ }
+
+ /**
+ * Retrieves the resource object-type this controller is responsible for managing.
+ *
+ * Overrides wp core `get_object_type()` to allow passing an item schema to retrieve the type from.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param null|array $schema Optional. The item schema. Default `null`.
+ * @return string
+ */
+ protected function get_object_type( $schema = null ) {
+
+ if ( empty( $schema ) ) {
+ return parent::get_object_type();
+ }
+
+ return $schema['title'];
+
+ }
+
+ /**
+ * Prepare request arguments for a database insert/update.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_Rest_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_item_for_database( $request ) {
+
+ $prepared = array();
+ $map = $this->map_schema_to_database();
+ $schema = $this->get_item_schema();
+
+ foreach ( $map as $req_key => $db_key ) {
+ if ( ! empty( $request[ $req_key ] ) ) {
+ $prepared[ $db_key ] = $request[ $req_key ];
+ }
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Prepares a single object for response.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Return early with a WP_Error if `$object` is a WP_Error.
+ * @since 1.0.0-beta.14 Pass the `$request` parameter to `prepare_links()`.
+ * @since 1.0.0-beta.27 Move big part of the logic to the new method `LLMS_REST_Controller_Stubs::prepare_object_data_for_response()`.
+ *
+ * @param obj $object Raw object from database.
+ * @param WP_REST_Request $request Request object.
+ * @return WP_Error|WP_REST_Response
+ */
+ public function prepare_item_for_response( $object, $request ) {
+
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $data = $this->prepare_object_data_for_response( $object, $request );
+
+ // Wrap the data in a response object.
+ $response = rest_ensure_response( $data );
+
+ // Add links.
+ $response->add_links( $this->prepare_links( $object, $request ) );
+
+ return $response;
+
+ }
+
+ /**
+ * Prepare links for the request.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.14 Added $request parameter.
+ *
+ * @param obj $object Item object.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_links( $object, $request ) {
+
+ $base = rest_url( sprintf( '/%1$s/%2$s', $this->namespace, $this->rest_base ) );
+
+ $links = array(
+ 'self' => array(
+ 'href' => sprintf( '%1$s/%2$d', $base, $this->get_object_id( $object ) ),
+ ),
+ 'collection' => array(
+ 'href' => $base,
+ ),
+ );
+
+ return $links;
+
+ }
+
+ /**
+ * Register routes.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return void
+ */
+ public function register_routes() {
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_items' ),
+ 'permission_callback' => array( $this, 'get_items_permissions_check' ),
+ 'args' => $this->get_collection_params(),
+ ),
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'create_item' ),
+ 'permission_callback' => array( $this, 'create_item_permissions_check' ),
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/(?P[\d]+)',
+ array(
+ 'args' => array(
+ 'id' => array(
+ 'description' => __( 'Unique identifier for the resource.', 'lifterlms' ),
+ 'type' => 'integer',
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_item' ),
+ 'permission_callback' => array( $this, 'get_item_permissions_check' ),
+ 'args' => $this->get_get_item_params(),
+ ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'update_item' ),
+ 'permission_callback' => array( $this, 'update_item_permissions_check' ),
+ 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), // see class-wp-rest-controller.php.
+ ),
+ array(
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => array( $this, 'delete_item' ),
+ 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
+ 'args' => $this->get_delete_item_args(),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ }
+
+ /**
+ * Update item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.12 Call `object_inserted` and `object_completely_inserted` after an object is
+ * respectively inserted in the DB and all its additional fields have been
+ * updated as well (completely inserted).
+ * @since 1.0.0-beta.27 Handle custom meta registered via `register_meta()` and custom rest fields registered via `register_rest_field()`.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response|WP_Error Response object or WP_Error on failure.
+ */
+ public function update_item( $request ) {
+
+ $object = $this->get_object( $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $schema = $this->get_item_schema();
+ $item = $this->prepare_item_for_database( $request );
+ // Exclude additional fields registered via `register_rest_field()`.
+ $item = array_diff_key( $item, $this->get_additional_fields() );
+ $object = $this->update_object( $item, $request );
+
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $this->object_inserted( $object, $request, $schema, false );
+
+ // Registered via `register_meta()`.
+ $meta_update = $this->update_meta( $object, $request, $schema );
+ if ( is_wp_error( $meta_update ) ) {
+ return $meta_update;
+ }
+
+ // Registered via `register_rest_field()`.
+ $fields_update = $this->update_additional_fields_for_object( $object, $request );
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
+ }
+
+ $this->object_completely_inserted( $object, $request, $schema, false );
+
+ $request->set_param( 'context', 'edit' );
+
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ return $response;
+
+ }
+
+ /**
+ * Update meta.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param object $object Inserted or updated object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @return void|WP_Error
+ */
+ protected function update_meta( $object, $request, $schema ) {
+
+ if ( empty( $schema['properties']['meta'] ) || empty( $request['meta'] ) ) {
+ return;
+ }
+
+ $request['meta'] = $this->exclude_disallowed_meta_fields( $request['meta'] );
+
+ if ( empty( $request['meta'] ) ) {
+ return;
+ }
+
+ $meta_update = $this->meta->update_value( $request['meta'], $this->get_object_id( $object ) );
+
+ if ( is_wp_error( $meta_update ) ) {
+ return $meta_update;
+ }
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-database-resource.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-database-resource.php
new file mode 100644
index 0000000000..f16dec60f4
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-database-resource.php
@@ -0,0 +1,273 @@
+create_prepare( $data );
+ if ( is_wp_error( $data ) ) {
+ return $data;
+ }
+
+ return $this->save( new $this->model(), $data );
+
+ }
+
+ /**
+ * Prepare data for creation.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $data Array of data.
+ * @return array
+ */
+ public function create_prepare( $data ) {
+
+ if ( ! empty( $data['id'] ) ) {
+ // Translators: %s = name of the resource type (for example: "API Key").
+ return new WP_Error( 'llms_rest_' . $this->id . '_exists', sprintf( __( 'Cannot create a new %s with a pre-defined ID.', 'lifterlms' ), $this->get_i18n_name() ) );
+ }
+
+ // Merge in default values.
+ $data = wp_parse_args( array_filter( $data ), $this->get_default_column_values() );
+
+ // Required Fields.
+ foreach ( $this->required_columns as $key ) {
+
+ if ( empty( $data[ $key ] ) ) {
+ return new WP_Error(
+ 'llms_rest_' . $this->id . '_missing_' . $key,
+ // Translators: %1$s = name of the resource type; %2$s = field name.
+ sprintf( __( '%1$s "%2$s" is required.', 'lifterlms' ), $this->get_i18n_name(), $key )
+ );
+ }
+ }
+
+ $err = $this->is_data_valid( $data );
+ if ( is_wp_error( $err ) ) {
+ return $err;
+ }
+
+ return $data;
+
+ }
+
+ /**
+ * Delete a the resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $id Resource ID.
+ * @return bool `true` on success, `false` if the resource couldn't be found or an error was encountered during deletion.
+ */
+ public function delete( $id ) {
+ $obj = $this->get( $id, false );
+ if ( $obj ) {
+ return $obj->delete();
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve an API Key object instance.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $id API Key ID.
+ * @param bool $hydrate If true, pulls all key data from the database on instantiation.
+ * @return obj|false
+ */
+ public function get( $id, $hydrate = true ) {
+ $obj = new $this->model( $id, $hydrate );
+ if ( $obj && $obj->exists() ) {
+ return $obj;
+ }
+ return false;
+ }
+
+ /**
+ * Get default column values.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_default_column_values() {
+
+ /**
+ * Allow customization of default Resource values.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $values An associative array of default values.
+ */
+ return apply_filters( 'llms_rest_' . $this->id . '_default_properties', $this->default_column_values );
+
+ }
+
+ /**
+ * Retrieve the translated resource name.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ protected function get_i18n_name() {
+ return __( 'Resource', 'lifterlms' );
+ }
+
+ /**
+ * Update a resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $data {
+ * Array of data to update.
+ *
+ * @type int $id (Required). Resource ID.
+ * }
+ * @return [type]
+ */
+ public function update( $data ) {
+
+ if ( empty( $data['id'] ) ) {
+ // Translators: %s = name of the resource type (for example: "API Key").
+ return new WP_Error( 'llms_rest_' . $this->id . '_missing_id', sprintf( __( 'No %s ID was supplied.', 'lifterlms' ), $this->get_i18n_name() ) );
+ }
+
+ $obj = $this->get( $data['id'] );
+ if ( ! $obj || ! $obj->exists() ) {
+ // Translators: %s = name of the resource type (for example: "API Key").
+ return new WP_Error( 'llms_rest_' . $this->id . '_invalid_' . $this->id, sprintf( __( 'The requested %s could not be located.', 'lifterlms' ), $this->get_i18n_name() ) );
+ }
+
+ $data = $this->update_prepare( $data );
+ if ( is_wp_error( $data ) ) {
+ return $data;
+ }
+
+ return $this->save( $obj, $data );
+
+ }
+
+ /**
+ * Prepare data for an update.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $data Associative array of data to set to a resources properties.
+ * @return object|WP_Error
+ */
+ protected function update_prepare( $data ) {
+
+ // Filter out write-protected keys.
+ $data = array_diff_key(
+ $data,
+ array_fill_keys( $this->read_only_columns, false )
+ );
+
+ $err = $this->is_data_valid( $data );
+ if ( is_wp_error( $err ) ) {
+ return $err;
+ }
+
+ return $data;
+
+ }
+
+ /**
+ * Persist data.
+ *
+ * This method assumes the supplied data has already been validated and sanitized.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $obj Instantiated object.
+ * @param array $data Associative array of data to persist.
+ * @return obj
+ */
+ protected function save( $obj, $data ) {
+
+ $obj->setup( $data )->save();
+ return $obj;
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-posts-controller.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-posts-controller.php
new file mode 100644
index 0000000000..e2a87f0b3e
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-posts-controller.php
@@ -0,0 +1,1854 @@
+set_bulk()` when there's no data to update.
+ * Fix wp:featured_media link, we don't expose any embeddable field.
+ * Also `self` and `collection` links prepared in the parent class.
+ * Added `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks:
+ * fired after inserting/updating an llms post into the database.
+ * @since 1.0.0-beta.8 Return links to those taxonomies which have an accessible rest route.
+ * Initialize `$prepared_item` array before adding values to it.
+ * @since 1.0.0-beta.9 Implemented a generic way to create and get an llms post object instance given a `post_type`.
+ * In `get_objects_from_query()` avoid performing an additional query, just return the already retrieved posts.
+ * Removed `"llms_rest_{$this->post_type}_filters_removed_for_response"` filter hooks,
+ * `"llms_rest_{$this->post_type}_filters_removed_for_response"` added.
+ * @since 1.0.0-beta.11 Fixed `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks fourth param:
+ * must be false when updating.
+ * @since 1.0.0-beta.12 Moved parameters to query args mapping from `$this->prepare_collection_params()` to `$this->map_params_to_query_args()`.
+ * @since 1.0.0-beta.14 Update `prepare_links()` to accept a second parameter, `WP_REST_Request`.
+ * @since 1.0.0-beta.21 Enable search.
+ */
+abstract class LLMS_REST_Posts_Controller extends LLMS_REST_Controller {
+
+ /**
+ * Post type.
+ *
+ * @var string
+ */
+ protected $post_type;
+
+ /**
+ * Route base.
+ *
+ * @var string
+ */
+ protected $collection_route_base_for_pagination;
+
+ /**
+ * Schema properties available for ordering the collection.
+ *
+ * @var string[]
+ */
+ protected $orderby_properties = array(
+ 'id',
+ 'title',
+ 'date_created',
+ 'date_updated',
+ 'menu_order',
+ 'relevance',
+ );
+
+ /**
+ * Whether search is allowed
+ *
+ * @var boolean
+ */
+ protected $is_searchable = true;
+
+ /**
+ * LLMS post class name.
+ *
+ * @since 1.0.0-beta.9
+ *
+ * @var string
+ */
+ protected $llms_post_class;
+
+ /**
+ * Constructor.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @return void
+ */
+ public function __construct() {
+ $this->meta = new WP_REST_Post_Meta_Fields( $this->post_type );
+ }
+
+ /**
+ * Retrieves an array of arguments for the delete endpoint.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array Delete endpoint arguments.
+ */
+ public function get_delete_item_args() {
+
+ return array(
+ 'force' => array(
+ 'description' => __( 'Bypass the trash and force course deletion.', 'lifterlms' ),
+ 'type' => 'boolean',
+ 'default' => false,
+ ),
+ );
+
+ }
+
+ /**
+ * Retrieves the query params for retrieving a single resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_get_item_params() {
+
+ $params = parent::get_get_item_params();
+ $schema = $this->get_item_schema();
+
+ if ( isset( $schema['properties']['password'] ) ) {
+ $params['password'] = array(
+ 'description' => __( 'Post password. Required if the post is password protected.', 'lifterlms' ),
+ 'type' => 'string',
+ );
+ }
+
+ return $params;
+
+ }
+
+ /**
+ * Determine if the current user can view the object.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param object $object Object.
+ * @return bool
+ */
+ protected function check_read_object_permissions( $object ) {
+ return $this->check_read_permission( $object );
+ }
+
+ /**
+ * Check if a given request has access to read items.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_items_permissions_check( $request ) {
+
+ // Everybody can list llms posts (in read mode).
+ if ( 'edit' === $request['context'] && ! $this->check_update_permission() ) {
+ return llms_rest_authorization_required_error();
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Retrieve pagination information from an objects query.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param WP_Query $query Objects query result returned by {@see LLMS_REST_Posts_Controller::get_objects_query()}.
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array {
+ * Array of pagination information.
+ *
+ * @type int $current_page Current page number.
+ * @type int $total_results Total number of results.
+ * @type int $total_pages Total number of results pages.
+ * }
+ */
+ protected function get_pagination_data_from_query( $query, $prepared, $request ) {
+
+ $total_results = (int) $query->found_posts;
+ $current_page = isset( $prepared['paged'] ) ? (int) $prepared['paged'] : 1;
+ $total_pages = (int) ceil( $total_results / (int) $query->get( 'posts_per_page' ) );
+
+ return compact( 'current_page', 'total_results', 'total_pages' );
+
+ }
+
+ /**
+ * Check if a given request has access to create an item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Use plural post type name.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function create_item_permissions_check( $request ) {
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->name;
+
+ if ( ! empty( $request['id'] ) ) {
+ // Translators: %s = The post type name.
+ return llms_rest_bad_request_error( sprintf( __( 'Cannot create existing %s.', 'lifterlms' ), $post_type_name ) );
+ }
+
+ if ( ! $this->check_create_permission() ) {
+ // Translators: %s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to create %s as this user.', 'lifterlms' ), $post_type_name ) );
+ }
+
+ if ( ! $this->check_assign_terms_permission( $request ) ) {
+ return llms_rest_authorization_required_error( __( 'Sorry, you are not allowed to assign the provided terms.', 'lifterlms' ) );
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Creates a single LLMS post.
+ *
+ * Extending classes can add additional object fields by overriding the method `update_additional_object_fields()`.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.7 Added `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks:
+ * fired after inserting/updating an llms post into the database.
+ * @since 1.0.0-beta.25 Allow updating meta with the same value as the stored one.
+ * @since 1.0.0-beta.27 Handle custom meta registered via `register_meta()` and custom rest fields registered via `register_rest_field()`.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function create_item( $request ) {
+
+ $schema = $this->get_item_schema();
+
+ $prepared_item = $this->prepare_item_for_database( $request );
+ if ( is_wp_error( $prepared_item ) ) {
+ return $prepared_item;
+ }
+
+ $prepared_item = array_diff_key( $prepared_item, $this->get_additional_fields() );
+ $object = $this->create_llms_post( $prepared_item );
+ if ( is_wp_error( $object ) ) {
+
+ if ( 'db_insert_error' === $object->get_error_code() ) {
+ $object->add_data( array( 'status' => 500 ) );
+ } else {
+ $object->add_data( array( 'status' => 400 ) );
+ }
+
+ return $object;
+ }
+
+ /**
+ * Fires after a single llms post is created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_insert_{$this->post_type}", $object, $request, $schema, true );
+
+ // Set all the other properties.
+ // TODO: maybe we want to filter the post properties that have already been inserted before.
+ $set_bulk_result = $object->set_bulk( $prepared_item, true, true );
+ if ( is_wp_error( $set_bulk_result ) ) {
+
+ if ( 'db_update_error' === $set_bulk_result->get_error_code() ) {
+ $set_bulk_result->add_data( array( 'status' => 500 ) );
+ } else {
+ $set_bulk_result->add_data( array( 'status' => 400 ) );
+ }
+
+ return $set_bulk_result;
+ }
+
+ $object_id = $object->get( 'id' );
+
+ $additional_fields = $this->update_additional_object_fields( $object, $request, $schema, $prepared_item );
+ if ( is_wp_error( $additional_fields ) ) {
+ return $additional_fields;
+ }
+
+ if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
+ $this->handle_featured_media( $request['featured_media'], $object_id );
+ }
+
+ $terms_update = $this->handle_terms( $object_id, $request );
+ if ( is_wp_error( $terms_update ) ) {
+ return $terms_update;
+ }
+
+ $meta_update = $this->update_meta( $object, $request, $schema );
+ if ( is_wp_error( $meta_update ) ) {
+ return $meta_update;
+ }
+
+ // Fields registered via `register_rest_field()`.
+ $fields_update = $this->update_additional_fields_for_object( $object, $request );
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
+ }
+
+ $request->set_param( 'context', 'edit' );
+
+ /**
+ * Fires after a single llms post is completely created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_after_insert_{$this->post_type}", $object, $request, $schema, true );
+
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ $response->set_status( 201 );
+
+ $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $object_id ) ) );
+
+ return $response;
+ }
+
+ /**
+ * Check if a given request has access to read an item.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_item_permissions_check( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ if ( 'edit' === $request['context'] && ! $this->check_update_permission( $object ) ) {
+ return llms_rest_authorization_required_error();
+ }
+
+ if ( ! empty( $request['password'] ) ) {
+ // Check post password, and return error if invalid.
+ if ( ! hash_equals( $object->get( 'password' ), $request['password'] ) ) {
+ return llms_rest_authorization_required_error( __( 'Incorrect password.', 'lifterlms' ) );
+ }
+ }
+
+ // Allow access to all password protected posts if the context is edit.
+ if ( 'edit' === $request['context'] ) {
+ add_filter( 'post_password_required', '__return_false' );
+ }
+
+ if ( ! $this->check_read_permission( $object ) ) {
+ return llms_rest_authorization_required_error();
+ }
+
+ return true;
+ }
+
+ /**
+ * Retrieves the query params for the objects collection
+ *
+ * @since 1.0.0-beta.19
+ *
+ * @return array Collection parameters.
+ */
+ public function get_collection_params() {
+
+ $query_params = parent::get_collection_params();
+ $schema = $this->get_item_schema();
+
+ if ( isset( $schema['properties']['status'] ) ) {
+ $query_params['status'] = array(
+ 'default' => 'publish',
+ 'description' => __( 'Limit result set to posts assigned one or more statuses.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'enum' => array_merge(
+ array_keys(
+ get_post_stati()
+ ),
+ array(
+ 'any',
+ )
+ ),
+ 'type' => 'string',
+ ),
+ 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),
+ );
+ }
+
+ return $query_params;
+
+ }
+
+ /**
+ * Format query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.7
+ * @since 1.0.0-beta.12 Moved parameters to query args mapping into a different method.
+ * @since 1.0.0-beta.18 Correctly return errors.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function prepare_collection_query_args( $request ) {
+
+ $prepared = parent::prepare_collection_query_args( $request );
+ if ( is_wp_error( $prepared ) ) {
+ return $prepared;
+ }
+
+ // Force the post_type argument, since it's not a user input variable.
+ $prepared['post_type'] = $this->post_type;
+
+ $query_args = $this->prepare_items_query( $prepared, $request );
+
+ return $query_args;
+
+ }
+
+ /**
+ * Map schema to query arguments to retrieve a collection of objects.
+ *
+ * @since 1.0.0-beta.12
+ * @since 1.0.0-beta.19 Map 'status' collection param to to 'post_status' query arg.
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param array $registered Registered collection params.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array|WP_Error
+ */
+ protected function map_params_to_query_args( $prepared, $registered, $request ) {
+
+ $args = array();
+
+ /*
+ * This array defines mappings between public API query parameters whose
+ * values are accepted as-passed, and their internal WP_Query parameter
+ * name equivalents (some are the same). Only values which are also
+ * present in $registered will be set.
+ */
+ $parameter_mappings = array(
+ 'order' => 'order',
+ 'orderby' => 'orderby',
+ 'page' => 'paged',
+ 'exclude' => 'post__not_in',
+ 'include' => 'post__in',
+ 'search' => 's',
+ 'status' => 'post_status',
+ );
+
+ /*
+ * For each known parameter which is both registered and present in the request,
+ * set the parameter's value on the query $args.
+ */
+ foreach ( $parameter_mappings as $api_param => $wp_param ) {
+ if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
+ $args[ $wp_param ] = $request[ $api_param ];
+ }
+ }
+
+ // Ensure our per_page parameter overrides any provided posts_per_page filter.
+ if ( isset( $registered['per_page'] ) ) {
+ $args['posts_per_page'] = $request['per_page'];
+ }
+
+ return $args;
+ }
+
+ /**
+ * Check if a given request has access to update an item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Use plural post type name.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function update_item_permissions_check( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->name;
+
+ if ( ! $this->check_update_permission( $object ) ) {
+ // Translators: %s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to update %s as this user.', 'lifterlms' ), $post_type_name ) );
+ }
+
+ if ( ! $this->check_assign_terms_permission( $request ) ) {
+ return llms_rest_authorization_required_error( __( 'Sorry, you are not allowed to assign the provided terms.', 'lifterlms' ) );
+ }
+
+ return true;
+ }
+
+ /**
+ * Updates a single llms post.
+ *
+ * Extending classes can add additional object fields by overriding the method `update_additional_object_fields()`.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.7 Don't execute `$object->set_bulk()` when there's no data to update:
+ * this fixes an issue when updating only properties which are not handled in `prepare_item_for_database()`.
+ * Added `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks:
+ * fired after inserting/updating an llms post into the database.
+ * @since 1.0.0-beta.11 Fixed `"llms_rest_insert_{$this->post_type}"` and `"llms_rest_insert_{$this->post_type}"` action hooks fourth param:
+ * must be false when updating.
+ * @since 1.0.0-beta.25 Allow updating meta with the same value as the stored one.
+ * @since 1.0.0-beta.27 Handle custom meta registered via `register_meta()` and custom rest fields registered via `register_rest_field()`.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function update_item( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $schema = $this->get_item_schema();
+ $prepared_item = $this->prepare_item_for_database( $request );
+
+ if ( is_wp_error( $prepared_item ) ) {
+ return $prepared_item;
+ }
+ $prepared_item = array_diff_key( $prepared_item, $this->get_additional_fields() );
+ $update_result = empty( array_diff_key( $prepared_item, array_flip( array( 'id' ) ) ) ) ? false : $object->set_bulk( $prepared_item, true, true );
+ if ( is_wp_error( $update_result ) ) {
+
+ if ( 'db_update_error' === $update_result->get_error_code() ) {
+ $update_result->add_data( array( 'status' => 500 ) );
+ } else {
+ $update_result->add_data( array( 'status' => 400 ) );
+ }
+
+ return $update_result;
+ }
+
+ /**
+ * Fires after a single llms post is created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_insert_{$this->post_type}", $object, $request, $schema, false );
+
+ $additional_fields = $this->update_additional_object_fields( $object, $request, $schema, $prepared_item, false );
+ if ( is_wp_error( $additional_fields ) ) {
+ return $additional_fields;
+ }
+
+ $object_id = $object->get( 'id' );
+
+ if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
+ $this->handle_featured_media( $request['featured_media'], $object_id );
+ }
+
+ $terms_update = $this->handle_terms( $object_id, $request );
+ if ( is_wp_error( $terms_update ) ) {
+ return $terms_update;
+ }
+
+ $meta_update = $this->update_meta( $object, $request, $schema );
+ if ( is_wp_error( $meta_update ) ) {
+ return $meta_update;
+ }
+
+ // Fields registered via `register_rest_field()`.
+ $fields_update = $this->update_additional_fields_for_object( $object, $request );
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
+ }
+
+ $request->set_param( 'context', 'edit' );
+
+ /**
+ * Fires after a single llms post is completely created or updated via the REST API.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param LLMS_Post $object Inserted or updated llms object.
+ * @param WP_REST_Request $request Request object.
+ * @param array $schema The item schema.
+ * @param bool $creating True when creating a post, false when updating.
+ */
+ do_action( "llms_rest_after_insert_{$this->post_type}", $object, $request, $schema, false );
+
+ return $this->prepare_item_for_response( $object, $request );
+
+ }
+
+ /**
+ * Updates a single llms post.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.7 return description updated.
+ *
+ * @param LLMS_Post_Model $object LMMS_Post_Model instance.
+ * @param array $prepared_item Array.
+ * @param WP_REST_Request $request Full details about the request.
+ * @param array $schema The item schema.
+ * @return bool|WP_Error True on success or false if nothing to update, WP_Error object if something went wrong during the update.
+ */
+ protected function update_additional_object_fields( $object, $prepared_item, $request, $schema ) {
+ return true;
+ }
+
+ /**
+ * Check if a given request has access to delete an item.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Provide a more significant error message when trying to delete an item without permissions.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return bool|WP_Error
+ */
+ public function delete_item_permissions_check( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ // LLMS_Post not found, we don't return a 404.
+ if ( in_array( 'llms_rest_not_found', $object->get_error_codes(), true ) ) {
+ return true;
+ }
+
+ return $object;
+ }
+
+ if ( ! $this->check_delete_permission( $object ) ) {
+ return llms_rest_authorization_required_error(
+ sprintf(
+ // Translators: %s = The post type name.
+ __( 'Sorry, you are not allowed to delete %s as this user.', 'lifterlms' ),
+ get_post_type_object( $this->post_type )->labels->name
+ )
+ );
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Deletes a single llms post.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+ */
+ public function delete_item( $request ) {
+
+ $object = $this->get_object( (int) $request['id'] );
+ $response = new WP_REST_Response();
+ $response->set_status( 204 );
+
+ if ( is_wp_error( $object ) ) {
+ // Course not found, we don't return a 404.
+ if ( in_array( 'llms_rest_not_found', $object->get_error_codes(), true ) ) {
+ return $response;
+ }
+
+ return $object;
+ }
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->singular_name;
+
+ $id = $object->get( 'id' );
+ $force = $this->is_delete_forced( $request );
+
+ // If we're forcing, then delete permanently.
+ if ( $force ) {
+ $result = wp_delete_post( $id, true );
+ } else {
+
+ $supports_trash = $this->is_trash_supported();
+
+ // If we don't support trashing for this type, error out.
+ if ( ! $supports_trash ) {
+ return new WP_Error(
+ 'llms_rest_trash_not_supported',
+ /* translators: %1$s: post type name, %2$s: force=true */
+ sprintf( __( 'The %1$s does not support trashing. Set \'%2$s\' to delete.', 'lifterlms' ), $post_type_name, 'force=true' ),
+ array( 'status' => 501 )
+ );
+ }
+
+ // Otherwise, only trash if we haven't already.
+ if ( 'trash' !== $object->get( 'status' ) ) {
+ // (Note that internally this falls through to `wp_delete_post` if
+ // the trash is disabled.)
+ $result = wp_trash_post( $id );
+ } else {
+ $result = true;
+ }
+
+ $request->set_param( 'context', 'edit' );
+ $object = $this->get_object( $id );
+ $response = $this->prepare_item_for_response( $object, $request );
+
+ }
+
+ if ( ! $result ) {
+ return new WP_Error(
+ 'llms_rest_cannot_delete',
+ /* translators: %s: post type name */
+ sprintf( __( 'The %s cannot be deleted.', 'lifterlms' ), $post_type_name ),
+ array( 'status' => 500 )
+ );
+ }
+
+ return $response;
+
+ }
+
+ /**
+ * Whether the delete should be forced.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return bool True if the delete should be forced, false otherwise.
+ */
+ protected function is_delete_forced( $request ) {
+ return isset( $request['force'] ) && (bool) $request['force'];
+ }
+
+ /**
+ * Whether the trash is supported.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return bool True if the trash is supported, false otherwise.
+ */
+ protected function is_trash_supported() {
+ return ( EMPTY_TRASH_DAYS > 0 );
+ }
+
+
+ /**
+ * Retrieve a query object based on arguments from a `get_items()` (collection) request.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Query
+ */
+ protected function get_objects_query( $prepared, $request ) {
+
+ return new WP_Query( $prepared );
+
+ }
+
+ /**
+ * Retrieve an array of objects from the result of `$this->get_objects_query()`.
+ *
+ * @since 1.0.0-beta.7
+ * @since 1.0.0-beta.9 Avoid performing an additional query, just return the already retrieved posts.
+ *
+ * @param WP_Query $query WP_Query query result.
+ * @return WP_Post[]
+ */
+ protected function get_objects_from_query( $query ) {
+
+ return $query->posts;
+
+ }
+
+ /**
+ * Prepare collection items for response.
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param array $objects Array of objects to be prepared for response.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_collection_items_for_response( $objects, $request ) {
+
+ $items = array();
+
+ // Allow access to all password protected posts if the context is edit.
+ if ( 'edit' === $request['context'] ) {
+ add_filter( 'post_password_required', '__return_false' );
+ }
+
+ $items = parent::prepare_collection_items_for_response( $objects, $request );
+
+ // Reset filter.
+ if ( 'edit' === $request['context'] ) {
+ remove_filter( 'post_password_required', '__return_false' );
+ }
+
+ return $items;
+
+ }
+
+ /**
+ * Prepare a single object output for response.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object object object.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array
+ */
+ protected function prepare_object_for_response( $object, $request ) {
+
+ $object_id = $object->get( 'id' );
+ $password_required = post_password_required( $object_id );
+ $password = $object->get( 'password' );
+
+ $data = array(
+ 'id' => $object->get( 'id' ),
+ 'date_created' => $object->get_date( 'date', 'Y-m-d H:i:s' ),
+ 'date_created_gmt' => $object->get_date( 'date_gmt', 'Y-m-d H:i:s' ),
+ 'date_updated' => $object->get_date( 'modified', 'Y-m-d H:i:s' ),
+ 'date_updated_gmt' => $object->get_date( 'modified_gmt', 'Y-m-d H:i:s' ),
+ 'menu_order' => $object->get( 'menu_order' ),
+ 'title' => array(
+ 'raw' => $object->get( 'title', true ),
+ 'rendered' => $object->get( 'title' ),
+ ),
+ 'password' => $password,
+ 'slug' => $object->get( 'name' ),
+ 'post_type' => $this->post_type,
+ 'permalink' => get_permalink( $object_id ),
+ 'status' => $object->get( 'status' ),
+ 'featured_media' => (int) get_post_thumbnail_id( $object_id ),
+ 'comment_status' => $object->get( 'comment_status' ),
+ 'ping_status' => $object->get( 'ping_status' ),
+ 'content' => array(
+ 'raw' => $object->get( 'content', true ),
+ 'rendered' => $password_required ? '' : apply_filters( 'the_content', $object->get( 'content', true ) ),
+ 'protected' => (bool) $password,
+ ),
+ 'excerpt' => array(
+ 'raw' => $object->get( 'excerpt', true ),
+ 'rendered' => $password_required ? '' : apply_filters( 'the_excerpt', $object->get( 'excerpt' ) ),
+ 'protected' => (bool) $password,
+ ),
+ );
+
+ return $data;
+
+ }
+
+ /**
+ * Prepares data of a single object for response.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param obj $object Raw object from database.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_object_data_for_response( $object, $request ) {
+
+ // Need to set the global $post because of references to the global $post when e.g. filtering the content, or processing blocks/shortcodes.
+ global $post;
+ $temp = $post;
+ $post = $object->get( 'post' ); // phpcs:ignore
+ setup_postdata( $post );
+
+ $removed_filters_for_response = $this->maybe_remove_filters_for_response( $object );
+
+ $has_password_filter = false;
+
+ if ( $this->can_access_password_content( $object, $request ) ) {
+ // Allow access to the post, permissions already checked before.
+ add_filter( 'post_password_required', '__return_false' );
+ $has_password_filter = true;
+ }
+
+ $data = parent::prepare_object_data_for_response( $object, $request );
+
+ // Filter data including only schema props.
+ $data = array_intersect_key( $data, array_flip( $this->get_fields_for_response( $request ) ) );
+
+ if ( $has_password_filter ) {
+ // Reset filter.
+ remove_filter( 'post_password_required', '__return_false' );
+ }
+
+ $this->maybe_add_removed_filters_for_response( $removed_filters_for_response );
+ $post = $temp; // phpcs:ignore
+ wp_reset_postdata();
+
+ return $data;
+
+ }
+
+ /**
+ * Determines the allowed query_vars for a get_items() response and prepares
+ * them for WP_Query.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
+ * @param WP_REST_Request $request Optional. Full details about the request.
+ * @return array Items query arguments.
+ */
+ protected function prepare_items_query( $prepared_args = array(), $request = null ) {
+
+ $query_args = array();
+
+ foreach ( $prepared_args as $key => $value ) {
+ $query_args[ $key ] = $value;
+ }
+
+ $query_args = $this->prepare_items_query_orderby_mappings( $query_args, $request );
+
+ // Turn exclude and include params into proper arrays.
+ foreach ( array( 'post__in', 'post__not_in' ) as $arg ) {
+ if ( isset( $query_args[ $arg ] ) && ! is_array( $query_args[ $arg ] ) ) {
+ $query_args[ $arg ] = array_map( 'absint', explode( ',', $query_args[ $arg ] ) );
+ }
+ }
+
+ return $query_args;
+
+ }
+
+ /**
+ * Map to proper WP_Query orderby param.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $query_args WP_Query arguments.
+ * @param WP_REST_Request $request Full details about the request.
+ * @return array Query arguments.
+ */
+ protected function prepare_items_query_orderby_mappings( $query_args, $request ) {
+
+ // Map to proper WP_Query orderby param.
+ if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
+ $orderby_mappings = array(
+ 'id' => 'ID',
+ 'title' => 'title',
+ 'data_created' => 'post_date',
+ 'date_updated' => 'post_modified',
+ );
+
+ if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
+ $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
+ }
+ }
+
+ return $query_args;
+
+ }
+
+ /**
+ * Prepares a single post for create or update.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.8 Initialize `$prepared_item` array before adding values to it.
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return array|WP_Error Array of llms post args or WP_Error.
+ */
+ protected function prepare_item_for_database( $request ) {
+
+ $prepared_item = array();
+
+ // LLMS Post ID.
+ if ( isset( $request['id'] ) ) {
+ $existing_object = $this->get_object( absint( $request['id'] ) );
+ if ( is_wp_error( $existing_object ) ) {
+ return $existing_object;
+ }
+
+ $prepared_item['id'] = absint( $request['id'] );
+ }
+
+ $schema = $this->get_item_schema();
+
+ // LLMS Post title.
+ if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
+ if ( is_string( $request['title'] ) ) {
+ $prepared_item['post_title'] = $request['title'];
+ } elseif ( ! empty( $request['title']['raw'] ) ) {
+ $prepared_item['post_title'] = $request['title']['raw'];
+ }
+ }
+
+ // LLMS Post content.
+ if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
+ if ( is_string( $request['content'] ) ) {
+ $prepared_item['post_content'] = $request['content'];
+ } elseif ( isset( $request['content']['raw'] ) ) {
+ $prepared_item['post_content'] = $request['content']['raw'];
+ }
+ }
+
+ // LLMS Post excerpt.
+ if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
+ if ( is_string( $request['excerpt'] ) ) {
+ $prepared_item['post_excerpt'] = $request['excerpt'];
+ } elseif ( isset( $request['excerpt']['raw'] ) ) {
+ $prepared_item['post_excerpt'] = $request['excerpt']['raw'];
+ }
+ }
+
+ // LLMS Post status.
+ if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) {
+ $status = $this->handle_status_param( $request['status'] );
+ if ( is_wp_error( $status ) ) {
+ return $status;
+ }
+
+ $prepared_item['post_status'] = $status;
+ }
+
+ // LLMS Post date.
+ if ( ! empty( $schema['properties']['date_created'] ) && ! empty( $request['date_created'] ) ) {
+ $date_data = rest_get_date_with_gmt( $request['date_created'] );
+
+ if ( ! empty( $date_data ) ) {
+ list( $prepared_item['post_date'], $prepared_item['post_date_gmt'] ) = $date_data;
+ $prepared_item['edit_date'] = true;
+ }
+ } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) {
+ $date_data = rest_get_date_with_gmt( $request['date_created_gmt'], true );
+
+ if ( ! empty( $date_data ) ) {
+ list( $prepared_item['post_date'], $prepared_item['post_date_gmt'] ) = $date_data;
+ $prepared_item['edit_date'] = true;
+ }
+ }
+
+ // LLMS Post slug.
+ if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) {
+ $prepared_item['post_name'] = $request['slug'];
+ }
+
+ // LLMS Post password.
+ if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) ) {
+ $prepared_item['post_password'] = $request['password'];
+ }
+
+ // LLMS Post Menu order.
+ if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
+ $prepared_item['menu_order'] = (int) $request['menu_order'];
+ }
+
+ // LLMS Post Comment status.
+ if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
+ $prepared_item['comment_status'] = $request['comment_status'];
+ }
+
+ // LLMS Post Ping status.
+ if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
+ $prepared_item['ping_status'] = $request['ping_status'];
+ }
+
+ return $prepared_item;
+
+ }
+
+ /**
+ * Get the LLMS Posts's schema, conforming to JSON Schema.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @return array
+ */
+ protected function get_item_schema_base() {
+
+ $schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => $this->post_type,
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'Unique Identifier. The WordPress Post ID.', 'lifterlms' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'date_created' => array(
+ 'description' => __( 'Creation date. Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'date_created_gmt' => array(
+ 'description' => __( 'Creation date (in GMT). Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'date_updated' => array(
+ 'description' => __( 'Date last modified. Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'date_updated_gmt' => array(
+ 'description' => __( 'Date last modified (in GMT). Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'menu_order' => array(
+ 'description' => __( 'Creation date (in GMT). Format: Y-m-d H:i:s', 'lifterlms' ),
+ 'type' => 'integer',
+ 'default' => 0,
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'absint',
+ ),
+ ),
+ 'title' => array(
+ 'description' => __( 'Post title.', 'lifterlms' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
+ 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
+ ),
+ 'required' => true,
+ 'properties' => array(
+ 'raw' => array(
+ 'description' => __( 'Raw title. Useful when displaying title in the WP Block Editor. Only returned in edit context.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'rendered' => array(
+ 'description' => __( 'Rendered title.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'content' => array(
+ 'type' => 'object',
+ 'description' => __( 'The HTML content of the post.', 'lifterlms' ),
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
+ 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
+ ),
+ 'required' => true,
+ 'properties' => array(
+ 'rendered' => array(
+ 'description' => __( 'Rendered HTML content.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'raw' => array(
+ 'description' => __( 'Raw HTML content. Useful when displaying title in the WP Block Editor. Only returned in edit context.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'protected' => array(
+ 'description' => __( 'Whether the content is protected with a password.', 'lifterlms' ),
+ 'type' => 'boolean',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'excerpt' => array(
+ 'type' => 'object',
+ 'description' => __( 'The HTML excerpt of the post.', 'lifterlms' ),
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
+ 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
+ ),
+ 'properties' => array(
+ 'rendered' => array(
+ 'description' => __( 'Rendered HTML excerpt.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'raw' => array(
+ 'description' => __( 'Raw HTML excerpt. Useful when displaying title in the WP Block Editor. Only returned in edit context.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'protected' => array(
+ 'description' => __( 'Whether the excerpt is protected with a password.', 'lifterlms' ),
+ 'type' => 'boolean',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ ),
+ ),
+ 'permalink' => array(
+ 'description' => __( 'Post URL.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'slug' => array(
+ 'description' => __( 'Post URL slug.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => array( $this, 'sanitize_slug' ),
+ ),
+ ),
+ 'post_type' => array(
+ 'description' => __( 'LifterLMS custom post type', 'lifterlms' ),
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'status' => array(
+ 'description' => __( 'The publication status of the post.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'publish',
+ 'enum' => array_keys(
+ get_post_stati(
+ array(
+ '_builtin' => true,
+ 'internal' => false,
+ )
+ )
+ ),
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'password' => array(
+ 'description' => __( 'Password used to protect access to the content.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ ),
+ 'featured_media' => array(
+ 'description' => __( 'Featured image ID.', 'lifterlms' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'comment_status' => array(
+ 'description' => __( 'Post comment status. Default comment status dependent upon general WordPress post discussion settings.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'open',
+ 'enum' => array( 'open', 'closed' ),
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'ping_status' => array(
+ 'description' => __( 'Post ping status. Default ping status dependent upon general WordPress post discussion settings.', 'lifterlms' ),
+ 'type' => 'string',
+ 'default' => 'open',
+ 'enum' => array( 'open', 'closed' ),
+ 'context' => array( 'view', 'edit' ),
+ ),
+ ),
+ );
+
+ return $schema;
+
+ }
+
+ /**
+ * Add custom fields registered via `register_meta`.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @param array $schema The resource item schema.
+ * @return array
+ */
+ protected function add_meta_fields_schema( $schema ) {
+ return post_type_supports( $this->post_type, 'custom-fields' ) ? parent::add_meta_fields_schema( $schema ) : $schema;
+ }
+
+ /**
+ * Get object.
+ *
+ * @since 1.0.0-beta.9
+ *
+ * @param int $id Object ID.
+ * @return LLMS_Course|WP_Error
+ */
+ protected function get_object( $id ) {
+
+ $class = $this->llms_post_class_from_post_type();
+
+ if ( ! $class ) {
+ return new WP_Error(
+ 'llms_rest_cannot_get_object',
+ /* translators: %s: post type */
+ sprintf( __( 'The %s cannot be retrieved.', 'lifterlms' ), $this->post_type ),
+ array( 'status' => 500 )
+ );
+ }
+
+ $object = llms_get_post( $id );
+ return $object && is_a( $object, $class ) ? $object : llms_rest_not_found_error();
+ }
+
+ /**
+ * Create an LLMS_Post_Model
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Implement generic llms post creation.
+ *
+ * @param array $object_args Object args.
+ * @return LLMS_Post_Model|WP_Error
+ */
+ protected function create_llms_post( $object_args ) {
+
+ $class = $this->llms_post_class_from_post_type();
+
+ if ( ! $class ) {
+ return new WP_Error(
+ 'llms_rest_cannot_create_object',
+ /* translators: %s: post type */
+ sprintf( __( 'The %s cannot be created.', 'lifterlms' ), $this->post_type ),
+ array( 'status' => 500 )
+ );
+ }
+
+ $object = new $class( 'new', $object_args );
+ return $object && is_a( $object, $class ) ? $object : llms_rest_not_found_error();
+ }
+
+ /**
+ * Prepare links for the request.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.2 Filter taxonomies by `public` property instead of `show_in_rest`.
+ * @since 1.0.0-beta.3 Filter taxonomies by `show_in_llms_rest` property instead of `public`.
+ * @since 1.0.0-beta.7 `self` and `collection` links prepared in the parent class.
+ * Fix wp:featured_media link, we don't expose any embeddable field.
+ * @since 1.0.0-beta.8 Return links to those taxonomies which have an accessible rest route.
+ * @since 1.0.0-beta.14 Added $request parameter.
+ *
+ * @param LLMS_Post_Model $object Object data.
+ * @param WP_REST_Request $request Request object.
+ * @return array Links for the given object.
+ */
+ protected function prepare_links( $object, $request ) {
+
+ $links = parent::prepare_links( $object, $request );
+
+ $object_id = $object->get( 'id' );
+
+ // Content.
+ $links['content'] = array(
+ 'href' => rest_url( sprintf( '/%s/%s/%d/%s', $this->namespace, $this->rest_base, $object_id, 'content' ) ),
+ );
+
+ // If we have a featured media, add that.
+ $featured_media = get_post_thumbnail_id( $object_id );
+ if ( $featured_media ) {
+ $image_url = rest_url( 'wp/v2/media/' . $featured_media );
+
+ $links['https://api.w.org/featuredmedia'] = array(
+ 'href' => $image_url,
+ );
+ }
+
+ $taxonomies = get_object_taxonomies( $this->post_type );
+
+ if ( ! empty( $taxonomies ) ) {
+ $links['https://api.w.org/term'] = array();
+
+ foreach ( $taxonomies as $tax ) {
+ $taxonomy_obj = get_taxonomy( $tax );
+
+ // Skip taxonomies that are not set to be shown in REST and LLMS REST.
+ if ( empty( $taxonomy_obj->show_in_rest ) || empty( $taxonomy_obj->show_in_llms_rest ) ) {
+ continue;
+ }
+
+ $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
+
+ $terms_url = add_query_arg(
+ 'post',
+ $object_id,
+ rest_url( 'wp/v2/' . $tax_base )
+ );
+
+ $links['https://api.w.org/term'][] = array(
+ 'href' => $terms_url,
+ 'taxonomy' => $tax,
+ );
+ }
+ }
+
+ return $links;
+
+ }
+
+ /**
+ * Re-add filters previously removed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object Object.
+ * @return array Array of filters removed for response.
+ */
+ protected function maybe_remove_filters_for_response( $object ) {
+
+ $filters_to_be_removed = $this->get_filters_to_be_removed_for_response( $object );
+ $filters_removed = array();
+
+ // Need to remove some filters.
+ foreach ( $filters_to_be_removed as $hook => $filters ) {
+ foreach ( $filters as $filter_data ) {
+ $has_filter = has_filter( $hook, $filter_data['callback'] );
+
+ if ( false !== $has_filter && $filter_data['priority'] === $has_filter ) {
+ remove_filter( $hook, $filter_data['callback'], $filter_data['priority'] );
+ if ( ! isset( $filters_removed[ $hook ] ) ) {
+ $filters_removed[ $hook ] = array();
+ }
+ $filters_removed[ $hook ][] = $filter_data;
+
+ }
+ }
+ }
+
+ return $filters_removed;
+
+ }
+
+ /**
+ * Re-add filters previously removed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $filters_removed Array of filters removed to be re-added.
+ * @return void
+ */
+ protected function maybe_add_removed_filters_for_response( $filters_removed ) {
+
+ if ( ! empty( $filters_removed ) ) {
+ foreach ( $filters_removed as $hook => $filters ) {
+ foreach ( $filters as $filter_data ) {
+ add_filter(
+ $hook,
+ $filter_data['callback'],
+ $filter_data['priority'],
+ isset( $filter_data['accepted_args'] ) ? $filter_data['accepted_args'] : 1
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Get action/filters to be removed before preparing the item for response.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.9 Removed `"llms_rest_{$this->post_type}_filters_removed_for_reponse"` filter hooks,
+ * `"llms_rest_{$this->post_type}_filters_removed_for_response"` added.
+ *
+ * @param LLMS_Post_Model $object LLMS_Post_Model object.
+ * @return array Array of action/filters to be removed for response.
+ */
+ protected function get_filters_to_be_removed_for_response( $object ) {
+
+ /**
+ * Modify the array of filters to be removed before building the response.
+ *
+ * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
+ *
+ * @since 1.0.0-beta.9
+ *
+ * @param array $filters Array of filters to be removed.
+ * @param LLMS_Post_Model $object LLMS_Post_Model object.
+ */
+ return apply_filters( "llms_rest_{$this->post_type}_filters_removed_for_response", array(), $object );
+
+ }
+
+ /**
+ * Determines validity and normalizes the given status parameter.
+ * Heavily based on WP_REST_Posts_Controller::handle_status_param().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Use plural post type name.
+ *
+ * @param string $status Status.
+ * @return string|WP_Error Status or WP_Error if lacking the proper permission.
+ */
+ protected function handle_status_param( $status ) {
+
+ $post_type_object = get_post_type_object( $this->post_type );
+ $post_type_name = $post_type_object->labels->name;
+
+ switch ( $status ) {
+ case 'draft':
+ case 'pending':
+ break;
+ case 'private':
+ if ( ! current_user_can( $post_type_object->cap->publish_posts ) ) {
+ // Translators: %s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to create private %s.', 'lifterlms' ), $post_type_name ) );
+ }
+ break;
+ case 'publish':
+ case 'future':
+ if ( ! current_user_can( $post_type_object->cap->publish_posts ) ) {
+ // Translators: $s = The post type name.
+ return llms_rest_authorization_required_error( sprintf( __( 'Sorry, you are not allowed to publish %s.', 'lifterlms' ), $post_type_name ) );
+ }
+ break;
+ default:
+ if ( ! get_post_status_object( $status ) ) {
+ $status = 'draft';
+ }
+ break;
+ }
+
+ return $status;
+ }
+
+ /**
+ * Determines the featured media based on a request param
+ *
+ * Heavily based on WP_REST_Posts_Controller::handle_featured_media().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.18 Fixed call to undefined function `llms_bad_request_error()`, must be `llms_rest_bad_request_error()`.
+ *
+ * @param int $featured_media Featured Media ID.
+ * @param int $object_id LLMS object ID.
+ * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error.
+ */
+ protected function handle_featured_media( $featured_media, $object_id ) {
+
+ $featured_media = (int) $featured_media;
+ if ( $featured_media ) {
+ $result = set_post_thumbnail( $object_id, $featured_media );
+ if ( $result ) {
+ return true;
+ } else {
+ return llms_rest_bad_request_error( __( 'Invalid featured media ID.', 'lifterlms' ) );
+ }
+ } else {
+ return delete_post_thumbnail( $object_id );
+ }
+
+ }
+
+ /**
+ * Updates the post's terms from a REST request.
+ *
+ * Heavily based on WP_REST_Posts_Controller::handle_terms().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.2 Filter taxonomies by `public` property instead of `show_in_rest`.
+ * @since 1.0.0-beta.3 Filter taxonomies by `show_in_llms_rest` property instead of `public`.
+ *
+ * @param int $object_id The post ID to update the terms form.
+ * @param WP_REST_Request $request The request object with post and terms data.
+ * @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null.
+ */
+ protected function handle_terms( $object_id, $request ) {
+
+ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_llms_rest' => true ) );
+
+ foreach ( $taxonomies as $taxonomy ) {
+ $base = $this->get_taxonomy_rest_base( $taxonomy );
+
+ if ( ! isset( $request[ $base ] ) ) {
+ continue;
+ }
+
+ // We could use LLMS_Post_Model::set_terms() but it doesn't return a WP_Error which can be useful here.
+ $result = wp_set_object_terms( $object_id, $request[ $base ], $taxonomy->name );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ }
+ }
+
+ /**
+ * Checks whether current user can assign all terms sent with the current request.
+ *
+ * Heavily based on WP_REST_Posts_Controller::check_assign_terms_permission().
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Filter taxonomies by `show_in_llms_rest` property instead of `public`.
+ *
+ * @param WP_REST_Request $request The request object with post and terms data.
+ * @return bool Whether the current user can assign the provided terms.
+ */
+ protected function check_assign_terms_permission( $request ) {
+ $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_llms_rest' => true ) );
+ foreach ( $taxonomies as $taxonomy ) {
+ $base = $this->get_taxonomy_rest_base( $taxonomy );
+
+ if ( ! isset( $request[ $base ] ) ) {
+ continue;
+ }
+
+ foreach ( $request[ $base ] as $term_id ) {
+ // Invalid terms will be rejected later.
+ if ( ! get_term( $term_id, $taxonomy->name ) ) {
+ continue;
+ }
+
+ if ( ! current_user_can( 'assign_term', (int) $term_id ) ) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Maps a taxonomy name to the relative rest base
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param object $taxonomy The taxonomy object.
+ * @return string The taxonomy rest base.
+ */
+ protected function get_taxonomy_rest_base( $taxonomy ) {
+
+ return ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
+
+ }
+
+ /**
+ * Checks if a post can be edited.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return bool Whether the post can be created
+ */
+ protected function check_create_permission() {
+
+ $post_type = get_post_type_object( $this->post_type );
+ return current_user_can( $post_type->cap->publish_posts );
+
+ }
+
+ /**
+ * Checks if an llms post can be edited.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object Optional. The LLMS_Post_model object. Default null.
+ * @return bool Whether the post can be edited.
+ */
+ protected function check_update_permission( $object = null ) {
+
+ $post_type = get_post_type_object( $this->post_type );
+ return is_null( $object ) ? current_user_can( $post_type->cap->edit_posts ) : current_user_can( $post_type->cap->edit_post, $object->get( 'id' ) );
+
+ }
+
+ /**
+ * Checks if an llms post can be deleted.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object The LLMS_Post_model object.
+ * @return bool Whether the post can be deleted.
+ */
+ protected function check_delete_permission( $object ) {
+
+ $post_type = get_post_type_object( $this->post_type );
+ return current_user_can( $post_type->cap->delete_post, $object->get( 'id' ) );
+
+ }
+
+ /**
+ * Checks if an llms post can be read.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0 Fix fatals when searching for llms post type based resources
+ * but the query post type parameter is forced to be something else.
+ *
+ * @param LLMS_Post_Model $object The LLMS_Post_model object.
+ * @return bool Whether the post can be read.
+ */
+ protected function check_read_permission( $object ) {
+
+ if ( is_wp_error( $object ) ) {
+ return false;
+ }
+
+ $post_type = get_post_type_object( $this->post_type );
+ $status = $object->get( 'status' );
+ $id = $object->get( 'id' );
+ $wp_post = $object->get( 'post' );
+
+ // Is the post readable?
+ if ( 'publish' === $status || current_user_can( $post_type->cap->read_post, $id ) ) {
+ return true;
+ }
+
+ $post_status_obj = get_post_status_object( $status );
+ if ( $post_status_obj && $post_status_obj->public ) {
+ return true;
+ }
+
+ // Can we read the parent if we're inheriting?
+ if ( 'inherit' === $status && $wp_post->post_parent > 0 ) {
+ $parent = get_post( $wp_post->post_parent );
+ if ( $parent ) {
+ return $this->check_read_permission( $parent );
+ }
+ }
+
+ /*
+ * If there isn't a parent, but the status is set to inherit, assume
+ * it's published (as per get_post_status()).
+ */
+ if ( 'inherit' === $status ) {
+ return true;
+ }
+
+ return false;
+
+ }
+
+
+ /**
+ * Checks if the user can access password-protected content.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param LLMS_Post_Model $object The LLMS_Post_model object.
+ * @param WP_REST_Request $request Request data to check.
+ * @return bool True if the user can access password-protected content, otherwise false.
+ */
+ public function can_access_password_content( $object, $request ) {
+
+ if ( empty( $object->get( 'password' ) ) ) {
+ // No filter required.
+ return false;
+ }
+
+ // Edit context always gets access to password-protected posts.
+ if ( 'edit' === $request['context'] ) {
+ return true;
+ }
+
+ // No password, no auth.
+ if ( empty( $request['password'] ) ) {
+ return false;
+ }
+
+ // Double-check the request password.
+ return hash_equals( $object->get( 'password' ), $request['password'] );
+ }
+
+ /**
+ * Get the llms post model class from the controller post type.
+ *
+ * @since 1.0.0-beta.9
+ *
+ * @return string|bool The llms post model class name if it exists or FALSE if it doesn't.
+ */
+ protected function llms_post_class_from_post_type() {
+
+ if ( isset( $this->llms_post_class ) ) {
+ return $this->llms_post_class;
+ }
+
+ $post_type = explode( '_', str_replace( 'llms_', '', $this->post_type ) );
+ $class = 'LLMS';
+
+ foreach ( $post_type as $part ) {
+ $class .= '_' . ucfirst( $part );
+ }
+
+ if ( class_exists( $class ) ) {
+ $this->llms_post_class = $class;
+ } else {
+ $this->llms_post_class = false;
+ }
+
+ return $this->llms_post_class;
+ }
+
+ /**
+ * Sanitizes and validates the list of post statuses, including whether the user can query private statuses
+ *
+ * Heavily based on the WordPress WP_REST_Posts_Controller::sanitize_post_statuses().
+ *
+ * @since 1.0.0-beta.19
+ *
+ * @param string|array $statuses One or more post statuses.
+ * @param WP_REST_Request $request Full details about the request.
+ * @param string $parameter Additional parameter to pass to validation.
+ * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
+ */
+ public function sanitize_post_statuses( $statuses, $request, $parameter ) {
+ $statuses = wp_parse_slug_list( $statuses );
+
+ $attributes = $request->get_attributes();
+ $default_status = $attributes['args']['status']['default'];
+
+ foreach ( $statuses as $status ) {
+ if ( $status === $default_status ) {
+ continue;
+ }
+
+ $post_type_obj = get_post_type_object( $this->post_type );
+
+ if ( current_user_can( $post_type_obj->cap->edit_posts ) || 'private' === $status && current_user_can( $post_type_obj->cap->read_private_posts ) ) {
+ $result = rest_validate_request_arg( $status, $request, $parameter );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ } else {
+ return llms_rest_authorization_required_error( __( 'Status is forbidden.', 'lifterlms' ) );
+ }
+ }
+
+ return $statuses;
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-users-controller.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-users-controller.php
new file mode 100644
index 0000000000..57e664442f
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-users-controller.php
@@ -0,0 +1,788 @@
+ 'ID',
+ 'username' => 'user_login',
+ 'email' => 'user_email',
+ 'url' => 'user_url',
+ 'name' => 'display_name',
+ );
+
+ /**
+ * Constructor.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @return void
+ */
+ public function __construct() {
+ $this->meta = new WP_REST_User_Meta_Fields();
+ }
+
+ /**
+ * Determine if the current user has permissions to manage the role(s) present in a request
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return true|WP_Error
+ */
+ protected function check_roles_permissions( $request ) {
+
+ global $wp_roles;
+
+ $schema = $this->get_item_schema();
+ $roles = array();
+ if ( ! empty( $request['roles'] ) ) {
+ $roles = $request['roles'];
+ } elseif ( ! empty( $schema['properties']['roles']['default'] ) ) {
+ $roles = $schema['properties']['roles']['default'];
+ }
+
+ foreach ( $roles as $role ) {
+
+ if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
+ // Translators: %s = role key.
+ return llms_rest_bad_request_error( sprintf( __( 'The role %s does not exist.', 'lifterlms' ), $role ) );
+ }
+
+ $potential_role = $wp_roles->role_objects[ $role ];
+
+ /*
+ * Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
+ * Multisite super admins can freely edit their blog roles -- they possess all caps.
+ */
+ if ( ! ( is_multisite()
+ && current_user_can( 'manage_sites' ) )
+ && get_current_user_id() === $request['id']
+ && ! $potential_role->has_cap( 'edit_users' )
+ ) {
+ return llms_rest_authorization_required_error( __( 'You are not allowed to give users this role.', 'lifterlms' ) );
+ }
+
+ // Include admin functions to get access to `get_editable_roles()`.
+ require_once ABSPATH . 'wp-admin/includes/admin.php';
+
+ // The new role must be editable by the logged-in user.
+ $editable_roles = get_editable_roles();
+
+ if ( empty( $editable_roles[ $role ] ) ) {
+ return llms_rest_authorization_required_error( __( 'You are not allowed to give users this role.', 'lifterlms' ) );
+ }
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Insert the prepared data into the database
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return obj Object Instance of object from `$this->get_object()`.
+ */
+ protected function create_object( $prepared, $request ) {
+
+ $object_id = wp_insert_user( $prepared );
+
+ if ( is_wp_error( $object_id ) ) {
+ return $object_id;
+ }
+
+ return $this->update_additional_data( $object_id, $prepared, $request );
+
+ }
+
+
+ /**
+ * Delete the object
+ *
+ * Note: we do not return 404s when the resource to delete cannot be found. We assume it's already been deleted and respond with 204.
+ * Errors returned by this method should be any error other than a 404!
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $object Instance of the object from `$this->get_object()`.
+ * @param WP_REST_Request $request Request object.
+ * @return true|WP_Error `true` when the object is removed, `WP_Error` on failure.
+ */
+ protected function delete_object( $object, $request ) {
+
+ $id = $object->get( 'id' );
+ $reassign = 0 === $request['reassign'] ? null : $request['reassign'];
+
+ if ( ! empty( $reassign ) ) {
+ if ( $reassign === $id || ! get_userdata( $reassign ) ) {
+ return llms_rest_bad_request_error( __( 'Invalid user ID for reassignment.', 'lifterlms' ) );
+ }
+ }
+
+ // Include admin user functions to get access to `wp_delete_user()`.
+ require_once ABSPATH . 'wp-admin/includes/user.php';
+
+ $result = wp_delete_user( $id, $reassign );
+
+ if ( ! $result ) {
+ return llms_rest_server_error( __( 'The user could not be deleted.', 'lifterlms' ) );
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Determine if the current user can view the object
+ *
+ * @since 1.0.0-beta.7
+ *
+ * @param object $object Object.
+ * @return bool
+ */
+ protected function check_read_object_permissions( $object ) {
+ return $this->check_read_item_permissions( $this->get_object_id( $object ) );
+ }
+
+ /**
+ * Retrieves the query params for the objects collection
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array Collection parameters.
+ */
+ public function get_collection_params() {
+
+ $params = parent::get_collection_params();
+
+ $params['roles'] = array(
+ 'description' => __( 'Include only users keys matching matching a specific role. Accepts a single role or a comma separated list of roles.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => $this->get_enum_roles(),
+ ),
+ );
+
+ return $params;
+
+ }
+
+ /**
+ * Retrieve arguments for deleting a resource
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_delete_item_args() {
+ return array(
+ 'reassign' => array(
+ 'type' => 'integer',
+ 'description' => __( 'Reassign the deleted user\'s posts and links to this user ID.', 'lifterlms' ),
+ 'default' => 0,
+ 'sanitize_callback' => 'absint',
+ ),
+ );
+ }
+
+ /**
+ * Retrieve an array of allowed user role values.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string[]
+ */
+ protected function get_enum_roles() {
+
+ global $wp_roles;
+ return array_keys( $wp_roles->roles );
+
+ }
+
+ /**
+ * Get the item schema base.
+ *
+ * @since 1.0.0-beta.27
+ *
+ * @return array
+ */
+ protected function get_item_schema_base() {
+
+ $schema = array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => $this->resource_name,
+ 'type' => 'object',
+ 'properties' => array(
+ 'id' => array(
+ 'description' => __( 'Unique identifier for the user.', 'lifterlms' ),
+ 'type' => 'integer',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'username' => array(
+ 'description' => __( 'Login name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => array( $this, 'sanitize_username' ),
+ ),
+ ),
+ 'name' => array(
+ 'description' => __( 'Display name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'first_name' => array(
+ 'description' => __( 'First name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'last_name' => array(
+ 'description' => __( 'Last name for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'email' => array(
+ 'description' => __( 'The email address for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'email',
+ 'context' => array( 'edit' ),
+ 'required' => true,
+ ),
+ 'url' => array(
+ 'description' => __( 'URL of the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'description' => array(
+ 'description' => __( 'Description of the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ 'nickname' => array(
+ 'description' => __( 'The nickname for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'registered_date' => array(
+ 'description' => __( 'Registration date for the user.', 'lifterlms' ),
+ 'type' => 'string',
+ 'format' => 'date-time',
+ 'context' => array( 'edit' ),
+ 'readonly' => true,
+ ),
+ 'roles' => array(
+ 'description' => __( 'Roles assigned to the user.', 'lifterlms' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ 'enum' => $this->get_enum_roles(),
+ ),
+ 'context' => array( 'edit' ),
+ 'default' => array( 'student' ),
+ ),
+ 'password' => array(
+ 'description' => __( 'Password for the user (never included).', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array(), // Password is never displayed.
+ 'arg_options' => array(
+ 'sanitize_callback' => array( $this, 'sanitize_password' ),
+ ),
+ ),
+ 'billing_address_1' => array(
+ 'description' => __( 'User address line 1.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_address_2' => array(
+ 'description' => __( 'User address line 2.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_city' => array(
+ 'description' => __( 'User address city name.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_state' => array(
+ 'description' => __( 'User address ISO code for the state, province, or district.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_postcode' => array(
+ 'description' => __( 'User address postal code.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ 'billing_country' => array(
+ 'description' => __( 'User address ISO code for the country.', 'lifterlms' ),
+ 'type' => 'string',
+ 'context' => array( 'edit' ),
+ 'arg_options' => array(
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ ),
+ ),
+ );
+
+ if ( get_option( 'show_avatars' ) ) {
+
+ $avatar_properties = array();
+ foreach ( rest_get_avatar_sizes() as $size ) {
+ $avatar_properties[ $size ] = array(
+ // Translators: %d = avatar image size in pixels.
+ 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.', 'lifterlms' ), $size ),
+ 'type' => 'string',
+ 'format' => 'uri',
+ 'context' => array( 'view', 'edit' ),
+ );
+ }
+
+ $schema['properties']['avatar_urls'] = array(
+ 'description' => __( 'Avatar URLs for the user.', 'lifterlms' ),
+ 'type' => 'object',
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ 'properties' => $avatar_properties,
+ );
+
+ }
+
+ return $schema;
+
+ }
+
+ /**
+ * Retrieve a query object based on arguments from a `get_items()` (collection) request
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.12 Parse `search` and `search_columns` args.
+ *
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return WP_User_Query
+ */
+ protected function get_objects_query( $prepared, $request ) {
+
+ if ( 'id' === $prepared['orderby'] ) {
+ $prepared['orderby'] = 'ID';
+ } elseif ( 'registered_date' === $prepared['orderby'] ) {
+ $prepared['orderby'] = 'registered';
+ }
+
+ $args = array(
+ 'paged' => $prepared['page'],
+ 'number' => $prepared['per_page'],
+ 'order' => strtoupper( $prepared['order'] ),
+ 'orderby' => $prepared['orderby'],
+ );
+
+ if ( ! empty( $prepared['roles'] ) ) {
+ $args['role__in'] = $prepared['roles'];
+ }
+
+ if ( ! empty( $prepared['include'] ) ) {
+ $args['include'] = $prepared['include'];
+ }
+
+ if ( ! empty( $prepared['exclude'] ) ) {
+ $args['exclude'] = $prepared['exclude'];
+ }
+
+ if ( ! empty( $prepared['search'] ) ) {
+ $args['search'] = $prepared['search'];
+ }
+
+ if ( ! empty( $prepared['search_columns'] ) ) {
+ $args['search_columns'] = $prepared['search_columns'];
+ }
+
+ return new WP_User_Query( $args );
+
+ }
+
+
+ /**
+ * Retrieve an array of objects from the result of `$this->get_objects_query()`
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param obj $query Objects query result.
+ * @return WP_User[]
+ */
+ protected function get_objects_from_query( $query ) {
+ return $query->get_results();
+ }
+
+ /**
+ * Retrieve pagination information from an objects query.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_User_Query $query Objects query result returned by {@see LLMS_REST_Users_Controller::get_objects_query()}.
+ * @param array $prepared Array of collection arguments.
+ * @param WP_REST_Request $request Request object.
+ * @return array {
+ * Array of pagination information.
+ *
+ * @type int $current_page Current page number.
+ * @type int $total_results Total number of results.
+ * @type int $total_pages Total number of results pages.
+ * }
+ */
+ protected function get_pagination_data_from_query( $query, $prepared, $request ) {
+
+ $current_page = absint( $prepared['page'] );
+ $total_results = $query->get_total();
+ $total_pages = absint( ceil( $total_results / $prepared['per_page'] ) );
+
+ return compact( 'current_page', 'total_results', 'total_pages' );
+
+ }
+
+ /**
+ * Map request keys to database keys for insertion
+ *
+ * Array keys are the request fields (as defined in the schema) and
+ * array values are the database fields.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.11 Correctly map request's `billing_postcode` param to `billing_zip` meta.
+ *
+ * @return array
+ */
+ protected function map_schema_to_database() {
+
+ $map = parent::map_schema_to_database();
+
+ $map['username'] = 'user_login';
+ $map['password'] = 'user_pass';
+ $map['name'] = 'display_name';
+ $map['email'] = 'user_email';
+ $map['url'] = 'user_url';
+ $map['registered_date'] = 'user_registered';
+ $map['billing_postcode'] = 'billing_zip';
+
+ // Not inserted/read via database calls.
+ unset( $map['roles'], $map['avatar_urls'] );
+
+ return $map;
+
+ }
+
+ /**
+ * Prepare request arguments for a database insert/update
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_Rest_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_item_for_database( $request ) {
+
+ $prepared = parent::prepare_item_for_database( $request );
+
+ // If we're creating a new item, maybe add some defaults.
+ if ( empty( $prepared['id'] ) ) {
+
+ // Pass an explicit false to `wp_insert_user()`.
+ $prepared['role'] = false;
+
+ if ( empty( $prepared['user_pass'] ) ) {
+ $prepared['user_pass'] = wp_generate_password( 22 );
+ }
+
+ if ( empty( $prepared['user_login'] ) ) {
+ $prepared['user_login'] = LLMS_Person_Handler::generate_username( $prepared['user_email'] );
+ }
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Prepare an object for response
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.14 Only add remapped keys to the response when the schema key is present in the expected response fields array.
+ *
+ * @param LLMS_Abstract_User_Data $object User object.
+ * @param WP_REST_Request $request Request object.
+ * @return array
+ */
+ protected function prepare_object_for_response( $object, $request ) {
+
+ $prepared = array();
+ $map = array_flip( $this->map_schema_to_database() );
+ $fields = $this->get_fields_for_response( $request );
+
+ // Write Only.
+ unset( $map['user_pass'] );
+
+ foreach ( $map as $db_key => $schema_key ) {
+ if ( in_array( $schema_key, $fields, true ) ) {
+ $prepared[ $schema_key ] = $object->get( $db_key );
+ }
+ }
+
+ if ( in_array( 'roles', $fields, true ) ) {
+ $prepared['roles'] = $object->get_user()->roles;
+ }
+
+ if ( in_array( 'avatar_urls', $fields, true ) ) {
+ $prepared['avatar_urls'] = rest_get_avatar_urls( $object->get( 'user_email' ) );
+ }
+
+ return $prepared;
+
+ }
+
+ /**
+ * Validate a username is valid and allowed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $value User-submitted username.
+ * @param WP_REST_Request $request Request object.
+ * @param string $param Parameter name.
+ * @return WP_Error|string Sanitized username if valid or error object.
+ */
+ public function sanitize_password( $value, $request, $param ) {
+
+ $password = (string) $value;
+
+ if ( false !== strpos( $password, '\\' ) ) {
+ return llms_rest_bad_request_error( __( 'Passwords cannot contain the "\\" character.', 'lifterlms' ) );
+ }
+
+ // @todo: Should validate against password strength too, maybe?
+
+ return $password;
+
+ }
+
+ /**
+ * Validate a username is valid and allowed
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $value User-submitted username.
+ * @param WP_REST_Request $request Request object.
+ * @param string $param Parameter name.
+ * @return WP_Error|string Sanitized username if valid or error object.
+ */
+ public function sanitize_username( $value, $request, $param ) {
+
+ $username = (string) $value;
+
+ if ( ! validate_username( $username ) ) {
+ return llms_rest_bad_request_error( __( 'Username contains invalid characters.', 'lifterlms' ) );
+ }
+
+ /**
+ * Filter defined in WP Core.
+ *
+ * @link https://developer.wordpress.org/reference/hooks/illegal_user_logins/
+ *
+ * @param array $illegal_logins Array of banned usernames.
+ */
+ $illegal_logins = (array) apply_filters( 'illegal_user_logins', array() );
+ if ( in_array( strtolower( $username ), array_map( 'strtolower', $illegal_logins ), true ) ) {
+ return llms_rest_bad_request_error( __( 'Username is not allowed.', 'lifterlms' ) );
+ }
+
+ return $username;
+
+ }
+
+ /**
+ * Updates additional information not handled by WP Core insert/update user functions
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.10 Fixed setting roles instead of appending them.
+ * @since 1.0.0-beta.11 Made sure to set user's meta with the correct db key.
+ *
+ * @param int $object_id WP User id.
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return LLMS_Abstract_User_Data|WP_error
+ */
+ protected function update_additional_data( $object_id, $prepared, $request ) {
+
+ $object = $this->get_object( $object_id );
+
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ $metas = array(
+ 'billing_address_1',
+ 'billing_address_2',
+ 'billing_city',
+ 'billing_state',
+ 'billing_postcode',
+ 'billing_country',
+ );
+
+ $map = $this->map_schema_to_database();
+
+ foreach ( $metas as $meta ) {
+ if ( ! empty( $map[ $meta ] ) && ! empty( $prepared[ $map[ $meta ] ] ) ) {
+ $object->set( $map[ $meta ], $prepared[ $map[ $meta ] ] );
+ }
+ }
+
+ if ( ! empty( $request['roles'] ) ) {
+ $user = $object->get_user();
+ $user->set_role( '' );
+ foreach ( $request['roles'] as $role ) {
+ $user->add_role( $role );
+ }
+ }
+
+ return $object;
+
+ }
+
+ /**
+ * Update item
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param WP_REST_Request $request Request object.
+ * @return WP_REST_Response|WP_Error Response object or `WP_Error` on failure.
+ */
+ public function update_item( $request ) {
+
+ $object = $this->get_object( $request['id'] );
+ if ( is_wp_error( $object ) ) {
+ return $object;
+ }
+
+ // Ensure we're not trying to update the email to an email that already exists.
+ $owner_id = email_exists( $request['email'] );
+
+ if ( $owner_id && $owner_id !== $request['id'] ) {
+ return llms_rest_bad_request_error( __( 'Invalid email address.', 'lifterlms' ) );
+ }
+
+ // Cannot change a username.
+ if ( ! empty( $request['username'] ) && $request['username'] !== $object->get( 'user_login' ) ) {
+ return llms_rest_bad_request_error( __( 'Username is not editable.', 'lifterlms' ) );
+ }
+
+ return parent::update_item( $request );
+
+ }
+
+ /**
+ * Update the object in the database with prepared data
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $prepared Prepared item data.
+ * @param WP_REST_Request $request Request object.
+ * @return obj Object Instance of object from `$this->get_object()`.
+ */
+ protected function update_object( $prepared, $request ) {
+
+ $prepared['ID'] = $prepared['id'];
+
+ $object_id = wp_update_user( $prepared );
+ if ( is_wp_error( $object_id ) ) {
+ return $object_id;
+ }
+
+ unset( $prepared['ID'] );
+
+ return $this->update_additional_data( $object_id, $prepared, $request );
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-webhook-data.php b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-webhook-data.php
new file mode 100644
index 0000000000..b064d9c743
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/class-llms-rest-webhook-data.php
@@ -0,0 +1,323 @@
+ format
+ *
+ * @var string[]
+ */
+ protected $columns = array(
+
+ 'status' => '%s',
+ 'name' => '%s',
+ 'delivery_url' => '%s',
+ 'secret' => '%s',
+ 'topic' => '%s',
+ 'user_id' => '%d',
+ 'created' => '%s',
+ 'updated' => '%s',
+ 'failure_count' => '%d',
+
+ );
+
+ /**
+ * Database Table Name
+ *
+ * @var string
+ */
+ protected $table = 'webhooks';
+
+ /**
+ * The record type
+ *
+ * Used for filters/actions.
+ *
+ * @var string
+ */
+ protected $type = 'webhook';
+
+ /**
+ * Constructor
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $id API Key ID.
+ * @param bool $hydrate If true, hydrates the object on instantiation if an ID is supplied.
+ */
+ public function __construct( $id = null, $hydrate = true ) {
+
+ $this->id = $id;
+ if ( $this->id && $hydrate ) {
+ $this->hydrate();
+ }
+
+ // Adds created and updated dates on instantiation.
+ parent::__construct();
+
+ }
+
+
+ /**
+ * Retrieve an admin nonce url for deleting an API key.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_delete_link() {
+
+ return add_query_arg(
+ array(
+ 'section' => 'webhooks',
+ 'delete-webhook' => $this->get( 'id' ),
+ 'delete-webhook-nonce' => wp_create_nonce( 'delete' ),
+ ),
+ LLMS_REST_API()->keys()->get_admin_url()
+ );
+
+ }
+
+ /**
+ * Generate a delivery signature from a delivery payload.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $payload JSON-encoded payload.
+ * @return string
+ */
+ public function get_delivery_signature( $payload ) {
+
+ /**
+ * Allow overriding of signature generation.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $signature Custom signature. Return a string to replace the default signature.
+ * @param string $payload JSON-encoded body to be delivered.
+ * @param int $id Webhook id.
+ */
+ $signature = apply_filters( 'llms_rest_webhook_signature_pre', null, $payload, $this->get( 'id' ) );
+ if ( $signature && is_string( $signature ) ) {
+ return $signature;
+ }
+
+ /**
+ * Customize the hash algorithm used to generate the webhook delivery signature.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string $algo Hash algorithm. Defaults to 'sha256'. List of supported algorithms available at https://www.php.net/manual/en/function.hash-hmac-algos.php.
+ * @param string $payload JSON-encoded body to be delivered.
+ * @param int $id Webhook ID.
+ */
+ $hash_algo = apply_filters( 'llms_rest_webhook_hash_algorithm', 'sha256', $payload, $this->get( 'id' ) );
+ $ts = llms_current_time( 'timestamp' );
+ $message = $ts . '.' . $payload;
+ $hash = hash_hmac( $hash_algo, $message, $this->get( 'secret' ) );
+
+ return sprintf( 't=%1$d,v1=%2$s', $ts, $hash );
+
+ }
+
+ /**
+ * Retrieve the admin URL where the api key is managed.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_edit_link() {
+ return add_query_arg(
+ array(
+ 'section' => 'webhooks',
+ 'edit-webhook' => $this->get( 'id' ),
+ ),
+ LLMS_REST_API()->keys()->get_admin_url()
+ );
+ }
+
+ /**
+ * Retrieve the topic event
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_event() {
+
+ $topic = explode( '.', $this->get( 'topic' ) );
+ return apply_filters( 'llms_rest_webhook_get_event', isset( $topic[1] ) ? $topic[1] : '', $this->get( 'id' ) );
+
+ }
+
+ /**
+ * Retrieve an array of hooks for the webhook topic.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string[]
+ */
+ public function get_hooks() {
+
+ if ( 'action' === $this->get_resource() ) {
+ $hooks = array( $this->get_event() => 1 );
+ } else {
+ $all_hooks = LLMS_REST_API()->webhooks()->get_hooks();
+ $topic = $this->get( 'topic' );
+ $hooks = isset( $all_hooks[ $topic ] ) ? $all_hooks[ $topic ] : array();
+ }
+
+ return apply_filters( 'llms_rest_webhook_get_hooks', $hooks, $this->get( 'id' ) );
+
+ }
+
+ /**
+ * Retrieve a payload for webhook delivery.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.6 Retrieve proper payload for enrollment and progress resources.
+ *
+ * @param array $args Numeric array of arguments from the originating hook.
+ * @return array
+ */
+ protected function get_payload( $args ) {
+
+ // Switch current user to the user who created the webhook.
+ $current_user = get_current_user_id();
+ wp_set_current_user( $this->get( 'user_id' ) );
+
+ $resource = $this->get_resource();
+ $event = $this->get_event();
+
+ $payload = array();
+ if ( 'deleted' === $event ) {
+
+ if ( in_array( $this->get_resource(), array( 'enrollment', 'progress' ), true ) ) {
+ $payload['student_id'] = $args[0];
+ $payload['post_id'] = $args[1];
+ } else {
+ $payload['id'] = $args[0];
+ }
+ } elseif ( 'action' === $resource ) {
+
+ $payload['action'] = current( $this->get_hooks() );
+ $payload['args'] = $args;
+
+ } else {
+
+ if ( 'enrollment' === $resource ) {
+ $endpoint = sprintf( '/llms/v1/students/%1$d/enrollments/%2$d', $args[0], $args[1] );
+ } elseif ( 'progress' === $resource ) {
+ $endpoint = sprintf( '/llms/v1/students/%1$d/progress/%2$d', $args[0], $args[1] );
+ } else {
+ $endpoint = sprintf( '/llms/v1/%1$ss/%2$d', $resource, $args[0] );
+ }
+
+ $payload = llms_rest_get_api_endpoint_data( $endpoint );
+
+ }
+
+ // Restore the current user.
+ wp_set_current_user( $current_user );
+
+ /**
+ * Filter the webhook payload prior to delivery
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $payload Webhook payload.
+ * @param string $resource Webhook resource.
+ * @param string $event Webhook event.
+ * @param array $args Numeric array of arguments from the originating hook.
+ * @param LLMS_REST_Webhook $this Webhook object.
+ */
+ return apply_filters( 'llms_rest_webhook_get_payload', $payload, $resource, $event, $args, $this );
+
+ }
+
+ /**
+ * Retrieve the topic resource.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ public function get_resource() {
+
+ $topic = explode( '.', $this->get( 'topic' ) );
+ return apply_filters( 'llms_rest_webhook_get_resource', $topic[0], $this->get( 'id' ) );
+
+ }
+
+ /**
+ * Retrieve a user agent string to use for delivering webhooks.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ protected function get_user_agent() {
+ global $wp_version;
+ return sprintf( 'LifterLMS/%1$s Hookshot (WordPress/%2$s)', LLMS()->version, $wp_version );
+ }
+
+ /**
+ * Increment delivery failures and after max allowed failures are reached, set status to disabled.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return LLMS_REST_Webhook
+ */
+ protected function set_delivery_failure() {
+
+ $failures = absint( $this->get( 'failure_count' ) );
+
+ $this->set( 'failure_count', ++$failures );
+
+ /**
+ * Filter the number of times a webhook is allowed to fail before it is automatically disabled.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $num Number of allowed failures. Default: 5.
+ */
+ $max_allowed = apply_filters( 'llms_rest_webhook_max_delivery_failures', 5 );
+
+ if ( $failures > $max_allowed ) {
+
+ $this->set( 'status', 'disabled' );
+
+ /**
+ * Fires immediately after a webhook has been disabled due to exceeding its maximum allowed failures.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param int $webhook_id ID of the webhook.
+ */
+ do_action( 'llms_rest_webhook_disabled_by_delivery_failures', $this->get( 'id' ) );
+
+ }
+
+ return $this;
+
+ }
+
+}
diff --git a/libraries/lifterlms-rest/includes/abstracts/index.php b/libraries/lifterlms-rest/includes/abstracts/index.php
new file mode 100644
index 0000000000..9c65c1efa6
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/abstracts/index.php
@@ -0,0 +1 @@
+keys()->delete( llms_filter_input( INPUT_GET, 'revoke-key', FILTER_VALIDATE_INT ) );
+ if ( $delete ) {
+ LLMS_Admin_Notices::flash_notice( esc_html__( 'The API Key has been successfully deleted.', 'lifterlms' ), 'success' );
+ return llms_redirect_and_exit( admin_url( 'admin.php?page=llms-settings&tab=rest-api§ion=keys' ) );
+ }
+ } elseif ( llms_verify_nonce( 'llms_rest_webhook_nonce', 'create-update-webhook', 'POST' ) ) {
+ return $this->handle_webhook_upsert();
+ } elseif ( llms_verify_nonce( 'delete-webhook-nonce', 'delete', 'GET' ) ) {
+ $delete = LLMS_REST_API()->webhooks()->delete( llms_filter_input( INPUT_GET, 'delete-webhook', FILTER_VALIDATE_INT ) );
+ if ( $delete ) {
+ LLMS_Admin_Notices::flash_notice( esc_html__( 'The webhook has been successfully deleted.', 'lifterlms' ), 'success' );
+ return llms_redirect_and_exit( admin_url( 'admin.php?page=llms-settings&tab=rest-api§ion=webhooks' ) );
+ }
+ } elseif ( llms_verify_nonce( 'dl-key-nonce', 'dl-key', 'GET' ) ) {
+ return $this->handle_key_download();
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Generate and download a api key credentials file.
+ *
+ * @since 1.0.0-beta.3
+ *
+ * @return false|void
+ */
+ protected function handle_key_download() {
+
+ $info = $this->prepare_key_download();
+ if ( ! $info ) {
+ return false;
+ }
+
+ header( 'Content-type: text/plain' );
+ header( 'Content-Disposition: attachment; filename="' . $info['fn'] );
+ header( 'Pragma: no-cache' );
+ header( 'Expires: 0' );
+
+ // Translators: %s = Consumer Key.
+ printf( __( 'Consumer Key: %s', 'lifterlms' ), $info['ck'] );
+ echo "\r\n";
+ // Translators: %s = Consumer Secret.
+ printf( __( 'Consumer Secret: %s', 'lifterlms' ), $info['cs'] );
+ die();
+
+ }
+
+ /**
+ * Handle creating/updating a webhook via admin interfaces.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.27 Replaced use of the deprecated `FILTER_SANITIZE_STRING` constant.
+ *
+ * @return true|void|WP_Error true on update success, void (redirect) on creation success, WP_Error on failure.
+ */
+ protected function handle_webhook_upsert() {
+
+ $data = array(
+ 'name' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_webhook_name' ),
+ 'status' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_webhook_status' ),
+ 'topic' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_webhook_topic' ),
+ 'delivery_url' => llms_filter_input( INPUT_POST, 'llms_rest_webhook_delivery_url', FILTER_SANITIZE_URL ),
+ 'secret' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_webhook_secret' ),
+ );
+
+ if ( 'action' === $data['topic'] ) {
+ $data['topic'] .= '.' . llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_webhook_action' );
+ }
+
+ $hook_id = llms_filter_input( INPUT_POST, 'llms_rest_webhook_id', FILTER_SANITIZE_NUMBER_INT );
+
+ if ( ! $hook_id ) {
+
+ $hook = LLMS_REST_API()->webhooks()->create( $data );
+ if ( ! is_wp_error( $hook ) ) {
+ return llms_redirect_and_exit( $hook->get_edit_link(), array( 'status' => 301 ) );
+ }
+ } else {
+
+ $hook = LLMS_REST_API()->webhooks()->get( $hook_id );
+ if ( ! $hook ) {
+
+ // Translators: %s = Webhook ID.
+ $hook = new WP_Error( 'llms_rest_api_webhook_not_found', sprintf( __( '"%s" is not a valid Webhook.', 'lifterlms' ), $hook_id ) );
+
+ } else {
+
+ $data['id'] = $hook_id;
+ $hook = LLMS_REST_API()->webhooks()->update( $data );
+
+ }
+ }
+
+ if ( is_wp_error( $hook ) ) {
+ // Translators: %1$s = error message; %2$s = error code.
+ LLMS_Admin_Notices::flash_notice( sprintf( __( 'Error: %1$s [Code: %2$s]', 'lifterlms' ), $hook->get_error_message(), $hook->get_error_code() ), 'error' );
+ return $hook;
+ }
+
+ return true;
+
+ }
+
+ /**
+ * Validates `GET` information from the credential download URL and prepares information for generating the file.
+ *
+ * @since 1.0.0-beta.3
+ * @since 1.0.0-beta.27 Replaced use of the deprecated `FILTER_SANITIZE_STRING` constant.
+ *
+ * @return false|array
+ */
+ protected function prepare_key_download() {
+
+ $key_id = llms_filter_input( INPUT_GET, 'id', FILTER_SANITIZE_NUMBER_INT );
+ $consumer_key = llms_filter_input_sanitize_string( INPUT_GET, 'ck' );
+
+ // return if missing required fields.
+ if ( ! $key_id || ! $consumer_key ) {
+ return false;
+ }
+
+ // return if key doesn't exist.
+ $key = LLMS_REST_API()->keys()->get( $key_id );
+ if ( ! $key ) {
+ return false;
+ }
+
+ // validate the decoded consumer key looks like the stored truncated key.
+ $consumer_key = base64_decode( $consumer_key ); //phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- This is benign usage.
+ if ( substr( $consumer_key, -7 ) !== $key->get( 'truncated_key' ) ) {
+ return false;
+ }
+
+ return array(
+ 'fn' => sanitize_file_name( $key->get( 'description' ) ) . '.txt',
+ 'ck' => $consumer_key,
+ 'cs' => $key->get( 'consumer_secret' ),
+ );
+
+ }
+
+}
+
+return new LLMS_REST_Admin_Form_Controller();
diff --git a/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-api-keys.php b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-api-keys.php
new file mode 100644
index 0000000000..45560080b2
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-api-keys.php
@@ -0,0 +1,313 @@
+ 'top',
+ 'id' => 'rest_keys_options_start',
+ 'type' => 'sectionstart',
+ );
+
+ $settings[] = array(
+ 'title' => $key_id || $add_key ? __( 'API Key Details', 'lifterlms' ) : __( 'API Keys', 'lifterlms' ),
+ 'type' => 'title-with-html',
+ 'id' => 'rest_keys_options_title',
+ 'html' => $key_id || $add_key ? '' : '' . __( 'Add API Key', 'lifterlms' ) . ' ',
+ );
+
+ if ( $add_key || $key_id ) {
+
+ $key = $add_key ? false : new LLMS_REST_API_Key( $key_id );
+ if ( self::$generated_key ) {
+ $key = self::$generated_key;
+ }
+ if ( $add_key || $key->exists() ) {
+
+ $user_id = $key ? $key->get( 'user_id' ) : get_current_user_id();
+
+ $settings[] = array(
+ 'title' => __( 'Description', 'lifterlms' ),
+ 'desc' => ' ' . __( 'A friendly, human-readable, name used to identify the key.', 'lifterlms' ),
+ 'id' => 'llms_rest_key_description',
+ 'type' => 'text',
+ 'value' => $key ? $key->get( 'description' ) : '',
+ 'custom_attributes' => array(
+ 'required' => 'required',
+ ),
+ );
+
+ $settings[] = array(
+ 'title' => __( 'User', 'lifterlms' ),
+ 'class' => 'llms-select2-student',
+ 'desc' => sprintf(
+ // Translators: %1$s = opening anchor tag to capabilities doc; %2$s closing anchor tag.
+ __( 'The owner is used to determine what user %1$scapabilities%2$s are available to the API key.', 'lifterlms' ),
+ '',
+ ' '
+ ),
+ 'custom_attributes' => array(
+ 'data-placeholder' => __( 'Select a user', 'lifterlms' ),
+ ),
+ 'id' => 'llms_rest_key_user_id',
+ 'options' => llms_make_select2_student_array( array( $user_id ) ),
+ 'type' => 'select',
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Permissions', 'lifterlms' ),
+ 'desc' => ' ' . sprintf(
+ // Translators: %1$s = opening anchor tag to doc; %2$s closing anchor tag.
+ __( 'Determines what kind of requests can be made with the API key. %1$sRead more%2$s.', 'lifterlms' ),
+ '',
+ ' '
+ ),
+ 'id' => 'llms_rest_key_permissions',
+ 'type' => 'select',
+ 'options' => LLMS_REST_API()->keys()->get_permissions(),
+ 'value' => $key ? $key->get( 'permissions' ) : '',
+ );
+
+ if ( $key && ! self::$generated_key ) {
+
+ $settings[] = array(
+ 'title' => __( 'Consumer key ending in', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'class' => 'code',
+ 'id' => 'llms_rest_key__read_only_key',
+ 'type' => 'text',
+ 'value' => '…' . $key->get( 'truncated_key' ),
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Last accessed at', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'id' => 'llms_rest_key__read_only_date',
+ 'type' => 'text',
+ 'value' => $key->get_last_access_date(),
+ );
+
+ } elseif ( self::$generated_key ) {
+
+ $settings[] = array(
+ 'type' => 'custom-html',
+ 'id' => 'llms_rest_key_onetime_notice',
+ 'value' => '' . __( 'Make sure to copy or download the consumer key and consumer secret. After leaving this page they will not be displayed again.', 'lifterlms' ) . '
',
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Consumer key', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'css' => 'width:400px',
+ 'class' => 'code widefat',
+ 'id' => 'llms_rest_key__read_only_key',
+ 'type' => 'text',
+ 'value' => $key->get( 'consumer_key_one_time' ),
+ );
+
+ $settings[] = array(
+ 'title' => __( 'Consumer secret', 'lifterlms' ),
+ 'custom_attributes' => array(
+ 'readonly' => 'readonly',
+ ),
+ 'css' => 'width:400px',
+ 'class' => 'code widefat',
+ 'id' => 'llms_rest_key__read_only_secret',
+ 'type' => 'text',
+ 'value' => $key->get( 'consumer_secret' ),
+ );
+
+ }
+
+ $buttons = ' ';
+ if ( self::$generated_key ) {
+ $download_url = wp_nonce_url(
+ admin_url(
+ add_query_arg(
+ array(
+ 'id' => $key->get( 'id' ),
+ 'ck' => base64_encode( $key->get( 'consumer_key_one_time' ) ), //phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- This is benign usage.
+ ),
+ 'admin.php'
+ )
+ ),
+ 'dl-key',
+ 'dl-key-nonce'
+ );
+ $buttons .= ' ' . __( 'Download Keys', 'lifterlms' ) . ' ';
+ } else {
+ $buttons .= '' . __( 'Save', 'lifterlms' ) . ' ';
+ }
+ if ( $key ) {
+ $buttons .= $buttons ? ' ' : ' ';
+ $buttons .= '' . __( 'Revoke', 'lifterlms' ) . ' ';
+ }
+ $buttons .= wp_nonce_field( 'lifterlms-settings', '_wpnonce', true, false );
+
+ $settings[] = array(
+ 'type' => 'custom-html',
+ 'id' => 'llms_rest_key_buttons',
+ 'value' => $buttons,
+ );
+
+ } else {
+
+ $settings[] = array(
+ 'id' => 'rest_keys_options_invalid_error',
+ 'type' => 'custom-html',
+ 'value' => __( 'Invalid api key.', 'lifterlms' ),
+ );
+
+ }
+ } else {
+
+ $settings[] = array(
+ 'id' => 'llms_api_keys_table',
+ 'table' => new LLMS_REST_Table_API_Keys(),
+ 'type' => 'table',
+ );
+
+ }
+
+ $settings[] = array(
+ 'id' => 'rest_keys_options_end',
+ 'type' => 'sectionend',
+ );
+
+ return $settings;
+
+ }
+
+ /**
+ * Form handler to save Create / Update an API key.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.3 Remove key copy message in favor of message directly above the key fields.
+ *
+ * @return null|LLMS_REST_API_Key|WP_Error
+ */
+ public static function save() {
+
+ $ret = null;
+
+ $key_id = llms_filter_input( INPUT_GET, 'edit-key', FILTER_SANITIZE_NUMBER_INT );
+ if ( $key_id ) {
+ $ret = self::save_update( $key_id );
+ } elseif ( llms_filter_input( INPUT_GET, 'add-key', FILTER_SANITIZE_NUMBER_INT ) ) {
+ $ret = self::save_create();
+ }
+
+ if ( is_wp_error( $ret ) ) {
+ // Translators: %1$s = Error message; %2$s = Error code.
+ LLMS_Admin_Settings::set_error( sprintf( __( 'Error: %1$s [Code: %2$s]', 'lifterlms' ), $ret->get_error_message(), $ret->get_error_code() ) );
+ }
+
+ return $ret;
+
+ }
+
+ /**
+ * Form handler to create a new API key.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.27 Replaced use of the deprecated `FILTER_SANITIZE_STRING` constant.
+ *
+ * @return LLMS_REST_API_Key|WP_Error
+ */
+ protected static function save_create() {
+
+ $create = LLMS_REST_API()->keys()->create(
+ array(
+ 'description' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_key_description' ),
+ 'user_id' => llms_filter_input( INPUT_POST, 'llms_rest_key_user_id', FILTER_SANITIZE_NUMBER_INT ),
+ 'permissions' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_key_permissions' ),
+ )
+ );
+
+ if ( ! is_wp_error( $create ) ) {
+ self::$generated_key = $create;
+ }
+
+ return $create;
+
+ }
+
+ /**
+ * Form handler to save an API key.
+ *
+ * @since 1.0.0-beta.1
+ * @since 1.0.0-beta.27 Replaced use of the deprecated `FILTER_SANITIZE_STRING` constant.
+ *
+ * @param int $key_id API Key ID.
+ * @return LLMS_REST_API_Key|WP_Error
+ */
+ protected static function save_update( $key_id ) {
+
+ $key = LLMS_REST_API()->keys()->get( $key_id );
+ if ( ! $key ) {
+ // Translators: %s = Invalid API Key ID.
+ return new WP_Error( 'llms_rest_api_key_not_found', sprintf( __( '"%s" is not a valid API Key.', 'lifterlms' ), $key_id ) );
+ }
+
+ $update = LLMS_REST_API()->keys()->update(
+ array(
+ 'id' => $key_id,
+ 'description' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_key_description' ),
+ 'user_id' => llms_filter_input( INPUT_POST, 'llms_rest_key_user_id', FILTER_SANITIZE_NUMBER_INT ),
+ 'permissions' => llms_filter_input_sanitize_string( INPUT_POST, 'llms_rest_key_permissions' ),
+ )
+ );
+
+ return $update;
+
+ }
+
+}
+
diff --git a/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-page.php b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-page.php
new file mode 100644
index 0000000000..9f715aeadd
--- /dev/null
+++ b/libraries/lifterlms-rest/includes/admin/class-llms-rest-admin-settings-page.php
@@ -0,0 +1,156 @@
+id = 'rest-api';
+ $this->label = __( 'REST API', 'lifterlms' );
+
+ // Output Stuff.
+ add_filter( 'lifterlms_settings_tabs_array', array( $this, 'add_settings_page' ), 20 );
+ add_action( 'lifterlms_sections_' . $this->id, array( $this, 'output_sections_nav' ) );
+ add_action( 'lifterlms_settings_' . $this->id, array( $this, 'output' ) );
+
+ // Maybe Save API Keys.
+ add_action( 'lifterlms_settings_save_' . $this->id, array( 'LLMS_Rest_Admin_Settings_API_Keys', 'save' ) );
+
+ // Disable the default page's save button.
+ add_filter( 'llms_settings_rest-api_has_save_button', '__return_false' );
+
+ add_filter( 'llms_table_get_table_classes', array( $this, 'get_table_classes' ), 10, 2 );
+ add_action( 'lifterlms_admin_field_title-with-html', array( $this, 'output_title_field' ), 10 );
+
+ }
+
+ /**
+ * Retrieve the id of the current tab/section
+ *
+ * Overrides parent function to set "keys" as the default section instead of the nonexistant "main".
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return string
+ */
+ protected function get_current_section() {
+
+ $current = parent::get_current_section();
+ if ( 'main' === $current ) {
+ $all = array_keys( $this->get_sections() );
+ $current = $all ? $all[0] : 'main';
+ }
+ return $current;
+
+ }
+
+ /**
+ * Get the page sections
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_sections() {
+
+ $sections = array();
+
+ if ( current_user_can( 'manage_lifterlms_api_keys' ) ) {
+ $sections['keys'] = __( 'API Keys', 'lifterlms' );
+ }
+
+ if ( current_user_can( 'manage_lifterlms_webhooks' ) ) {
+ $sections['webhooks'] = __( 'Webhooks', 'lifterlms' );
+ }
+
+ /**
+ * Modify the available tabs on the REST API settings screen.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $sections Array of settings page tabs.
+ */
+ return apply_filters( 'llms_rest_api_settings_sections', $sections );
+
+ }
+
+ /**
+ * Get settings array
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @return array
+ */
+ public function get_settings() {
+
+ $curr_section = $this->get_current_section();
+
+ $settings = array();
+ if ( current_user_can( 'manage_lifterlms_api_keys' ) && 'keys' === $curr_section ) {
+ $settings = LLMS_Rest_Admin_Settings_API_Keys::get_fields();
+ } elseif ( current_user_can( 'manage_lifterlms_webhooks' ) && 'webhooks' === $curr_section ) {
+ $settings = LLMS_Rest_Admin_Settings_Webhooks::get_fields();
+ }
+
+ return apply_filters( 'llms_rest_api_settings_' . $curr_section, $settings );
+
+ }
+
+ /**
+ * Add CSS classes to the API Keys Table.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param string[] $classes Array of css class names.
+ * @param string $id Table ID.
+ * @return string[]
+ */
+ public function get_table_classes( $classes, $id ) {
+
+ if ( in_array( $id, array( 'rest-api-keys', 'rest-webhooks' ), true ) ) {
+ $classes[] = 'text-left';
+ }
+ return $classes;
+
+ }
+
+ /**
+ * Outputs a custom "title" field with HTML content as the settings section title.
+ *
+ * @since 1.0.0-beta.1
+ *
+ * @param array $field Settings field arguments.
+ * @return void
+ */
+ public function output_title_field( $field ) {
+
+ echo '' . esc_html( $field['title'] ) . ' ' . $field['html'] . '
';
+ echo '