PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 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 All 140 releases
← All changes | inc/TemplateHooks/Admin/AdminEditQizTemplate.php +548 -506 4.3.9 → 4.4.9 View file →
@@ -1,506 +1,548 @@
1 -<?php
2 -
3 -namespace LearnPress\TemplateHooks\Admin;
4 -
5 -use Exception;
6 -use LearnPress\Databases\DataBase;
7 -use LearnPress\Filters\PostFilter;
8 -use LearnPress\Helpers\Config;
9 -use LearnPress\Helpers\Singleton;
10 -use LearnPress\Helpers\Template;
11 -use LearnPress\Models\Question\QuestionPostModel;
12 -use LearnPress\Models\QuizPostModel;
13 -use LearnPress\TemplateHooks\TemplateAJAX;
14 -use LP_Database;
15 -use LP_Post_DB;
16 -use LP_Post_Type_Filter;
17 -use stdClass;
18 -
19 -/**
20 - * Template Admin Edit Quiz.
21 - *
22 - * @since 4.2.9
23 - * @version 1.0.0
24 - */
25 -class AdminEditQizTemplate {
26 - use Singleton;
27 -
28 - /**
29 - * @var QuizPostModel
30 - */
31 - public $quizPostModel;
32 -
33 - public function init() {
34 - add_action( 'learn-press/admin/edit-quiz/layout', [ $this, 'edit_quiz_layout' ] );
35 - add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
36 - add_filter(
37 - 'wp_default_editor',
38 - function ( $r ) {
39 - global $post;
40 - if ( ! $post ) {
41 - return $r;
42 - }
43 -
44 - if ( $post->post_type !== LP_QUIZ_CPT ) {
45 - return $r;
46 - }
47 -
48 - return 'html';
49 - },
50 - 1000
51 - );
52 - }
53 -
54 - /**
55 - * Layout for edit course curriculum.
56 - *
57 - * @since 4.2.9
58 - * @version 1.0.0
59 - */
60 - public function edit_quiz_layout( QuizPostModel $quizPostModel ) {
61 - wp_enqueue_style( 'lp-edit-quiz' );
62 - wp_enqueue_script( 'lp-edit-quiz' );
63 -
64 - $args = [
65 - 'id_url' => 'edit-quiz',
66 - 'quiz_id' => $quizPostModel->ID,
67 - ];
68 - /**
69 - * @uses self::render_edit_quiz
70 - */
71 - $call_back = array(
72 - 'class' => self::class,
73 - 'method' => 'render_edit_quiz',
74 - );
75 -
76 - echo TemplateAJAX::load_content_via_ajax( $args, $call_back );
77 - }
78 -
79 - /**
80 - * Allow callback for AJAX.
81 - *
82 - * @param array $callbacks
83 - *
84 - * @return array
85 - */
86 - public function allow_callback( array $callbacks ): array {
87 - $callbacks[] = get_class( $this ) . ':render_edit_quiz';
88 - $callbacks[] = get_class( $this ) . ':render_list_items_not_assign';
89 -
90 - return $callbacks;
91 - }
92 -
93 - /**
94 - * Render edit course curriculum html.
95 - *
96 - * @throws Exception
97 - */
98 - public static function render_edit_quiz( array $data ): stdClass {
99 - $quiz_id = $data['quiz_id'] ?? 0;
100 - $quizPostModel = QuizPostModel::find( $quiz_id, true );
101 - if ( ! $quizPostModel ) {
102 - throw new Exception( __( 'Quiz not found', 'learnpress' ) );
103 - }
104 -
105 - // Check permission
106 - $quizPostModel->check_capabilities_create_item_course();
107 -
108 - self::instance()->quizPostModel = $quizPostModel;
109 -
110 - $content = new stdClass();
111 - $content->content = self::instance()->html_edit_quiz( $quizPostModel );
112 -
113 - return $content;
114 - }
115 -
116 - /**
117 - * Render html for edit quiz.
118 - *
119 - * @param QuizPostModel $quizPostModel
120 - *
121 - * @return string
122 - * @throws Exception
123 - */
124 - public function html_edit_quiz( QuizPostModel $quizPostModel ): string {
125 - $html_questions = '';
126 -
127 - // Get sections items
128 - $question_ids = $quizPostModel->get_question_ids();
129 - $count_questions = count( $question_ids );
130 -
131 - foreach ( $question_ids as $question_id ) {
132 - $questionPostModel = QuestionPostModel::find( $question_id, true );
133 - $html_questions .= $this->html_edit_question( $questionPostModel );
134 - }
135 -
136 - $section_questions = [
137 - 'wrap' => '<div class="lp-edit-list-questions">',
138 - 'list-sections' => $html_questions,
139 - 'question-clone' => $this->html_edit_question(),
140 - 'wrap_end' => '</div>',
141 - ];
142 -
143 - $section = [
144 - 'wrap' => '<div class="lp-edit-quiz-wrap">',
145 - 'heading' => '<div class="heading">',
146 - 'h4' => sprintf(
147 - '<h4>%s</h4>',
148 - __( 'Details', 'learnpress' )
149 - ),
150 - 'count-questions' => sprintf(
151 - '<div class="total-items" data-count="%s">%s</div>',
152 - $count_questions,
153 - sprintf(
154 - __( '<span class="count">%1$s</span> %2$s', 'learnpress' ),
155 - $count_questions,
156 - sprintf(
157 - '<span class="one">%s</span><span class="plural">%s</span>',
158 - __( 'Question', 'learnpress' ),
159 - __( 'Questions', 'learnpress' )
160 - )
161 - )
162 - ),
163 - 'quiz-toggle' =>
164 - '<div class="lp-question-toggle-all lp-collapse">
165 - <span class="lp-icon-angle-down"></span>
166 - <span class="lp-icon-angle-up"></span>
167 - </div>',
168 - 'heading_end' => '</div>',
169 - 'questions' => Template::combine_components( $section_questions ),
170 - 'add_new_question' => $this->html_add_new_question(),
171 - 'select_items' => $this->html_popup_items_to_select_clone( $quizPostModel ),
172 - 'wrap_end' => '</div>',
173 - ];
174 -
175 - return Template::combine_components( $section );
176 - }
177 -
178 - /**
179 - * HTML for edit question.
180 - *
181 - * @param $questionPostModel
182 - *
183 - * @return string
184 - */
185 - public function html_edit_question( $questionPostModel = null ): string {
186 - $is_clone = false;
187 - $question_id = 0;
188 - $question_title = '';
189 - $html_edit_main = '';
190 -
191 - if ( $questionPostModel instanceof QuestionPostModel ) {
192 - $question_id = $questionPostModel->ID;
193 - $question_title = $questionPostModel->post_title;
194 -
195 - $section_edit_details = [
196 - 'wrap' => '<div class="question-edit-details lp-section-toggle">',
197 - 'header' => sprintf(
198 - '<div class="lp-question-data-edit-header lp-trigger-toggle">
199 - <label>%s</label>
200 - <div class="lp-tinymce-toggle"><span class="lp-icon-angle-down"></span><span class="lp-icon-angle-up"></span></div>
201 - </div>',
202 - __( 'Option Details', 'learnpress' )
203 - ),
204 - 'collapse' => '<div class="lp-section-collapse">',
205 - 'point' => AdminEditQuestionTemplate::instance()->html_edit_mark( $questionPostModel ),
206 - 'hint' => AdminEditQuestionTemplate::instance()->html_edit_hint( $questionPostModel ),
207 - 'explanation' => AdminEditQuestionTemplate::instance()->html_edit_explanation( $questionPostModel ),
208 - 'collapse_end' => '</div>',
209 - 'wrap_end' => '</div>',
210 - ];
211 -
212 - $section_edit_main = [
213 - 'wrap' => sprintf(
214 - '<div class="lp-question-edit-main" data-question-id="%d">',
215 - $question_id
216 - ),
217 - 'left' => '<div class="lp-question-edit-left">',
218 - 'description' => AdminEditQuestionTemplate::instance()->html_edit_question_description( $questionPostModel ),
219 - 'answers' => AdminEditQuestionTemplate::instance()->html_edit_question_by_type( $questionPostModel ),
220 - 'left_end' => '</div>',
221 - 'details' => Template::combine_components( $section_edit_details ),
222 - 'wrap_end' => '</div>',
223 - ];
224 -
225 - $html_edit_main = Template::combine_components( $section_edit_main );
226 - } else {
227 - $is_clone = true;
228 - }
229 -
230 - $section = [
231 - 'wrap' => sprintf(
232 - '<div data-question-id="%s"
233 - class="lp-question-item lp-section-toggle lp-collapse %s"
234 - data-question-type="%s">',
235 - $question_id,
236 - $is_clone ? 'clone lp-hidden' : '',
237 - $is_clone ? '' : $questionPostModel->get_type()
238 - ),
239 - 'head' => '<div class="lp-question-head">',
240 - 'drag' => sprintf(
241 - '<span class="drag lp-icon-drag" title="%s"></span>',
242 - __( 'Drag to reorder section', 'learnpress' )
243 - ),
244 - 'loading' => '<span class="lp-icon-spinner"></span>',
245 - 'title' => AdminEditQuestionTemplate::instance()->html_input_question_title( $question_title ),
246 - 'btn-update' => sprintf(
247 - '<button type="button" class="lp-btn-update-question-title button">%s</button>',
248 - __( 'Update' )
249 - ),
250 - 'btn-cancel' => sprintf(
251 - '<button type="button" class="lp-btn-cancel-update-question-title button">%s</button>',
252 - __( 'Cancel' )
253 - ),
254 - 'type' => sprintf(
255 - '<span class="lp-question-type-label">%s</span>',
256 - $questionPostModel instanceof QuestionPostModel ? $questionPostModel->get_type_label() : ''
257 - ),
258 - 'btn-edit' => sprintf(
259 - '<a class="lp-btn-edit-question-title lp-icon-edit-square" title="%s" href="%s" target="_blank"></a>',
260 - __( 'Edit question detail', 'learnpress' ),
261 - $questionPostModel instanceof QuestionPostModel ? $questionPostModel->get_edit_link() : '#'
262 - ),
263 - 'btn-delete' => sprintf(
264 - '<span class="lp-btn-remove-question lp-icon-trash-o" title="%s" data-title="%s" data-content="%s"></span>',
265 - __( 'Remove question', 'learnpress' ),
266 - __( 'Are you sure?', 'learnpress' ),
267 - __( 'This question will be removed from this quiz. The question will no longer be assigned to this quiz, but will not be permanently deleted.', 'learnpress' )
268 - ),
269 - 'toggle' => '<div class="lp-question-toggle"><span class="lp-icon-angle-down"></span><span class="lp-icon-angle-up"></span></div>',
270 - 'head_end' => '</div>',
271 - 'edit_main' => $html_edit_main,
272 - 'wrap_end' => '</div>',
273 - ];
274 -
275 - return Template::combine_components( $section );
276 - }
277 -
278 - /**
279 - * HTML for add new question.
280 - *
281 - * @return string
282 - */
283 - public function html_add_new_question(): string {
284 - $html_question_types = sprintf(
285 - '<option value="">%s</option>',
286 - esc_html__( 'Select question type', 'learnpress' )
287 - );
288 - $question_types = QuestionPostModel::get_types();
289 - foreach ( $question_types as $type => $label ) {
290 - $html_question_types .= sprintf(
291 - '<option value="%s">%s</option>',
292 - esc_attr( $type ),
293 - esc_html( $label )
294 - );
295 - }
296 - $html_question_types = Template::instance()->nest_elements(
297 - [
298 - sprintf(
299 - '<select class="lp-question-type-new"
300 - name="lp-question-type-new" data-mess-empty-type="%s">',
301 - esc_attr__( 'Question type is required', 'learnpress' )
302 - )
303 - => '</select>',
304 - ],
305 - $html_question_types
306 - );
307 -
308 - $section = [
309 - 'wrap' => '<div class="add-new-question">',
310 - 'icon' => '<span class="lp-icon-plus"></span>',
311 - 'input' => sprintf(
312 - '<input class="lp-question-title-new-input"
313 - name="lp-question-title-new-input"
314 - type="text"
315 - title="%1$s"
316 - placeholder="%1$s"
317 - data-mess-empty-title="%2$s"
318 - data-send="%3$s">',
319 - esc_attr__( 'Create a new question', 'learnpress' ),
320 - esc_attr__( 'Question title is required', 'learnpress' ),
321 - Template::convert_data_to_json(
322 - [
323 - 'id_url' => 'create-question-add-to-quiz',
324 - 'quiz_id' => $this->quizPostModel->get_id(),
325 - 'action' => 'create_question_add_to_quiz',
326 - ]
327 - ),
328 - ),
329 - 'types' => $html_question_types,
330 - 'button' => sprintf(
331 - '<button type="button" class="lp-btn-add-question lp-btn-edit-primary button" title="%s">%s</button>',
332 - esc_attr__( 'Enter title question and choice type', 'learnpress' ),
333 - __( 'Add Question', 'learnpress' )
334 - ),
335 - 'btn-select-items' => sprintf(
336 - '<button type="button"
337 - class="button lp-btn-show-popup-items-to-select"
338 - data-template="#lp-tmpl-select-question-bank">%s</button>',
339 - __( 'Question Bank', 'learnpress' )
340 - ),
341 - 'wrap_end' => '</div>',
342 - ];
343 -
344 - return Template::combine_components( $section );
345 - }
346 -
347 - /**
348 - * HTML for popup items to select clone.
349 - *
350 - * @param QuizPostModel $quizPostModel
351 - *
352 - * @return string
353 - */
354 - public function html_popup_items_to_select_clone( QuizPostModel $quizPostModel ): string {
355 - $tabs = [
356 - LP_QUESTION_CPT => __( 'Questions', 'learnpress' ),
357 - ];
358 -
359 - /**
360 - * @uses self::render_list_items_not_assign
361 - */
362 - $html_items = TemplateAJAX::load_content_via_ajax(
363 - [
364 - 'id_url' => 'list-questions-not-assign',
365 - 'enableScrollToView' => false,
366 - 'quiz_id' => $quizPostModel->ID,
367 - 'paged' => 1,
368 - ],
369 - [
370 - 'class' => self::class,
371 - 'method' => 'render_list_items_not_assign',
372 - ]
373 - );
374 -
375 - $section = [
376 - 'wrap-script-template' => '<script type="text/template" id="lp-tmpl-select-question-bank">',
377 - 'popup' => AdminTemplate::html_popup_items_to_select_clone( $tabs, $html_items ),
378 - 'wrap-script-template-end' => '</script>',
379 - ];
380 -
381 - return Template::combine_components( $section );
382 - }
383 -
384 - /**
385 - * Render list items not assign to any quiz.
386 - *
387 - * @throws Exception
388 - *
389 - * @since 4.2.8.7
390 - * @version 1.0.1
391 - */
392 - public static function render_list_items_not_assign( $data ): stdClass {
393 - $content = new stdClass();
394 - $quiz_id = $data['quiz_id'] ?? 0;
395 - $item_selecting = $data['item_selecting'] ?? [];
396 - $search_title = $data['search_title'] ?? '';
397 - $paged = intval( $data['paged'] ?? 1 );
398 - $item_selecting_compare = new stdClass();
399 -
400 - $quizPostModel = QuizPostModel::find( $quiz_id, true );
401 - if ( ! $quizPostModel ) {
402 - throw new Exception( __( 'Quiz not found', 'learnpress' ) );
403 - }
404 -
405 - $lp_posts_db = LP_Post_DB::getInstance();
406 - $filter = new PostFilter();
407 - $filter->only_fields = [
408 - 'DISTINCT(p.ID) AS ID',
409 - 'p.post_title',
410 - 'p.post_type',
411 - ];
412 - $filter->post_type = LP_QUESTION_CPT;
413 - $filter->post_status = [ 'publish' ];
414 - $filter->order_by = 'p.ID';
415 - $filter->page = $paged;
416 -
417 - if ( ! empty( $search_title ) ) {
418 - $filter->post_title = $search_title;
419 - }
420 -
421 - // Old logic: Get all questions not assigned to any quiz.
422 - // New logic: Get all questions not assigned to the quiz.
423 - $filter->where[] = $lp_posts_db->wpdb->prepare(
424 - "AND p.ID NOT IN ( SELECT question_id FROM {$lp_posts_db->tb_lp_quiz_questions} WHERE quiz_id = %d )",
425 - $quizPostModel->ID
426 - );
427 -
428 -
429 - $total_rows = 0;
430 - $posts = $lp_posts_db->get_posts( $filter, $total_rows );
431 - $total_pages = LP_Database::get_total_pages( $filter->limit, $total_rows );
432 -
433 - $html_lis = '';
434 - if ( empty( $posts ) ) {
435 - $html_lis = sprintf( '<li>%s</li>', __( 'No items found', 'learnpress' ) );
436 - } else {
437 - if ( ! empty( $item_selecting ) ) {
438 - foreach ( $item_selecting as $item ) {
439 - if ( ! isset( $item['id'] ) || ! isset( $item['type'] ) ) {
440 - continue;
441 - }
442 -
443 - $item_selecting_compare->{$item['id']} = new stdClass();
444 - }
445 - }
446 -
447 - foreach ( $posts as $post ) {
448 - /**
449 - * @var $questionPostModel QuestionPostModel
450 - */
451 - $questionPostModel = QuestionPostModel::find( $post->ID, true );
452 - if ( ! $questionPostModel ) {
453 - continue;
454 - }
455 -
456 - $checked = '';
457 - if ( isset( $item_selecting_compare->{$post->ID} ) ) {
458 - $checked = ' checked="checked"';
459 - }
460 -
461 - $title_display = sprintf(
462 - '<span class="title">%s<strong>(#%d - %s)</strong></span>',
463 - $post->post_title,
464 - $post->ID,
465 - $questionPostModel->get_type_label()
466 - );
467 -
468 - $html_lis .= sprintf(
469 - '<li class="lp-select-item">%s%s</li>',
470 - sprintf(
471 - '<input name="lp-select-item"
472 - data-id="%d" data-type-label="%s"
473 - data-type="%s"
474 - data-title="%s" %s data-edit-link="%s"
475 - data-title-selected="%s"
476 - type="checkbox" />',
477 - esc_attr( $post->ID ?? 0 ),
478 - esc_attr( $questionPostModel->get_type_label() ?? '' ),
479 - esc_attr( $questionPostModel->get_type() ?? '' ),
480 - esc_attr( $title_display ), // For JS display on list selected.
481 - esc_attr( $checked ),
482 - $questionPostModel->get_edit_link(),
483 - esc_attr( $questionPostModel->get_the_title() ?? '' )
484 - ),
485 - $title_display
486 - );
487 - }
488 - }
489 -
490 - $section = [
491 - 'ul' => '<ul class="list-items">',
492 - 'items' => $html_lis,
493 - 'ul_end' => '</ul>',
494 - 'pagination' => Template::instance()->html_pagination(
495 - [
496 - 'total_pages' => $total_pages,
497 - 'paged' => $paged,
498 - ]
499 - ),
500 - ];
501 -
502 - $content->content = Template::combine_components( $section );
503 -
504 - return $content;
505 - }
506 -}
1 +<?php
2 +
3 +namespace LearnPress\TemplateHooks\Admin;
4 +
5 +use Exception;
6 +use LearnPress\Databases\DataBase;
7 +use LearnPress\Filters\PostFilter;
8 +use LearnPress\Helpers\Config;
9 +use LearnPress\Helpers\Response;
10 +use LearnPress\Helpers\Singleton;
11 +use LearnPress\Helpers\Template;
12 +use LearnPress\Models\Question\QuestionPostModel;
13 +use LearnPress\Models\QuizPostModel;
14 +use LearnPress\Models\UserModel;
15 +use LearnPress\TemplateHooks\TemplateAJAX;
16 +use LP_Database;
17 +use LP_Post_DB;
18 +use LP_Post_Type_Filter;
19 +use stdClass;
20 +use Throwable;
21 +
22 +defined( 'ABSPATH' ) || exit;
23 +
24 +/**
25 + * Template Admin Edit Quiz.
26 + *
27 + * @since 4.2.9
28 + * @version 1.0.3
29 + */
30 +class AdminEditQizTemplate {
31 + use Singleton;
32 +
33 + /**
34 + * @var QuizPostModel
35 + */
36 + public $quizPostModel;
37 +
38 + public function init(): void {
39 + add_action( 'learn-press/admin/edit-quiz/layout', [ $this, 'edit_quiz_layout' ] );
40 + add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
41 + add_filter(
42 + 'wp_default_editor',
43 + function ( $r ) {
44 + global $post;
45 + if ( ! $post ) {
46 + return $r;
47 + }
48 +
49 + if ( $post->post_type !== LP_QUIZ_CPT ) {
50 + return $r;
51 + }
52 +
53 + return 'html';
54 + },
55 + 1000
56 + );
57 + }
58 +
59 + /**
60 + * Layout for edit course curriculum.
61 + *
62 + * @since 4.2.9
63 + * @version 1.0.0
64 + */
65 + public function edit_quiz_layout( QuizPostModel $quizPostModel ) {
66 + wp_enqueue_style( 'lp-edit-quiz' );
67 + wp_enqueue_script( 'lp-edit-quiz' );
68 +
69 + $args = [
70 + 'id_url' => 'edit-quiz',
71 + 'quiz_id' => $quizPostModel->ID,
72 + ];
73 + /**
74 + * @uses self::render_edit_quiz
75 + */
76 + $call_back = array(
77 + 'class' => self::class,
78 + 'method' => 'render_edit_quiz',
79 + );
80 +
81 + echo TemplateAJAX::load_content_via_ajax( $args, $call_back );
82 + }
83 +
84 + /**
85 + * Allow callback for AJAX.
86 + *
87 + * @param array $callbacks
88 + *
89 + * @return array
90 + */
91 + public function allow_callback( array $callbacks ): array {
92 + $callbacks[] = get_class( $this ) . ':render_edit_quiz';
93 + $callbacks[] = get_class( $this ) . ':render_list_items_not_assign';
94 +
95 + return $callbacks;
96 + }
97 +
98 + /**
99 + * Render edit course curriculum html.
100 + *
101 + * @throws Exception
102 + * @since 4.2.9
103 + * @version 1.0.1
104 + */
105 + public static function render_edit_quiz( array $data ): stdClass {
106 + $content = new stdClass();
107 +
108 + try {
109 + $quiz_id = $data['quiz_id'] ?? 0;
110 + $quizPostModel = QuizPostModel::find( $quiz_id, true );
111 + if ( ! $quizPostModel ) {
112 + throw new Exception( __( 'Quiz not found', 'learnpress' ) );
113 + }
114 +
115 + // Check permission
116 + $quizPostModel->check_capabilities_create_item_course();
117 +
118 + self::instance()->quizPostModel = $quizPostModel;
119 +
120 + $content->content = self::instance()->html_edit_quiz( $quizPostModel );
121 + } catch ( Throwable $e ) {
122 + $content->content = Template::print_message(
123 + $e->getMessage(),
124 + Response::STATUS_ERROR,
125 + false
126 + );
127 + }
128 +
129 + return $content;
130 + }
131 +
132 + /**
133 + * Render html for edit quiz.
134 + *
135 + * @param QuizPostModel $quizPostModel
136 + *
137 + * @return string
138 + * @throws Exception
139 + */
140 + public function html_edit_quiz( QuizPostModel $quizPostModel ): string {
141 + $html_questions = '';
142 +
143 + // Get sections items
144 + $question_ids = $quizPostModel->get_question_ids();
145 + $count_questions = count( $question_ids );
146 +
147 + foreach ( $question_ids as $question_id ) {
148 + $questionPostModel = QuestionPostModel::find( $question_id, true );
149 + $html_questions .= $this->html_edit_question( $questionPostModel );
150 + }
151 +
152 + $section_questions = [
153 + 'wrap' => '<div class="lp-edit-list-questions">',
154 + 'list-sections' => $html_questions,
155 + 'question-clone' => $this->html_edit_question(),
156 + 'wrap_end' => '</div>',
157 + ];
158 +
159 + $section = [
160 + 'wrap' => '<div class="lp-edit-quiz-wrap">',
161 + 'heading' => '<div class="heading">',
162 + 'h4' => sprintf(
163 + '<h4>%s</h4>',
164 + __( 'Details', 'learnpress' )
165 + ),
166 + 'count-questions' => sprintf(
167 + '<div class="total-items" data-count="%d">%s</div>',
168 + $count_questions,
169 + sprintf(
170 + __( '<span class="count">%1$d</span> %2$s', 'learnpress' ),
171 + $count_questions,
172 + sprintf(
173 + '<span class="one">%s</span><span class="plural">%s</span>',
174 + __( 'Question', 'learnpress' ),
175 + __( 'Questions', 'learnpress' )
176 + )
177 + )
178 + ),
179 + 'quiz-toggle' =>
180 + '<div class="lp-question-toggle-all lp-collapse">
181 + <span class="lp-icon-angle-down"></span>
182 + <span class="lp-icon-angle-up"></span>
183 + </div>',
184 + 'heading_end' => '</div>',
185 + 'questions' => Template::combine_components( $section_questions ),
186 + 'add_new_question' => $this->html_add_new_question(),
187 + 'select_items' => $this->html_popup_items_to_select_clone( $quizPostModel ),
188 + 'wrap_end' => '</div>',
189 + ];
190 +
191 + return Template::combine_components( $section );
192 + }
193 +
194 + /**
195 + * HTML for edit question.
196 + *
197 + * @param $questionPostModel
198 + *
199 + * @return string
200 + */
201 + public function html_edit_question( $questionPostModel = null ): string {
202 + $is_clone = false;
203 + $question_id = 0;
204 + $question_title = '';
205 + $html_edit_main = '';
206 +
207 + if ( $questionPostModel instanceof QuestionPostModel ) {
208 + $question_id = $questionPostModel->ID;
209 + $question_title = $questionPostModel->post_title;
210 +
211 + $section_edit_details = [
212 + 'wrap' => '<div class="question-edit-details lp-section-toggle">',
213 + 'header' => sprintf(
214 + '<div class="lp-question-data-edit-header lp-trigger-toggle">
215 + <label>%s</label>
216 + <div class="lp-tinymce-toggle">
217 + <span class="lp-icon-angle-down"></span>
218 + <span class="lp-icon-angle-up"></span>
219 + </div>
220 + </div>',
221 + __( 'Option Details', 'learnpress' )
222 + ),
223 + 'collapse' => '<div class="lp-section-collapse">',
224 + 'point' => AdminEditQuestionTemplate::instance()->html_edit_mark( $questionPostModel ),
225 + 'hint' => AdminEditQuestionTemplate::instance()->html_edit_hint( $questionPostModel ),
226 + 'explanation' => AdminEditQuestionTemplate::instance()->html_edit_explanation( $questionPostModel ),
227 + 'collapse_end' => '</div>',
228 + 'wrap_end' => '</div>',
229 + ];
230 +
231 + $section_edit_main = [
232 + 'wrap' => sprintf(
233 + '<div class="lp-question-edit-main" data-question-id="%d">',
234 + $question_id
235 + ),
236 + 'left' => '<div class="lp-question-edit-left">',
237 + 'description' => AdminEditQuestionTemplate::instance()->html_edit_question_description( $questionPostModel ),
238 + 'answers' => AdminEditQuestionTemplate::instance()->html_edit_question_by_type( $questionPostModel ),
239 + 'left_end' => '</div>',
240 + 'details' => Template::combine_components( $section_edit_details ),
241 + 'wrap_end' => '</div>',
242 + ];
243 +
244 + $html_edit_main = Template::combine_components( $section_edit_main );
245 + } else {
246 + $is_clone = true;
247 + }
248 +
249 + $section = [
250 + 'wrap' => sprintf(
251 + '<div data-question-id="%s"
252 + class="lp-question-item lp-section-toggle lp-collapse %s"
253 + data-question-type="%s">',
254 + esc_attr( $question_id ),
255 + esc_attr( $is_clone ? 'clone lp-hidden' : '' ),
256 + esc_attr( $is_clone ? '' : $questionPostModel->get_type() )
257 + ),
258 + 'head' => '<div class="lp-question-head">',
259 + 'drag' => sprintf(
260 + '<span class="drag lp-icon-drag" title="%s"></span>',
261 + esc_attr__( 'Drag to reorder section', 'learnpress' )
262 + ),
263 + 'loading' => '<span class="lp-icon-spinner"></span>',
264 + 'title' => AdminEditQuestionTemplate::instance()->html_input_question_title( $question_title ),
265 + 'btn-update' => sprintf(
266 + '<button type="button" class="lp-btn-update-question-title button">%s</button>',
267 + __( 'Update' )
268 + ),
269 + 'btn-cancel' => sprintf(
270 + '<button type="button" class="lp-btn-cancel-update-question-title button">%s</button>',
271 + __( 'Cancel' )
272 + ),
273 + 'type' => sprintf(
274 + '<span class="lp-question-type-label">%s</span>',
275 + esc_html(
276 + $questionPostModel instanceof QuestionPostModel ? $questionPostModel->get_type_label() : ''
277 + )
278 + ),
279 + 'btn-edit' => sprintf(
280 + '<a class="lp-btn-edit-question-title lp-icon-edit-square" title="%s" href="%s" target="_blank"></a>',
281 + esc_attr__( 'Edit question detail', 'learnpress' ),
282 + esc_url(
283 + $questionPostModel instanceof QuestionPostModel ? $questionPostModel->get_edit_link() : '#'
284 + )
285 + ),
286 + 'btn-delete' => sprintf(
287 + '<span class="lp-btn-remove-question lp-icon-trash-o" title="%s" data-title="%s" data-content="%s"></span>',
288 + esc_attr__( 'Remove question', 'learnpress' ),
289 + esc_attr__( 'Are you sure?', 'learnpress' ),
290 + esc_attr__(
291 + 'This question will be removed from this quiz. The question will no longer be assigned to this quiz, but will not be permanently deleted.',
292 + 'learnpress'
293 + )
294 + ),
295 + 'toggle' => '<div class="lp-question-toggle">
296 + <span class="lp-icon-angle-down"></span>
297 + <span class="lp-icon-angle-up"></span>
298 + </div>',
299 + 'head_end' => '</div>',
300 + 'edit_main' => $html_edit_main,
301 + 'wrap_end' => '</div>',
302 + ];
303 +
304 + return Template::combine_components( $section );
305 + }
306 +
307 + /**
308 + * HTML for add new question.
309 + *
310 + * @return string
311 + */
312 + public function html_add_new_question(): string {
313 + $html_question_types = sprintf(
314 + '<option value="">%s</option>',
315 + esc_html__( 'Select question type', 'learnpress' )
316 + );
317 + $question_types = QuestionPostModel::get_types();
318 + foreach ( $question_types as $type => $label ) {
319 + $html_question_types .= sprintf(
320 + '<option value="%s">%s</option>',
321 + esc_attr( $type ),
322 + esc_html( $label )
323 + );
324 + }
325 + $html_question_types = Template::instance()->nest_elements(
326 + [
327 + sprintf(
328 + '<select class="lp-question-type-new"
329 + name="lp-question-type-new" data-mess-empty-type="%s">',
330 + esc_attr__( 'Question type is required', 'learnpress' )
331 + )
332 + => '</select>',
333 + ],
334 + $html_question_types
335 + );
336 +
337 + $section = [
338 + 'wrap' => '<div class="add-new-question">',
339 + 'icon' => '<span class="lp-icon-plus"></span>',
340 + 'input' => sprintf(
341 + '<input class="lp-question-title-new-input"
342 + name="lp-question-title-new-input"
343 + type="text"
344 + title="%1$s"
345 + placeholder="%1$s"
346 + data-mess-empty-title="%2$s"
347 + data-send="%3$s">',
348 + esc_attr__( 'Create a new question', 'learnpress' ),
349 + esc_attr__( 'Question title is required', 'learnpress' ),
350 + Template::convert_data_to_json(
351 + [
352 + 'id_url' => 'create-question-add-to-quiz',
353 + 'quiz_id' => $this->quizPostModel->get_id(),
354 + 'action' => 'create_question_add_to_quiz',
355 + ]
356 + ),
357 + ),
358 + 'types' => $html_question_types,
359 + 'button' => sprintf(
360 + '<button type="button" class="lp-btn-add-question lp-btn-edit-primary button" title="%s">%s</button>',
361 + esc_attr__( 'Enter title question and choice type', 'learnpress' ),
362 + __( 'Add Question', 'learnpress' )
363 + ),
364 + 'btn-select-items' => sprintf(
365 + '<button type="button"
366 + class="button lp-btn-show-popup-items-to-select"
367 + data-template="#lp-tmpl-select-question-bank">%s</button>',
368 + __( 'Question Bank', 'learnpress' )
369 + ),
370 + 'wrap_end' => '</div>',
371 + ];
372 +
373 + return Template::combine_components( $section );
374 + }
375 +
376 + /**
377 + * HTML for popup items to select clone.
378 + *
379 + * @param QuizPostModel $quizPostModel
380 + *
381 + * @return string
382 + */
383 + public function html_popup_items_to_select_clone( QuizPostModel $quizPostModel ): string {
384 + $tabs = [
385 + LP_QUESTION_CPT => __( 'Questions', 'learnpress' ),
386 + ];
387 +
388 + /**
389 + * @uses self::render_list_items_not_assign
390 + */
391 + $html_items = TemplateAJAX::load_content_via_ajax(
392 + [
393 + 'id_url' => 'list-questions-not-assign',
394 + 'enableScrollToView' => false,
395 + 'quiz_id' => $quizPostModel->ID,
396 + 'paged' => 1,
397 + ],
398 + [
399 + 'class' => self::class,
400 + 'method' => 'render_list_items_not_assign',
401 + ]
402 + );
403 +
404 + $section = [
405 + 'wrap-script-template' => '<script type="text/template" id="lp-tmpl-select-question-bank">',
406 + 'popup' => AdminTemplate::html_popup_items_to_select_clone( $tabs, $html_items ),
407 + 'wrap-script-template-end' => '</script>',
408 + ];
409 +
410 + return Template::combine_components( $section );
411 + }
412 +
413 + /**
414 + * Render list items not assign to any quiz.
415 + *
416 + * @throws Exception
417 + *
418 + * @since 4.2.8.7
419 + * @version 1.0.2
420 + */
421 + public static function render_list_items_not_assign( $data ): stdClass {
422 + $content = new stdClass();
423 +
424 + try {
425 + // Check permission
426 + if ( ! current_user_can( UserModel::ROLE_ADMINISTRATOR ) ) {
427 + throw new Exception( esc_html__( 'Access denied.', 'learnpress' ) );
428 + }
429 +
430 + $quiz_id = $data['quiz_id'] ?? 0;
431 + $item_selecting = $data['item_selecting'] ?? [];
432 + $search_title = $data['search_title'] ?? '';
433 + $paged = intval( $data['paged'] ?? 1 );
434 + $item_selecting_compare = new stdClass();
435 +
436 + $quizPostModel = QuizPostModel::find( $quiz_id, true );
437 + if ( ! $quizPostModel ) {
438 + throw new Exception( __( 'Quiz not found', 'learnpress' ) );
439 + }
440 +
441 + $lp_posts_db = LP_Post_DB::getInstance();
442 + $filter = new PostFilter();
443 + $filter->only_fields = [
444 + 'DISTINCT(p.ID) AS ID',
445 + 'p.post_title',
446 + 'p.post_type',
447 + ];
448 + $filter->post_type = LP_QUESTION_CPT;
449 + $filter->post_status = [ 'publish' ];
450 + $filter->order_by = 'p.ID';
451 + $filter->page = $paged;
452 +
453 + if ( ! empty( $search_title ) ) {
454 + $filter->post_title = $search_title;
455 + }
456 +
457 + // Old logic: Get all questions not assigned to any quiz.
458 + // New logic: Get all questions not assigned to the quiz.
459 + $filter->where[] = $lp_posts_db->wpdb->prepare(
460 + "AND p.ID NOT IN ( SELECT question_id FROM {$lp_posts_db->tb_lp_quiz_questions} WHERE quiz_id = %d )",
461 + $quizPostModel->ID
462 + );
463 +
464 + $total_rows = 0;
465 + $posts = $lp_posts_db->get_posts( $filter, $total_rows );
466 + $total_pages = LP_Database::get_total_pages( $filter->limit, $total_rows );
467 +
468 + $html_lis = '';
469 + if ( empty( $posts ) ) {
470 + $html_lis = sprintf( '<li>%s</li>', __( 'No items found', 'learnpress' ) );
471 + } else {
472 + if ( ! empty( $item_selecting ) ) {
473 + foreach ( $item_selecting as $item ) {
474 + if ( ! isset( $item['id'] ) || ! isset( $item['type'] ) ) {
475 + continue;
476 + }
477 +
478 + $item_selecting_compare->{$item['id']} = new stdClass();
479 + }
480 + }
481 +
482 + foreach ( $posts as $post ) {
483 + /**
484 + * @var $questionPostModel QuestionPostModel
485 + */
486 + $questionPostModel = QuestionPostModel::find( $post->ID, true );
487 + if ( ! $questionPostModel ) {
488 + continue;
489 + }
490 +
491 + $checked = '';
492 + if ( isset( $item_selecting_compare->{$post->ID} ) ) {
493 + $checked = ' checked="checked"';
494 + }
495 +
496 + $title_display = sprintf(
497 + '<span class="title">%s<strong>(#%d - %s)</strong></span>',
498 + esc_html( $post->post_title ),
499 + $post->ID,
500 + esc_html( $questionPostModel->get_type_label() )
501 + );
502 +
503 + $html_lis .= sprintf(
504 + '<li class="lp-select-item">%s%s</li>',
505 + sprintf(
506 + '<input name="lp-select-item"
507 + data-id="%d" data-type-label="%s"
508 + data-type="%s"
509 + data-title="%s" %s data-edit-link="%s"
510 + data-title-selected="%s"
511 + type="checkbox" />',
512 + esc_attr( $post->ID ?? 0 ),
513 + esc_attr( $questionPostModel->get_type_label() ?? '' ),
514 + esc_attr( $questionPostModel->get_type() ?? '' ),
515 + esc_attr( $title_display ), // For JS display on list selected.
516 + esc_attr( $checked ),
517 + esc_url( $questionPostModel->get_edit_link() ),
518 + esc_attr( $questionPostModel->get_the_title() ?? '' )
519 + ),
520 + $title_display
521 + );
522 + }
523 + }
524 +
525 + $section = [
526 + 'ul' => '<ul class="list-items">',
527 + 'items' => $html_lis,
528 + 'ul_end' => '</ul>',
529 + 'pagination' => Template::instance()->html_pagination(
530 + [
531 + 'total_pages' => $total_pages,
532 + 'paged' => $paged,
533 + ]
534 + ),
535 + ];
536 +
537 + $content->content = Template::combine_components( $section );
538 + } catch ( Throwable $e ) {
539 + $content->content = Template::print_message(
540 + $e->getMessage(),
541 + Response::STATUS_ERROR,
542 + false
543 + );
544 + }
545 +
546 + return $content;
547 + }
548 +}