Your IP : 216.73.216.138


Current Path : /home/ereika83/public_html/wp-content/plugins/sfwd-lms/src/Core/Modules/AI/
Upload File :
Current File : /home/ereika83/public_html/wp-content/plugins/sfwd-lms/src/Core/Modules/AI/Course_Outline.php

<?php
/**
 * Course outline AI module.
 *
 * @package LearnDash\Core
 */

namespace LearnDash\Core\Modules\AI;

use Exception;
use InvalidArgumentException;
use LDLMS_Course_Steps;
use LDLMS_Factory_Post;
use LDLMS_Post_Types;
use LearnDash\Core\Services\ChatGPT;
use SFWD_LMS;
use StellarWP\Learndash\lucatume\DI52\App;

/**
 * Course outline generator AI class.
 *
 * @since 4.6.0
 */
class Course_Outline {
	/**
	 * Page slug.
	 *
	 * @since 4.6.0
	 *
	 * @var string
	 */
	public static $slug = 'learndash-ai-course-outline';

	/**
	 * AJAX actions.
	 *
	 * @since 4.6.0
	 *
	 * @var array<string, string>
	 */
	public static $ajax_actions = [
		'get_course' => 'ld_get_course',
	];

	/**
	 * Maximum number of allowed lessons.
	 *
	 * @since 4.6.0
	 *
	 * @var int
	 */
	private static $max_lessons = 30;

	/**
	 * ChatGPT client.
	 *
	 * @since 4.6.0
	 *
	 * @var ChatGPT
	 */
	private $chatgpt;

	/**
	 * Constructor.
	 *
	 * @since 4.6.0
	 *
	 * @param ChatGPT $chatgpt ChatGPT client.
	 */
	public function __construct( ChatGPT $chatgpt ) {
		$this->chatgpt = $chatgpt;
	}

	/**
	 * Add the course wizard button to the course list table.
	 * TODO: Add this button via the Global Header Component rather than by injecting it like this.
	 *
	 * @since 4.6.0
	 *
	 * return void
	 */
	public function add_button(): void {
		$screen = get_current_screen();

		if ( is_object( $screen ) && 'edit-' . learndash_get_post_type_slug( LDLMS_Post_Types::COURSE ) === $screen->id ) {
			$label = wp_sprintf(
				// translators: Course label.
				__( 'Create %s Outline from AI', 'learndash' ),
				learndash_get_custom_label( 'course' )
			);
			?>
			<script>
				( function() {
					window.addEventListener( 'load', function() {
						const settingsElem  = document.getElementsByClassName( 'ld-global-header-new-settings' );
						const newEntityElem =  document.getElementsByClassName( 'global-new-entity-button' );

						const link = document.createElement( 'a' );
						const text = document.createTextNode( '<?php echo esc_html( $label ); ?>' );
						link.setAttribute( 'href', '<?php echo esc_url( admin_url( 'admin.php?page=' . static::$slug ) ); ?>' );
						link.setAttribute( 'class', 'global-new-entity-button button button-primary button-small' );
						link.appendChild( text );

						settingsElem[0].insertBefore( link, newEntityElem[0] );
					});
				} )();
			</script>
			<?php
		}
	}

	/**
	 * Register admin pages.
	 *
	 * @since 4.6.0
	 *
	 * @return void
	 */
	public function register_page(): void {
		add_submenu_page(
			'learndash-lms',
			wp_sprintf(
				// translators: Course label.
				__( 'Create %s Outline from AI', 'learndash' ),
				learndash_get_custom_label( 'course' )
			),
			'',
			LEARNDASH_ADMIN_CAPABILITY_CHECK,
			static::$slug,
			App::callback( $this, 'render' )
		);
	}

	/**
	 * Add admin styles.
	 *
	 * @since 4.6.0
	 *
	 * @return void
	 */
	public function add_scripts(): void {
		?>
		<style>
			#adminmenu .toplevel_page_learndash-lms .wp-submenu a[href="admin.php?page=<?php echo esc_attr( static::$slug ); ?>"] {
				display: none;
			}
		</style>
		<?php
		$screen = get_current_screen();

		if ( is_object( $screen ) && 'learndash-lms_page_' . static::$slug === $screen->id ) {
			?>
				<style>
					.select2-container {
						border: 1px solid #8c8f94;
						border-radius: 4px;
					}

					.select2-container.select2-container--default {
						height: 30px;
					}

					.select2-container .select2-selection--single {
						height: 30px;
						line-height: 30px;
					}
				</style>
				<script>
					( function() {
						jQuery( function( $ ) {
							$( '.submenu-ldlms-courses' ).addClass( 'current' );

							$( 'select[name="course_id"]' ).select2({
								width: '100%',
								allowClear: true,
								placeholder: '',
								multiple: false,
								maximumSelectionSize: 1,
								minimumInputLength: 3,
								style: '30px',
								ajax: {
									url: "<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>",
									method: 'GET',
									dataType: 'json',
									delay: 300,
									data: function ( params ) {
										return {
											action: '<?php echo esc_attr( static::$ajax_actions['get_course'] ); ?>',
											nonce: '<?php echo esc_attr( wp_create_nonce( static::$ajax_actions['get_course'] ) ); ?>',
											keyword: params.term
										};
									},
									processResults: function ( response, params ) {
										params.page = params.page || 1;

										return {
											results: response.data.items,
										};
									},
									cache: false
								}
							});
						} );
					} )();
				</script>
			<?php
		}
	}

	/**
	 * Enqueue admin scripts.
	 *
	 * @since 4.6.0
	 *
	 * @return void
	 */
	public function enqueue_admin_scripts(): void {
		$screen = get_current_screen();
		if ( is_object( $screen ) && 'learndash-lms_page_' . static::$slug === $screen->id ) {
			wp_enqueue_style( 'ld-tailwindcss' );
		}
	}

	/**
	 * Render page.
	 *
	 * @since 4.6.0
	 *
	 * @todo Change with the new admin view render after Breezy template update.
	 *
	 * @return void
	 */
	public function render(): void {
		SFWD_LMS::get_view(
			'AI/course-outline',
			[
				'api_key' => $this->chatgpt->get_api_key(),
			],
			true
		);
	}

	/**
	 * Execute ChatGPT command.
	 *
	 * @since 4.6.0
	 *
	 * @throws InvalidArgumentException Invalid argument value.
	 *
	 * @return void
	 */
	public function init(): void {
		if ( ! current_user_can( 'manage_options' ) || ! check_admin_referer( static::$slug ) ) {
			wp_die( 'Unauthorized access.' );
		}

		if ( isset( $_POST['course_id'] ) && isset( $_POST['lesson_count'] ) && isset( $_POST['course_idea'] ) ) {
			try {
				$course_id    = sanitize_text_field( wp_unslash( $_POST['course_id'] ) );
				$lesson_count = absint( $_POST['lesson_count'] );
				$course_idea  = sanitize_textarea_field( wp_unslash( $_POST['course_idea'] ) );

				if ( $lesson_count > self::$max_lessons ) {
					throw new InvalidArgumentException(
						wp_sprintf(
							// translators: 1$: Maximum amount of lessons, 2$: Lessons label.
							__( 'Error: %1$s is maximum number of %2$s you can enter.', 'learndash' ),
							self::$max_lessons,
							learndash_get_custom_label_lower( 'lessons' )
						)
					);
				}

				$course       = json_decode( $course_id, true );
				$course_title = is_array( $course ) && ! empty( $course['title'] ) ? $course['title'] : get_the_title( absint( $course ) );
				$course_id    = ! is_array( $course ) || empty( $course['title'] ) ? absint( $course ) : 0;

				$command = "Create a numbered bullet list with {$lesson_count} lesson titles for a '{$course_title}' course on the topic of '{$course_idea}'.";

				$response = $this->chatgpt->send_command( $command );
			} catch ( Exception $e ) {
				learndash_safe_redirect(
					admin_url(
						add_query_arg(
							[
								'page'  => static::$slug,
								'error' => rawurlencode( $e->getMessage() ),
							],
							'admin.php'
						)
					)
				);

				exit;
			}

			$parsed_response = $this->parse(
				$response,
				array(
					'id'    => $course_id,
					'title' => $course_title,
				)
			);
			$process         = $this->process( $parsed_response );

			learndash_safe_redirect(
				add_query_arg(
					[
						'page'    => static::$slug,
						'success' => rawurlencode( $process ),
					],
					admin_url( 'admin.php' )
				)
			);

			exit;
		}
	}

	/**
	 * Parse ChatGPT response.
	 *
	 * @since 4.6.0
	 *
	 * @param string               $response_text Response text given by ChatGPT.
	 * @param array<string, mixed> $course_args   Course args.
	 * @return array<string, mixed>
	 */
	private function parse( string $response_text, array $course_args ): array {
		$lines = explode( PHP_EOL, trim( $response_text ) );

		$lesson_titles = [];
		foreach ( $lines as $line ) {
			if ( preg_match( '/^\d+\./', $line ) ) {
				$lesson_title = trim( substr( $line, strpos( $line, '.' ) + 1 ) );
				if ( ! empty( $lesson_title ) ) {
					$lesson_titles[] = $lesson_title;
				}
			}
		}

		return [
			'course_title'  => $course_args['title'],
			'course_id'     => $course_args['id'],
			'lesson_count'  => count( $lesson_titles ),
			'lesson_titles' => $lesson_titles,
		];
	}

	/**
	 * Process parsed response.
	 *
	 * @since 4.6.0
	 *
	 * @param array<string, mixed> $parsed_response Response text after being parsed.
	 * @return string
	 */
	private function process( array $parsed_response ): string {
		$course_id = $parsed_response['course_id'];
		/**
		 * Course title.
		 *
		 * @var string
		 */
		$course_title  = $parsed_response['course_title'];
		$lesson_titles = is_array( $parsed_response['lesson_titles'] ) ? $parsed_response['lesson_titles'] : [];

		if ( empty( $course_id ) ) {
			$course_id = wp_insert_post(
				[
					'post_title'  => $course_title,
					'post_type'   => learndash_get_post_type_slug( LDLMS_Post_Types::COURSE ),
					'post_status' => 'publish',
				]
			);
		}

		$course_id    = absint( $course_id );
		$lesson_count = count( $lesson_titles );
		for ( $i = 0; $i < $lesson_count; $i++ ) {
			$lesson_title = $lesson_titles[ $i ];
			$lesson_id    = wp_insert_post(
				[
					'post_title'  => $lesson_title,
					'post_type'   => 'sfwd-lessons',
					'post_status' => 'publish',
				]
			);

			learndash_update_setting( $lesson_id, 'course', $course_id );

			learndash_course_add_child_to_parent( $course_id, $lesson_id, $course_id );
		}

		$message = wp_sprintf(
			// translators: 1$: lessons count, 2$: lesson label, 3$: course label, 4$: clickable course title.
			__( '%1$d %2$s have been created successfully for the %3$s: %4$s.', 'learndash' ),
			$lesson_count,
			// phpcs:ignore WordPress.WP.I18n.MissingArgDomain
			_n(
				// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralSingle
				learndash_get_custom_label( 'lesson' ),
				// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralPlural
				learndash_get_custom_label( 'lessons' ),
				$lesson_count
			),
			learndash_get_custom_label_lower( 'course' ),
			'<a href="' . get_edit_post_link( $course_id ) . '">' . get_the_title( $course_id ) . '</a>'
		);

		return $message;
	}

	/**
	 * AJAX request handler.
	 *
	 * @since 4.6.0
	 *
	 * @return void
	 */
	public function handle_ajax_request(): void {
		if ( ! current_user_can( LEARNDASH_ADMIN_CAPABILITY_CHECK ) ) {
			wp_send_json_error( [ 'message' => 'Unauthorized' ], 401 );
		}

		// phpcs:ignore
		if ( ! isset( $_GET['nonce'] ) || ! isset( $_GET['action'] ) || ! wp_verify_nonce( $_GET['nonce'], $_GET['action'] ) ) {
			wp_send_json_error( [ 'message' => 'Unauthorized' ], 401 );
		}

		switch ( $_GET['action'] ) {
			case static::$ajax_actions['get_course']:
				$keyword = isset( $_GET['keyword'] ) ? sanitize_text_field( wp_unslash( $_GET['keyword'] ) ) : '';

				$posts = get_posts(
					[
						'post_type'   => learndash_get_post_type_slug( LDLMS_Post_Types::COURSE ),
						'post_status' => 'any',
						's'           => $keyword,
					]
				);

				$posts = array_map(
					function( $post ) {
						return [
							'id'   => $post->ID,
							'text' => $post->post_title . ' (ID: ' . $post->ID . ')',
						];
					},
					$posts
				);

				$items = array_merge(
					[
						[
							'id'   => wp_json_encode(
								array(
									'new'   => md5( microtime() ),
									'title' => $keyword,
								)
							),
							'text' => wp_sprintf(
								// translators: 1$: keyword, 2$: course label.
								esc_html__( '%1$s (New %2$s)', 'learndash' ),
								$keyword,
								learndash_get_custom_label( 'course' )
							),
						],
					],
					$posts
				);

				$response = [
					'items' => $items,
				];
				break;

			default:
				$response = [];
				break;
		}

		wp_send_json_success( $response );
	}
}