STRATO-apps/wordpress_03/app/wp-content/plugins/tutor-pro/tools/exporters/CourseExporter.php
SHA-256: d82dc386463adbf85de6e4b653b12bae3dae6aff0e6c64d9d215a6f8606d7288
<?php
/**
* Course Exporter
*
* @package TutorPro\Tools
* @author Themeum<support@themeum.com>
* @link https://themeum.com
* @since 3.6.0
*/
namespace TutorPro\Tools;
use Tutor\Helpers\QueryHelper;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Handle a course exporting
*
* @since 3.6.0
*/
class CourseExporter {
/**
* List of all sub-file types related to a course export.
*
* @since 3.8.1
*
* @var string[]
*/
private static array $course_sub_files = array(
Exporter::COURSE,
Exporter::ORDERS,
Exporter::PROGRESS,
Exporter::REVIEWS,
);
/**
* Export a course as array
*
* @since 3.6.0
*
* @since 3.8.1 Added `$keep_user_data` parameter.
*
* @throws \Exception Throws exception if course id is invalid.
*
* @param integer $course_id Course id.
* @param array $content_types Content types to export.
* like lesson post type, quiz post type etc.
*
* @param bool $keep_media_files To keep media files.
*
* @return array Export the entire course as an array
*/
public static function export( int $course_id, array $content_types, $keep_media_files = false ) {
global $wpdb;
// Course Post.
$course_data = get_post( $course_id );
if ( ! is_a( $course_data, 'WP_Post' ) ) {
throw new \Exception( __( 'Invalid post', 'tutor-pro' ) );
}
if ( tutor()->course_post_type !== $course_data->post_type ) {
throw new \Exception( __( 'Invalid course', 'tutor-pro' ) );
}
// Thumbnail.
$course_data->thumbnail_url = get_the_post_thumbnail_url( $course_id, 'full' );
// Post Meta.
$course_data->meta = get_post_meta( $course_id );
// Taxonomies (categories, tags).
$course_data->taxonomies = self::get_course_terms( $course_id );
// Child posts (lessons, topics, quizzes).
$course_data->contents = ! empty( $content_types ) ? self::get_child_posts( $course_id, $content_types, $keep_media_files ) : array();
// Get all the linked child posts except topics.
$course_data->child_posts = QueryHelper::get_all(
$wpdb->posts,
array(
'post_parent' => $course_id,
'post_type' => array(
'NOT IN',
array( tutor()->topics_post_type, tutor()->enrollment_post_type ),
),
),
'ID'
);
// Attachment links.
$course_data->attachment_links = array();
if ( $keep_media_files ) {
$course_data->attachment_links = self::get_attachment_links_from_meta( $course_data->meta, '_tutor_attachments' );
}
return $course_data;
}
/**
* Get all the child posts of a course
*
* @since 3.6.0
*
* @param int $course_id Course id.
* @param array $content_types Course content post types.
* @param bool $keep_media_files Whether to keep media files.
*
* @return array
*/
public static function get_child_posts( int $course_id, array $content_types, $keep_media_files ): array {
$response = array();
// Get topics.
$post_args = array(
'nopaging' => true,
'posts_per_page' => -1,
'ignore_sticky_posts' => true,
'cache_results' => false,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'no_found_rows' => true,
'order_by' => 'none',
);
$topics = array();
$topic_query = tutor_utils()->get_topics( $course_id, $post_args );
if ( is_a( $topic_query, 'WP_Query' ) ) {
$topics = $topic_query->get_posts();
}
if ( ! count( $topics ) ) {
return $response;
}
foreach ( $topics as $topic ) {
$child_post_types = $content_types;
$post_args['post_type'] = $child_post_types;
$children_query = tutor_utils()->get_course_contents_by_topic( $topic->ID, -1, $post_args );
if ( is_a( $children_query, 'WP_Query' ) ) {
$children = $children_query->get_posts();
foreach ( $children as $child ) {
// Add meta.
$child->meta = get_post_meta( $child->ID );
$child->child_posts = get_posts( array( 'post_parent' => $course_id ) );
$child->thumbnail_url = get_the_post_thumbnail_url( $child->ID, 'full' );
if ( $keep_media_files ) {
if ( tutor()->assignment_post_type === $child->post_type ) {
$child->attachment_links = self::get_attachment_links_from_meta( $child->meta, '_tutor_assignment_attachments' );
} else {
$child->attachment_links = self::get_attachment_links_from_meta( $child->meta, '_tutor_attachments' );
}
}
if ( tutor()->quiz_post_type === $child->post_type ) {
$child->question_answer = self::get_question_answer_by_quiz_id( $child->ID );
}
}
// Add children.
$topic->children = $children;
}
}
$response = $topics;
return $response;
}
/**
* Get question answers of a quiz
*
* @since 3.6.0
*
* @param integer $quiz_id Quiz id.
*
* @return array
*/
public static function get_question_answer_by_quiz_id( int $quiz_id ): array {
global $wpdb;
$query = "SELECT
q.*,
a.*
FROM {$wpdb->prefix}tutor_quiz_questions q
LEFT JOIN {$wpdb->prefix}tutor_quiz_question_answers a
ON q.question_id = a.belongs_question_id
WHERE q.quiz_id = %d
ORDER BY q.question_order ASC, a.answer_order ASC";
$results = $wpdb->get_results( $wpdb->prepare( $query, $quiz_id ) ); //phpcs:ignore
$organized = array();
foreach ( $results as $row ) {
$question_id = $row->question_id;
// Initialize question if not exists.
if ( ! isset( $organized[ $question_id ] ) ) {
$organized[ $question_id ] = array(
'question' => array(
'question_id' => $row->question_id,
'quiz_id' => $row->quiz_id,
'question_title' => $row->question_title,
'question_description' => $row->question_description,
'answer_explanation' => $row->answer_explanation,
'question_type' => $row->question_type,
'question_mark' => $row->question_mark,
'question_settings' => maybe_unserialize( $row->question_settings ),
'question_order' => $row->question_order,
),
'answers' => array(),
);
}
// Add answer.
$organized[ $question_id ]['answers'][] = array(
'answer_id' => $row->answer_id,
'belongs_question_id' => $row->belongs_question_id,
'belongs_question_type' => $row->belongs_question_type,
'answer_title' => $row->answer_title,
'is_correct' => $row->is_correct,
'image_id' => $row->image_id,
'image_url' => $row->image_id ? wp_get_attachment_url( $row->image_id ) : '',
'answer_two_gap_match' => $row->answer_two_gap_match,
'answer_view_format' => $row->answer_view_format,
'answer_settings' => maybe_unserialize( $row->answer_settings ),
'answer_order' => $row->answer_order,
);
}
// Reset array keys and return.
return array_values( $organized );
}
/**
* Get course terms
*
* Course categories, tags
*
* @since 3.6.0
*
* @param integer $course_id Course id.
*
* @return object {categories, tags}
*/
public static function get_course_terms( int $course_id ): object {
$term_args = array(
'order_by' => 'none',
'hide_empty' => false,
'object_ids' => $course_id,
'fields' => 'all',
);
$response = (object) array(
'categories' => tutor_utils()->get_course_categories( 0, $term_args, true ),
'tags' => tutor_utils()->get_course_tags( $term_args ),
);
return $response;
}
/**
* Get attachment links from meta array
*
* @since 3.6.0
*
* @param array $meta_data Array of meta data.
* @param string $meta_key Meta key that holds the attachment ids.
*
* @return array
*/
public static function get_attachment_links_from_meta( $meta_data, $meta_key ): array {
$attachment_links = array();
if ( is_array( $meta_data ) && count( $meta_data ) ) {
$attachment_ids = isset( $meta_data[ $meta_key ] ) && is_array( $meta_data[ $meta_key ] ) && ! empty( $meta_data[ $meta_key ] ) ? $meta_data[ $meta_key ][0] : array();
foreach ( maybe_unserialize( $attachment_ids ) as $attachment_id ) {
$url = wp_get_attachment_url( $attachment_id );
if ( $url ) {
$attachment_links[] = $url;
}
}
}
return $attachment_links;
}
/**
* Get the list of course sub-files to export.
*
* @since 3.8.1
*
* @param bool $keep_user_data Whether to include all sub-files.
* @return string[] List of sub-file identifiers.
*/
public function get_sub_files( $keep_user_data ) {
if ( ! $keep_user_data ) {
return array( Exporter::COURSE );
}
if ( tutor_utils()->is_addon_enabled( 'subscription' ) ) {
// Need to add plans and subscription before order.
self::$course_sub_files = Helper::insert_items_before_target( Exporter::ORDERS, self::$course_sub_files, array( Exporter::PLANS, Exporter::SUBSCRIPTIONS ) );
}
if ( tutor_utils()->is_addon_enabled( TUTOR_ENROLLMENTS()->basename ) ) {
array_push( self::$course_sub_files, Exporter::ENROLLMENTS );
}
return self::$course_sub_files;
}
/**
* Check if a given file is considered a course sub-file.
*
* @since 3.8.1
*
* @param string $content_type The type of content (e.g., course, bundle).
* @param string $sub_file The sub-file identifier to check.
* @return bool True if the file is a course sub-file, false otherwise.
*/
public static function is_sub_file( string $content_type, string $sub_file ) {
return tutor()->course_post_type === $content_type && in_array( $sub_file, self::$course_sub_files );
}
/**
* Check if the given content key represents a course.
*
* @since 3.8.1
*
* @param string $key Content key.
* @return bool True if the key matches CourseExporter::COURSE, false otherwise.
*/
public static function is_course( string $key ): bool {
return Exporter::COURSE === $key;
}
}