PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.7
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
learnpress / inc / TemplateHooks / CourseBuilder / Course / BuilderEditCourseTemplate.php

BuilderEditCourseTemplate.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.7, at inc/TemplateHooks/CourseBuilder/Course/BuilderEditCourseTemplate.php

1,331 lines 45.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Template hooks Course Builder.
4 *
5 * @since 4.3.0
6 * @version 1.0.0
7 */
8
9 namespace LearnPress\TemplateHooks\CourseBuilder\Course;
10
11 use Exception;
12 use LearnPress\CourseBuilder\CourseBuilder;
13 use LearnPress\CourseBuilder\CourseBuilderAccessPolicy;
14 use LearnPress\Helpers\Singleton;
15 use LearnPress\Helpers\Template;
16 use LearnPress\Models\CourseModel;
17 use LearnPress\Models\CoursePostModel;
18 use LearnPress\Models\PostModel;
19 use LearnPress\Models\UserModel;
20 use LearnPress\TemplateHooks\Admin\AdminTemplate;
21 use LearnPress\TemplateHooks\Admin\AI\AdminEditCourseCurriculumWithAITemplate;
22 use LearnPress\TemplateHooks\Admin\AI\AdminEditWithAITemplate;
23 use LearnPress\TemplateHooks\Course\AdminEditCurriculumTemplate;
24 use LearnPress\TemplateHooks\CourseBuilder\BuilderPopupTemplate;
25 use LearnPress\TemplateHooks\TemplateAJAX;
26 use LP_Settings;
27 use LP_WP_Filesystem;
28 use Throwable;
29 use WP_User;
30
31 class BuilderEditCourseTemplate {
32 use Singleton;
33
34 public function init() {
35 add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
36
37 // Register filter for adding edit popup button in Course Builder curriculum
38 add_filter( 'learn-press/admin/curriculum/section-item/actions', [ $this, 'add_edit_popup_button' ], 10, 5 );
39 }
40
41 /**
42 * HTML edit/create course on Course Builder screen
43 *
44 * @param array $data
45 *
46 * @since 4.3.6
47 * @version 1.0.1
48 * @return string
49 */
50 public function layout( array $data = [] ): string {
51 $html = '';
52
53 try {
54 // Check permission
55 $userModel = $data['userModel'] ?? false;
56 if ( ! $userModel || ! $userModel->is_instructor() ) {
57 throw new Exception( __( 'You do not have permission to create or edit courses', 'learnpress' ) );
58 }
59
60 $userCoursePostModel = new CoursePostModel();
61 if ( ! $userCoursePostModel->check_capabilities_create() ) {
62 throw new Exception( __( 'You do not have permission to create or edit courses', 'learnpress' ) );
63 }
64
65 $item_id = $data['item_id'] ?? '';
66 if ( empty( $item_id ) ) {
67 throw new Exception( __( 'Invalid course ID', 'learnpress' ) );
68 }
69
70 if ( ! CourseBuilderAccessPolicy::can_access_tab_post( 'courses', $item_id ) ) {
71 throw new Exception( __( "Sorry, you don't have permission to access this content", 'learnpress' ) );
72 }
73
74 $is_create_new = $item_id === CourseBuilder::POST_NEW;
75 $courseModel = false;
76
77 if ( ! $is_create_new ) {
78 $courseModel = CourseModel::find( (int) $item_id, true );
79 if ( ! $courseModel ) {
80 throw new Exception( __( 'Course not found', 'learnpress' ) );
81 }
82
83 if ( $courseModel->get_status() === PostModel::STATUS_TRASH ) {
84 throw new Exception(
85 __(
86 'You cannot edit this item because it is in the Trash. Please restore it and try again.',
87 'learnpress'
88 )
89 );
90 }
91 }
92
93 $data['courseModel'] = $courseModel;
94
95 $section = [
96 'wrap' => sprintf(
97 '<div class="lp-cb-content" data-post-id="%1$s">',
98 esc_attr( $item_id ),
99 ),
100 'header' => $this->html_header( $data ),
101 'tabs' => $this->html_tabs( $data ),
102 'wrap_end' => '</div>',
103 ];
104
105 $html = Template::combine_components( $section );
106 } catch ( Throwable $e ) {
107 $html = Template::print_message( $e->getMessage(), 'error', false );
108 }
109
110 return $html;
111 }
112
113 /**
114 * HTML header
115 *
116 * @param array $data
117 *
118 * @return string
119 */
120 public function html_header( array $data = [] ): string {
121 /** @var UserModel $userModel */
122 $userModel = $data['userModel'] ?? false;
123 if ( ! $userModel instanceof UserModel ) {
124 return '';
125 }
126
127 /** @var CourseModel|false $courseModel */
128 $courseModel = $data['courseModel'] ?? false;
129 $hide_instructor_access_admin_screen = LP_Settings::is_hide_instructor_access_admin_screen();
130 $more_actions_icon = LP_WP_Filesystem::get_icon_svg( 'ico-cb-more.svg' );
131 $title = $courseModel ? $courseModel->get_title() : __( 'Add New Course', 'learnpress' );
132 $status_badge = $courseModel ? $courseModel->get_status() : '';
133 $status = '';
134 if ( $courseModel ) {
135 $status = $courseModel->get_status();
136 }
137 $main_action_status = in_array(
138 $status,
139 array(
140 'publish',
141 'draft',
142 'pending',
143 'future',
144 'private',
145 ),
146 true
147 ) ? $status : 'publish';
148 $wp_user = new WP_User( $userModel );
149 $hide_wp_edit_link = $hide_instructor_access_admin_screen && user_can( $wp_user, UserModel::ROLE_INSTRUCTOR );
150
151 $section = [
152 'header_wrap' => '<div class="lp-cb-header">',
153 'header_left' => '<div class="lp-cb-header__left">',
154 'title' => sprintf(
155 '<h1 class="lp-cb-header__title">%s</h1>',
156 esc_html( $title )
157 ),
158 'status_badge' => $courseModel ? sprintf(
159 '<span class="course-status %s">%s</span>',
160 $status_badge,
161 esc_html( $courseModel->get_post_model()->get_status_i18n() )
162 ) : '',
163 'link_edit_on_wp' => $courseModel && ! $hide_wp_edit_link ? sprintf(
164 '<a href="%1$s" class="lp-cb-admin-link" target="_blank" title="%2$s"%3$s>
165 <span class="dashicons dashicons-wordpress"></span>
166 <span>%2$s</span>
167 </a>',
168 esc_url( admin_url( "post.php?post={$courseModel->get_id()}&action=edit" ) ),
169 esc_attr__( 'Edit with WordPress', 'learnpress' ),
170 PostModel::STATUS_TRASH === $status ? ' style="display:none"' : ''
171 ) : '',
172 'header_left_end' => '</div>',
173 'header_actions' => '<div class="lp-cb-header__actions">',
174 'preview_btn' => $courseModel && $courseModel->get_status() !== PostModel::STATUS_TRASH ? sprintf(
175 '<a href="%1$s" class="cb-button cb-btn-preview cb-btn-secondary lp-button" target="_blank">%2$s</a>',
176 esc_url( get_permalink( $courseModel->get_id() ) ),
177 esc_html__( 'Preview', 'learnpress' )
178 ) : '',
179 'dropdown_wrap' => '<div class="cb-header-actions-dropdown cb-header-actions-dropdown--single">',
180 'update_btn' => sprintf(
181 '<div class="cb-btn-update cb-btn-primary cb-btn-main-action lp-button"
182 data-status="%1$s"
183 data-title-update="%2$s"
184 data-title-publish="%3$s"
185 data-title-draft="%4$s"
186 data-title-submit-review="%5$s">%6$s</div>',
187 esc_attr( $main_action_status ),
188 esc_attr__( 'Update', 'learnpress' ),
189 esc_attr__( 'Publish', 'learnpress' ),
190 esc_attr__( 'Save Draft', 'learnpress' ),
191 esc_attr__( 'Submit for Review', 'learnpress' ),
192 esc_html__( 'Update', 'learnpress' )
193 ),
194 'dropdown_wrap_end' => '</div>',
195 'expanded_actions' => $courseModel ? sprintf(
196 '<div class="cb-header-action-expanded">
197 <button type="button" class="course-action-expanded lp-button" aria-haspopup="true" aria-expanded="false" aria-label="%1$s">
198 %2$s
199 </button>
200 <div class="cb-header-action-expanded__items">
201 <div class="cb-header-action-expanded__duplicate cb-btn-duplicate-course lp-button"
202 data-title="%3$s"
203 data-content="%4$s">
204 <span class="dashicons dashicons-admin-page"></span>
205 %5$s
206 </div>
207 <div class="cb-header-action-expanded__trash cb-btn-trash lp-button">
208 <span class="dashicons dashicons-trash"></span>
209 %6$s
210 </div>
211 </div>
212 </div>',
213 esc_attr__( 'More actions', 'learnpress' ),
214 $more_actions_icon,
215 esc_attr__( 'Are you sure?', 'learnpress' ),
216 esc_attr__( 'Are you sure you want to duplicate this course?', 'learnpress' ),
217 esc_html__( 'Duplicate', 'learnpress' ),
218 esc_html__( 'Move to Trash', 'learnpress' )
219 ) : '',
220 'header_actions_end' => '</div>',
221 'header_end' => '</div>',
222 ];
223
224 return Template::combine_components( $section );
225 }
226
227 /**
228 * Render tabs
229 *
230 * @param array $data
231 *
232 * @return string
233 */
234 public function html_tabs( array $data = [] ): string {
235 $section_tab = [
236 'tabs_wrap' => '<div class="lp-cb-tabs">',
237 'tabs' => '',
238 'tabs_end' => '</div>',
239 ];
240
241 $section_content = [
242 'wrap' => '<div class="lp-cb-tab-content">',
243 'content' => '',
244 'wrap-end' => '</div>',
245 ];
246
247 $tabs = apply_filters(
248 'learn-press/course-builder/course/edit/tabs',
249 [
250 'overview' => [
251 'title' => esc_html__( 'Overview', 'learnpress' ),
252 'html' => $this->html_tab_overview( $data ),
253 ],
254 'curriculum' => [
255 'title' => esc_html__( 'Curriculum', 'learnpress' ),
256 'html' => $this->html_tab_curriculum( $data ),
257 ],
258 'settings' => [
259 'title' => esc_html__( 'Settings', 'learnpress' ),
260 'html' => $this->html_tab_settings( $data ),
261 ],
262 ],
263 $data
264 );
265
266 $tab_active = array_key_first( $tabs );
267 if ( isset( $_GET['tab'] ) ) {
268 $tab_active = $_GET['tab'];
269 }
270
271 foreach ( $tabs as $key => $tab ) {
272 $is_active = $key === $tab_active;
273
274 $section_tab['tabs'] .= sprintf(
275 '<a href="#" class="lp-cb-tabs__item %s" data-tab-section="%s">%s</a>',
276 $is_active ? 'is-active' : '',
277 esc_attr( $tab['slug'] ?? $key ),
278 esc_html( $tab['title'] ?? '' )
279 );
280
281 /**
282 * @uses html_tab_overview
283 * @uses html_tab_curriculum
284 * @uses html_tab_settings
285 */
286 $section_content['content'] .= sprintf(
287 '<div class="lp-cb-tab-panel %s" data-section="%s">%s</div>',
288 $is_active ? '' : 'lp-hidden',
289 esc_attr( $key ),
290 $tab['html']
291 );
292 }
293
294 $section = [
295 'tabs' => Template::combine_components( $section_tab ),
296 'contents' => Template::combine_components( $section_content ),
297 ];
298
299 return Template::combine_components( $section );
300 }
301
302 public function html_tab_overview( array $data = [] ) {
303 wp_enqueue_script( 'lp-course-builder' );
304
305 $courseMoel = $data['courseModel'] ?? null;
306 $course_id = 0;
307 if ( $courseMoel instanceof CourseModel ) {
308 $course_id = $courseMoel->get_id();
309 }
310
311 $html_edit_title = $this->edit_title( $courseMoel );
312 $html_edit_permalink = $this->edit_permalink( $courseMoel );
313 $html_edit_features = $this->edit_featured_image( $courseMoel );
314 $html_edit_publish = $this->edit_publish( $courseMoel );
315 $html_edit_desc = $this->edit_desc( $courseMoel );
316 $html_edit_cat = $this->edit_categories( $courseMoel );
317 $html_edit_tags = $this->edit_tags( $courseMoel );
318
319 $section = [
320 'wrapper' => sprintf( '<div class="cb-section__course-edit" data-course-id="%s">', $course_id ),
321 'content_wrapper' => '<div class="cb-course-edit-content">',
322 // Left column
323 'left_column' => '<div class="cb-course-edit-column cb-course-edit-column--left">',
324 'edit_title' => $html_edit_title,
325 'edit_permalink' => $html_edit_permalink,
326 'edit_publish' => $html_edit_publish,
327 'edit_features' => $html_edit_features,
328 'left_column_end' => '</div>',
329 // Right column
330 'right_column' => '<div class="cb-course-edit-column cb-course-edit-column--right">',
331 'edit_desc' => $html_edit_desc,
332 'edit_term_category' => '<div class="cb-course-edit-terms-categories-wrapper">',
333 'edit_cat' => $html_edit_cat,
334 'edit_term' => $html_edit_tags,
335 'edit_term_category_end' => '</div>',
336 'right_column_end' => '</div>',
337 'content_wrapper_end' => '</div>',
338 'ai_templates' => $this->html_overview_ai_templates(),
339 'wrapper_end' => '</div>',
340 ];
341
342 return Template::combine_components( $section );
343 }
344
345 /**
346 * HTML Curriculum
347 *
348 * @param array $data
349 *
350 * @return string
351 */
352 public function html_tab_curriculum( array $data = [] ): string {
353 wp_enqueue_script( 'lp-cb-edit-curriculum' );
354 wp_enqueue_style( 'lp-cb-edit-curriculum' );
355 wp_enqueue_script( 'lp-cb-admin-learnpress' );
356
357 $course_id = CourseBuilder::get_item_id();
358
359 if ( $course_id === CourseBuilder::POST_NEW ) {
360 return Template::print_message( __( 'Please save Course before add Section', 'learnpress' ), 'info', false );
361 }
362
363 $courseModel = $data['courseModel'] ?? null;
364 if ( ! $courseModel instanceof CourseModel ) {
365 return Template::print_message( __( 'Course is invalid!', 'learnpress' ), 'error', false );
366 }
367
368 return $this->html_curriculum( $courseModel );
369 }
370
371 public function html_tab_settings( array $data = [] ) {
372 wp_enqueue_script( 'lp-cb-edit-curriculum' );
373 wp_enqueue_script( 'lp-tom-select' );
374 wp_enqueue_style( 'lp-cb-edit-curriculum' );
375 wp_enqueue_script( 'lp-cb-learnpress' );
376
377 $course_id = CourseBuilder::get_item_id();
378
379 if ( $course_id === CourseBuilder::POST_NEW ) {
380 return Template::print_message( __( 'Please save Course before setting course', 'learnpress' ), 'info', false );
381 }
382
383 $courseModel = $data['courseModel'] ?? null;
384 if ( ! $courseModel instanceof CourseModel ) {
385 return Template::print_message( __( 'Course is invalid!', 'learnpress' ), 'error', false );
386 }
387
388 if ( ! class_exists( 'LP_Meta_Box_Course' ) ) {
389 require_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/course/settings.php';
390 }
391
392 add_filter( 'learnpress/course/metabox/tabs', [ $this, 'filter_course_builder_settings_tabs' ], 999 );
393 add_filter(
394 'learn-press/course/meta-box/assessment/final-quiz/edit-link',
395 [
396 $this,
397 'filter_course_builder_assessment_final_quiz_edit_link',
398 ],
399 10,
400 2
401 );
402
403 $metabox = \LP_Meta_Box_Course::instance();
404 ob_start();
405 $metabox->output( $courseModel );
406 $settings = ob_get_clean();
407
408 remove_filter( 'learnpress/course/metabox/tabs', [ $this, 'filter_course_builder_settings_tabs' ], 999 );
409 remove_filter(
410 'learn-press/course/meta-box/assessment/final-quiz/edit-link',
411 [
412 $this,
413 'filter_course_builder_assessment_final_quiz_edit_link',
414 ],
415 10
416 );
417
418 $output = [
419 'wrapper' => sprintf( '<div class="cb-section__course-edit" data-course-id="%s">', $course_id ),
420 'form_setting' => '<form name="lp-form-setting-course" class="lp-form-setting-course" method="post" enctype="multipart/form-data">',
421 'settings' => $settings,
422 'form_setting_end' => '</form>',
423 'wrapper_end' => '</div>',
424 ];
425
426 return Template::combine_components( $output );
427 }
428
429 /**
430 * Allow callback for AJAX.
431 * @use self::render_edit_course_curriculum
432 * @use self::render_html
433 *
434 * @param array $callbacks
435 *
436 * @return array
437 */
438 public function allow_callback( array $callbacks ): array {
439 $callbacks[] = AdminEditCurriculumTemplate::class . ':render_edit_course_curriculum';
440
441 return $callbacks;
442 }
443
444 public function edit_title( $course_model ) {
445 $title = '';
446 if ( ! empty( $course_model ) ) {
447 $post_id = absint( $course_model->get_id() );
448 $title = $post_id ? (string) get_post_field( 'post_title', $post_id, 'raw' ) : '';
449 }
450 $char_count = mb_strlen( wp_strip_all_tags( $title ) );
451 $ai_button = $this->html_overview_ai_button( '#lp-tmpl-edit-title-ai' );
452 $edit = [
453 'wrapper' => '<div class="cb-course-edit-title">',
454 'label_wrap' => '<div class="cb-course-edit-title__label-wrap">',
455 'label' => sprintf( '<label for="title" class="cb-course-edit-title__label">%s <span class="required">*</span></label>', __( 'Course Title', 'learnpress' ) ),
456 'char_count' => sprintf( '<span class="cb-course-edit-title__char-count">%s</span>', sprintf( __( '%d characters', 'learnpress' ), $char_count ) ),
457 'ai_button' => $ai_button,
458 'label_wrap_end' => '</div>',
459 'input' => sprintf( '<input type="text" name="course_title" size="30" value="%s" id="title" class="cb-course-edit-title__input" placeholder="%s">', esc_attr( $title ), esc_attr__( 'example', 'learnpress' ) ),
460 'wrapper_end' => '</div>',
461 ];
462
463 return Template::combine_components( $edit );
464 }
465
466 public function edit_permalink( $course_model ) {
467 $post_id = ! empty( $course_model ) ? $course_model->get_id() : '';
468 $post_name = '';
469
470 // Hide permalink for new courses
471 if ( empty( $post_id ) || $post_id === CourseBuilder::POST_NEW ) {
472 return '';
473 }
474
475 if ( $post_id ) {
476 $post = get_post( $post_id );
477 $post_name = $post ? $post->post_name : '';
478 }
479
480 // Get base URL for courses
481 $courses_page_id = learn_press_get_page_id( 'courses' );
482 $base_url = '';
483 if ( $courses_page_id ) {
484 $base_url = trailingslashit( get_permalink( $courses_page_id ) );
485 } else {
486 $base_url = trailingslashit( home_url() ) . 'courses/';
487 }
488
489 $full_url = urldecode( (string) get_permalink( $post_id ) );
490 $display_data = $this->get_course_display_permalink_data( (int) $post_id, (string) $post_name );
491 $display_url = (string) ( $display_data['url'] ?? '' );
492 $editor_slug = (string) ( $display_data['slug'] ?? '' );
493
494 if ( empty( $display_url ) ) {
495 $display_url = $full_url;
496 }
497
498 if ( empty( $full_url ) ) {
499 $full_url = $display_url;
500 }
501
502 if ( empty( $editor_slug ) ) {
503 $editor_slug = (string) $post_name;
504 }
505
506 if ( empty( $editor_slug ) && ! empty( $display_url ) ) {
507 $display_path = parse_url( $display_url, PHP_URL_PATH );
508 if ( is_string( $display_path ) && '' !== $display_path ) {
509 $editor_slug = basename( untrailingslashit( $display_path ) );
510 }
511 }
512
513 // Use publish-style permalink as editable base, but keep href as current permalink.
514 if (
515 ! empty( $editor_slug ) &&
516 false === strpos( $display_url, '?p=' ) &&
517 false === strpos( $display_url, '&p=' ) &&
518 false === strpos( $display_url, '?lp_course=' ) &&
519 false === strpos( $display_url, '&lp_course=' )
520 ) {
521 $base_url = trailingslashit( preg_replace( '/' . preg_quote( $editor_slug, '/' ) . '\/?$/', '', $display_url ) );
522 }
523
524 $state_a = sprintf(
525 '<span class="cb-permalink-label">%s</span>
526 <div class="cb-permalink-display">
527 <a href="%s" target="_blank" class="cb-permalink-url">%s</a>
528 <button type="button" class="cb-permalink-edit-btn" title="%s">
529 <span class="dashicons dashicons-edit"></span>
530 </button>
531 </div>',
532 __( 'Permalink', 'learnpress' ),
533 esc_url( $full_url ),
534 esc_html( $display_url ),
535 __( 'Edit', 'learnpress' )
536 );
537
538 $state_b = sprintf(
539 '<div class="cb-permalink-editor lp-hidden">
540 <span class="cb-permalink-prefix">%s</span>
541 <div class="cb-permalink-input-row">
542 <input type="text" name="course_permalink" id="course_permalink" value="%s" class="cb-permalink-slug-input" placeholder="%s">
543 <div class="cb-permalink-actions">
544 <button type="button" class="cb-permalink-ok-btn">%s</button>
545 <button type="button" class="cb-permalink-cancel-btn">%s</button>
546 </div>
547 </div>
548 </div>',
549 esc_html( $base_url ),
550 esc_attr( $editor_slug ),
551 esc_attr__( 'your-slug', 'learnpress' ),
552 __( 'OK', 'learnpress' ),
553 __( 'Cancel', 'learnpress' )
554 );
555
556 $hidden_base = sprintf(
557 '<input type="hidden" id="cb-permalink-base-url" value="%s">',
558 esc_attr( $base_url )
559 );
560
561 $edit = [
562 'wrapper' => '<div class="cb-course-edit-permalink">',
563 'state_a' => $state_a,
564 'state_b' => $state_b,
565 'hidden_base' => $hidden_base,
566 'wrapper_end' => '</div>',
567 ];
568
569 return Template::combine_components( $edit );
570 }
571
572 /**
573 * Get permalink display URL in publish-style format for editing slug.
574 *
575 * @param int $post_id
576 * @param string $post_name
577 *
578 * @return array<string, string>
579 */
580 private function get_course_display_permalink_data( int $post_id, string $post_name = '' ): array {
581 $display_url = urldecode( (string) get_permalink( $post_id ) );
582 $sample_slug = $post_name;
583
584 if ( ! function_exists( 'get_sample_permalink' ) ) {
585 require_once ABSPATH . 'wp-admin/includes/post.php';
586 }
587
588 if ( function_exists( 'get_sample_permalink' ) ) {
589 $sample_permalink = get_sample_permalink( $post_id );
590 if ( is_array( $sample_permalink ) && ! empty( $sample_permalink[0] ) ) {
591 $sample_slug = ! empty( $sample_permalink[1] ) ? (string) $sample_permalink[1] : $sample_slug;
592 $display_url = str_replace(
593 [ '%postname%', '%pagename%' ],
594 $sample_slug,
595 (string) $sample_permalink[0]
596 );
597 }
598 }
599
600 $post = get_post( $post_id );
601 if ( $post instanceof \WP_Post ) {
602 $display_url = \LP_Helper::handle_lp_permalink_structure( $display_url, $post );
603 }
604
605 return [
606 'url' => urldecode( $display_url ),
607 'slug' => urldecode( (string) $sample_slug ),
608 ];
609 }
610
611 public function edit_desc( $course_model ) {
612 $desc = ! empty( $course_model ) ? $course_model->get_description() : '';
613 $word_count = str_word_count( wp_strip_all_tags( $desc ) );
614 $editor_id = 'course_description_editor';
615 $ai_button = $this->html_overview_ai_button( '#lp-tmpl-edit-description-ai' );
616 $editor_settings = array(
617 'textarea_name' => 'course_description',
618 'textarea_rows' => 10,
619 'teeny' => false,
620 'media_buttons' => true,
621 'tinymce' => array(
622 'content_style' => "body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; font-size: 14px; line-height: 1.6; color: #1e1e1e; }",
623 'toolbar1' => 'formatselect,bold,italic,underline,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link,unlink,spellchecker,wp_adv',
624 'toolbar2' => 'strikethrough,hr,forecolor,pastetext,removeformat,charmap,outdent,indent,undo,redo,wp_help',
625 ),
626 'quicktags' => true,
627 );
628
629 $edit = [
630 'wrapper' => '<div class="cb-course-edit-desc">',
631 'label_wrap' => '<div class="cb-course-edit-desc__label-wrap">',
632 'label' => sprintf( '<label for="course_description" class="cb-course-edit-desc__label">%s</label>', __( 'Description', 'learnpress' ) ),
633 'ai_button' => $ai_button,
634 'label_wrap_end' => '</div>',
635 'edit' => AdminTemplate::editor_tinymce(
636 $desc,
637 $editor_id,
638 $editor_settings
639 ),
640 'wrapper_end' => '</div>',
641 ];
642
643 return Template::combine_components( $edit );
644 }
645
646 /**
647 * Check if Overview AI button and templates should be shown.
648 *
649 * @return bool
650 */
651 protected function can_show_overview_ai_button(): bool {
652 return \LP_Settings::get_option( 'enable_open_ai', 'no' ) === 'yes'
653 && ! empty( \LP_Settings::get_option( 'open_ai_secret_key', '' ) );
654 }
655
656 /**
657 * Render icon-only AI button in Course Builder overview.
658 *
659 * @param string $template_id
660 *
661 * @return string
662 */
663 protected function html_overview_ai_button( string $template_id ): string {
664 if ( ! $this->can_show_overview_ai_button() ) {
665 return '';
666 }
667
668 $button_label = esc_html__( 'Generate with AI', 'learnpress' );
669
670 return sprintf(
671 '<button type="button" class="cb-course-edit-ai-btn lp-btn-generate-with-ai" data-template="%1$s" title="%2$s" aria-label="%2$s"><i class="lp-ico-ai" aria-hidden="true"></i></button>',
672 esc_attr( $template_id ),
673 esc_attr( $button_label )
674 );
675 }
676
677 /**
678 * Render AI popup templates for Course Builder overview edit page.
679 *
680 * @return string
681 */
682 protected function html_overview_ai_templates(): string {
683 if ( ! $this->can_show_overview_ai_button() ) {
684 return '';
685 }
686
687 try {
688 return AdminEditWithAITemplate::instance()->render_for_frontend( [ 'title', 'description', 'image' ] );
689 } catch ( Throwable $e ) {
690 error_log( __METHOD__ . ': ' . $e->getMessage() );
691 }
692
693 return '';
694 }
695
696 /**
697 * Render AI popup template for curriculum edit in Course Builder.
698 *
699 * @return string
700 */
701 protected function html_curriculum_ai_templates(): string {
702 if ( ! $this->can_show_overview_ai_button() ) {
703 return '';
704 }
705
706 try {
707 return AdminEditCourseCurriculumWithAITemplate::instance()->render_for_frontend();
708 } catch ( Throwable $e ) {
709 error_log( __METHOD__ . ': ' . $e->getMessage() );
710 }
711
712 return '';
713 }
714
715 public function edit_categories( $course_model ) {
716 if ( ! function_exists( 'post_categories_meta_box' ) ) {
717 require_once ABSPATH . 'wp-admin/includes/meta-boxes.php';
718 }
719 if ( ! function_exists( 'wp_popular_terms_checklist' ) ) {
720 require_once ABSPATH . 'wp-admin/includes/template.php';
721 }
722
723 $post_id = ! empty( $course_model ) ? $course_model->get_id() : get_the_ID();
724 $post = get_post( $post_id );
725
726 $force_checked_ontop_false = function ( $args ) {
727 if ( isset( $args['taxonomy'] ) && 'course_category' === $args['taxonomy'] ) {
728 $args['checked_ontop'] = false;
729 }
730
731 return $args;
732 };
733
734 ob_start();
735
736 add_filter( 'wp_terms_checklist_args', $force_checked_ontop_false );
737
738 if ( function_exists( 'post_categories_meta_box' ) ) {
739 \post_categories_meta_box(
740 $post,
741 array(
742 'id' => 'course_categorydiv',
743 'title' => __( 'Categories', 'learnpress' ),
744 'callback' => 'post_categories_meta_box',
745 'args' => array(
746 'taxonomy' => 'course_category',
747 'checked_ontop' => false,
748 ),
749 )
750 );
751 }
752
753 remove_filter( 'wp_terms_checklist_args', $force_checked_ontop_false );
754 $html_meta_box = ob_get_clean();
755
756 // Build add new category form (between header and content)
757 $parent_terms = get_terms(
758 [
759 'taxonomy' => 'course_category',
760 'hide_empty' => false,
761 ]
762 );
763 $parent_options = sprintf( '<option value="0">— %s —</option>', __( 'Parent Category', 'learnpress' ) );
764 if ( ! empty( $parent_terms ) && ! is_wp_error( $parent_terms ) ) {
765 foreach ( $parent_terms as $term ) {
766 $parent_options .= sprintf(
767 '<option value="%d">%s</option>',
768 $term->term_id,
769 esc_html( $term->name )
770 );
771 }
772 }
773
774 $form_add_category = sprintf(
775 '<div class="cb-course-edit-terms__form-add-category" style="display:none;">
776 <input type="text" class="cb-course-edit-category__input" placeholder="%s" id="cb-newcourse_category" />
777 <select class="cb-course-edit-category__select-parent" id="cb-newcourse_category_parent">%s</select>
778 <button type="button" class="cb-course-edit-category__btn-cancel">%s</button>
779 <button type="button" class="cb-course-edit-category__btn-save" id="cb-course_category-add-submit">%s</button>
780 </div>',
781 esc_attr__( 'Enter Category Name', 'learnpress' ),
782 $parent_options,
783 esc_html__( 'Cancel', 'learnpress' ),
784 esc_html__( 'Add', 'learnpress' ),
785 );
786
787 $edit = [
788 'wrapper' => '<div class="cb-course-edit-categories__wrapper">',
789 'header' => '<div class="cb-terms-header">',
790 'label_wrap' => '<div class="cb-terms-header__label-wrap">',
791 'label' => sprintf( '<label class="cb-terms-header__label">%s</label>', __( 'Categories', 'learnpress' ) ),
792 'btn_search' => sprintf(
793 '<button type="button" class="cb-terms-header__btn-search" data-toggle-target="#cb-course-edit-categories-search-toolbar" aria-expanded="false" aria-label="%s">
794 <i class="lp-icon-search"></i>
795 </button>',
796 esc_attr__( 'Search categories', 'learnpress' )
797 ),
798 'label_wrap_end' => '</div>',
799 'btn_add_new' => sprintf( '<button class="cb-course-edit-category__btn-add-new cb-terms-header__btn-add-new">%s</button>', __( 'Add New', 'learnpress' ) ),
800 'header_end' => '</div>',
801 'form_add_category' => $form_add_category,
802 'search' => sprintf(
803 '<div class="cb-course-edit-categories__toolbar cb-terms-search-toolbar" id="cb-course-edit-categories-search-toolbar">
804 <label class="cb-course-edit-categories__search-wrap">
805 <span class="screen-reader-text">%1$s</span>
806 <input type="search" class="cb-course-edit-category__search-input" placeholder="%2$s" />
807 </label>
808 </div>',
809 esc_html__( 'Search categories', 'learnpress' ),
810 esc_attr__( 'Search categories', 'learnpress' )
811 ),
812 'content' => $html_meta_box,
813 'wrapper_end' => '</div>',
814 ];
815
816 return Template::combine_components( $edit );
817 }
818
819 public function edit_tags( $course_model ) {
820 $course_terms = ! empty( $course_model ) ? $course_model->get_tags() : [];
821 $tags = get_terms(
822 [
823 'taxonomy' => LP_COURSE_TAXONOMY_TAG,
824 'hide_empty' => false,
825 ]
826 );
827
828 $selected_tag_ids = array_map(
829 function ( $term ) {
830 return (int) $term->term_id;
831 },
832 $course_terms
833 );
834
835 $html_selected_chips = '';
836 $html_available_chips = '';
837
838 if ( ! empty( $tags ) && ! is_wp_error( $tags ) ) {
839 foreach ( $tags as $tag ) {
840 $tag_id = $tag->term_id;
841 $tag_name = $tag->name;
842 $tag_count = $tag->count;
843 $is_checked = in_array( (int) $tag_id, $selected_tag_ids, true );
844 $html_chip = $this->input_checkbox_tag_item( $tag_id, $tag_name, $is_checked, $tag_count );
845
846 if ( $is_checked ) {
847 $html_selected_chips .= $html_chip;
848 } else {
849 $html_available_chips .= $html_chip;
850 }
851 }
852 }
853
854 $html_chips = $html_selected_chips . $html_available_chips;
855 $count_all = substr_count( $html_chips, 'class="cb-tag-chip"' );
856 $empty_default = esc_html__( 'No tags found.', 'learnpress' );
857 $empty_search = esc_html__( 'No matching tags.', 'learnpress' );
858
859 $toolbar = sprintf(
860 '<div class="cb-course-edit-tags__toolbar cb-terms-search-toolbar" id="cb-course-edit-tags-search-toolbar">
861 <label class="cb-course-edit-tags__search-wrap">
862 <span class="screen-reader-text">%1$s</span>
863 <input type="search" class="cb-course-edit-tags__search-input" placeholder="%2$s" />
864 </label>
865 </div>',
866 esc_html__( 'Search tags', 'learnpress' ),
867 esc_attr__( 'Search tags', 'learnpress' )
868 );
869
870 $edit = [
871 'wrapper' => '<div class="cb-course-edit-tags__wrapper">',
872 'header' => '<div class="cb-terms-header">',
873 'label_wrap' => '<div class="cb-terms-header__label-wrap">',
874 'label' => sprintf( '<label class="cb-terms-header__label">%s</label>', __( 'Tags', 'learnpress' ) ),
875 'btn_search' => sprintf(
876 '<button type="button" class="cb-terms-header__btn-search" data-toggle-target="#cb-course-edit-tags-search-toolbar" aria-expanded="false" aria-label="%s">
877 <i class="lp-icon-search"></i>
878 </button>',
879 esc_attr__( 'Search tags', 'learnpress' )
880 ),
881 'label_wrap_end' => '</div>',
882 'btn_add_new' => sprintf( '<button class="cb-course-edit-tag__btn-add-new cb-terms-header__btn-add-new">%s</button>', __( 'Add New', 'learnpress' ) ),
883 'header_end' => '</div>',
884 'form_add_tag_wrapper' => '<div class="cb-course-edit-terms__form-add-tag" style="display:none;">',
885 'input' => '<input type="text" class="cb-course-edit-tags__input" placeholder="' . esc_attr__( 'Enter Tag Name', 'learnpress' ) . '"/>',
886 'btn_cancel' => sprintf( '<button type="button" class="cb-course-edit-tag__btn-cancel">%s</button>', __( 'Cancel', 'learnpress' ) ),
887 'button' => '<button type="button" class="cb-course-edit-tags__btn-save">' . esc_html__( 'Add', 'learnpress' ) . '</button>',
888 'form_add_tag_wrapper_end' => '</div>',
889 'toolbar' => $toolbar,
890 'wrapper_checkbox' => '<div class="cb-course-edit-tags__checkbox-wrapper">',
891 'checkbox' => $html_chips,
892 'wrapper_checkbox_end' => '</div>',
893 'empty' => sprintf(
894 '<p class="cb-course-edit-tags__empty%1$s" data-empty-default="%2$s" data-empty-search="%3$s">%2$s</p>',
895 $count_all > 0 ? ' lp-hidden' : '',
896 esc_attr( $empty_default ),
897 esc_attr( $empty_search )
898 ),
899 'wrapper_end' => '</div>',
900 ];
901
902 return Template::combine_components( $edit );
903 }
904
905 public function input_checkbox_tag_item( $term_id, $term_name, $is_checked, $count = 0 ) {
906 if ( 0 === $count ) {
907 $tag_obj = get_term( $term_id, LP_COURSE_TAXONOMY_TAG );
908 if ( $tag_obj && ! is_wp_error( $tag_obj ) ) {
909 $count = $tag_obj->count;
910 }
911 }
912
913 $tag_name_search = wp_strip_all_tags( $term_name );
914 if ( function_exists( 'mb_strtolower' ) ) {
915 $tag_name_search = mb_strtolower( $tag_name_search );
916 } else {
917 $tag_name_search = strtolower( $tag_name_search );
918 }
919
920 $html = sprintf(
921 '<div class="cb-tag-chip" data-tag-name="%s" data-term-id="%d">',
922 esc_attr( $tag_name_search ),
923 (int) $term_id
924 );
925 $html .= sprintf(
926 '<input type="checkbox" name="course_tags[]" value="%s" id="course_tag_%s" %s>',
927 $term_id,
928 $term_id,
929 checked( $is_checked, true, false )
930 );
931 $html .= sprintf(
932 '<label for="course_tag_%s"><span class="cb-tag-chip__name">%s</span><span class="cb-tag-chip__count">(%d)</span><span class="cb-tag-chip__remove">&times;</span></label>',
933 $term_id,
934 esc_html( $term_name ),
935 $count
936 );
937 $html .= '</div>';
938
939 return $html;
940 }
941
942 public function edit_featured_image( $course_model ) {
943 $post_id = ! empty( $course_model ) ? $course_model->get_id() : '';
944 $ai_button = $this->html_overview_ai_button( '#lp-tmpl-edit-image-ai' );
945
946 $thumbnail_id = ! empty( $post_id ) ? get_post_thumbnail_id( $post_id ) : '';
947 $thumbnail_url = '';
948 $thumbnail_alt = '';
949
950 if ( $thumbnail_id ) {
951 $thumbnail_url = wp_get_attachment_image_url( $thumbnail_id, 'medium' );
952 $thumbnail_alt = get_post_meta( $thumbnail_id, '_wp_attachment_image_alt', true );
953 }
954
955 $has_image = ! empty( $thumbnail_url );
956
957 $featured_image_html = '<div class="cb-featured-image-container">';
958
959 // Upload area
960 $featured_image_html .= sprintf(
961 '<div class="cb-featured-image-dropzone %s" data-post-id="%s">',
962 $has_image ? 'has-image' : '',
963 esc_attr( $post_id )
964 );
965
966 if ( $has_image ) {
967 $featured_image_html .= sprintf(
968 '<img src="%s" alt="%s" class="cb-featured-image-preview__img">',
969 esc_url( $thumbnail_url ),
970 esc_attr( $thumbnail_alt )
971 );
972 } else {
973 $featured_image_html .= '<div class="cb-featured-image-upload-content">';
974 $featured_image_html .= '<span class="cb-featured-image-icon"><span class="cb-featured-image-icon__image" aria-hidden="true"></span></span>';
975 $featured_image_html .= sprintf(
976 '<p class="cb-featured-image-text"><a href="#" class="cb-featured-image-link">%s</a></p>',
977 __( 'Click to upload', 'learnpress' )
978 );
979 $featured_image_html .= sprintf(
980 '<p class="cb-featured-image-hint">%s</p>',
981 __( 'JPG, JPEG, PNG less than 1MB', 'learnpress' )
982 );
983 $featured_image_html .= '</div>';
984 }
985
986 $featured_image_html .= '</div>';
987
988 $featured_image_html .= sprintf(
989 '<input type="hidden" name="course_thumbnail_id" id="course_thumbnail_id" value="%s">',
990 esc_attr( $thumbnail_id )
991 );
992
993 // Action buttons wrapper
994 $featured_image_html .= '<div class="cb-featured-image-actions">';
995
996 // Remove button (only show when has image)
997 if ( $has_image ) {
998 $featured_image_html .= sprintf(
999 '<button type="button" class="cb-remove-featured-image">%s</button>',
1000 '<span class="cb-remove-featured-image__icon" aria-hidden="true"></span>'
1001 );
1002
1003 $featured_image_html .= sprintf(
1004 '<button type="button" class="cb-change-featured-image">%s</button>',
1005 __( 'Replace', 'learnpress' )
1006 );
1007 }
1008
1009 $featured_image_html .= '</div>'; // End actions wrapper
1010 $featured_image_html .= '</div>'; // End container
1011
1012 $edit = [
1013 'wrapper' => '<div class="cb-course-edit-featured-image">',
1014 'label_wrap' => '<div class="cb-course-edit-featured-image__label-wrap">',
1015 'label' => sprintf(
1016 '<label class="cb-course-edit-featured-image__title">%s</label>',
1017 __( 'Featured Image', 'learnpress' )
1018 ),
1019 'ai_button' => $ai_button,
1020 'label_wrap_end' => '</div>',
1021 'edit' => $featured_image_html,
1022 'wrapper_end' => '</div>',
1023 ];
1024
1025 return Template::combine_components( $edit );
1026 }
1027
1028 /**
1029 * Render publish panel (status, visibility, publish date, danger zone) for Course Builder overview.
1030 *
1031 * @param CourseModel|string $course_model
1032 *
1033 * @return string
1034 */
1035 public function edit_publish( $course_model ): string {
1036 $post_id = ! empty( $course_model ) ? absint( $course_model->get_id() ) : 0;
1037 $post = $post_id ? get_post( $post_id ) : null;
1038
1039 $current_status = 'draft';
1040 if ( $post && ! empty( $post->post_status ) ) {
1041 $current_status = sanitize_key( $post->post_status );
1042 }
1043
1044 $status_for_select = in_array( $current_status, [ 'publish', 'draft', 'pending', 'future' ], true )
1045 ? $current_status
1046 : 'publish';
1047 $current_password = $post ? (string) ( $post->post_password ?? '' ) : '';
1048 $visibility = 'private' === $current_status
1049 ? 'private'
1050 : ( ! empty( $current_password ) ? 'password' : 'public' );
1051 $published_on = '';
1052
1053 if ( $post && ! empty( $post->post_date ) && '0000-00-00 00:00:00' !== $post->post_date ) {
1054 $published_on = wp_date( 'Y-m-d\TH:i', strtotime( $post->post_date ), wp_timezone() );
1055 }
1056
1057 $has_future_publish_date = false;
1058 if ( $post && ! empty( $post->post_date ) && '0000-00-00 00:00:00' !== $post->post_date ) {
1059 $has_future_publish_date = strtotime( $post->post_date ) > current_time( 'timestamp' );
1060 }
1061
1062 $is_scheduled_status = 'future' === $status_for_select
1063 || ( in_array( $status_for_select, [ 'draft', 'pending' ], true ) && $has_future_publish_date );
1064 $primary_status_value = $is_scheduled_status ? 'future' : 'publish';
1065 $primary_status_label = $is_scheduled_status
1066 ? __( 'Scheduled', 'learnpress' )
1067 : __( 'Published', 'learnpress' );
1068 $selected_status_for_ui = in_array( $status_for_select, [ 'draft', 'pending' ], true )
1069 ? $status_for_select
1070 : $primary_status_value;
1071 $status_options = [
1072 $primary_status_value => $primary_status_label,
1073 'draft' => __( 'Draft', 'learnpress' ),
1074 'pending' => __( 'Pending Review', 'learnpress' ),
1075 ];
1076
1077 $publish_date_label = $has_future_publish_date
1078 ? __( 'Scheduled for', 'learnpress' )
1079 : __( 'Published on', 'learnpress' );
1080
1081 $status_options_html = '';
1082 foreach ( $status_options as $value => $label ) {
1083 $status_options_html .= sprintf(
1084 '<option value="%1$s" %2$s>%3$s</option>',
1085 esc_attr( $value ),
1086 selected( $selected_status_for_ui, $value, false ),
1087 esc_html( $label )
1088 );
1089 }
1090
1091 $visibility_options_html = sprintf(
1092 '<option value="public" %1$s>%2$s</option><option value="private" %3$s>%4$s</option><option value="password" %5$s>%6$s</option>',
1093 selected( $visibility, 'public', false ),
1094 esc_html__( 'Public', 'learnpress' ),
1095 selected( $visibility, 'private', false ),
1096 esc_html__( 'Private', 'learnpress' ),
1097 selected( $visibility, 'password', false ),
1098 esc_html__( 'Password protected', 'learnpress' )
1099 );
1100
1101 $edit = [
1102 'wrapper' => '<div class="cb-course-edit-publish">',
1103 'title' => sprintf( '<h3 class="cb-course-edit-publish__title">%s</h3>', esc_html__( 'Publish', 'learnpress' ) ),
1104 'status_row' => sprintf(
1105 '<div class="cb-course-edit-publish__row">
1106 <label for="cb-course-publish-status" class="cb-course-edit-publish__label">%1$s</label>
1107 <select id="cb-course-publish-status" name="cb_course_publish_status" class="cb-course-edit-publish__control" data-publish-label="%3$s" data-future-label="%4$s" data-primary-status="%5$s">%2$s</select>
1108 </div>',
1109 esc_html__( 'Status', 'learnpress' ),
1110 $status_options_html,
1111 esc_attr__( 'Published', 'learnpress' ),
1112 esc_attr__( 'Scheduled', 'learnpress' ),
1113 esc_attr( $primary_status_value )
1114 ),
1115 'visibility_row' => sprintf(
1116 '<div class="cb-course-edit-publish__row">
1117 <label for="cb-course-publish-visibility" class="cb-course-edit-publish__label">%1$s</label>
1118 <select id="cb-course-publish-visibility" name="cb_course_publish_visibility" class="cb-course-edit-publish__control">%2$s</select>
1119 </div>',
1120 esc_html__( 'Visibility', 'learnpress' ),
1121 $visibility_options_html
1122 ),
1123 'password_row' => sprintf(
1124 '<div class="cb-course-edit-publish__row cb-course-edit-publish__password-row %1$s">
1125 <label for="cb-course-publish-password" class="cb-course-edit-publish__label">%2$s</label>
1126 <input type="text" id="cb-course-publish-password" name="cb_course_publish_password" class="cb-course-edit-publish__control" value="%3$s" autocomplete="new-password">
1127 </div>',
1128 'password' === $visibility ? '' : 'lp-hidden',
1129 esc_html__( 'Password', 'learnpress' ),
1130 esc_attr( $current_password )
1131 ),
1132 'published_on_row' => sprintf(
1133 '<div class="cb-course-edit-publish__row">
1134 <label for="cb-course-publish-date" id="cb-course-publish-date-label" class="cb-course-edit-publish__label">%1$s</label>
1135 <input type="datetime-local" id="cb-course-publish-date" name="cb_course_publish_date" class="cb-course-edit-publish__control" value="%2$s">
1136 </div>',
1137 esc_html( $publish_date_label ),
1138 esc_attr( $published_on )
1139 ),
1140 'wrapper_end' => '</div>',
1141 ];
1142
1143 return Template::combine_components( $edit );
1144 }
1145
1146 protected function html_curriculum( CourseModel $course_model ): string {
1147 ob_start();
1148 AdminEditCurriculumTemplate::instance()->edit_course_curriculum_layout(
1149 $course_model,
1150 [ 'is_course_builder' => true ]
1151 );
1152 $html_curriculum = ob_get_clean();
1153
1154 return $html_curriculum
1155 . $this->html_curriculum_popup_templates( $course_model )
1156 . $this->html_curriculum_ai_templates();
1157 }
1158
1159 protected function html_curriculum_popup_templates( CourseModel $course_model ): string {
1160 $popup_templates = apply_filters(
1161 'learn-press/course-builder/courses/curriculum/popup-templates',
1162 [
1163 'lesson' => sprintf(
1164 '<script type="text/template" id="%1$s"><div class="lp-builder-popup-overlay"></div><div class="lp-builder-popup lp-builder-popup--loading">%2$s</div></script>',
1165 esc_attr(
1166 sprintf(
1167 'lp-tmpl-builder-popup-curriculum-lesson-course-%d',
1168 $course_model->get_id()
1169 )
1170 ),
1171 TemplateAJAX::load_content_via_ajax(
1172 [
1173 'id_url' => 'builder-popup-lesson-curriculum',
1174 'lesson_id' => 0,
1175 'html_no_load_ajax_first' => sprintf(
1176 '<div class="lp-builder-popup__loader"><div class="lp-loading-circle"></div><span>%s</span></div>',
1177 esc_html__( 'Loading...', 'learnpress' )
1178 ),
1179 ],
1180 [
1181 'class' => BuilderPopupTemplate::class,
1182 'method' => 'render_lesson_popup',
1183 ]
1184 )
1185 ),
1186 'quiz' => sprintf(
1187 '<script type="text/template" id="%1$s"><div class="lp-builder-popup-overlay"></div><div class="lp-builder-popup lp-builder-popup--loading">%2$s</div></script>',
1188 esc_attr(
1189 sprintf(
1190 'lp-tmpl-builder-popup-curriculum-quiz-course-%d',
1191 $course_model->get_id()
1192 )
1193 ),
1194 TemplateAJAX::load_content_via_ajax(
1195 [
1196 'id_url' => 'builder-popup-quiz-curriculum',
1197 'quiz_id' => 0,
1198 'html_no_load_ajax_first' => sprintf(
1199 '<div class="lp-builder-popup__loader"><div class="lp-loading-circle"></div><span>%s</span></div>',
1200 esc_html__( 'Loading...', 'learnpress' )
1201 ),
1202 ],
1203 [
1204 'class' => BuilderPopupTemplate::class,
1205 'method' => 'render_quiz_popup',
1206 ]
1207 )
1208 ),
1209 ],
1210 $course_model
1211 );
1212
1213 return Template::combine_components( $popup_templates );
1214 }
1215
1216
1217 /**
1218 * Add edit popup button for lesson and quiz items in Course Builder curriculum.
1219 * Replace the default edit button with popup button for lesson and quiz items.
1220 *
1221 * @param array $section_action Array of action buttons.
1222 * @param object|null $item Item data.
1223 * @param PostModel|null $itemModel Item model.
1224 * @param CourseModel $courseModel .
1225 * @param array $context_data Context data passed from AJAX.
1226 *
1227 * @return array
1228 * @since 4.3.0
1229 * @version 1.0.2
1230 *
1231 */
1232 public function add_edit_popup_button( array $section_action, $item, $itemModel, $courseModel, $context_data = [] ): array {
1233 // Check if we are in Course Builder context via the flag passed in AJAX args
1234 $is_course_builder = ! empty( $context_data['is_course_builder'] );
1235
1236 if ( ! $is_course_builder ) {
1237 return $section_action;
1238 }
1239
1240 $item_id = $item->item_id ?? 0;
1241 $item_type = $item->item_type ?? '';
1242
1243 $popup_data_attr = '';
1244 $popup_type = '';
1245 if ( $item_type === LP_LESSON_CPT ) {
1246 $popup_data_attr = sprintf( 'data-popup-lesson="%s"', $item_id );
1247 $popup_type = 'lesson';
1248 } elseif ( $item_type === LP_QUIZ_CPT ) {
1249 $popup_data_attr = sprintf( 'data-popup-quiz="%s"', $item_id );
1250 $popup_type = 'quiz';
1251 }
1252
1253 $popup_type = sanitize_key(
1254 (string) apply_filters(
1255 'learn-press/course-builder/courses/curriculum/popup-item-type',
1256 $popup_type,
1257 $item_type,
1258 $item,
1259 $itemModel,
1260 $courseModel,
1261 $context_data
1262 )
1263 );
1264 if ( empty( $popup_type ) ) {
1265 return $section_action;
1266 }
1267
1268 $popup_template_id = sprintf(
1269 'lp-tmpl-builder-popup-curriculum-%1$s-course-%2$d',
1270 $popup_type,
1271 $courseModel->get_id()
1272 );
1273
1274 // Replace edit button with popup button - use lp-icon-edit-square instead of lp-icon-expand
1275 $section_action['edit'] = sprintf(
1276 '<li title="%s" class="lp-btn-edit-item-popup"
1277 data-course-id="%s"
1278 data-template="#%s"
1279 data-popup-type="%s"
1280 data-popup-id="%d"
1281 %s>
1282 <a class="lp-icon-edit-square edit-link edit-popup-link"></a>
1283 </li>',
1284 __( 'Edit in popup', 'learnpress' ),
1285 $courseModel->get_id(),
1286 esc_attr( $popup_template_id ),
1287 esc_attr( $popup_type ),
1288 $item_id,
1289 $popup_data_attr
1290 );
1291
1292 return $section_action;
1293 }
1294
1295 /**
1296 * Keep supported course settings tabs in Course Builder.
1297 *
1298 * @param array $tabs
1299 *
1300 * @return array
1301 */
1302 public function filter_course_builder_settings_tabs( array $tabs ): array {
1303 $allowed_tabs = [ 'general', 'offline', 'price', 'extra', 'assessment', 'author', 'material' ];
1304
1305 foreach ( array_keys( $tabs ) as $tab_key ) {
1306 if ( ! in_array( $tab_key, $allowed_tabs, true ) ) {
1307 unset( $tabs[ $tab_key ] );
1308 }
1309 }
1310
1311 return apply_filters( 'learn-press/course-builder/edit-course/settings/tabs', $tabs );
1312 }
1313
1314 /**
1315 * Convert final quiz edit link to Course Builder quiz settings URL.
1316 *
1317 * @param string $url
1318 * @param int $final_quiz_id
1319 *
1320 * @return string
1321 */
1322 public function filter_course_builder_assessment_final_quiz_edit_link( string $url, int $final_quiz_id ): string {
1323 $final_quiz_id = absint( $final_quiz_id );
1324 if ( ! $final_quiz_id ) {
1325 return $url;
1326 }
1327
1328 return CourseBuilder::get_tab_link( 'quizzes', $final_quiz_id, 'settings' ) . '#_lp_passing_grade';
1329 }
1330 }
1331