post_type || ! current_user_can( 'manage_options' ) ) { return $actions; } $url = wp_nonce_url( admin_url( 'admin.php?action=accessnow_slms_clone_course&course_id=' . $post->ID ), 'accessnow_slms_clone_' . $post->ID ); $actions['slms_clone'] = sprintf( '%s', esc_url( $url ), esc_html__( 'Clone', 'accessnow-lms' ) ); return $actions; } public static function handle_clone() { $course_id = absint( $_GET['course_id'] ?? 0 ); if ( ! $course_id || ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Unauthorized.', 'accessnow-lms' ) ); } check_admin_referer( 'accessnow_slms_clone_' . $course_id ); $new_course_id = self::clone_course( $course_id ); if ( is_wp_error( $new_course_id ) ) { wp_die( esc_html( $new_course_id->get_error_message() ) ); } wp_safe_redirect( get_edit_post_link( $new_course_id, 'raw' ) ); exit; } private static function clone_course( $course_id ) { $data = Export_Import::gather_course_data( $course_id ); if ( ! $data || empty( $data['course'] ) ) { return new \WP_Error( 'slms_clone_failed', __( 'Could not read course data.', 'accessnow-lms' ) ); } // Generate unique title with (Copy) suffix $data['course']['title'] = self::generate_copy_title( $data['course']['title'] ); // Create course as draft $new_course_id = wp_insert_post( wp_slash( array( 'post_type' => 'slms_course', 'post_title' => sanitize_text_field( $data['course']['title'] ), 'post_content' => wp_kses_post( $data['course']['content'] ), 'post_status' => 'draft', ) ), true ); if ( is_wp_error( $new_course_id ) ) { return $new_course_id; } // Course meta if ( ! empty( $data['course']['meta'] ) ) { foreach ( $data['course']['meta'] as $key => $value ) { update_post_meta( $new_course_id, $key, wp_slash( $value ) ); } } // Course thumbnail $thumb_id = get_post_thumbnail_id( $course_id ); if ( $thumb_id ) { $new_thumb = self::clone_attachment( $thumb_id ); if ( $new_thumb ) { set_post_thumbnail( $new_course_id, $new_thumb ); } } // Units if ( ! empty( $data['units'] ) ) { foreach ( $data['units'] as $unit_data ) { $unit_id = wp_insert_post( wp_slash( array( 'post_type' => 'slms_unit', 'post_title' => sanitize_text_field( $unit_data['title'] ), 'post_status' => $unit_data['status'] ?? 'publish', ) ), true ); if ( is_wp_error( $unit_id ) ) { continue; } update_post_meta( $unit_id, '_slms_parent_course', $new_course_id ); if ( isset( $unit_data['meta']['_slms_sort_order'] ) ) { update_post_meta( $unit_id, '_slms_sort_order', absint( $unit_data['meta']['_slms_sort_order'] ) ); } // Keys added through accessnow_slms_export_meta_keys (sort order and header are handled here). self::copy_meta( $unit_id, $unit_data, array_diff( Export_Import::meta_keys( 'slms_unit' ), array( '_slms_sort_order', Lesson_Header::META ) ) ); if ( ! empty( $unit_data['meta'][ Lesson_Header::META ] ) ) { update_post_meta( $unit_id, Lesson_Header::META, Lesson_Header::sanitize_style( $unit_data['meta'][ Lesson_Header::META ] ) ); } self::clone_thumbnail( $unit_id, $unit_data ); // Lessons if ( ! empty( $unit_data['lessons'] ) ) { foreach ( $unit_data['lessons'] as $lesson_data ) { $lesson_id = wp_insert_post( wp_slash( array( 'post_type' => 'slms_lesson', 'post_title' => sanitize_text_field( $lesson_data['title'] ), 'post_content' => wp_kses_post( $lesson_data['content'] ), 'post_status' => $lesson_data['status'] ?? 'publish', ) ), true ); if ( is_wp_error( $lesson_id ) ) { continue; } update_post_meta( $lesson_id, '_slms_parent_unit', $unit_id ); self::copy_meta( $lesson_id, $lesson_data, Export_Import::meta_keys( 'slms_lesson' ) ); self::clone_thumbnail( $lesson_id, $lesson_data ); // Lesson attachments — clone the originals (matched by file name) // Find the original lesson's attachment IDs from the gathered data if ( ! empty( $lesson_data['attachments'] ) ) { // Look up original attachment IDs by matching filenames $new_att_ids = array(); foreach ( $lesson_data['attachments'] as $att_info ) { $orig_att = self::find_attachment_by_filename( $att_info['filename'] ); if ( $orig_att ) { $new_att = self::clone_attachment( $orig_att ); if ( $new_att ) { $new_att_ids[] = $new_att; } } } if ( ! empty( $new_att_ids ) ) { update_post_meta( $lesson_id, '_slms_attachments', $new_att_ids ); } } // Quiz if ( ! empty( $lesson_data['quiz'] ) ) { $quiz_data = $lesson_data['quiz']; $quiz_id = wp_insert_post( wp_slash( array( 'post_type' => 'slms_quiz', 'post_title' => sanitize_text_field( $quiz_data['title'] ), 'post_status' => $quiz_data['status'] ?? 'publish', ) ), true ); if ( ! is_wp_error( $quiz_id ) ) { update_post_meta( $quiz_id, '_slms_parent_lesson', $lesson_id ); foreach ( Export_Import::meta_keys( 'slms_quiz' ) as $mk ) { if ( isset( $quiz_data['meta'][ $mk ] ) ) { update_post_meta( $quiz_id, $mk, wp_slash( $quiz_data['meta'][ $mk ] ) ); } } } } } } } } return $new_course_id; } /** Give a cloned unit or lesson a copy of the original's header image. */ private static function clone_thumbnail( $post_id, $post_data ) { if ( empty( $post_data['thumbnail']['filename'] ) ) { return; } $orig = self::find_attachment_by_filename( $post_data['thumbnail']['filename'] ); $new = $orig ? self::clone_attachment( $orig ) : null; if ( $new ) { set_post_thumbnail( $post_id, $new ); } } private static function clone_attachment( $attachment_id ) { $file_path = get_attached_file( $attachment_id ); if ( ! $file_path || ! file_exists( $file_path ) ) { return null; } $orig_post = get_post( $attachment_id ); if ( ! $orig_post ) { return null; } $new_id = wp_insert_attachment( array( 'post_title' => $orig_post->post_title, 'post_mime_type' => $orig_post->post_mime_type, 'post_status' => 'inherit', ), $file_path ); if ( is_wp_error( $new_id ) || ! $new_id ) { return null; } // Copy attachment metadata (includes thumbnail sizes) $metadata = wp_get_attachment_metadata( $attachment_id ); if ( $metadata ) { wp_update_attachment_metadata( $new_id, $metadata ); } // Copy alt text $alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); if ( $alt ) { update_post_meta( $new_id, '_wp_attachment_image_alt', $alt ); } return $new_id; } private static function find_attachment_by_filename( $filename ) { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off lookup while cloning. $result = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = '_wp_attached_file' AND meta_value LIKE %s LIMIT 1", '%' . $wpdb->esc_like( $filename ) ) ); return $result ? (int) $result : null; } private static function generate_copy_title( $title ) { // If already ends with "(Copy)" or "(Copy N)", increment if ( preg_match( '/\(Copy(?:\s+(\d+))?\)\s*$/', $title, $m ) ) { $n = isset( $m[1] ) && '' !== $m[1] ? (int) $m[1] + 1 : 2; return preg_replace( '/\(Copy(?:\s+\d+)?\)\s*$/', "(Copy {$n})", $title ); } return $title . ' (Copy)'; } /** * Copy the given meta keys from gathered course data to a new post, leaving out empty values. * * @param int $post_id Post to write to. * @param array $data Gathered post data (Export_Import::gather_course_data()). * @param string[] $keys Meta keys. */ private static function copy_meta( $post_id, $data, $keys ) { foreach ( $keys as $key ) { if ( isset( $data['meta'][ $key ] ) && '' !== $data['meta'][ $key ] ) { update_post_meta( $post_id, $key, wp_slash( $data['meta'][ $key ] ) ); } } } }