PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
← All changes | inc/TemplateHooks/Admin/AdminEditQuestionTemplate.php +957 -957 4.3.94.4.8 View file →
@@ -1,957 +1,957 @@
1 -<?php
2 -
3 -namespace LearnPress\TemplateHooks\Admin;
4 -
5 -use Exception;
6 -use LearnPress\Helpers\Singleton;
7 -use LearnPress\Helpers\Template;
8 -use LearnPress\Models\Question\QuestionAnswerModel;
9 -use LearnPress\Models\Question\QuestionPostFIBModel;
10 -use LearnPress\Models\Question\QuestionPostModel;
11 -use LearnPress\TemplateHooks\TemplateAJAX;
12 -use stdClass;
13 -
14 -/**
15 - * Template Admin Edit Quiz.
16 - *
17 - * @since 4.2.9
18 - * @version 1.0.0
19 - */
20 -class AdminEditQuestionTemplate {
21 - use Singleton;
22 -
23 - public function init() {
24 - add_action( 'learn-press/admin/edit-question/layout', [ $this, 'edit_layout' ] );
25 - add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
26 - add_filter(
27 - 'wp_default_editor',
28 - function ( $r ) {
29 - global $post;
30 - if ( ! $post ) {
31 - return $r;
32 - }
33 -
34 - if ( $post->post_type !== LP_QUESTION_CPT ) {
35 - return $r;
36 - }
37 -
38 - return 'html';
39 - },
40 - 1000
41 - );
42 - }
43 -
44 - /**
45 - * Layout for edit question.
46 - *
47 - * @use self::render_edit_question
48 - *
49 - * @since 4.2.9
50 - * @version 1.0.0
51 - */
52 - public function edit_layout( QuestionPostModel $questionPostModel ) {
53 - wp_enqueue_style( 'lp-edit-question' );
54 - wp_enqueue_script( 'lp-edit-question' );
55 -
56 - $args = [
57 - 'id_url' => 'edit-question',
58 - 'question_id' => $questionPostModel->ID,
59 - ];
60 - $call_back = array(
61 - 'class' => self::class,
62 - 'method' => 'render_edit_question',
63 - );
64 -
65 - echo TemplateAJAX::load_content_via_ajax( $args, $call_back );
66 - }
67 -
68 - /**
69 - * Allow callback for AJAX.
70 - * @use self::render_edit_quiz
71 - * @use self::render_list_items_not_assign
72 - *
73 - * @param array $callbacks
74 - *
75 - * @return array
76 - */
77 - public function allow_callback( array $callbacks ): array {
78 - $callbacks[] = self::class . ':render_edit_question';
79 -
80 - return $callbacks;
81 - }
82 -
83 - /**
84 - * Render edit question html.
85 - *
86 - * @throws Exception
87 - */
88 - public static function render_edit_question( array $data ): stdClass {
89 - $question_id = $data['question_id'] ?? 0;
90 - $questionPostModel = QuestionPostModel::find( $question_id, true );
91 - if ( ! $questionPostModel ) {
92 - throw new Exception( __( 'Question not found', 'learnpress' ) );
93 - }
94 -
95 - // Check permission
96 - $questionPostModel->check_capabilities_create_item_course();
97 -
98 - $content = new stdClass();
99 - $content->content = self::instance()->html_edit_question( $questionPostModel );
100 -
101 - return $content;
102 - }
103 -
104 - public function html_edit_question( QuestionPostModel $questionPostModel ): string {
105 - $section_edit_details = [
106 - 'wrap' => '<div class="question-edit-details lp-section-toggle">',
107 - 'header' => sprintf(
108 - '<div class="lp-question-data-edit-header lp-trigger-toggle">
109 - <label>%s</label>
110 - <span class="lp-icon-angle-down"></span><span class="lp-icon-angle-up"></span>
111 - </div>',
112 - __( 'Option Details', 'learnpress' )
113 - ),
114 - 'collapse' => '<div class="lp-section-collapse">',
115 - 'point' => AdminEditQuestionTemplate::instance()->html_edit_mark( $questionPostModel ),
116 - 'hint' => AdminEditQuestionTemplate::instance()->html_edit_hint( $questionPostModel ),
117 - 'explanation' => AdminEditQuestionTemplate::instance()->html_edit_explanation( $questionPostModel ),
118 - 'collapse_end' => '</div>',
119 - 'wrap_end' => '</div>',
120 - ];
121 -
122 - $section_edit_main = [
123 - 'wrap' => sprintf(
124 - '<div class="lp-edit-question-wrap lp-question-edit-main" data-question-id="%s">',
125 - esc_attr( $questionPostModel->ID )
126 - ),
127 - 'left' => '<div class="lp-question-edit-left">',
128 - 'answers' => AdminEditQuestionTemplate::instance()->html_edit_question_by_type( $questionPostModel ),
129 - 'left_end' => '</div>',
130 - 'details' => Template::combine_components( $section_edit_details ),
131 - 'wrap_end' => '</div>',
132 - ];
133 -
134 - return Template::combine_components( $section_edit_main );
135 - }
136 -
137 - /**
138 - * HTML for field settings.
139 - *
140 - * @param array $args
141 - *
142 - * @return string
143 - */
144 - public function html_field_settings( array $args = [] ): string {
145 - $section = [
146 - 'wrap' => '<div class="lp-question-field-settings">',
147 - 'label' => sprintf(
148 - '<div class="lp-question-field-settings__label"><label>%s</label></div>',
149 - $args['label'] ?? ''
150 - ),
151 - 'input' => $args['input'] ?? '',
152 - 'description' => sprintf(
153 - '<div class="lp-question-field-settings___desc">%s</div>',
154 - $args['description'] ?? ''
155 - ),
156 - 'wrap_end' => '</div>',
157 - ];
158 -
159 - return Template::combine_components( $section );
160 - }
161 -
162 - /**
163 - * HTML for edit question mark.
164 - *
165 - * @param QuestionPostModel $questionPostModel
166 - *
167 - * @return string
168 - */
169 - public function html_edit_mark( QuestionPostModel $questionPostModel ): string {
170 - $point = $questionPostModel->get_mark();
171 -
172 - $args = [
173 - 'label' => __( 'Points', 'learnpress' ),
174 - 'input' => sprintf(
175 - '<input type="number" name="lp-question-point-input"
176 - class="lp-auto-save-question"
177 - data-key-auto-save="question_mark"
178 - value="%s" min="0" step="0.01">',
179 - esc_attr( $point )
180 - ),
181 - 'description' => __( 'Points for choosing the correct answer.', 'learnpress' ),
182 - ];
183 -
184 - return $this->html_field_settings( $args );
185 - }
186 -
187 - /**
188 - * HTML for edit question hint.
189 - *
190 - * @param QuestionPostModel $questionPostModel
191 - *
192 - * @return string
193 - */
194 - public function html_edit_hint( QuestionPostModel $questionPostModel ): string {
195 - $question_id = $questionPostModel->ID;
196 - $hint = $questionPostModel->get_hint();
197 -
198 - $editor_id = 'lp-question-hint-' . $question_id;
199 -
200 - $args = [
201 - 'label' => __( 'Hint', 'learnpress' ),
202 - 'input' => AdminTemplate::editor_tinymce(
203 - $hint,
204 - $editor_id,
205 - [
206 - 'editor_height' => 50,
207 - 'editor_class' => 'lp-editor-tinymce lp-auto-save-question',
208 - ]
209 - ),
210 - 'description' => __( 'The instructions for the user to select the right answer. The text will be shown when users click the "Hint" button.', 'learnpress' ),
211 - ];
212 -
213 - return $this->html_field_settings( $args );
214 - }
215 -
216 - /**
217 - * HTML for edit question explanation.
218 - *
219 - * @param QuestionPostModel|null $questionPostModel
220 - *
221 - * @return string
222 - */
223 - public function html_edit_explanation( QuestionPostModel $questionPostModel ): string {
224 - $question_id = $questionPostModel->ID;
225 - $explanation = $questionPostModel->get_explanation();
226 - $editor_id = 'lp-question-explanation-' . $question_id;
227 -
228 - $args = [
229 - 'label' => __( 'Explanation', 'learnpress' ),
230 - 'input' => AdminTemplate::editor_tinymce(
231 - $explanation,
232 - $editor_id,
233 - [
234 - 'editor_height' => 50,
235 - 'editor_class' => 'lp-editor-tinymce lp-auto-save-question',
236 - ]
237 - ),
238 - 'description' => __( 'The explanation will be displayed when students click the "Check Answer" button.', 'learnpress' ),
239 - ];
240 -
241 - return $this->html_field_settings( $args );
242 - }
243 -
244 - /**
245 - * HTML for edit question description.
246 - *
247 - * @param QuestionPostModel|null $questionPostModel
248 - *
249 - * @return string
250 - */
251 - public function html_edit_question_description( QuestionPostModel $questionPostModel ): string {
252 - $question_id = $questionPostModel->ID;
253 - $question_description = $questionPostModel->get_the_content();
254 - $editor_id = 'lp-question-description-' . $question_id;
255 -
256 - $section = [
257 - 'wrap' => '<div class="lp-question-data-edit">',
258 - 'header' => sprintf(
259 - '<div class="lp-question-data-edit-header">
260 - <label>%s</label>
261 - </div>',
262 - __( 'Description', 'learnpress' )
263 - ),
264 - 'tinymce' => AdminTemplate::editor_tinymce(
265 - $question_description,
266 - $editor_id,
267 - [
268 - 'editor_class' => 'lp-editor-tinymce lp-auto-save-question',
269 - 'editor_height' => 50,
270 - ]
271 - ),
272 - 'wrap_end' => '</div>',
273 - ];
274 -
275 - return Template::combine_components( $section );
276 - }
277 -
278 - /**
279 - * HTML for edit question by type.
280 - *
281 - * @param QuestionPostModel|null $questionPostModel
282 - *
283 - * @return string
284 - */
285 - public function html_edit_question_by_type( QuestionPostModel $questionPostModel ): string {
286 - $html_answers_config = AdminEditQuestionTemplate::instance()->html_answer_option( $questionPostModel );
287 -
288 - $section = [
289 - 'wrap' => '<div class="lp-question-data-edit lp-question-by-type">',
290 - 'header' => sprintf(
291 - '<div class="lp-question-data-edit-header">
292 - <label>%s</label>
293 - <span class="lp-question-type-label">%s</span>
294 - </div>',
295 - __( 'Config Your Answer', 'learnpress' ),
296 - esc_html( $questionPostModel->get_type_label() )
297 - ),
298 - /*'button-save' => sprintf(
299 - '<button type="button" class="lp-btn-update-question-answer button lp-hidden">%s</button>',
300 - __( 'Save Answer', 'learnpress' )
301 - ),*/
302 - 'answers-config' => $html_answers_config,
303 - 'wrap_end' => '</div>',
304 - ];
305 -
306 - return Template::combine_components( $section );
307 - }
308 -
309 - /**
310 - * HTML input question title.
311 - *
312 - * @param string $question_title
313 - *
314 - * @return string
315 - */
316 - public function html_input_question_title( string $question_title = '' ): string {
317 - return sprintf(
318 - '<input class="lp-question-title-input"
319 - name="lp-question-title-input"
320 - type="text"
321 - value="%1$s"
322 - data-old="%1$s"
323 - placeholder="%2$s"
324 - data-mess-empty-title="%3$s">',
325 - esc_attr( $question_title ),
326 - esc_attr__( 'Update question title', 'learnpress' ),
327 - esc_attr__( 'Question title is required', 'learnpress' )
328 - );
329 - }
330 -
331 - /**
332 - * Get html edit question type.
333 - *
334 - * @param QuestionPostModel $questionPostModel
335 - *
336 - * @return string
337 - */
338 - public function html_answer_option( QuestionPostModel $questionPostModel ): string {
339 - $type = $questionPostModel->get_type();
340 -
341 - // For addon sorting choice old <= v4.0.1
342 - if ( class_exists( 'LP_Addon_Sorting_Choice_Preload' ) && $type === 'sorting_choice' ) {
343 - if ( version_compare( LP_ADDON_SORTING_CHOICE_VER, '4.0.1', '<=' ) ) {
344 - $type = 'sorting_choice_old';
345 - }
346 - }
347 -
348 - switch ( $type ) {
349 - case 'true_or_false':
350 - $html = self::html_answer_type_true_or_false( $questionPostModel );
351 - break;
352 - case 'single_choice':
353 - $html = self::html_answer_type_single_choice( $questionPostModel );
354 - break;
355 - case 'multi_choice':
356 - $html = self::html_answer_type_multiple_choice( $questionPostModel );
357 - break;
358 - case 'fill_in_blanks':
359 - $html = self::html_answer_type_fill_in_blanks( $questionPostModel );
360 - break;
361 - case 'sorting_choice_old':
362 - $html = self::html_answer_type_sorting_choice_old( $questionPostModel );
363 - break;
364 - case $type:
365 - $html = apply_filters( 'learn-press/edit-question/type/html', '', $type, $questionPostModel );
366 - break;
367 - default:
368 - $html = '';
369 - break;
370 - }
371 -
372 - // For create new question, type is empty.
373 - if ( empty( $type ) ) {
374 - $html_question_types = sprintf(
375 - '<option value="">%s</option>',
376 - esc_html__( 'Select question type', 'learnpress' )
377 - );
378 - $question_types = QuestionPostModel::get_types();
379 - foreach ( $question_types as $type => $label ) {
380 - $html_question_types .= sprintf(
381 - '<option value="%s">%s</option>',
382 - esc_attr( $type ),
383 - esc_html( $label )
384 - );
385 - }
386 - $html = Template::instance()->nest_elements(
387 - [
388 - sprintf(
389 - '<select class="lp-question-type-new"
390 - name="lp-question-type-new" data-mess-empty-type="%s">',
391 - esc_attr__( 'Question type is required', 'learnpress' )
392 - )
393 - => '</select>',
394 - ],
395 - $html_question_types
396 - );
397 - $html .= sprintf(
398 - '<button class="lp-btn-question-create-type button"
399 - type="button">%s %s
400 - </button>',
401 - '<span class="lp-icon-spinner"></span> ',
402 - __( 'Create Question Type', 'learnpress' )
403 - );
404 - $html = Template::instance()->nest_elements(
405 - [ '<div class="lp-question-type-new-wrap">' => '</div>' ],
406 - $html
407 - );
408 - }
409 -
410 - $answers = $questionPostModel->get_answer_option();
411 - if ( $questionPostModel->get_type() === 'fill_in_blanks' ) {
412 - $answers = $answers[0] ?? [];
413 - }
414 -
415 - $section = [
416 - 'wrap' => sprintf(
417 - '<div class="lp-answers-config" data-question-type="%s" data-answers="%s">',
418 - esc_attr( $type ),
419 - Template::convert_data_to_json( $answers )
420 - ),
421 - 'answers' => $html,
422 - 'wrap_end' => '</div>',
423 - ];
424 -
425 - return Template::combine_components( $section );
426 - }
427 -
428 - /**
429 - * Get html edit question type true or false.
430 - *
431 - * @param QuestionPostModel $questionPostModel
432 - *
433 - * @return string
434 - */
435 - public function html_answer_type_true_or_false( QuestionPostModel $questionPostModel ): string {
436 - $name_radio = 'lp-input-answer-set-true-' . $questionPostModel->ID;
437 - $answers = $questionPostModel->get_answer_option();
438 -
439 - $html_answers = '';
440 - foreach ( $answers as $answer ) {
441 - $questionAnswerModel = new QuestionAnswerModel( $answer );
442 -
443 - $html_answers .= sprintf(
444 - '<div class="lp-question-answer-item" data-answer-id="%6$s">
445 - <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
446 - <span class="lp-icon-spinner"></span>
447 - <input type="text" class="%1$s" name="%1$s" value="%2$s" />
448 - <input type="radio" class="lp-input-answer-set-true lp-auto-save-question-answer" name="%4$s" %3$s value="%5$s" />
449 - </div>',
450 - 'lp-question-answer-title-input lp-auto-save-question-answer',
451 - esc_attr( $questionAnswerModel->title ),
452 - $questionAnswerModel->is_true === 'yes' ? 'checked' : '',
453 - $name_radio,
454 - esc_attr( $questionAnswerModel->is_true ),
455 - esc_attr( $questionAnswerModel->question_answer_id )
456 - );
457 - }
458 -
459 - $section = [
460 - 'header' => sprintf(
461 - '<div class="lp-question-choice-header">
462 - <span>%s</span><span>%s</span>
463 - </div>',
464 - __( 'Answers', 'learnpress' ),
465 - __( 'Corrects', 'learnpress' )
466 - ),
467 - 'answers' => $html_answers,
468 - ];
469 -
470 - return Template::combine_components( $section );
471 - }
472 -
473 - /**
474 - * Get html edit single choice.
475 - *
476 - * @param QuestionPostModel $questionPostModel
477 - *
478 - * @return string
479 - */
480 - public function html_answer_type_single_choice( QuestionPostModel $questionPostModel ): string {
481 - $name_radio = 'lp-input-answer-set-true-' . $questionPostModel->ID;
482 - $answers = $questionPostModel->get_answer_option();
483 - $answers[] = null;
484 -
485 - $html_answers = '';
486 - foreach ( $answers as $answer ) {
487 - $is_clone = true;
488 - $title = '';
489 - $is_true = 'no';
490 - $question_answer_id = 0;
491 -
492 - if ( ! is_null( $answer ) ) {
493 - $is_clone = false;
494 - $questionAnswerModel = new QuestionAnswerModel( $answer );
495 - $title = $questionAnswerModel->title;
496 - $is_true = $questionAnswerModel->is_true;
497 - $question_answer_id = $questionAnswerModel->question_answer_id;
498 - }
499 -
500 - $html_answers .= sprintf(
501 - '<div class="lp-question-answer-item %7$s" data-answer-id="%6$s">
502 - <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
503 - %8$s
504 - <input type="text" class="%1$s" name="%1$s" value="%2$s" />
505 - <span class="lp-icon-trash-o lp-btn-delete-question-answer"></span>
506 - <input type="radio" class="lp-input-answer-set-true lp-auto-save-question-answer" name="%4$s" %3$s value="%5$s" />
507 - </div>',
508 - 'lp-question-answer-title-input lp-auto-save-question-answer',
509 - esc_attr( $title ),
510 - $is_true === 'yes' ? 'checked' : '',
511 - $name_radio,
512 - esc_attr( $is_true ),
513 - esc_attr( $question_answer_id ),
514 - $is_clone ? 'clone lp-hidden' : '',
515 - '<span class="lp-icon-spinner"></span>'
516 - );
517 - }
518 -
519 - $html_answers .= sprintf(
520 - '<div class="lp-question-answer-item-add-new">
521 - <span class="lp-icon-plus" title="Add answer option"></span>
522 - <input type="text" class="%1$s" name="%1$s" value="" data-mess-empty-title="%2$s" />
523 - <button type="button" class="button lp-btn-add-question-answer lp-btn-edit-primary">%3$s</button>
524 - </div>',
525 - 'lp-question-answer-title-new-input',
526 - esc_attr__( 'Answer title is required', 'learnpress' ),
527 - __( 'Add Option', 'learnpress' )
528 - );
529 -
530 - $section = [
531 - 'header' => sprintf(
532 - '<div class="lp-question-choice-header">
533 - <span>%s</span><span>%s</span>
534 - </div>',
535 - __( 'Answers', 'learnpress' ),
536 - __( 'Corrects', 'learnpress' )
537 - ),
538 - 'answers' => $html_answers,
539 - ];
540 -
541 - return Template::combine_components( $section );
542 - }
543 -
544 - /**
545 - * Get html edit question type multiple choice.
546 - *
547 - * @param QuestionPostModel $questionPostModel
548 - *
549 - * @return string
550 - */
551 - public function html_answer_type_multiple_choice( QuestionPostModel $questionPostModel ): string {
552 - $name_checkbox = 'lp-input-answer-set-true-' . $questionPostModel->ID;
553 - $answers = $questionPostModel->get_answer_option();
554 - $answers[] = null;
555 -
556 - $html_answers = '';
557 - foreach ( $answers as $answer ) {
558 - $is_clone = true;
559 - $title = '';
560 - $is_true = 'no';
561 - $question_answer_id = 0;
562 -
563 - if ( ! is_null( $answer ) ) {
564 - $is_clone = false;
565 - $questionAnswerModel = new QuestionAnswerModel( $answer );
566 - $title = $questionAnswerModel->title;
567 - $is_true = $questionAnswerModel->is_true;
568 - $question_answer_id = $questionAnswerModel->question_answer_id;
569 - }
570 -
571 - $html_answers .= sprintf(
572 - '<div class="lp-question-answer-item %7$s" data-answer-id="%6$s">
573 - <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
574 - %8$s
575 - <input type="text" class="%1$s" name="%1$s" value="%2$s" />
576 - <span class="lp-icon-trash-o lp-btn-delete-question-answer"></span>
577 - <input type="checkbox" class="lp-input-answer-set-true lp-auto-save-question-answer" name="%4$s" %3$s value="%5$s" />
578 - </div>',
579 - 'lp-question-answer-title-input lp-auto-save-question-answer',
580 - esc_attr( $title ),
581 - $is_true === 'yes' ? 'checked' : '',
582 - $name_checkbox,
583 - esc_attr( $is_true ),
584 - esc_attr( $question_answer_id ),
585 - $is_clone ? 'clone lp-hidden' : '',
586 - '<span class="lp-icon-spinner"></span>'
587 - );
588 - }
589 -
590 - $html_answers .= sprintf(
591 - '<div class="lp-question-answer-item-add-new">
592 - <span class="lp-icon-plus" title="Add answer option"></span>
593 - <input type="text" class="%1$s" name="%1$s" value="" data-mess-empty-title="%2$s" />
594 - <button type="button" class="button lp-btn-add-question-answer lp-btn-edit-primary">%3$s</button>
595 - </div>',
596 - 'lp-question-answer-title-new-input',
597 - esc_attr__( 'Answer title is required', 'learnpress' ),
598 - __( 'Add Option', 'learnpress' )
599 - );
600 -
601 - $section = [
602 - 'header' => sprintf(
603 - '<div class="lp-question-choice-header">
604 - <span>%s</span><span>%s</span>
605 - </div>',
606 - __( 'Answers', 'learnpress' ),
607 - __( 'Corrects', 'learnpress' )
608 - ),
609 - 'answers' => $html_answers,
610 - ];
611 -
612 - return Template::combine_components( $section );
613 - }
614 -
615 - /**
616 - * Get html edit question type multiple choice.
617 - *
618 - * @param QuestionPostModel $questionPostModel
619 - *
620 - * @return string
621 - */
622 - public function html_answer_type_sorting_choice_old( QuestionPostModel $questionPostModel ): string {
623 - $answers = $questionPostModel->get_answer_option();
624 - $answers[] = null;
625 -
626 - $html_answers = '';
627 - foreach ( $answers as $answer ) {
628 - $is_clone = true;
629 - $title = '';
630 - $question_answer_id = 0;
631 -
632 - if ( ! is_null( $answer ) ) {
633 - $is_clone = false;
634 - $questionAnswerModel = new QuestionAnswerModel( $answer );
635 - $title = $questionAnswerModel->title;
636 - $question_answer_id = $questionAnswerModel->question_answer_id;
637 - }
638 -
639 - $html_answers .= sprintf(
640 - '<div class="lp-question-answer-item %s" data-answer-id="%d">
641 - <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
642 - <span class="lp-icon-spinner"></span>
643 - <input type="text" class="%s" name="%s" value="%s" />
644 - <span class="lp-icon-trash-o lp-btn-delete-question-answer"></span>
645 - </div>',
646 - $is_clone ? 'clone lp-hidden' : '',
647 - esc_attr( $question_answer_id ),
648 - 'lp-question-answer-title-input lp-auto-save-question-answer',
649 - 'lp-question-answer-title-input',
650 - esc_attr( $title )
651 - );
652 - }
653 -
654 - $html_answers .= sprintf(
655 - '<div class="lp-question-answer-item-add-new">
656 - <span class="lp-icon-plus" title="Add answer option"></span>
657 - <input type="text" class="%1$s" name="%1$s" value="" data-mess-empty-title="%2$s" />
658 - <button type="button" class="button lp-btn-add-question-answer lp-btn-edit-primary">%3$s</button>
659 - </div>',
660 - 'lp-question-answer-title-new-input',
661 - esc_attr__( 'Answer title is required', 'learnpress' ),
662 - __( 'Add Option', 'learnpress' )
663 - );
664 -
665 - $section = [
666 - 'header' => sprintf(
667 - '<div class="lp-question-choice-header">
668 - <span>%s</span><span>%s</span>
669 - </div>',
670 - __( 'Answers', 'learnpress' ),
671 - __( 'Corrects', 'learnpress' )
672 - ),
673 - 'answers' => $html_answers,
674 - ];
675 -
676 - return Template::combine_components( $section );
677 - }
678 -
679 - /**
680 - * Get html edit question type fill in blanks.
681 - *
682 - * @param QuestionPostModel $questionPostModel
683 - *
684 - * @return string
685 - */
686 - public function html_answer_type_fill_in_blanks( QuestionPostModel $questionPostModel ): string {
687 - $questionPostFIBModel = new QuestionPostFIBModel( $questionPostModel );
688 - $name = 'lp-question-fib-input-' . $questionPostFIBModel->ID;
689 - $answers = $questionPostFIBModel->get_answer_option();
690 - $options = [
691 - 'clone' =>
692 - [
693 - 'fill' => '',
694 - 'match_case' => 0,
695 - 'comparison' => 'equal',
696 - 'id' => 'clone',
697 - ],
698 - ];
699 - /**
700 - * @var QuestionAnswerModel[] $answers
701 - */
702 - $questionAnswerModel = $answers[0] ?? null;
703 - if ( $questionAnswerModel instanceof QuestionAnswerModel ) {
704 - $meta_data = $questionAnswerModel->get_all_metadata();
705 -
706 - if ( is_array( $meta_data ) ) {
707 - $options = array_merge(
708 - $meta_data,
709 - $options
710 - );
711 - }
712 - }
713 -
714 - $content = '';
715 -
716 - if ( $questionAnswerModel instanceof QuestionAnswerModel ) {
717 - $content = $this->convert_content_fib_to_span( $questionAnswerModel->title );
718 - }
719 -
720 - $section = [
721 - 'input' => AdminTemplate::editor_tinymce(
722 - $content,
723 - $name,
724 - [
725 - 'default_editor' => 'html', // Use HTML editor by default tinymce
726 - 'tinymce' => true,
727 - 'quicktags' => true, // Set true to show tab "Code" in editor
728 - ]
729 - ),
730 - 'desc' => sprintf(
731 - '<div class="lp-question-fib-desc">%s</div>',
732 - __( 'Select a word in the passage above and click "Insert a new blank" to make that word a blank for filling.', 'learnpress' )
733 - ),
734 - 'buttons' => '<div class="lp-question-fib-buttons">',
735 - 'btn-insert-new' => sprintf(
736 - '<button type="button" class="lp-btn-fib-insert-blank button"
737 - data-default-text="%s"
738 - data-mess-inserted="%s"
739 - data-mess-require-select-text="%s">%s %s</button>',
740 - esc_html__( 'Enter answer correct on here', 'learnpress' ),
741 - __( 'This text inserted to blank', 'learnpress' ),
742 - __( 'You must select a text or position on Editor to insert new blank.', 'learnpress' ),
743 - '<span class="lp-icon-spinner"></span>',
744 - __( 'Insert new blank', 'learnpress' )
745 - ),
746 - 'btn-delete-all-blanks' => sprintf(
747 - '<button type="button"
748 - class="lp-btn-fib-delete-all-blanks button"
749 - data-title="%s"
750 - data-content="%s"
751 - title="%s">%s %s
752 - </button>',
753 - esc_attr__( 'Are you sure?', 'learnpress' ),
754 - esc_html__( 'This action will delete all blanks in the editor, only keep text content.', 'learnpress' ),
755 - esc_attr__( 'Delete all blanks on this editor.', 'learnpress' ),
756 - '<span class="lp-icon-spinner"></span>',
757 - __( 'Delete all blanks', 'learnpress' )
758 - ),
759 - 'btn-save' => sprintf(
760 - '<button type="button" class="lp-btn-fib-save-content button lp-btn-edit-primary active">%s %s</button>',
761 - '<span class="lp-icon-spinner"></span> ',
762 - __( 'Save content', 'learnpress' )
763 - ),
764 - 'btn-clear' => sprintf(
765 - '<button type="button"
766 - class="lp-btn-fib-clear-all-content button"
767 - data-title="%s"
768 - data-content="%s"
769 - title="%s">%s %s
770 - </button>',
771 - esc_attr__( 'Are you sure?', 'learnpress' ),
772 - esc_html__( 'This action will delete all content in the editor.', 'learnpress' ),
773 - esc_attr__( 'Clear all content on this editor.', 'learnpress' ),
774 - '<span class="lp-icon-spinner"></span>',
775 - __( 'Clear content', 'learnpress' )
776 - ),
777 - 'buttons_end' => '</div>',
778 - 'options' => $this->html_fib_options( $options ),
779 - ];
780 -
781 - return Template::combine_components( $section );
782 - }
783 -
784 - public function html_fib_options( $options ): string {
785 - if ( empty( $options ) || ! is_array( $options ) ) {
786 - return '';
787 - }
788 -
789 - $html = '';
790 - $i = 1;
791 - foreach ( $options as $option ) {
792 - $match_case = $option['match_case'] ?? 0;
793 - $comparison = $option['comparison'] ?? '';
794 - $id = $option['id'] ?? '';
795 -
796 - /*if ( $id === 'clone' ) {
797 - $i = 0;
798 - }*/
799 -
800 - $section_header = [
801 - 'wrap' => '<div class="lp-question-fib-option-header">',
802 - 'number' => sprintf(
803 - '<span class="lp-question-fib-option-index">%s.</span>',
804 - $i++
805 - ),
806 - 'loading' => '<span class="lp-icon-spinner"></span>',
807 - 'title' => sprintf(
808 - '<input type="text"
809 - name="lp-question-fib-option-title-input-%s"
810 - value="%s"
811 - class="lp-question-fib-option-title-input" />',
812 - esc_attr( $id ),
813 - esc_html( $option['fill'] ?? '' )
814 - ),
815 - 'btn-delete' => sprintf(
816 - '<span
817 - class="lp-btn-fib-option-delete lp-icon-trash-o"
818 - data-title="%s" data-content="%s">
819 - </span>',
820 - esc_attr__( 'Are you sure?', 'learnpress' ),
821 - esc_html__( 'Delete this blank and keep text', 'learnpress' )
822 - ),
823 - 'toggle' => '<div class="lp-trigger-toggle"><span class="lp-icon-angle-down"></span><span class="lp-icon-angle-up"></span></div>',
824 - 'wrap_end' => '</div>',
825 - ];
826 -
827 - $section_detail = [
828 - 'wrap' => '<div class="lp-question-fib-option-detail lp-section-collapse">',
829 - 'match-case' => sprintf(
830 - '<label>
831 - <input type="checkbox" class="lp-question-fib-option-match-case-input" %s name="%s" data-key="%s" value="1" /> %s
832 - </label>',
833 - $match_case ? 'checked' : '',
834 - esc_attr( 'lp-question-fib-option-match-case-input' . $id ),
835 - 'match_case',
836 - __( 'Match case', 'learnpress' )
837 - ),
838 - 'wrap_match_case' => sprintf(
839 - '<div class="lp-question-fib-option-match-case-wrap %s">',
840 - $match_case ? '' : 'lp-hidden'
841 - ),
842 - 'separator-label' => sprintf(
843 - '<div>%s</div>',
844 - __( 'Comparison', 'learnpress' )
845 - ),
846 - 'equal' => sprintf(
847 - '<div>
848 - <label>
849 - <input type="radio"
850 - data-key ="comparison"
851 - name="lp-question-fib-option-comparison-input-%s"
852 - class="lp-question-fib-option-comparison-input" value="equal" %s /> %s
853 - </label>
854 - <p>%s</p>
855 - </div>',
856 - esc_attr( $id ),
857 - $comparison === 'equal' ? 'checked' : '',
858 - __( 'Equal', 'learnpress' ),
859 - __( 'Match two words are equality.', 'learnpress' )
860 - ),
861 - 'range' => sprintf(
862 - '<div>
863 - <label>
864 - <input type="radio"
865 - data-key ="comparison"
866 - name="lp-question-fib-option-comparison-input-%s"
867 - class="lp-question-fib-option-comparison-input" value="range" %s /> %s
868 - </label>
869 - <p>%s</p>
870 - </div>',
871 - esc_attr( $id ),
872 - $comparison === 'range' ? 'checked' : '',
873 - __( 'Range', 'learnpress' ),
874 - __( 'Match any number in a range. Use 100, 200 to match any value from 100 to 200.', 'learnpress' )
875 - ),
876 - 'any' => sprintf(
877 - '<div>
878 - <label>
879 - <input type="radio"
880 - data-key ="comparison"
881 - name="lp-question-fib-option-comparison-input-%s"
882 - class="lp-question-fib-option-comparison-input" value="any" %s /> %s
883 - </label>
884 - <p>%s</p>
885 - </div>',
886 - esc_attr( $id ),
887 - $comparison === 'any' ? 'checked' : '',
888 - __( 'Any', 'learnpress' ),
889 - __( 'Match any value in a set of words. Use fill, blank, or question to match any value in the set.', 'learnpress' )
890 - ),
891 - 'wrap_match_case_end' => '</div>', // End match case wrap
892 - 'wrap_end' => '</div>',
893 - ];
894 -
895 - $section = [
896 - 'wrap' => sprintf(
897 - '<div class="lp-question-fib-blank-option-item %s lp-section-toggle lp-collapse" data-id="%s">',
898 - $id === 'clone' ? 'clone lp-hidden' : '',
899 - esc_attr( $id )
900 - ),
901 - 'header' => Template::combine_components( $section_header ),
902 - 'detail' => Template::combine_components( $section_detail ),
903 - 'wrap_end' => '</div>',
904 - ];
905 -
906 - $html .= Template::combine_components( $section );
907 - }
908 -
909 - $html = Template::instance()->nest_elements(
910 - [ '<div class="lp-question-fib-blank-options">' => '</div>' ],
911 - $html
912 - );
913 -
914 - return $html;
915 - }
916 -
917 - /**
918 - * Convert content of fill in blanks shortcode to span elements display on Editor.
919 - *
920 - * @param string $content
921 - *
922 - * @return string
923 - */
924 - public function convert_content_fib_to_span( string $content = '' ): string {
925 - $regex_str = get_shortcode_regex( array( 'fib' ) );
926 - $pattern = "/{$regex_str}/";
927 - preg_match_all(
928 - $pattern,
929 - $content,
930 - $all_shortcode,
931 - PREG_SET_ORDER
932 - );
933 -
934 - if ( ! empty( $all_shortcode ) ) {
935 - foreach ( $all_shortcode as $shortcode ) {
936 - $atts = shortcode_parse_atts( $shortcode[0] );
937 -
938 - $fill = $atts['fill'] ?? '';
939 - $id = $atts['id'] ?? '';
940 - if ( empty( $id ) ) {
941 - $ida = explode( '=', str_replace( ']', '', $atts[1] ) );
942 - $id = isset( $ida[1] ) ? str_replace( '"', '', $ida[1] ) : '';
943 - }
944 -
945 - $new_str = sprintf(
946 - '<span class="lp-question-fib-input" data-id="%s">%s</span>',
947 - esc_attr( $id ),
948 - esc_html( $fill )
949 - );
950 -
951 - $content = str_replace( $shortcode[0], $new_str, $content );
952 - }
953 - }
954 -
955 - return $content;
956 - }
957 -}
1 +<?php
2 +
3 +namespace LearnPress\TemplateHooks\Admin;
4 +
5 +use Exception;
6 +use LearnPress\Helpers\Singleton;
7 +use LearnPress\Helpers\Template;
8 +use LearnPress\Models\Question\QuestionAnswerModel;
9 +use LearnPress\Models\Question\QuestionPostFIBModel;
10 +use LearnPress\Models\Question\QuestionPostModel;
11 +use LearnPress\TemplateHooks\TemplateAJAX;
12 +use stdClass;
13 +
14 +/**
15 + * Template Admin Edit Quiz.
16 + *
17 + * @since 4.2.9
18 + * @version 1.0.0
19 + */
20 +class AdminEditQuestionTemplate {
21 + use Singleton;
22 +
23 + public function init() {
24 + add_action( 'learn-press/admin/edit-question/layout', [ $this, 'edit_layout' ] );
25 + add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
26 + add_filter(
27 + 'wp_default_editor',
28 + function ( $r ) {
29 + global $post;
30 + if ( ! $post ) {
31 + return $r;
32 + }
33 +
34 + if ( $post->post_type !== LP_QUESTION_CPT ) {
35 + return $r;
36 + }
37 +
38 + return 'html';
39 + },
40 + 1000
41 + );
42 + }
43 +
44 + /**
45 + * Layout for edit question.
46 + *
47 + * @use self::render_edit_question
48 + *
49 + * @since 4.2.9
50 + * @version 1.0.0
51 + */
52 + public function edit_layout( QuestionPostModel $questionPostModel ) {
53 + wp_enqueue_style( 'lp-edit-question' );
54 + wp_enqueue_script( 'lp-edit-question' );
55 +
56 + $args = [
57 + 'id_url' => 'edit-question',
58 + 'question_id' => $questionPostModel->ID,
59 + ];
60 + $call_back = array(
61 + 'class' => self::class,
62 + 'method' => 'render_edit_question',
63 + );
64 +
65 + echo TemplateAJAX::load_content_via_ajax( $args, $call_back );
66 + }
67 +
68 + /**
69 + * Allow callback for AJAX.
70 + * @use self::render_edit_quiz
71 + * @use self::render_list_items_not_assign
72 + *
73 + * @param array $callbacks
74 + *
75 + * @return array
76 + */
77 + public function allow_callback( array $callbacks ): array {
78 + $callbacks[] = self::class . ':render_edit_question';
79 +
80 + return $callbacks;
81 + }
82 +
83 + /**
84 + * Render edit question html.
85 + *
86 + * @throws Exception
87 + */
88 + public static function render_edit_question( array $data ): stdClass {
89 + $question_id = $data['question_id'] ?? 0;
90 + $questionPostModel = QuestionPostModel::find( $question_id, true );
91 + if ( ! $questionPostModel ) {
92 + throw new Exception( __( 'Question not found', 'learnpress' ) );
93 + }
94 +
95 + // Check permission
96 + $questionPostModel->check_capabilities_create_item_course();
97 +
98 + $content = new stdClass();
99 + $content->content = self::instance()->html_edit_question( $questionPostModel );
100 +
101 + return $content;
102 + }
103 +
104 + public function html_edit_question( QuestionPostModel $questionPostModel ): string {
105 + $section_edit_details = [
106 + 'wrap' => '<div class="question-edit-details lp-section-toggle">',
107 + 'header' => sprintf(
108 + '<div class="lp-question-data-edit-header lp-trigger-toggle">
109 + <label>%s</label>
110 + <span class="lp-icon-angle-down"></span><span class="lp-icon-angle-up"></span>
111 + </div>',
112 + __( 'Option Details', 'learnpress' )
113 + ),
114 + 'collapse' => '<div class="lp-section-collapse">',
115 + 'point' => AdminEditQuestionTemplate::instance()->html_edit_mark( $questionPostModel ),
116 + 'hint' => AdminEditQuestionTemplate::instance()->html_edit_hint( $questionPostModel ),
117 + 'explanation' => AdminEditQuestionTemplate::instance()->html_edit_explanation( $questionPostModel ),
118 + 'collapse_end' => '</div>',
119 + 'wrap_end' => '</div>',
120 + ];
121 +
122 + $section_edit_main = [
123 + 'wrap' => sprintf(
124 + '<div class="lp-edit-question-wrap lp-question-edit-main" data-question-id="%s">',
125 + esc_attr( $questionPostModel->ID )
126 + ),
127 + 'left' => '<div class="lp-question-edit-left">',
128 + 'answers' => AdminEditQuestionTemplate::instance()->html_edit_question_by_type( $questionPostModel ),
129 + 'left_end' => '</div>',
130 + 'details' => Template::combine_components( $section_edit_details ),
131 + 'wrap_end' => '</div>',
132 + ];
133 +
134 + return Template::combine_components( $section_edit_main );
135 + }
136 +
137 + /**
138 + * HTML for field settings.
139 + *
140 + * @param array $args
141 + *
142 + * @return string
143 + */
144 + public function html_field_settings( array $args = [] ): string {
145 + $section = [
146 + 'wrap' => '<div class="lp-question-field-settings">',
147 + 'label' => sprintf(
148 + '<div class="lp-question-field-settings__label"><label>%s</label></div>',
149 + $args['label'] ?? ''
150 + ),
151 + 'input' => $args['input'] ?? '',
152 + 'description' => sprintf(
153 + '<div class="lp-question-field-settings___desc">%s</div>',
154 + $args['description'] ?? ''
155 + ),
156 + 'wrap_end' => '</div>',
157 + ];
158 +
159 + return Template::combine_components( $section );
160 + }
161 +
162 + /**
163 + * HTML for edit question mark.
164 + *
165 + * @param QuestionPostModel $questionPostModel
166 + *
167 + * @return string
168 + */
169 + public function html_edit_mark( QuestionPostModel $questionPostModel ): string {
170 + $point = $questionPostModel->get_mark();
171 +
172 + $args = [
173 + 'label' => __( 'Points', 'learnpress' ),
174 + 'input' => sprintf(
175 + '<input type="number" name="lp-question-point-input"
176 + class="lp-auto-save-question"
177 + data-key-auto-save="question_mark"
178 + value="%s" min="0" step="0.01">',
179 + esc_attr( $point )
180 + ),
181 + 'description' => __( 'Points for choosing the correct answer.', 'learnpress' ),
182 + ];
183 +
184 + return $this->html_field_settings( $args );
185 + }
186 +
187 + /**
188 + * HTML for edit question hint.
189 + *
190 + * @param QuestionPostModel $questionPostModel
191 + *
192 + * @return string
193 + */
194 + public function html_edit_hint( QuestionPostModel $questionPostModel ): string {
195 + $question_id = $questionPostModel->ID;
196 + $hint = $questionPostModel->get_hint();
197 +
198 + $editor_id = 'lp-question-hint-' . $question_id;
199 +
200 + $args = [
201 + 'label' => __( 'Hint', 'learnpress' ),
202 + 'input' => AdminTemplate::editor_tinymce(
203 + $hint,
204 + $editor_id,
205 + [
206 + 'editor_height' => 50,
207 + 'editor_class' => 'lp-editor-tinymce lp-auto-save-question',
208 + ]
209 + ),
210 + 'description' => __( 'The instructions for the user to select the right answer. The text will be shown when users click the "Hint" button.', 'learnpress' ),
211 + ];
212 +
213 + return $this->html_field_settings( $args );
214 + }
215 +
216 + /**
217 + * HTML for edit question explanation.
218 + *
219 + * @param QuestionPostModel|null $questionPostModel
220 + *
221 + * @return string
222 + */
223 + public function html_edit_explanation( QuestionPostModel $questionPostModel ): string {
224 + $question_id = $questionPostModel->ID;
225 + $explanation = $questionPostModel->get_explanation();
226 + $editor_id = 'lp-question-explanation-' . $question_id;
227 +
228 + $args = [
229 + 'label' => __( 'Explanation', 'learnpress' ),
230 + 'input' => AdminTemplate::editor_tinymce(
231 + $explanation,
232 + $editor_id,
233 + [
234 + 'editor_height' => 50,
235 + 'editor_class' => 'lp-editor-tinymce lp-auto-save-question',
236 + ]
237 + ),
238 + 'description' => __( 'The explanation will be displayed when students click the "Check Answer" button.', 'learnpress' ),
239 + ];
240 +
241 + return $this->html_field_settings( $args );
242 + }
243 +
244 + /**
245 + * HTML for edit question description.
246 + *
247 + * @param QuestionPostModel|null $questionPostModel
248 + *
249 + * @return string
250 + */
251 + public function html_edit_question_description( QuestionPostModel $questionPostModel ): string {
252 + $question_id = $questionPostModel->ID;
253 + $question_description = $questionPostModel->post_content;
254 + $editor_id = 'lp-question-description-' . $question_id;
255 +
256 + $section = [
257 + 'wrap' => '<div class="lp-question-data-edit">',
258 + 'header' => sprintf(
259 + '<div class="lp-question-data-edit-header">
260 + <label>%s</label>
261 + </div>',
262 + __( 'Description', 'learnpress' )
263 + ),
264 + 'tinymce' => AdminTemplate::editor_tinymce(
265 + $question_description,
266 + $editor_id,
267 + [
268 + 'editor_class' => 'lp-editor-tinymce lp-auto-save-question',
269 + 'editor_height' => 50,
270 + ]
271 + ),
272 + 'wrap_end' => '</div>',
273 + ];
274 +
275 + return Template::combine_components( $section );
276 + }
277 +
278 + /**
279 + * HTML for edit question by type.
280 + *
281 + * @param QuestionPostModel|null $questionPostModel
282 + *
283 + * @return string
284 + */
285 + public function html_edit_question_by_type( QuestionPostModel $questionPostModel ): string {
286 + $html_answers_config = AdminEditQuestionTemplate::instance()->html_answer_option( $questionPostModel );
287 +
288 + $section = [
289 + 'wrap' => '<div class="lp-question-data-edit lp-question-by-type">',
290 + 'header' => sprintf(
291 + '<div class="lp-question-data-edit-header">
292 + <label>%s</label>
293 + <span class="lp-question-type-label">%s</span>
294 + </div>',
295 + __( 'Config Your Answer', 'learnpress' ),
296 + esc_html( $questionPostModel->get_type_label() )
297 + ),
298 + /*'button-save' => sprintf(
299 + '<button type="button" class="lp-btn-update-question-answer button lp-hidden">%s</button>',
300 + __( 'Save Answer', 'learnpress' )
301 + ),*/
302 + 'answers-config' => $html_answers_config,
303 + 'wrap_end' => '</div>',
304 + ];
305 +
306 + return Template::combine_components( $section );
307 + }
308 +
309 + /**
310 + * HTML input question title.
311 + *
312 + * @param string $question_title
313 + *
314 + * @return string
315 + */
316 + public function html_input_question_title( string $question_title = '' ): string {
317 + return sprintf(
318 + '<input class="lp-question-title-input"
319 + name="lp-question-title-input"
320 + type="text"
321 + value="%1$s"
322 + data-old="%1$s"
323 + placeholder="%2$s"
324 + data-mess-empty-title="%3$s">',
325 + esc_attr( $question_title ),
326 + esc_attr__( 'Update question title', 'learnpress' ),
327 + esc_attr__( 'Question title is required', 'learnpress' )
328 + );
329 + }
330 +
331 + /**
332 + * Get html edit question type.
333 + *
334 + * @param QuestionPostModel $questionPostModel
335 + *
336 + * @return string
337 + */
338 + public function html_answer_option( QuestionPostModel $questionPostModel ): string {
339 + $type = $questionPostModel->get_type();
340 +
341 + // For addon sorting choice old <= v4.0.1
342 + if ( class_exists( 'LP_Addon_Sorting_Choice_Preload' ) && $type === 'sorting_choice' ) {
343 + if ( version_compare( LP_ADDON_SORTING_CHOICE_VER, '4.0.1', '<=' ) ) {
344 + $type = 'sorting_choice_old';
345 + }
346 + }
347 +
348 + switch ( $type ) {
349 + case 'true_or_false':
350 + $html = self::html_answer_type_true_or_false( $questionPostModel );
351 + break;
352 + case 'single_choice':
353 + $html = self::html_answer_type_single_choice( $questionPostModel );
354 + break;
355 + case 'multi_choice':
356 + $html = self::html_answer_type_multiple_choice( $questionPostModel );
357 + break;
358 + case 'fill_in_blanks':
359 + $html = self::html_answer_type_fill_in_blanks( $questionPostModel );
360 + break;
361 + case 'sorting_choice_old':
362 + $html = self::html_answer_type_sorting_choice_old( $questionPostModel );
363 + break;
364 + case $type:
365 + $html = apply_filters( 'learn-press/edit-question/type/html', '', $type, $questionPostModel );
366 + break;
367 + default:
368 + $html = '';
369 + break;
370 + }
371 +
372 + // For create new question, type is empty.
373 + if ( empty( $type ) ) {
374 + $html_question_types = sprintf(
375 + '<option value="">%s</option>',
376 + esc_html__( 'Select question type', 'learnpress' )
377 + );
378 + $question_types = QuestionPostModel::get_types();
379 + foreach ( $question_types as $type => $label ) {
380 + $html_question_types .= sprintf(
381 + '<option value="%s">%s</option>',
382 + esc_attr( $type ),
383 + esc_html( $label )
384 + );
385 + }
386 + $html = Template::instance()->nest_elements(
387 + [
388 + sprintf(
389 + '<select class="lp-question-type-new"
390 + name="lp-question-type-new" data-mess-empty-type="%s">',
391 + esc_attr__( 'Question type is required', 'learnpress' )
392 + )
393 + => '</select>',
394 + ],
395 + $html_question_types
396 + );
397 + $html .= sprintf(
398 + '<button class="lp-btn-question-create-type button"
399 + type="button">%s %s
400 + </button>',
401 + '<span class="lp-icon-spinner"></span> ',
402 + __( 'Create Question Type', 'learnpress' )
403 + );
404 + $html = Template::instance()->nest_elements(
405 + [ '<div class="lp-question-type-new-wrap">' => '</div>' ],
406 + $html
407 + );
408 + }
409 +
410 + $answers = $questionPostModel->get_answer_option();
411 + if ( $questionPostModel->get_type() === 'fill_in_blanks' ) {
412 + $answers = $answers[0] ?? [];
413 + }
414 +
415 + $section = [
416 + 'wrap' => sprintf(
417 + '<div class="lp-answers-config" data-question-type="%s" data-answers="%s">',
418 + esc_attr( $type ),
419 + Template::convert_data_to_json( $answers )
420 + ),
421 + 'answers' => $html,
422 + 'wrap_end' => '</div>',
423 + ];
424 +
425 + return Template::combine_components( $section );
426 + }
427 +
428 + /**
429 + * Get html edit question type true or false.
430 + *
431 + * @param QuestionPostModel $questionPostModel
432 + *
433 + * @return string
434 + */
435 + public function html_answer_type_true_or_false( QuestionPostModel $questionPostModel ): string {
436 + $name_radio = 'lp-input-answer-set-true-' . $questionPostModel->ID;
437 + $answers = $questionPostModel->get_answer_option();
438 +
439 + $html_answers = '';
440 + foreach ( $answers as $answer ) {
441 + $questionAnswerModel = new QuestionAnswerModel( $answer );
442 +
443 + $html_answers .= sprintf(
444 + '<div class="lp-question-answer-item" data-answer-id="%6$s">
445 + <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
446 + <span class="lp-icon-spinner"></span>
447 + <input type="text" class="%1$s" name="%1$s" value="%2$s" />
448 + <input type="radio" class="lp-input-answer-set-true lp-auto-save-question-answer" name="%4$s" %3$s value="%5$s" />
449 + </div>',
450 + 'lp-question-answer-title-input lp-auto-save-question-answer',
451 + esc_attr( $questionAnswerModel->title ),
452 + $questionAnswerModel->is_true === 'yes' ? 'checked' : '',
453 + $name_radio,
454 + esc_attr( $questionAnswerModel->is_true ),
455 + esc_attr( $questionAnswerModel->question_answer_id )
456 + );
457 + }
458 +
459 + $section = [
460 + 'header' => sprintf(
461 + '<div class="lp-question-choice-header">
462 + <span>%s</span><span>%s</span>
463 + </div>',
464 + __( 'Answers', 'learnpress' ),
465 + __( 'Corrects', 'learnpress' )
466 + ),
467 + 'answers' => $html_answers,
468 + ];
469 +
470 + return Template::combine_components( $section );
471 + }
472 +
473 + /**
474 + * Get html edit single choice.
475 + *
476 + * @param QuestionPostModel $questionPostModel
477 + *
478 + * @return string
479 + */
480 + public function html_answer_type_single_choice( QuestionPostModel $questionPostModel ): string {
481 + $name_radio = 'lp-input-answer-set-true-' . $questionPostModel->ID;
482 + $answers = $questionPostModel->get_answer_option();
483 + $answers[] = null;
484 +
485 + $html_answers = '';
486 + foreach ( $answers as $answer ) {
487 + $is_clone = true;
488 + $title = '';
489 + $is_true = 'no';
490 + $question_answer_id = 0;
491 +
492 + if ( ! is_null( $answer ) ) {
493 + $is_clone = false;
494 + $questionAnswerModel = new QuestionAnswerModel( $answer );
495 + $title = $questionAnswerModel->title;
496 + $is_true = $questionAnswerModel->is_true;
497 + $question_answer_id = $questionAnswerModel->question_answer_id;
498 + }
499 +
500 + $html_answers .= sprintf(
501 + '<div class="lp-question-answer-item %7$s" data-answer-id="%6$s">
502 + <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
503 + %8$s
504 + <input type="text" class="%1$s" name="%1$s" value="%2$s" />
505 + <span class="lp-icon-trash-o lp-btn-delete-question-answer"></span>
506 + <input type="radio" class="lp-input-answer-set-true lp-auto-save-question-answer" name="%4$s" %3$s value="%5$s" />
507 + </div>',
508 + 'lp-question-answer-title-input lp-auto-save-question-answer',
509 + esc_attr( $title ),
510 + $is_true === 'yes' ? 'checked' : '',
511 + $name_radio,
512 + esc_attr( $is_true ),
513 + esc_attr( $question_answer_id ),
514 + $is_clone ? 'clone lp-hidden' : '',
515 + '<span class="lp-icon-spinner"></span>'
516 + );
517 + }
518 +
519 + $html_answers .= sprintf(
520 + '<div class="lp-question-answer-item-add-new">
521 + <span class="lp-icon-plus" title="Add answer option"></span>
522 + <input type="text" class="%1$s" name="%1$s" value="" data-mess-empty-title="%2$s" />
523 + <button type="button" class="button lp-btn-add-question-answer lp-btn-edit-primary">%3$s</button>
524 + </div>',
525 + 'lp-question-answer-title-new-input',
526 + esc_attr__( 'Answer title is required', 'learnpress' ),
527 + __( 'Add Option', 'learnpress' )
528 + );
529 +
530 + $section = [
531 + 'header' => sprintf(
532 + '<div class="lp-question-choice-header">
533 + <span>%s</span><span>%s</span>
534 + </div>',
535 + __( 'Answers', 'learnpress' ),
536 + __( 'Corrects', 'learnpress' )
537 + ),
538 + 'answers' => $html_answers,
539 + ];
540 +
541 + return Template::combine_components( $section );
542 + }
543 +
544 + /**
545 + * Get html edit question type multiple choice.
546 + *
547 + * @param QuestionPostModel $questionPostModel
548 + *
549 + * @return string
550 + */
551 + public function html_answer_type_multiple_choice( QuestionPostModel $questionPostModel ): string {
552 + $name_checkbox = 'lp-input-answer-set-true-' . $questionPostModel->ID;
553 + $answers = $questionPostModel->get_answer_option();
554 + $answers[] = null;
555 +
556 + $html_answers = '';
557 + foreach ( $answers as $answer ) {
558 + $is_clone = true;
559 + $title = '';
560 + $is_true = 'no';
561 + $question_answer_id = 0;
562 +
563 + if ( ! is_null( $answer ) ) {
564 + $is_clone = false;
565 + $questionAnswerModel = new QuestionAnswerModel( $answer );
566 + $title = $questionAnswerModel->title;
567 + $is_true = $questionAnswerModel->is_true;
568 + $question_answer_id = $questionAnswerModel->question_answer_id;
569 + }
570 +
571 + $html_answers .= sprintf(
572 + '<div class="lp-question-answer-item %7$s" data-answer-id="%6$s">
573 + <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
574 + %8$s
575 + <input type="text" class="%1$s" name="%1$s" value="%2$s" />
576 + <span class="lp-icon-trash-o lp-btn-delete-question-answer"></span>
577 + <input type="checkbox" class="lp-input-answer-set-true lp-auto-save-question-answer" name="%4$s" %3$s value="%5$s" />
578 + </div>',
579 + 'lp-question-answer-title-input lp-auto-save-question-answer',
580 + esc_attr( $title ),
581 + $is_true === 'yes' ? 'checked' : '',
582 + $name_checkbox,
583 + esc_attr( $is_true ),
584 + esc_attr( $question_answer_id ),
585 + $is_clone ? 'clone lp-hidden' : '',
586 + '<span class="lp-icon-spinner"></span>'
587 + );
588 + }
589 +
590 + $html_answers .= sprintf(
591 + '<div class="lp-question-answer-item-add-new">
592 + <span class="lp-icon-plus" title="Add answer option"></span>
593 + <input type="text" class="%1$s" name="%1$s" value="" data-mess-empty-title="%2$s" />
594 + <button type="button" class="button lp-btn-add-question-answer lp-btn-edit-primary">%3$s</button>
595 + </div>',
596 + 'lp-question-answer-title-new-input',
597 + esc_attr__( 'Answer title is required', 'learnpress' ),
598 + __( 'Add Option', 'learnpress' )
599 + );
600 +
601 + $section = [
602 + 'header' => sprintf(
603 + '<div class="lp-question-choice-header">
604 + <span>%s</span><span>%s</span>
605 + </div>',
606 + __( 'Answers', 'learnpress' ),
607 + __( 'Corrects', 'learnpress' )
608 + ),
609 + 'answers' => $html_answers,
610 + ];
611 +
612 + return Template::combine_components( $section );
613 + }
614 +
615 + /**
616 + * Get html edit question type multiple choice.
617 + *
618 + * @param QuestionPostModel $questionPostModel
619 + *
620 + * @return string
621 + */
622 + public function html_answer_type_sorting_choice_old( QuestionPostModel $questionPostModel ): string {
623 + $answers = $questionPostModel->get_answer_option();
624 + $answers[] = null;
625 +
626 + $html_answers = '';
627 + foreach ( $answers as $answer ) {
628 + $is_clone = true;
629 + $title = '';
630 + $question_answer_id = 0;
631 +
632 + if ( ! is_null( $answer ) ) {
633 + $is_clone = false;
634 + $questionAnswerModel = new QuestionAnswerModel( $answer );
635 + $title = $questionAnswerModel->title;
636 + $question_answer_id = $questionAnswerModel->question_answer_id;
637 + }
638 +
639 + $html_answers .= sprintf(
640 + '<div class="lp-question-answer-item %s" data-answer-id="%d">
641 + <span class="drag lp-icon-drag" title="Drag to reorder section"></span>
642 + <span class="lp-icon-spinner"></span>
643 + <input type="text" class="%s" name="%s" value="%s" />
644 + <span class="lp-icon-trash-o lp-btn-delete-question-answer"></span>
645 + </div>',
646 + $is_clone ? 'clone lp-hidden' : '',
647 + esc_attr( $question_answer_id ),
648 + 'lp-question-answer-title-input lp-auto-save-question-answer',
649 + 'lp-question-answer-title-input',
650 + esc_attr( $title )
651 + );
652 + }
653 +
654 + $html_answers .= sprintf(
655 + '<div class="lp-question-answer-item-add-new">
656 + <span class="lp-icon-plus" title="Add answer option"></span>
657 + <input type="text" class="%1$s" name="%1$s" value="" data-mess-empty-title="%2$s" />
658 + <button type="button" class="button lp-btn-add-question-answer lp-btn-edit-primary">%3$s</button>
659 + </div>',
660 + 'lp-question-answer-title-new-input',
661 + esc_attr__( 'Answer title is required', 'learnpress' ),
662 + __( 'Add Option', 'learnpress' )
663 + );
664 +
665 + $section = [
666 + 'header' => sprintf(
667 + '<div class="lp-question-choice-header">
668 + <span>%s</span><span>%s</span>
669 + </div>',
670 + __( 'Answers', 'learnpress' ),
671 + __( 'Corrects', 'learnpress' )
672 + ),
673 + 'answers' => $html_answers,
674 + ];
675 +
676 + return Template::combine_components( $section );
677 + }
678 +
679 + /**
680 + * Get html edit question type fill in blanks.
681 + *
682 + * @param QuestionPostModel $questionPostModel
683 + *
684 + * @return string
685 + */
686 + public function html_answer_type_fill_in_blanks( QuestionPostModel $questionPostModel ): string {
687 + $questionPostFIBModel = new QuestionPostFIBModel( $questionPostModel );
688 + $name = 'lp-question-fib-input-' . $questionPostFIBModel->ID;
689 + $answers = $questionPostFIBModel->get_answer_option();
690 + $options = [
691 + 'clone' =>
692 + [
693 + 'fill' => '',
694 + 'match_case' => 0,
695 + 'comparison' => 'equal',
696 + 'id' => 'clone',
697 + ],
698 + ];
699 + /**
700 + * @var QuestionAnswerModel[] $answers
701 + */
702 + $questionAnswerModel = $answers[0] ?? null;
703 + if ( $questionAnswerModel instanceof QuestionAnswerModel ) {
704 + $meta_data = $questionAnswerModel->get_all_metadata();
705 +
706 + if ( is_array( $meta_data ) ) {
707 + $options = array_merge(
708 + $meta_data,
709 + $options
710 + );
711 + }
712 + }
713 +
714 + $content = '';
715 +
716 + if ( $questionAnswerModel instanceof QuestionAnswerModel ) {
717 + $content = $this->convert_content_fib_to_span( $questionAnswerModel->title );
718 + }
719 +
720 + $section = [
721 + 'input' => AdminTemplate::editor_tinymce(
722 + $content,
723 + $name,
724 + [
725 + 'default_editor' => 'html', // Use HTML editor by default tinymce
726 + 'tinymce' => true,
727 + 'quicktags' => true, // Set true to show tab "Code" in editor
728 + ]
729 + ),
730 + 'desc' => sprintf(
731 + '<div class="lp-question-fib-desc">%s</div>',
732 + __( 'Select a word in the passage above and click "Insert a new blank" to make that word a blank for filling.', 'learnpress' )
733 + ),
734 + 'buttons' => '<div class="lp-question-fib-buttons">',
735 + 'btn-insert-new' => sprintf(
736 + '<button type="button" class="lp-btn-fib-insert-blank button"
737 + data-default-text="%s"
738 + data-mess-inserted="%s"
739 + data-mess-require-select-text="%s">%s %s</button>',
740 + esc_html__( 'Enter answer correct on here', 'learnpress' ),
741 + __( 'This text inserted to blank', 'learnpress' ),
742 + __( 'You must select a text or position on Editor to insert new blank.', 'learnpress' ),
743 + '<span class="lp-icon-spinner"></span>',
744 + __( 'Insert new blank', 'learnpress' )
745 + ),
746 + 'btn-delete-all-blanks' => sprintf(
747 + '<button type="button"
748 + class="lp-btn-fib-delete-all-blanks button"
749 + data-title="%s"
750 + data-content="%s"
751 + title="%s">%s %s
752 + </button>',
753 + esc_attr__( 'Are you sure?', 'learnpress' ),
754 + esc_html__( 'This action will delete all blanks in the editor, only keep text content.', 'learnpress' ),
755 + esc_attr__( 'Delete all blanks on this editor.', 'learnpress' ),
756 + '<span class="lp-icon-spinner"></span>',
757 + __( 'Delete all blanks', 'learnpress' )
758 + ),
759 + 'btn-save' => sprintf(
760 + '<button type="button" class="lp-btn-fib-save-content button lp-btn-edit-primary active">%s %s</button>',
761 + '<span class="lp-icon-spinner"></span> ',
762 + __( 'Save content', 'learnpress' )
763 + ),
764 + 'btn-clear' => sprintf(
765 + '<button type="button"
766 + class="lp-btn-fib-clear-all-content button"
767 + data-title="%s"
768 + data-content="%s"
769 + title="%s">%s %s
770 + </button>',
771 + esc_attr__( 'Are you sure?', 'learnpress' ),
772 + esc_html__( 'This action will delete all content in the editor.', 'learnpress' ),
773 + esc_attr__( 'Clear all content on this editor.', 'learnpress' ),
774 + '<span class="lp-icon-spinner"></span>',
775 + __( 'Clear content', 'learnpress' )
776 + ),
777 + 'buttons_end' => '</div>',
778 + 'options' => $this->html_fib_options( $options ),
779 + ];
780 +
781 + return Template::combine_components( $section );
782 + }
783 +
784 + public function html_fib_options( $options ): string {
785 + if ( empty( $options ) || ! is_array( $options ) ) {
786 + return '';
787 + }
788 +
789 + $html = '';
790 + $i = 1;
791 + foreach ( $options as $option ) {
792 + $match_case = $option['match_case'] ?? 0;
793 + $comparison = $option['comparison'] ?? '';
794 + $id = $option['id'] ?? '';
795 +
796 + /*if ( $id === 'clone' ) {
797 + $i = 0;
798 + }*/
799 +
800 + $section_header = [
801 + 'wrap' => '<div class="lp-question-fib-option-header">',
802 + 'number' => sprintf(
803 + '<span class="lp-question-fib-option-index">%s.</span>',
804 + $i++
805 + ),
806 + 'loading' => '<span class="lp-icon-spinner"></span>',
807 + 'title' => sprintf(
808 + '<input type="text"
809 + name="lp-question-fib-option-title-input-%s"
810 + value="%s"
811 + class="lp-question-fib-option-title-input" />',
812 + esc_attr( $id ),
813 + esc_html( $option['fill'] ?? '' )
814 + ),
815 + 'btn-delete' => sprintf(
816 + '<span
817 + class="lp-btn-fib-option-delete lp-icon-trash-o"
818 + data-title="%s" data-content="%s">
819 + </span>',
820 + esc_attr__( 'Are you sure?', 'learnpress' ),
821 + esc_html__( 'Delete this blank and keep text', 'learnpress' )
822 + ),
823 + 'toggle' => '<div class="lp-trigger-toggle"><span class="lp-icon-angle-down"></span><span class="lp-icon-angle-up"></span></div>',
824 + 'wrap_end' => '</div>',
825 + ];
826 +
827 + $section_detail = [
828 + 'wrap' => '<div class="lp-question-fib-option-detail lp-section-collapse">',
829 + 'match-case' => sprintf(
830 + '<label>
831 + <input type="checkbox" class="lp-question-fib-option-match-case-input" %s name="%s" data-key="%s" value="1" /> %s
832 + </label>',
833 + $match_case ? 'checked' : '',
834 + esc_attr( 'lp-question-fib-option-match-case-input' . $id ),
835 + 'match_case',
836 + __( 'Match case', 'learnpress' )
837 + ),
838 + 'wrap_match_case' => sprintf(
839 + '<div class="lp-question-fib-option-match-case-wrap %s">',
840 + $match_case ? '' : 'lp-hidden'
841 + ),
842 + 'separator-label' => sprintf(
843 + '<div>%s</div>',
844 + __( 'Comparison', 'learnpress' )
845 + ),
846 + 'equal' => sprintf(
847 + '<div>
848 + <label>
849 + <input type="radio"
850 + data-key ="comparison"
851 + name="lp-question-fib-option-comparison-input-%s"
852 + class="lp-question-fib-option-comparison-input" value="equal" %s /> %s
853 + </label>
854 + <p>%s</p>
855 + </div>',
856 + esc_attr( $id ),
857 + $comparison === 'equal' ? 'checked' : '',
858 + __( 'Equal', 'learnpress' ),
859 + __( 'Match two words are equality.', 'learnpress' )
860 + ),
861 + 'range' => sprintf(
862 + '<div>
863 + <label>
864 + <input type="radio"
865 + data-key ="comparison"
866 + name="lp-question-fib-option-comparison-input-%s"
867 + class="lp-question-fib-option-comparison-input" value="range" %s /> %s
868 + </label>
869 + <p>%s</p>
870 + </div>',
871 + esc_attr( $id ),
872 + $comparison === 'range' ? 'checked' : '',
873 + __( 'Range', 'learnpress' ),
874 + __( 'Match any number in a range. Use 100, 200 to match any value from 100 to 200.', 'learnpress' )
875 + ),
876 + 'any' => sprintf(
877 + '<div>
878 + <label>
879 + <input type="radio"
880 + data-key ="comparison"
881 + name="lp-question-fib-option-comparison-input-%s"
882 + class="lp-question-fib-option-comparison-input" value="any" %s /> %s
883 + </label>
884 + <p>%s</p>
885 + </div>',
886 + esc_attr( $id ),
887 + $comparison === 'any' ? 'checked' : '',
888 + __( 'Any', 'learnpress' ),
889 + __( 'Match any value in a set of words. Use fill, blank, or question to match any value in the set.', 'learnpress' )
890 + ),
891 + 'wrap_match_case_end' => '</div>', // End match case wrap
892 + 'wrap_end' => '</div>',
893 + ];
894 +
895 + $section = [
896 + 'wrap' => sprintf(
897 + '<div class="lp-question-fib-blank-option-item %s lp-section-toggle lp-collapse" data-id="%s">',
898 + $id === 'clone' ? 'clone lp-hidden' : '',
899 + esc_attr( $id )
900 + ),
901 + 'header' => Template::combine_components( $section_header ),
902 + 'detail' => Template::combine_components( $section_detail ),
903 + 'wrap_end' => '</div>',
904 + ];
905 +
906 + $html .= Template::combine_components( $section );
907 + }
908 +
909 + $html = Template::instance()->nest_elements(
910 + [ '<div class="lp-question-fib-blank-options">' => '</div>' ],
911 + $html
912 + );
913 +
914 + return $html;
915 + }
916 +
917 + /**
918 + * Convert content of fill in blanks shortcode to span elements display on Editor.
919 + *
920 + * @param string $content
921 + *
922 + * @return string
923 + */
924 + public function convert_content_fib_to_span( string $content = '' ): string {
925 + $regex_str = get_shortcode_regex( array( 'fib' ) );
926 + $pattern = "/{$regex_str}/";
927 + preg_match_all(
928 + $pattern,
929 + $content,
930 + $all_shortcode,
931 + PREG_SET_ORDER
932 + );
933 +
934 + if ( ! empty( $all_shortcode ) ) {
935 + foreach ( $all_shortcode as $shortcode ) {
936 + $atts = shortcode_parse_atts( $shortcode[0] );
937 +
938 + $fill = $atts['fill'] ?? '';
939 + $id = $atts['id'] ?? '';
940 + if ( empty( $id ) ) {
941 + $ida = explode( '=', str_replace( ']', '', $atts[1] ) );
942 + $id = isset( $ida[1] ) ? str_replace( '"', '', $ida[1] ) : '';
943 + }
944 +
945 + $new_str = sprintf(
946 + '<span class="lp-question-fib-input" data-id="%s">%s</span>',
947 + esc_attr( $id ),
948 + esc_html( $fill )
949 + );
950 +
951 + $content = str_replace( $shortcode[0], $new_str, $content );
952 + }
953 + }
954 +
955 + return $content;
956 + }
957 +}