LLMS_Course
LLMS_Course model class
Source Source
File: includes/models/model.llms.course.php
class LLMS_Course extends LLMS_Post_Model implements LLMS_Interface_Post_Instructors {
use LLMS_Trait_Audio_Video_Embed;
use LLMS_Trait_Sales_Page;
/**
* Meta properties.
*
* @var array
*/
protected $properties = array(
// Public.
'average_grade' => 'float',
'average_progress' => 'float',
'capacity' => 'absint',
'capacity_message' => 'text',
'course_closed_message' => 'text',
'course_opens_message' => 'text',
'content_restricted_message' => 'text',
'enable_capacity' => 'yesno',
'end_date' => 'text',
'enrolled_students' => 'absint',
'enrollment_closed_message' => 'text',
'enrollment_end_date' => 'text',
'enrollment_opens_message' => 'text',
'enrollment_period' => 'yesno',
'enrollment_start_date' => 'text',
'has_prerequisite' => 'yesno',
'instructors' => 'array',
'length' => 'text',
'prerequisite' => 'absint',
'prerequisite_track' => 'absint',
'tile_featured_video' => 'yesno',
'time_period' => 'yesno',
'start_date' => 'text',
// Private.
'temp_calc_data' => 'array',
'last_data_calc_run' => 'absint',
);
/**
* Default property values
*
* @var array
*/
protected $property_defaults = array(
'enrolled_students' => 0,
);
/**
* DB post type name.
*
* @var string
*/
protected $db_post_type = 'course';
/**
* Model post type name.
*
* @var string
*/
protected $model_post_type = 'course';
/**
* Constructor for this class and the traits it uses.
*
* @since 5.3.0
*
* @param string|int|LLMS_Post_Model|WP_Post $model 'new', WP post id, instance of an extending class, instance of WP_Post.
* @param array $args Args to create the post, only applies when $model is 'new'.
*/
public function __construct( $model, $args = array() ) {
$this->construct_audio_video_embed();
$this->construct_sales_page();
parent::__construct( $model, $args );
}
/**
* Retrieve an instance of the Post Instructors model
*
* @since 3.13.0
*
* @return LLMS_Post_Instructors
*/
public function instructors() {
return new LLMS_Post_Instructors( $this );
}
/**
* Retrieve the total points available for the course
*
* @since 3.24.0
*
* @return int
*/
public function get_available_points() {
$points = 0;
foreach ( $this->get_lessons() as $lesson ) {
$points += $lesson->get( 'points' );
}
/**
* Filters the total available points for the course.
*
* @since 3.24.0
*
* @param int $points Number of available points.
* @param LLMS_Course $course Course object.
*/
return apply_filters( 'llms_course_get_available_points', $points, $this );
}
/**
* Get course's prerequisite id based on the type of prerequisite
*
* @since 3.0.0
* @since 3.7.3 Unknown.
*
* @param string $type Optional. Type of prereq to retrieve id for [course|track]. Default is 'course'.
* @return int|false Post ID of a course, taxonomy ID of a track, or false if none found.
. */
public function get_prerequisite_id( $type = 'course' ) {
if ( $this->has_prerequisite( $type ) ) {
switch ( $type ) {
case 'course':
$key = 'prerequisite';
break;
case 'course_track':
$key = 'prerequisite_track';
break;
}
if ( isset( $key ) ) {
return $this->get( $key );
}
}
return false;
}
/**
* Retrieve course categories
*
* @since 3.3.0
*
* @param array $args Array of args passed to wp_get_post_terms.
* @return array
*/
public function get_categories( $args = array() ) {
return wp_get_post_terms( $this->get( 'id' ), 'course_cat', $args );
}
/**
* Get Difficulty
*
* @since 1.0.0
* @since 3.24.0 Unknown.
* @since 7.2.0 Added support for showing multiple difficulties.
*
* @param string $field Optional. Which field to return from the available term fields.
* Any public variables from a WP_Term object are acceptable: term_id, name, slug, and more.
* Default is 'name'.
* @return string
*/
public function get_difficulty( $field = 'name' ) {
$terms = get_the_terms( $this->get( 'id' ), 'course_difficulty' );
if ( false === $terms ) {
return '';
}
$difficulties = wp_list_pluck( $terms, $field );
return implode( ', ', $difficulties );
}
/**
* Retrieve course instructor information
*
* @since 3.13.0
*
* @param boolean $exclude_hidden Optional. If true, excludes hidden instructors from the return array. Default is `false`.
* @return array
*/
public function get_instructors( $exclude_hidden = false ) {
/**
* Filters the course's instructors list
*
* @since 3.13.0
*
* @param array $instructors Instructor data array.
* @param LLMS_Course $course Course object.
* @param boolearn $exclude_hidden If true, excludes hidden instructors from the return array.
*/
return apply_filters(
'llms_course_get_instructors',
$this->instructors()->get_instructors( $exclude_hidden ),
$this,
$exclude_hidden
);
}
/**
* Get course lessons
*
* @since 3.0.0
* @since 3.24.0 Unknown.
*
* @param string $return Optional. Type of return [ids|posts|lessons]. Default is 'lessons'.
* @return int[]|WP_Post[]|LLMS_Lesson[] The type depends on value of `$return`.
*/
public function get_lessons( $return = 'lessons' ) {
$lessons = array();
foreach ( $this->get_sections( 'sections' ) as $section ) {
$lessons = array_merge( $lessons, $section->get_lessons( 'posts' ) );
}
if ( 'ids' === $return ) {
$ret = wp_list_pluck( $lessons, 'ID' );
} elseif ( 'posts' === $return ) {
$ret = $lessons;
} else {
$ret = array_map( 'llms_get_post', $lessons );
}
return $ret;
}
/**
* Retrieve the number of course's lessons.
*
* This is less expensive than counting the result of {@see LLMS_Course::get_lessons()},
* and should be preferred when you only need to count the number of lessons of a course.
*
* @since 7.1.0
*
* @return int
*/
public function get_lessons_count() {
$query = new WP_Query(
array(
'meta_key' => '_llms_parent_course',
'meta_value' => $this->get( 'id' ),
'post_type' => 'lesson',
'posts_per_page' => -1,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'fields' => 'ids',
'orderby' => 'ID',
'order' => 'ASC',
)
);
return $query->post_count;
}
/**
* Retrieve an array of quizzes within a course
*
* @since 3.12.0
* @since 3.16.0 Unknown.
*
* @return int[] Array of WP_Post IDs of the quizzes.
*/
public function get_quizzes() {
$quizzes = array();
foreach ( $this->get_lessons( 'lessons' ) as $lesson ) {
if ( $lesson->has_quiz() ) {
$quizzes[] = $lesson->get( 'quiz' );
}
}
return $quizzes;
}
/**
* Get course sections
*
* @since 3.0.0
* @since 3.24.0 Unknown.
*
* @param string $return Optional. Type of return [ids|posts|sections]. Default is 'sections'.
* @return int[]|WP_Post[]|LLMS_Section[] The type depends on value of `$return`.
*/
public function get_sections( $return = 'sections' ) {
$q = new WP_Query(
array(
'meta_key' => '_llms_order',
'meta_query' => array(
array(
'key' => '_llms_parent_course',
'value' => $this->id,
),
),
'order' => 'ASC',
'orderby' => 'meta_value_num',
'post_type' => 'section',
'posts_per_page' => 500,
)
);
if ( 'ids' === $return ) {
$r = wp_list_pluck( $q->posts, 'ID' );
} elseif ( 'posts' === $return ) {
$r = $q->posts;
} else {
$r = array();
foreach ( $q->posts as $p ) {
$r[] = new LLMS_Section( $p );
}
}
return $r;
}
/**
* Retrieve the number of enrolled students in the course
*
* The cached value is calculated in the `LLMS_Processor_Course_Data` background processor.
*
* If, for whatever reason, it's not found, it will be calculated on demand and saved for later use.
*
* @since 3.15.0
* @since 4.12.0 Use cached value where possible.
* @since 6.0.0 Don't access `LLMS_Student_Query` properties directly.
*
* @param boolean $skip_cache Default: `false`. Whether or not to bypass the cache. If `true`, bypasses the cache.
* @return int
*/
public function get_student_count( $skip_cache = false ) {
$count = ! $skip_cache ? $this->get( 'enrolled_students' ) : false;
/**
* Query enrolled students when `$skip_cache=true` or when there's no stored meta data.
*
* The second condition is necessary to disambiguate between a cached `0` and a `0` that's
* returned as the default value when the metadata doesn't exist.
*/
if ( false === $count || ! isset( $this->enrolled_students ) ) {
$query = new LLMS_Student_Query(
array(
'post_id' => $this->get( 'id' ),
'statuses' => array( 'enrolled' ),
'per_page' => 1,
'sort' => array(
'id' => 'ASC',
),
)
);
$count = $query->get_found_results();
// Cache result for later use.
$this->set( 'enrolled_students', $count );
}
/**
* Filter the number of actively enrolled students in the course
*
* @since 4.12.0
*
* @param int $count Number of students enrolled in the course.
* @param LLMS_Course $course Instance of the course object.
*/
$count = apply_filters( 'llms_course_get_student_count', $count, $this );
return absint( $count );
}
/**
* Get an array of student IDs based on enrollment status in the course
*
* @since 3.0.0
*
* @param string|string[] $statuses Optional. List of enrollment statuses to query by. Students matching at least one of the provided statuses will be returned. Default is 'enrolled'.
* @param integer $limit Optional. Number of results. Default is `50`.
* @param integer $skip Optional. Number of results to skip (for pagination). Default is `0`.
* @return array
*/
public function get_students( $statuses = 'enrolled', $limit = 50, $skip = 0 ) {
return llms_get_enrolled_students( $this->get( 'id' ), $statuses, $limit, $skip );
}
/**
* Retrieve course tags
*
* @since 3.3.0
*
* @param array $args Array of args passed to wp_get_post_terms.
* @return array
*/
public function get_tags( $args = array() ) {
return wp_get_post_terms( $this->get( 'id' ), 'course_tag', $args );
}
/**
* Get the properties that will be explicitly excluded from the array representation of the model.
*
* This stub can be overloaded by an extending class and the property list is filterable via the
* {@see llms_get_{$this->model_post_type}_excluded_to_array_properties} filter.
*
* @since 5.4.1
*
* @return string[]
*/
protected function get_to_array_excluded_properties() {
/**
* Disable course property exclusion while running `toArray()`.
*
* This hook is intended to allow developers to retain the functionality implemented
* prior to the introduction of this hook.
*
* The LifterLMS developers consider the presence of these properties to be a bug but
* acknowledge that the removal of these properties could be seen as a backwards incompatible
* "feature" removal.
*
* This hook disables the exclusion of the following properties: 'average_grade', 'average_progress',
* 'enrolled_students', 'last_data_calc_run', and 'temp_calc_data'. Any excluded properties added in the
* future will not be excluded when using this hook.
*
* @example `add_filter( 'llms_course_to_array_disable_prop_exclusion', '__return_true' );`
*
* @since 5.4.1
*
* @param boolean $disable Whether or not to disable property exclusions.
*/
$disable = apply_filters( 'llms_course_to_array_disable_prop_exclusion', false );
if ( $disable ) {
return array();
}
return array(
'average_grade',
'average_progress',
'enrolled_students',
'last_data_calc_run',
'temp_calc_data',
);
}
/**
* Retrieve course tracks
*
* @since 3.3.0
*
* @param array $args Array of args passed to wp_get_post_terms.
* @return array
*/
public function get_tracks( $args = array() ) {
return wp_get_post_terms( $this->get( 'id' ), 'course_track', $args );
}
/**
* Retrieve an array of students currently enrolled in the course
*
* @since 1.0.0
* @since 3.0.0 Use `LLMS_Course::get_students()`.
*
* @param integer $limit Number of results.
* @param integer $skip Number of results to skip (for pagination).
* @return array
*/
public function get_enrolled_students( $limit, $skip ) {
return $this->get_students( 'enrolled', $limit, $skip );
}
/**
* Get a user's percentage completion through the course
*
* @since 1.0.0
* @since 3.17.2 Unknown.
*
* @return float
*/
public function get_percent_complete( $user_id = '' ) {
$student = llms_get_student( $user_id );
if ( ! $student ) {
return 0;
}
return $student->get_progress( $this->get( 'id' ), 'course' );
}
/**
* Retrieve an instance of the LLMS_Product for this course
*
* @since 3.3.0
*
* @return LLMS_Product
*/
public function get_product() {
return new LLMS_Product( $this->get( 'id' ) );
}
/**
* Compare a course meta info date to the current date and get a bool
*
* @since 3.0.0
*
* @param string $date_key Property key, eg "start_date" or "enrollment_end_date".
* @return boolean Returns `true` when the date is in the past and `false` when the date is in the future.
*/
public function has_date_passed( $date_key ) {
$now = current_time( 'timestamp' );
$date = $this->get_date( $date_key, 'U' );
/**
* If there's no date, we can't make a comparison
* so assume it's unset and unnecessary
* so return 'false'.
*/
if ( ! $date ) {
return false;
}
return $now > $date;
}
/**
* Determine if the course is at capacity based on course capacity settings
*
* @since 3.0.0
* @since 3.15.0 Unknown.
*
* @return boolean Returns `true` if not at capacity & `false` if at or over capacity.
*/
public function has_capacity() {
// Capacity disabled, so there is capacity.
if ( 'yes' !== $this->get( 'enable_capacity' ) ) {
return true;
}
$capacity = $this->get( 'capacity' );
// No capacity restriction set, so it has capacity.
if ( ! $capacity ) {
return true;
}
// Compare results.
return ( $this->get_student_count() < $capacity );
}
/**
* Determine if prerequisites are enabled and there are prereqs configured
*
* @since 3.0.0
* @since 3.7.5 Unknown.
*
* @param string $type Determine if a specific type of prereq exists [any|course|track].
* @return boolean Returns true if prereq is enabled and there is a prerequisite course or track.
*/
public function has_prerequisite( $type = 'any' ) {
if ( 'yes' === $this->get( 'has_prerequisite' ) ) {
if ( 'any' === $type ) {
return ( $this->get( 'prerequisite' ) || $this->get( 'prerequisite_track' ) );
} elseif ( 'course' === $type ) {
return ( $this->get( 'prerequisite' ) ) ? true : false;
} elseif ( 'course_track' === $type ) {
return ( $this->get( 'prerequisite_track' ) ) ? true : false;
}
}
return false;
}
/**
* Determine if students can access course content based on the current date
*
* @since 3.0.0
* @since 3.7.0 Unknown.
*
* @return boolean
*/
public function is_enrollment_open() {
// If no period is set, enrollment is automatically open.
if ( 'yes' !== $this->get( 'enrollment_period' ) ) {
$is_open = true;
} else {
$is_open = ( $this->has_date_passed( 'enrollment_start_date' ) && ! $this->has_date_passed( 'enrollment_end_date' ) );
}
/**
* Filters whether or not course enrollment is open.
*
* @since Unknown
*
* @param boolean $is_open Whether or not enrollment is open.
* @param LLMS_Course $course Course object.
*/
return apply_filters( 'llms_is_course_enrollment_open', $is_open, $this );
}
/**
* Determine if students can access course content based on the current date
*
* Note that enrollment does not affect the outcome of this check as regardless
* of enrollment, once a course closes content is locked.
*
* @since 3.0.0
* @since 3.7.0 Unknown.
*
* @return boolean
*/
public function is_open() {
// If a course time period is not enabled, just return true (content is accessible).
if ( 'yes' !== $this->get( 'time_period' ) ) {
$is_open = true;
} else {
$is_open = ( $this->has_date_passed( 'start_date' ) && ! $this->has_date_passed( 'end_date' ) );
}
/**
* Filters whether or not the course is considered open based on the current date.
*
* @since Unknown
*
* @param boolean $is_open Whether or not enrollment is open.
* @param LLMS_Course $course Course object.
*/
return apply_filters( 'llms_is_course_open', $is_open, $this );
}
/**
* Determine if a prerequisite is completed for a student
*
* @since 3.0.0
*
* @param string $type Type of prereq [course|track].
* @return boolean
*/
public function is_prerequisite_complete( $type = 'course', $student_id = null ) {
if ( ! $student_id ) {
$student_id = get_current_user_id();
}
// No user or no prereqs so no reason to proceed.
if ( ! $student_id || ! $this->has_prerequisite( $type ) ) {
return false;
}
$prereq_id = $this->get_prerequisite_id( $type );
// No prereq id of this type, no need to proceed.
if ( ! $prereq_id ) {
return false;
}
// Setup student.
$student = new LLMS_Student( $student_id );
return $student->is_complete( $prereq_id, $type );
}
/**
* Save instructor information
*
* @since 3.13.0
*
* @param array $instructors Array of course instructor information.
*/
public function set_instructors( $instructors = array() ) {
return $this->instructors()->set_instructors( $instructors );
}
/**
* Add data to the course model when converted to array
*
* Called before data is sorted and returned by $this->jsonSerialize().
*
* @since 3.3.0
* @since 3.8.0 Unknown.
*
* @param array $arr Data to be serialized.
* @return array
*/
public function toArrayAfter( $arr ) {
$product = $this->get_product();
$arr['access_plans'] = array();
foreach ( $product->get_access_plans( false, false ) as $p ) {
$arr['access_plans'][] = $p->toArray();
}
$arr['sections'] = array();
foreach ( $this->get_sections() as $s ) {
Expand full source code Collapse full source code View on GitHub
Properties Properties
The following post and post meta properties are accessible for this class. See LLMS_Post_Model::get() and LLMS_Post_Model::set() for more information.
- $audio_embed
-
(string) URL to an oEmbed enable audio URL.
- $average_grade
-
(float) Calculated value of the overall average grade of all enrolled students in the course..
- $average_progress
-
(float) Calculated value of the overall average progress of all enrolled students in the course..
- $capacity
-
(int) Number of students who can be enrolled in the course before enrollment closes.
- $capacity_message
-
(string) Message displayed when capacity has been reached.
- $content_restricted_message
-
(string) Message displayed when non-enrolled visitors try to access lessons/quizzes directly.
- $course_closed_message
-
(string) Message displayed to visitors when the course is accessed after the Course End Date has passed. Only applicable when $time_period is 'yes'.
- $course_opens_message
-
(string) Message displayed to visitors when the course is accessed before the Course Start Date has passed. Only applicable when $time_period is 'yes'.
- $enable_capacity
-
(string) Whether capacity restrictions are enabled [yes|no].
- $enrollment_closed_message
-
(string) Message displayed to non-enrolled visitors when the course is accessed after the Enrollment End Date has passed. Only applicable when $enrollment_period is 'yes'.
- $enrollment_end_date
-
(string) After this date, registration closes.
- $enrollment_opens_message
-
(string) Message displayed to non-enrolled visitors when the course is accessed before the Enrollment Start Date has passed. Only applicable when $enrollment_period is 'yes'.
- $enrollment_period
-
(string) Whether or not a course time period restriction is enabled [yes|no] (all checks should check for 'yes' as an empty string might be returned).
- $enrollment_start_date
-
(string) Before this date, registration is closed.
- $end_date
-
(string) Date when a course closes. Students may no longer view content or complete lessons / quizzes after this date..
- $has_prerequisite
-
(string) Determine if prerequisites are enabled [yes|no].
- $instructors
-
(array) Course instructor user information.
- $prerequisite
-
(int) WP Post ID of a the prerequisite course.
- $prerequisite_track
-
(int) WP Tax ID of a the prerequisite track.
- $start_date
-
(string) Date when a course is opens. Students may register before this date but can only view content and complete lessons or quizzes after this date..
- $length
-
(string) User defined course length.
- $sales_page_content_page_id
-
(int) WP Post ID of the WP page to redirect to when $sales_page_content_type is 'page'.
- $sales_page_content_type
-
(string) Sales page behavior [none,content,page,url].
- $sales_page_content_url
-
(string) Redirect URL for a sales page, when $sales_page_content_type is 'url'.
- $tile_featured_video
-
(string) Displays the featured video instead of the featured image on course tiles [yes|no].
- $time_period
-
(string) Whether or not a course time period restriction is enabled [yes|no] (all checks should check for 'yes' as an empty string might be returned).
- $video_embed
-
(string) URL to an oEmbed enable video URL.
Methods Methods
- __construct — Constructor for this class and the traits it uses.
- get_audio — Attempt to get oEmbed for an audio provider
- get_available_points — Retrieve the total points available for the course
- get_categories — Retrieve course categories
- get_difficulty — Get Difficulty
- get_enrolled_students — Retrieve an array of students currently enrolled in the course
- get_instructors — Retrieve course instructor information
- get_lessons — Get course lessons
- get_percent_complete — Get a user's percentage completion through the course
- get_prerequisite_id — Get course's prerequisite id based on the type of prerequisite
- get_product — Retrieve an instance of the LLMS_Product for this course
- get_quizzes — Retrieve an array of quizzes within a course
- get_sales_page_url — Get the URL to a WP Page or Custom URL when sales page redirection is enabled
- get_sections — Get course sections
- get_student_count — Retrieve the number of enrolled students in the course
- get_students — Get an array of student IDs based on enrollment status in the course
- get_tags — Retrieve course tags
- get_to_array_excluded_properties — Get the properties that will be explicitly excluded from the array representation of the model.
- get_tracks — Retrieve course tracks
- get_video — Attempt to get oEmbed for a video provider
- has_capacity — Determine if the course is at capacity based on course capacity settings
- has_date_passed — Compare a course meta info date to the current date and get a bool
- has_prerequisite — Determine if prerequisites are enabled and there are prereqs configured
- has_sales_page_redirect — Determine if sales page redirection is enabled
- instructors — Retrieve an instance of the Post Instructors model
- is_enrollment_open — Determine if students can access course content based on the current date
- is_open — Determine if students can access course content based on the current date
- is_prerequisite_complete — Determine if a prerequisite is completed for a student
- set_instructors — Save instructor information
- toArrayAfter — Add data to the course model when converted to array
Changelog Changelog
| Version | Description |
|---|---|
| 6.0.0 | Removed deprecated items.
|
| 5.3.0 | Move audio and video embed methods to LLMS_Trait_Audio_Video_Embed. Move sales page methods to LLMS_Trait_Sales_Page. |
| 5.2.1 | Check for an empty sales page URL or ID. |
| 4.0.0 | Remove previously deprecated class methods. |
| 3.30.3 | Explicitly define class properties. |
| 1.0.0 | Introduced. |
User Contributed Notes User Contributed Notes
You must log in before being able to contribute a note or feedback.
As of Lifter LMS V3.34.0 (2019-08-15)
Many of the above methods are deprecated. Heres a snippet from the official changelog:
— — Swiped from LifterLMS Changelog — —
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()
Feedback
Thanks Joe, the reference was updated yesterday morning and perhaps you’re seeing a cached version that doesn’t have the updates from 3.34.0 yet. I’ve flushed the cache so you should see the deprecations now. — By Thomas Patrick Levy —
Hey Patrick, the page did have the deprications when I posted the snippet, but it did not include (or link to) the new best practices of obtaining the information these now deprecated methods retrieved. Best! — By bitojoe —