'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( '

%s

', 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 '
'; wp_nonce_field( 'accessnow_slms_export_course', 'accessnow_slms_export_nonce' ); if ( empty( $courses ) ) { echo '

' . esc_html__( 'No courses found.', 'accessnow-lms' ) . '

'; } else { echo ''; echo ''; echo ''; echo '
'; echo '
' . esc_html__( 'Include Media', 'accessnow-lms' ) . ''; echo ''; echo '

' . esc_html__( 'Untick for the text and settings only: a much smaller file, whose pictures keep pointing at this site.', 'accessnow-lms' ) . '

'; echo '
'; submit_button( __( 'Export Course (.zip)', 'accessnow-lms' ), 'primary', 'slms_do_export' ); } 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 '
'; wp_nonce_field( 'accessnow_slms_import_course', 'accessnow_slms_import_nonce' ); echo ''; echo ''; echo '
'; echo ''; printf( '

%s

', /* translators: %s: maximum upload size, e.g. "64 MB". */ sprintf( esc_html__( 'Maximum file size: %s', 'accessnow-lms' ), esc_html( size_format( wp_max_upload_size() ) ) ) ); echo '
'; submit_button( __( 'Import Course', 'accessnow-lms' ), 'primary', 'slms_do_import' ); 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( '/