includes/privacy/class-llms-privacy-exporters.php
<?php
/**
* LifterLMS Privacy Exporter
*
* @package LifterLMS/Privacy/Classes
*
* @since 3.18.0
* @version 6.0.0
*/
defined( 'ABSPATH' ) || exit;
/**
* LifterLMS Privacy Exporter class
*
* @since 3.18.0
* @since 3.30.3 Fixed spelling error.
* @since 3.37.9 Add export group descriptions.
*/
class LLMS_Privacy_Exporters extends LLMS_Privacy {
/**
* Export student achievement data by email address
*
* @since 3.18.0
* @since 3.37.9 Added `$group_description` to the group exporter.
*
* @param string $email_address Email address of the user to retrieve data for.
* @param int $page Process page number.
* @return array
*/
public static function achievement_data( $email_address, $page ) {
$data = array();
$student = self::get_student_by_email( $email_address );
if ( ! $student ) {
return self::get_return( $data );
}
$achievements = self::get_student_achievements( $student );
if ( $achievements ) {
$group_label = __( 'Achievements', 'lifterlms' );
$group_description = __( 'Student achievement data.', 'lifterlms' );
foreach ( $achievements as $achievement ) {
$data[] = array(
'group_id' => 'lifterlms_achievements',
'group_label' => $group_label,
'group_description' => $group_description,
'item_id' => sprintf( 'achievement-%d', $achievement->get( 'id' ) ),
'data' => self::get_achievement_data( $achievement ),
);
}
}
return self::get_return( $data );
}
/**
* Export student certificate data by email address
*
* @since 3.18.0
* @since 3.37.9 Added `$group_description` to the group exporter.
*
* @param string $email_address Email address of the user to retrieve data for.
* @param int $page Process page number.
* @return array
*/
public static function certificate_data( $email_address, $page ) {
$data = array();
$student = self::get_student_by_email( $email_address );
if ( ! $student ) {
return self::get_return( $data );
}
$certs = self::get_student_certificates( $student );
if ( $certs ) {
$group_label = __( 'Certificates', 'lifterlms' );
$group_description = __( 'Student certificate data.', 'lifterlms' );
foreach ( $certs as $cert ) {
$data[] = array(
'group_id' => 'lifterlms_certificates',
'group_label' => $group_label,
'group_description' => $group_description,
'item_id' => sprintf( 'certificate-%d', $cert->get( 'id' ) ),
'data' => self::get_certificate_data( $cert ),
);
}
}
return self::get_return( $data );
}
/**
* Get data for a certificate
*
* @since 3.18.0
*
* @param obj $achievement LLMS_User_Certificate.
* @return array
*/
private static function get_achievement_data( $achievement ) {
$data = array();
$data[] = array(
'name' => __( 'Title', 'lifterlms' ),
'value' => $achievement->get( 'title' ),
);
$data[] = array(
'name' => __( 'Description', 'lifterlms' ),
'value' => $achievement->get( 'content' ),
);
$data[] = array(
'name' => __( 'Earned Date', 'lifterlms' ),
'value' => $achievement->get_earned_date( 'Y-m-d H:i:s' ),
);
$data[] = array(
'name' => __( 'Image', 'lifterlms' ),
'value' => $achievement->get_image(),
);
return $data;
}
/**
* Get data for a certificate.
*
* @since 3.18.0
* @since 6.0.0 Replaced the use of the deprecated `certificate_title` meta key with the post's title property.
*
* @param LLMS_User_Certificate $cert Certificate object.
* @return array
*/
private static function get_certificate_data( $cert ) {
$data = array();
$title = $cert->get( 'title' );
$filename = llms()->certificates()->get_export( $cert->get( 'id' ), true );
if ( ! is_wp_error( $filename ) ) {
$title = '<a href="certificates/' . basename( $filename ) . '">' . $title . '</a>';
}
$data[] = array(
'name' => __( 'Title', 'lifterlms' ),
'value' => $title,
);
$data[] = array(
'name' => __( 'Earned Date', 'lifterlms' ),
'value' => $cert->get_earned_date( 'Y-m-d H:i:s' ),
);
return $data;
}
/**
* Get an array of enrollment data for a course or membership
*
* @since 3.18.0
* @since 3.30.3 Fixed spelling errors.
*
* @param int $post_id WP Post ID of course or membership.
* @param obj $student LLMS_Student.
* @param obj $post_type_object WP post type object.
* @return array
*/
private static function get_enrollment_data( $post_id, $student, $post_type_object ) {
$data = array();
$data[] = array(
// Translators: %s = post type singular name label (Course or Membership).
'name' => sprintf( __( '%s Title', 'lifterlms' ), $post_type_object->labels->singular_name ),
'value' => get_the_title( $post_id ),
);
$data[] = array(
'name' => __( 'Enrollment Status', 'lifterlms' ),
'value' => llms_get_enrollment_status_name( $student->get_enrollment_status( $post_id ) ),
);
$data[] = array(
'name' => __( 'Enrollment Date', 'lifterlms' ),
'value' => $student->get_enrollment_date( $post_id, 'enrolled', 'Y-m-d H:i:s' ),
);
if ( 'course' === $post_type_object->name ) {
$data[] = array(
'name' => __( 'Last Activity', 'lifterlms' ),
'value' => $student->get_enrollment_date( $post_id, 'updated', 'Y-m-d H:i:s' ),
);
$progress = $student->get_progress( $post_id, 'course' );
if ( is_numeric( $progress ) ) {
$progress .= '%';
}
$data[] = array(
'name' => __( 'Progress', 'lifterlms' ),
'value' => $progress,
);
$grade = $student->get_grade( $post_id );
if ( is_numeric( $grade ) ) {
$grade .= '%';
}
$data[] = array(
'name' => __( 'Grade', 'lifterlms' ),
'value' => $grade,
);
}
return apply_filters( 'llms_privacy_export_enrollment_data', $data, $post_id, $student, $post_type_object );
}
/**
* Retrieve export data for a single order
*
* @since 3.18.0
*
* @param LLMS_Order $order Order object.
* @return array
*/
private static function get_order_data( $order ) {
$data = array();
$props = self::get_order_data_props( 'export' );
foreach ( $props as $prop => $name ) {
$value = apply_filters( 'llms_privacy_export_order_data_prop_value', $order->get( $prop ), $prop, $order );
if ( $value ) {
$data[] = array(
'name' => $name,
'value' => $value,
);
}
}
$transactions = $order->get_transactions(
array(
'per_page' => 500,
)
);
if ( $transactions['transactions'] ) {
$txns = array();
foreach ( $transactions['transactions'] as $txn ) {
$txns[] = sprintf( '%1$s — %2$s (#%3$d)', $txn->get( 'date' ), $txn->get_price( 'amount' ), $txn->get( 'id' ) );
}
$data[] = array(
'name' => __( 'Transactions', 'lifterlms' ),
'value' => implode( '<br>', $txns ),
);
}
return apply_filters( 'llms_privacy_export_order_data', $data, $order );
}
/**
* Get export data for a single quiz attempt
*
* @since 3.18.0
*
* @param LLMS_Quiz_Attempt $attempt Quiz attempt object.
* @return array
*/
private static function get_quiz_attempt_data( $attempt ) {
$data = array();
$quiz = $attempt->get_quiz();
if ( $quiz ) {
$data[] = array(
'name' => __( 'Title', 'lifterlms' ),
'value' => $quiz->get( 'title' ),
);
}
$data[] = array(
'name' => __( 'Attempt ID', 'lifterlms' ),
'value' => $attempt->get_key(),
);
$data[] = array(
'name' => __( 'Attempt Number', 'lifterlms' ),
'value' => $attempt->get( 'attempt' ),
);
$data[] = array(
'name' => __( 'Status', 'lifterlms' ),
'value' => $attempt->l10n( 'status' ),
);
$grade = $attempt->get( 'grade' );
$data[] = array(
'name' => __( 'Grade', 'lifterlms' ),
'value' => is_numeric( $grade ) ? $grade . '%' : '–',
);
return $data;
}
/**
* Return export data to an exporter
*
* @since 3.18.0
*
* @param array $data Array of data.
* @return array
*/
private static function get_return( $data = array(), $done = true ) {
return array(
'data' => $data,
'done' => $done,
);
}
/**
* Get student data to export for a user
*
* @since 3.18.0
*
* @param LLMS_Student $student Student object.
* @return array
*/
private static function get_student_data( $student ) {
$data = array();
$props = self::get_student_data_props();
foreach ( $props as $prop => $name ) {
$value = apply_filters( 'llms_privacy_export_student_data_prop_value', $student->get( $prop ), $prop, $student );
if ( $value ) {
$data[] = array(
'name' => $name,
'value' => $value,
);
}
}
return apply_filters( 'llms_privacy_export_student_data', $data, $student );
}
/**
* Export student course data by email address
*
* @since 3.18.0
*
* @param string $email_address Email address of the user to retrieve data for.
* @param int $page Process page number.
* @return array
*/
public static function course_data( $email_address, $page ) {
return self::enrollment_data( $email_address, $page, 'course' );
}
/**
* General exporter for handling course and membership enrollment data
*
* @since 3.18.0
* @since 3.37.9 Added `$group_description` to the group exporter.
*
* @param string $email_address Requested user's email address
* @param int $page process page number
* @param string $post_type name of the post type
* @return array
*/
private static function enrollment_data( $email_address, $page, $post_type ) {
$data = array();
$student = self::get_student_by_email( $email_address );
if ( ! $student ) {
return self::get_return( $data );
}
$enrollments = self::get_student_enrollments( $student, $page, $post_type );
if ( $enrollments['results'] ) {
$post_type_obj = get_post_type_object( $post_type );
$group_id = 'lifterlms_' . $post_type;
foreach ( $enrollments['results'] as $post_id ) {
$data[] = array(
'group_id' => $group_id,
'group_label' => $post_type_obj->labels->name,
/* translators: %s: The name of the enrollment post type. */
'group_description' => sprintf( __( 'Student %s enrollment data.', 'lifterlms' ), $post_type_obj->labels->name ),
'item_id' => sprintf( '%1$s-%2$d', $post_type, $post_id ),
'data' => self::get_enrollment_data( $post_id, $student, $post_type_obj ),
);
}
}
return self::get_return( $data, $enrollments['done'] );
}
/**
* Add files to the zip file for a data export request.
*
* Adds certificate files into the `/certificates/` directory within the archive.
*
* @since 3.18.0
* @since 6.0.0 Replaced the use of the deprecated `wp_get_user_request_data()` function with `wp_get_user_request()`.
*
* @param string $archive_pathname Full path to the zip archive.
* @param string $archive_url Full URI to the zip archive.
* @param string $html_report_pathname Full path to the .html file within the archive.
* @param int $request_id WP Post ID of the export request.
* @return void
*/
public static function maybe_add_export_files( $archive_pathname, $archive_url, $html_report_pathname, $request_id ) {
if ( ! class_exists( 'ZipArchive' ) ) {
return;
}
$request = wp_get_user_request( $request_id );
$student = self::get_student_by_email( $request->email );
if ( ! $student ) {
return;
}
$certs = self::get_student_certificates( $student );
if ( ! $certs ) {
return;
}
$zip = new ZipArchive();
$delete = array();
if ( true === $zip->open( $archive_pathname ) ) {
foreach ( $certs as $cert ) {
$filepath = llms()->certificates()->get_export( $cert->get( 'id' ), true );
$delete[ $cert->certificate_id ] = $filepath;
if ( is_wp_error( $filepath ) ) {
continue;
}
$zip->addFile( $filepath, '/certificates/' . basename( $filepath ) );
}
}
$zip->close();
// cleanup all files
foreach ( $delete as $id => $path ) {
wp_delete_file( $path );
delete_post_meta( $id, '_llms_export_filepath' );
}
}
/**
* Export student membership data by email address
*
* @since 3.18.0
*
* @param string $email_address email address of the user to retrieve data for
* @param int $page process page number
* @return array
*/
public static function membership_data( $email_address, $page ) {
return self::enrollment_data( $email_address, $page, 'llms_membership' );
}
/**
* Export student orders data by email address
*
* @since 3.18.0
* @since 3.37.9 Added `$group_description` to the group exporter.
*
* @param string $email_address Email address of the user to retrieve data for.
* @param int $page Process page number.
* @return array
*/
public static function order_data( $email_address, $page ) {
$data = array();
$student = self::get_student_by_email( $email_address );
if ( ! $student ) {
return self::get_return( $data );
}
$orders = self::get_student_orders( $student, $page );
$group_label = __( 'Orders', 'lifterlms' );
$group_description = __( 'Student orders data.', 'lifterlms' );
foreach ( $orders['orders'] as $order ) {
$data[] = array(
'group_id' => 'lifterlms_orders',
'group_label' => $group_label,
'group_description' => $group_description,
'item_id' => sprintf( 'order-%d', $order->get( 'id' ) ),
'data' => self::get_order_data( $order ),
);
}
return self::get_return( $data, $orders['done'] );
}
/**
* Export student data by email address
*
* @since 3.18.0
* @since 3.37.9 Added `$group_description` to the group exporter.
*
* @param string $email_address Email address of the user to retrieve data for.
* @param int $page Process page number.
* @return array
*/
public static function student_data( $email_address, $page ) {
$data = array();
$student = self::get_student_by_email( $email_address );
if ( ! $student ) {
return self::get_return( $data );
}
$data[] = array(
'group_id' => 'lifterlms_student',
'group_label' => __( 'Personal Information', 'lifterlms' ),
'group_description' => __( 'Student personal information data.', 'lifterlms' ),
'item_id' => sprintf( 'student-%d', $student->get( 'id' ) ),
'data' => self::get_student_data( $student ),
);
return self::get_return( $data );
}
/**
* Export quiz attempt data by email address
*
* @since 3.18.0
* @since 3.37.9 Added `$group_description` to the group exporter.
*
* @param string $email_address Email address of the user to retrieve data for.
* @param int $page Process page number.
* @return array
*/
public static function quiz_data( $email_address, $page ) {
$data = array();
$student = self::get_student_by_email( $email_address );
if ( ! $student ) {
return self::get_return( $data );
}
$query = self::get_student_quizzes( $student, $page );
$done = true;
if ( $query->has_results() ) {
$group_label = __( 'Quiz Attempts', 'lifterlms' );
$group_descriptions = __( 'Student quiz attempt data', 'lifterlms' );
foreach ( $query->get_attempts() as $attempt ) {
$data[] = array(
'group_id' => 'lifterlms_quizzes',
'group_label' => $group_label,
'group_description' => $group_description,
'item_id' => sprintf( 'order-%d', $attempt->get( 'id' ) ),
'data' => self::get_quiz_attempt_data( $attempt ),
);
}
$done = $query->is_last_page();
}
return self::get_return( $data, $done );
}
}