Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor-pro/tutorai/CourseCreatorController.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
<?php
2
+
/**
3
+
* Handle ChatGPT course creation.
4
+
*
5
+
* @package TutorPro\TutorAI
6
+
* @author Themeum <support@themeum.com>
7
+
* @link https://themeum.com
8
+
* @since 3.0.0
9
+
*/
10
+
11
+
namespace TutorPro\TutorAI;
12
+
13
+
use Throwable;
14
+
use TUTOR\Course;
15
+
use Tutor\Helpers\HttpHelper;
16
+
use Tutor\Helpers\ValidationHelper;
17
+
use TUTOR\Input;
18
+
use Tutor\Models\CourseModel;
19
+
use TUTOR\Quiz;
20
+
use TUTOR\QuizBuilder;
21
+
use Tutor\Traits\JsonResponse;
22
+
23
+
/**
24
+
* AiCourseCreator Class.
25
+
*
26
+
* @since 3.0.0
27
+
*/
28
+
class CourseCreatorController {
29
+
use JsonResponse;
30
+
31
+
/**
32
+
* Topic menu order.
33
+
*
34
+
* @since 3.0.0
35
+
*
36
+
* @var integer
37
+
*/
38
+
private $topic_order = 0;
39
+
40
+
/**
41
+
* Course content menu order.
42
+
*
43
+
* @since 3.0.0
44
+
*
45
+
* @var integer
46
+
*/
47
+
private $content_order = 0;
48
+
49
+
/**
50
+
* Register hooks.
51
+
*
52
+
* @since 3.0.0
53
+
*
54
+
* @return void
55
+
*/
56
+
public function __construct() {
57
+
/**
58
+
* Handle AJAX request for creating course contents generated by AI.
59
+
*
60
+
* @since 3.0.0
61
+
*/
62
+
add_action( 'wp_ajax_tutor_pro_ai_course_create', array( $this, 'ajax_ai_course_create' ) );
63
+
64
+
/**
65
+
* Add a filter hook for course details response.
66
+
*
67
+
* @since 3.0.0
68
+
*/
69
+
add_filter( 'tutor_course_details_response', array( $this, 'extend_course_details_response' ) );
70
+
}
71
+
72
+
/**
73
+
* Validate payload.
74
+
*
75
+
* @since 3.0.0
76
+
*
77
+
* @param array $payload payload.
78
+
*
79
+
* @return object consist success, errors.
80
+
*/
81
+
public function validate_payload( $payload ) {
82
+
$errors = array();
83
+
$success = true;
84
+
85
+
if ( ! is_array( $payload ) ) {
86
+
$success = false;
87
+
$errors['payload'] = __( 'Invalid payload', 'tutor-pro' );
88
+
}
89
+
90
+
$rules = array(
91
+
'title' => 'required',
92
+
'description' => 'if_input|string',
93
+
'topics' => 'required|is_array',
94
+
);
95
+
96
+
$validation = ValidationHelper::validate(
97
+
$rules,
98
+
$payload
99
+
);
100
+
101
+
if ( ! $validation->success ) {
102
+
$success = false;
103
+
$errors = array_merge( $errors, $validation->errors );
104
+
}
105
+
106
+
return (object) array(
107
+
'success' => $success,
108
+
'errors' => $errors,
109
+
);
110
+
}
111
+
112
+
/**
113
+
* Add topic.
114
+
*
115
+
* @since 3.0.0
116
+
*
117
+
* @param int $course_id course id.
118
+
* @param string $title title.
119
+
* @param string $description description (optional).
120
+
*
121
+
* @return int|false
122
+
*/
123
+
private function add_topic( $course_id, $title, $description = '' ) {
124
+
$data = array(
125
+
'post_title' => $title,
126
+
'post_content' => $description,
127
+
'post_type' => tutor()->topics_post_type,
128
+
'post_parent' => $course_id,
129
+
'post_status' => 'publish',
130
+
'menu_order' => $this->topic_order,
131
+
);
132
+
133
+
$topic_id = wp_insert_post( $data );
134
+
135
+
if ( ! $topic_id ) {
136
+
return false;
137
+
}
138
+
139
+
return $topic_id;
140
+
}
141
+
142
+
/**
143
+
* Add lesson.
144
+
*
145
+
* @since 3.0.0
146
+
*
147
+
* @param int $topic_id topic id.
148
+
* @param string $title title.
149
+
* @param string $description description (optional).
150
+
*
151
+
* @return void
152
+
*/
153
+
private function add_lesson( $topic_id, $title, $description = '' ) {
154
+
$data = array(
155
+
'post_title' => $title,
156
+
'post_content' => $description,
157
+
'post_type' => tutor()->lesson_post_type,
158
+
'post_parent' => $topic_id,
159
+
'post_status' => 'publish',
160
+
'menu_order' => $this->content_order,
161
+
);
162
+
163
+
wp_insert_post( $data );
164
+
}
165
+
166
+
/**
167
+
* Check assignment addon active.
168
+
*
169
+
* @since 3.0.0
170
+
*
171
+
* @return boolean
172
+
*/
173
+
private function is_assignment_addon_active() {
174
+
$basename = plugin_basename( TUTOR_ASSIGNMENTS_FILE );
175
+
return tutor_utils()->is_addon_enabled( $basename );
176
+
}
177
+
178
+
/**
179
+
* Add assignment.
180
+
*
181
+
* @since 3.0.0
182
+
*
183
+
* @param int $topic_id topic id.
184
+
* @param string $title title.
185
+
* @param string $description description (optional).
186
+
*
187
+
* @return int|false
188
+
*/
189
+
private function add_assignment( $topic_id, $title, $description = '' ) {
190
+
$default_options = array(
191
+
'time_duration' => array(
192
+
'time' => 'weeks',
193
+
'value' => 1,
194
+
),
195
+
'total_mark' => 10,
196
+
'pass_mark' => 5,
197
+
'upload_files_limit' => 1,
198
+
'upload_file_size_limit' => 2,
199
+
);
200
+
201
+
$data = array(
202
+
'post_title' => $title,
203
+
'post_content' => $description,
204
+
'post_type' => tutor()->assignment_post_type,
205
+
'post_parent' => $topic_id,
206
+
'post_status' => 'publish',
207
+
'menu_order' => $this->content_order,
208
+
);
209
+
210
+
$assignment_id = wp_insert_post( $data );
211
+
if ( $assignment_id ) {
212
+
update_post_meta( $assignment_id, 'assignment_option', $default_options );
213
+
}
214
+
215
+
return $assignment_id ? $assignment_id : false;
216
+
}
217
+
218
+
/**
219
+
* Prepare question.
220
+
*
221
+
* @since 3.0.0
222
+
*
223
+
* @param array $question question.
224
+
*
225
+
* @return array
226
+
*/
227
+
private function prepare_question( $question ) {
228
+
$type = $question['type'] ?? '';
229
+
230
+
$arr[ QuizBuilder::TRACKING_KEY ] = QuizBuilder::FLAG_NEW;
231
+
$arr['question_title'] = $question['title'] ?? '';
232
+
$arr['question_description'] = $question['description'] ?? '';
233
+
$arr['question_type'] = $type;
234
+
$arr['question_mark'] = 1;
235
+
$arr['question_settings'] = Quiz::get_default_question_settings( $type );
236
+
237
+
$options = $question['options'] ?? array();
238
+
foreach ( $options as $option ) {
239
+
$arr ['question_answers'][] = array(
240
+
QuizBuilder::TRACKING_KEY => QuizBuilder::FLAG_NEW,
241
+
'answer_title' => $option['name'] ?? '',
242
+
'is_correct' => $option['is_correct'] ?? 0,
243
+
);
244
+
}
245
+
246
+
return $arr;
247
+
}
248
+
249
+
/**
250
+
* Add assignment.
251
+
*
252
+
* @since 3.0.0
253
+
*
254
+
* @param int $topic_id topic id.
255
+
* @param string $title title.
256
+
* @param string $description description.
257
+
* @param array $questions questions.
258
+
*
259
+
* @return void
260
+
*/
261
+
private function add_quiz( $topic_id, $title, $description = '', $questions = array() ) {
262
+
$allowed_types = array( 'true_false', 'multiple_choice', 'open_ended' );
263
+
$prepared_questions = array();
264
+
265
+
foreach ( $questions as $question ) {
266
+
$type = $question['type'] ?? '';
267
+
if ( ! in_array( $type, $allowed_types, true ) ) {
268
+
continue;
269
+
}
270
+
271
+
$prepared_questions[] = $this->prepare_question( $question );
272
+
}
273
+
274
+
$payload = array(
275
+
'post_title' => $title,
276
+
'post_content' => $description,
277
+
'menu_order' => $this->content_order,
278
+
'quiz_option' => Quiz::get_default_quiz_settings(),
279
+
'questions' => $prepared_questions,
280
+
);
281
+
282
+
( new QuizBuilder( false ) )->save_quiz( $topic_id, $payload );
283
+
}
284
+
285
+
/**
286
+
* Add course contents.
287
+
*
288
+
* @since 3.0.0
289
+
*
290
+
* @param int $course_id course id.
291
+
* @param array $payload payload.
292
+
*
293
+
* @return int course id.
294
+
*
295
+
* @throws Throwable If fail.
296
+
*/
297
+
private function add_course_contents( $course_id, $payload ) {
298
+
global $wpdb;
299
+
300
+
try {
301
+
$wpdb->query( 'START TRANSACTION' );
302
+
303
+
$base_slug = sanitize_title( $payload['title'] );
304
+
$unique_slug = wp_unique_post_slug( $base_slug, $course_id, get_post_status( $course_id ), tutor()->course_post_type, 0 );
305
+
306
+
$course_data = array(
307
+
'post_title' => $payload['title'],
308
+
'post_content' => $payload['description'] ?? '',
309
+
'post_name' => $unique_slug,
310
+
);
311
+
312
+
$wpdb->update( $wpdb->posts, $course_data, array( 'ID' => $course_id ) );
313
+
314
+
// Upload featured image add attached with course.
315
+
if ( ! empty( $payload['featured_image'] ) ) {
316
+
$featured_image = tutor_utils()->upload_base64_image( $payload['featured_image'] );
317
+
update_post_meta( $course_id, '_thumbnail_id', $featured_image->id );
318
+
}
319
+
320
+
// Backup course authors.
321
+
$course_authors = CourseModel::get_course_instructor_ids( $course_id );
322
+
323
+
// Delete all existing topics and it's content.
324
+
( new CourseModel() )->delete_course_data( $course_id );
325
+
326
+
// Re-assign course authors.
327
+
foreach ( $course_authors as $author_id ) {
328
+
add_user_meta( $author_id, '_tutor_instructor_course_id', $course_id );
329
+
}
330
+
331
+
// Create course contents.
332
+
$topics = $payload['topics'] ?? array();
333
+
foreach ( $topics as $topic ) {
334
+
$topic_id = $this->add_topic( $course_id, $topic['title'] ?? '' );
335
+
$this->topic_order++;
336
+
337
+
$topic_contents = $topic['contents'] ?? array();
338
+
$allowed_content_type = array( 'lesson', 'assignment', 'quiz' );
339
+
340
+
foreach ( $topic_contents as $content ) {
341
+
$content_type = $content['type'] ?? '';
342
+
if ( ! in_array( $content_type, $allowed_content_type, true ) ) {
343
+
continue;
344
+
}
345
+
346
+
$title = $content['title'] ?? '';
347
+
$description = $content['description'] ?? '';
348
+
349
+
/**
350
+
* Add lesson
351
+
*/
352
+
if ( 'lesson' === $content_type ) {
353
+
$this->add_lesson( $topic_id, $title, $description );
354
+
}
355
+
356
+
/**
357
+
* Add assignment
358
+
*/
359
+
if ( 'assignment' === $content_type && $this->is_assignment_addon_active() ) {
360
+
$this->add_assignment( $topic_id, $title, $description );
361
+
}
362
+
363
+
/**
364
+
* Add quiz
365
+
*/
366
+
if ( 'quiz' === $content_type ) {
367
+
$questions = $content['questions'] ?? array();
368
+
if ( count( $questions ) > 0 ) {
369
+
$this->add_quiz( $topic_id, $title, $description, $questions );
370
+
}
371
+
}
372
+
373
+
$this->content_order++;
374
+
}
375
+
}
376
+
377
+
$wpdb->query( 'COMMIT' );
378
+
} catch ( Throwable $error ) {
379
+
$wpdb->query( 'ROLLBACK' );
380
+
throw $error;
381
+
}
382
+
383
+
return $course_id;
384
+
}
385
+
386
+
/**
387
+
* Extend course details response
388
+
*
389
+
* @since 3.0.0
390
+
*
391
+
* @param array $data response data.
392
+
*
393
+
* @return array
394
+
*/
395
+
public function extend_course_details_response( array $data ) {
396
+
$course_id = $data['ID'] ?? 0;
397
+
if ( ! $course_id ) {
398
+
return $data;
399
+
}
400
+
401
+
$data['total_enrolled_student'] = tutor_utils()->get_total_enrolments( '', '', $course_id );
402
+
403
+
return $data;
404
+
}
405
+
406
+
/**
407
+
* Course create with AI
408
+
*
409
+
* @since 3.0.0
410
+
*
411
+
* @return void
412
+
*/
413
+
public function ajax_ai_course_create() {
414
+
tutor_utils()->check_nonce();
415
+
416
+
$course_id = Input::post( 'course_id', 0, Input::TYPE_INT );
417
+
$course_cls = new Course( false );
418
+
$course_cls->check_access( $course_id );
419
+
420
+
$payload = $_POST['payload'] ?? array(); //phpcs:ignore
421
+
if ( is_string( $payload ) ) {
422
+
$payload = json_decode( wp_unslash( $payload ), true );
423
+
}
424
+
425
+
$validation = $this->validate_payload( $payload );
426
+
427
+
if ( ! $validation->success ) {
428
+
$this->json_response(
429
+
tutor_utils()->error_message( 'validation_error' ),
430
+
$validation->errors,
431
+
HttpHelper::STATUS_UNPROCESSABLE_ENTITY
432
+
);
433
+
}
434
+
435
+
$total_enrollment = tutor_utils()->get_total_enrolments( '', '', $course_id );
436
+
if ( $total_enrollment > 0 ) {
437
+
$this->json_response(
438
+
__( "Re-creation isn't allowed due to enrolled students.", 'tutor-pro' ),
439
+
null,
440
+
HttpHelper::STATUS_BAD_REQUEST
441
+
);
442
+
}
443
+
444
+
try {
445
+
$this->add_course_contents( $course_id, $payload );
446
+
$this->json_response(
447
+
__( 'Successfully added', 'tutor-pro' ),
448
+
$total_enrollment
449
+
);
450
+
} catch ( \Throwable $th ) {
451
+
$this->json_response(
452
+
$th->getMessage(),
453
+
null,
454
+
HttpHelper::STATUS_INTERNAL_SERVER_ERROR
455
+
);
456
+
}
457
+
}
458
+
}
459
+