'slms_course',
'posts_per_page' => -1,
'post_status' => 'any',
'orderby' => 'title',
'order' => 'ASC',
)
);
echo '
';
echo '
' . esc_html__( 'Export / Import Courses', 'accessnow-lms' ) . '
';
// Success / error notices from import
$notice_key = self::notice_key();
$notice = get_transient( $notice_key );
if ( is_array( $notice ) && ! empty( $notice['message'] ) ) {
$type = in_array( $notice['type'] ?? '', array( 'error', 'warning', 'success' ), true ) ? $notice['type'] : 'error';
printf( '
', esc_attr( $type ), esc_html( $notice['message'] ) );
delete_transient( $notice_key );
}
// ── Export Section
echo '
';
echo '
';
echo '
' . esc_html__( 'Export Course', 'accessnow-lms' ) . '
';
echo '
' . esc_html__( 'Download a course as a .zip file including all units, lessons, quizzes, and questions. Media can be included or excluded.', 'accessnow-lms' ) . '
';
echo '
';
// ── Import Section
echo '
';
echo '
' . esc_html__( 'Import Course', 'accessnow-lms' ) . '
';
echo '
' . esc_html__( 'Upload a .zip file previously exported from AccessNow LMS. All content and media will be imported. Duplicate imports will create new copies.', 'accessnow-lms' ) . '
';
echo '
';
echo '
';
}
/* ─── Export Handler ───────────────────────────────────────── */
public static function handle_export() {
if ( ! isset( $_POST['slms_do_export'] ) ) {
return;
}
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['accessnow_slms_export_nonce'] ?? '' ) ), 'accessnow_slms_export_course' ) ) {
wp_die( esc_html__( 'Security check failed.', 'accessnow-lms' ) );
}
$course_id = absint( $_POST['slms_export_course_id'] ?? 0 );
$course = get_post( $course_id );
if ( ! $course || 'slms_course' !== $course->post_type ) {
wp_die( esc_html__( 'Invalid course.', 'accessnow-lms' ) );
}
$tmp_file = wp_tempnam( 'slms-export' );
if ( ! self::build_zip( $course_id, ! empty( $_POST['slms_export_media'] ), $tmp_file ) ) {
wp_die( esc_html__( 'Could not create ZIP file.', 'accessnow-lms' ) );
}
$slug = sanitize_title( $course->post_title );
$filename = 'slms-course-' . $slug . '-' . gmdate( 'Y-m-d' ) . '.zip';
header( 'Content-Type: application/zip' );
header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
header( 'Content-Length: ' . filesize( $tmp_file ) );
header( 'Pragma: no-cache' );
// Stream the temporary ZIP we just built (can be large, so not read into memory).
readfile( $tmp_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile
wp_delete_file( $tmp_file );
exit;
}
/**
* Writes a course's export ZIP: course.json, and with $include_media the files it uses under media/.
*
* @param int $course_id Course.
* @param bool $include_media Header images, attachments and the pictures in lesson text.
* @param string $path Where to write the ZIP.
* @return bool
*/
public static function build_zip( $course_id, $include_media, $path ) {
$data = self::gather_course_data( $course_id );
if ( ! $include_media ) {
$data = self::strip_media_from_data( $data );
}
$zip = new \ZipArchive();
if ( true !== $zip->open( $path, \ZipArchive::CREATE | \ZipArchive::OVERWRITE ) ) {
return false;
}
$zip->addFromString( 'course.json', wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE ) );
if ( $include_media ) {
foreach ( self::collect_media_files( $data ) as $relative_path => $absolute_path ) {
if ( file_exists( $absolute_path ) ) {
$zip->addFile( $absolute_path, 'media/' . $relative_path );
}
}
}
return $zip->close();
}
/**
* Attachments used inside post content: image (and other media) block ids, wp-image-N classes,
* and links to files in this site's uploads folder.
*
* @param string $html Post content.
* @return int[]
*/
public static function content_attachment_ids( $html ) {
$ids = array();
if ( preg_match_all( '/\bwp-image-(\d+)\b/', $html, $m ) ) {
$ids = array_merge( $ids, $m[1] );
}
if ( preg_match_all( '/