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/CourseBuilder/CourseBuilderTemplate.php +771 -771 4.3.84.4.8 View file →
@@ -1,771 +1,771 @@
1 -<?php
2 -/**
3 - * Template hooks Course Builder.
4 - *
5 - * @since 4.3.x
6 - * @version 1.0.0
7 - */
8 -
9 -namespace LearnPress\TemplateHooks\CourseBuilder;
10 -
11 -use Exception;
12 -use LearnPress\CourseBuilder\CourseBuilder;
13 -use LearnPress\Helpers\Singleton;
14 -use LearnPress\Helpers\Template;
15 -use LearnPress\Models\UserModel;
16 -use LearnPress\TemplateHooks\CourseBuilder\Course\BuilderCourseTemplate;
17 -use LearnPress\TemplateHooks\TemplateAJAX;
18 -use LP_Profile;
19 -use LP_Settings;
20 -use LP_WP_Filesystem;
21 -use Throwable;
22 -use WP_User;
23 -
24 -class CourseBuilderTemplate {
25 - use Singleton;
26 -
27 - const MENU_COURSES = 'courses';
28 - const MENU_LESSONS = 'lessons';
29 - const MENU_QUIZZES = 'quizzes';
30 - const MENU_QUESTIONS = 'questions';
31 - const MENU_SETTINGS = 'settings';
32 -
33 - public function init() {
34 - //add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
35 - add_action( 'learn-press/course-builder/layout', [ $this, 'layout' ] );
36 - // Show link to Course Builder in admin bar
37 - add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_menu' ), 80 );
38 - // Hide admin bar for instructor (not admin)
39 - add_filter( 'show_admin_bar', [ $this, 'hide_admin_bar_for_instructor' ] );
40 - // Dequeue theme styles on Course Builder page (must run during wp_head, not after)
41 - //add_action( 'wp_enqueue_scripts', [ $this, 'dequeue_theme_styles' ], 9999 );
42 - }
43 -
44 - /**
45 - * Hide admin bar for instructor users (not administrators).
46 - *
47 - * @param bool $show_admin_bar
48 - *
49 - * @return bool
50 - * @since 4.3.0
51 - */
52 - public function hide_admin_bar_for_instructor( bool $show_admin_bar ): bool {
53 - // Check enable hide admin bar for instructor
54 - if ( ! LP_Settings::get_option( 'hide_admin_bar_for_instructor', 'no' ) ) {
55 - return $show_admin_bar;
56 - }
57 -
58 - return $show_admin_bar;
59 - }
60 -
61 - /**
62 - * Allow callback for AJAX.
63 - * @use self::render_html_comments
64 - *
65 - * @param array $callbacks
66 - *
67 - * @return array
68 - */
69 - /*public function allow_callback( array $callbacks ): array {
70 - $callbacks[] = get_class( $this ) . ':sidebar';
71 -
72 - return $callbacks;
73 - }*/
74 -
75 - /**
76 - * Layout for Course Builder.
77 - *
78 - * @since 4.3.6
79 - * @version 1.0.0
80 - */
81 - public function layout() {
82 - try {
83 - // Enqueue assets(js,css) for Course Builder
84 - //$this->enqueue_assets();
85 -
86 - // Check permission
87 - $user_id = get_current_user_id();
88 - $userModel = UserModel::find( $user_id, true );
89 - if ( ! $userModel || ! $userModel->is_instructor() ) {
90 - throw new Exception( __( "Sorry, you don't have permission to access Course Builder", 'learnpress' ) );
91 - }
92 -
93 - $data = [
94 - 'userModel' => $userModel,
95 - ];
96 -
97 - $layout = [
98 - 'wrapper' => '<div class="learn-press-course-builder">',
99 - 'header' => $this->html_header( $data ),
100 - 'body' => '<div class="lp-cb-body">',
101 - 'sidebar' => $this->html_sidebar( $data ),
102 - 'content' => $this->html_content( $data ),
103 - 'body_end' => '</div>',
104 - 'wrapper_end' => '</div>',
105 - ];
106 -
107 - echo Template::combine_components( $layout );
108 - } catch ( Throwable $e ) {
109 - Template::print_message(
110 - wp_kses_post( $e->getMessage() ),
111 - 'error'
112 - );
113 - }
114 - }
115 -
116 - /**
117 - * Enqueue scripts, styles and localize data for Course Builder.
118 - *
119 - * @since 4.3.x
120 - * @version 1.0.0
121 - */
122 - protected function enqueue_assets() {}
123 -
124 - /**
125 - * Auto-detect and dequeue all theme/child-theme stylesheets.
126 - * Prevents theme CSS from interfering with Course Builder styles.
127 - *
128 - * Hooked to `wp_enqueue_scripts` at priority 9999 so it runs DURING wp_head(),
129 - * after themes have enqueued their styles but before they are printed.
130 - *
131 - * Only removes styles whose source URL is within the theme or child-theme directory.
132 - * WP core styles, plugin styles, and other assets remain untouched.
133 - *
134 - * @since 4.3.0
135 - * @version 1.0.0
136 - */
137 - /*public function dequeue_theme_styles() {
138 - global $wp_styles, $wp_scripts;
139 -
140 - if ( ! LP_Page_Controller::is_page_course_builder() ) {
141 - return;
142 - }
143 -
144 - $allowed_styles = apply_filters(
145 - 'learn-press/course-builder/allowed-styles',
146 - [
147 - 'dashicons',
148 - 'admin-bar',
149 - 'buttons',
150 - 'media-views',
151 - 'wp-components',
152 - 'wp-block-library',
153 - 'wp-editor',
154 - 'wp-edit-post',
155 - 'wp-block-editor',
156 - 'wp-components',
157 - 'wp-editor',
158 - 'wp-nux',
159 - 'wp-notices',
160 - ]
161 - );
162 -
163 - $allowed_scripts = apply_filters(
164 - 'learn-press/course-builder/allowed-scripts',
165 - [
166 - 'jquery',
167 - 'jquery-core',
168 - 'jquery-migrate',
169 - 'jquery-ui-core',
170 - 'jquery-ui-widget',
171 - 'wp-api-fetch',
172 - 'wp-i18n',
173 - 'wp-components',
174 - 'wp-element',
175 - 'react',
176 - 'react-dom',
177 - 'wp-polyfill',
178 - 'wp-hooks',
179 - 'lodash',
180 - 'moment',
181 - 'heartbeat',
182 - 'wp-data',
183 - 'wp-core-data',
184 - 'wp-url',
185 - 'wp-api',
186 - 'wp-block-editor',
187 - 'wp-blocks',
188 - 'wp-media-utils',
189 - 'wp-compose',
190 - 'regenerator-runtime',
191 - 'wp-a11y',
192 - ]
193 - );
194 -
195 - if ( ! empty( $wp_styles->queue ) ) {
196 - foreach ( $wp_styles->queue as $handle ) {
197 - if ( ! in_array( $handle, $allowed_styles ) && strpos( $handle, 'lp-' ) !== 0 && strpos( $handle, 'learn-press' ) !== 0 && strpos( $handle, 'learnpress' ) !== 0 ) {
198 - wp_dequeue_style( $handle );
199 - }
200 - }
201 - }
202 -
203 - if ( ! empty( $wp_scripts->queue ) ) {
204 - foreach ( $wp_scripts->queue as $handle ) {
205 - if ( ! in_array( $handle, $allowed_scripts ) && strpos( $handle, 'lp-' ) !== 0 && strpos( $handle, 'learn-press' ) !== 0 && strpos( $handle, 'learnpress' ) !== 0 ) {
206 - wp_dequeue_script( $handle );
207 - }
208 - }
209 - }
210 - }*/
211 -
212 - /**
213 - * Header with logo and user profile
214 - *
215 - * @param array $data
216 - *
217 - * @return string
218 - * @throws Exception
219 - * @version 1.0.0
220 - * @since 4.3.6
221 - */
222 - protected function html_header( array $data = [] ): string {
223 - /** @var UserModel $userModel */
224 - $userModel = $data['userModel'] ?? false;
225 - if ( ! $userModel ) {
226 - return '';
227 - }
228 -
229 - $avatar = $userModel->get_avatar_url();
230 - $display_name = $userModel->get_display_name();
231 - $profile = LP_Profile::instance( $userModel->get_id() );
232 - $profile_url = $profile->get_tab_link();
233 - $logout_url = wp_logout_url( home_url() );
234 - $logo_id = absint( LP_Settings::get_option( 'course_builder_logo_id', 0 ) );
235 -
236 - if ( $logo_id ) {
237 - $custom_logo = wp_get_attachment_image(
238 - $logo_id,
239 - 'full',
240 - false,
241 - [
242 - 'class' => 'lp-cb-top-header__logo-image',
243 - 'alt' => __( 'Course Builder', 'learnpress' ),
244 - 'loading' => 'eager',
245 - 'decoding' => 'async',
246 - ]
247 - );
248 - }
249 -
250 - $header = [
251 - 'wrapper' => '<header class="lp-cb-top-header">',
252 - 'logo' => sprintf(
253 - '<div class="lp-cb-top-header__logo">
254 - <a href="%s">%s</a>
255 - </div>',
256 - esc_url( CourseBuilder::get_link_course_builder() ),
257 - $custom_logo ?? LP_WP_Filesystem::get_icon_svg( 'ico-logo-course-builder.svg' ),
258 - ),
259 - 'user' => sprintf(
260 - '<div class="lp-cb-top-header__user">
261 - <div class="lp-cb-top-header__user-avatar">
262 - <img src="%s" class="lp-cb-top-header__user-avatar-image">
263 - <span class="lp-cb-top-header__online-dot"></span>
264 - </div>
265 - <div class="lp-cb-top-header__user-info">
266 - <span class="lp-cb-top-header__user-name">%s</span>
267 - <a href="%s" class="lp-cb-top-header__user-link" target="_blank">%s</a>
268 - </div>
269 - <a href="%s" class="lp-cb-top-header__logout" title="%s">
270 - %s
271 - </a>
272 - </div>',
273 - $avatar,
274 - esc_html( $display_name ),
275 - esc_url( $profile_url ),
276 - __( 'View Profile', 'learnpress' ),
277 - esc_url( $logout_url ),
278 - __( 'Logout', 'learnpress' ),
279 - LP_WP_Filesystem::get_icon_svg( 'ico-logout.svg' ),
280 - ),
281 - 'wrapper_end' => '</header>',
282 - ];
283 -
284 - return Template::combine_components( $header );
285 - }
286 -
287 - /**
288 - * HTML Sidebar
289 - *
290 - * @param array $data
291 - *
292 - * @return string
293 - */
294 - public function html_sidebar( array $data = [] ): string {
295 - $userModel = $data['userModel'] ?? false;
296 - if ( ! $userModel ) {
297 - return '';
298 - }
299 -
300 - $tabs = CourseBuilder::get_menus_arr();
301 - $nav_content = '';
302 - $is_admin = current_user_can( ADMIN_ROLE );
303 -
304 - $tabs = array_filter(
305 - $tabs,
306 - static function ( array $tab ) use ( $is_admin ): bool {
307 - if ( ! empty( $tab['admin_only'] ) && ! $is_admin ) {
308 - return false;
309 - }
310 -
311 - return true;
312 - }
313 - );
314 -
315 - usort(
316 - $tabs,
317 - static function ( array $a, array $b ): int {
318 - $a_priority = isset( $a['priority'] ) && is_numeric( $a['priority'] ) ? (int) $a['priority'] : PHP_INT_MAX;
319 - $b_priority = isset( $b['priority'] ) && is_numeric( $b['priority'] ) ? (int) $b['priority'] : PHP_INT_MAX;
320 -
321 - return $a_priority <=> $b_priority;
322 - }
323 - );
324 -
325 - foreach ( $tabs as $tab ) {
326 - $slug = $tab['slug'];
327 - $nav_item = $this->html_nav_item_main( $slug, $tab );
328 - $nav_content .= $nav_item;
329 - }
330 -
331 - $nav = [
332 - 'wrapper' => '<ul class="lp-cb-sidebar__nav">',
333 - 'content' => $nav_content,
334 - 'wrapper_end' => '</ul>',
335 - ];
336 -
337 - $sidebar_toggle_icon = LP_WP_Filesystem::get_icon_svg( 'ico-cb-sidebar-toggle.svg' );
338 - $toggle = sprintf(
339 - '<button type="button" class="lp-cb-sidebar__toggle" aria-label="%s" title="%s">
340 - %s
341 - </button>',
342 - esc_attr__( 'Toggle Sidebar', 'learnpress' ),
343 - esc_attr__( 'Toggle Sidebar', 'learnpress' ),
344 - $sidebar_toggle_icon
345 - );
346 -
347 - $sidebar = [
348 - 'wrapper' => '<aside id="lp-course-builder-sidebar" class="lp-cb-sidebar">',
349 - 'nav' => Template::combine_components( $nav ),
350 - 'toggle' => $toggle,
351 - 'footer' => $this->sidebar_footer( $data ),
352 - 'wrapper_end' => '</aside>',
353 - ];
354 -
355 - return Template::combine_components( $sidebar );
356 - }
357 -
358 - /**
359 - * HTML main content area
360 - *
361 - * @param array $data
362 - *
363 - * @return string
364 - * @throws Exception
365 - * @version 1.0.1
366 - * @since 4.3.6
367 - */
368 - public function html_content( array $data = [] ): string {
369 - $userModel = $data['userModel'] ?? false;
370 - if ( ! $userModel ) {
371 - return '';
372 - }
373 -
374 - $menu_current = CourseBuilder::get_menu_current();
375 -
376 - //Switch layout display by menu, via model, @since 4.3.7
377 - switch ( $menu_current ) {
378 - case self::MENU_COURSES:
379 - $content = BuilderCourseTemplate::instance()->layout( $data );
380 - break;
381 - default:
382 - if ( has_action( "learn-press/course-builder/{$menu_current}/layout" ) ) {
383 - // Hook old @since 4.3.6.
384 - ob_start();
385 - do_action( "learn-press/course-builder/{$menu_current}/layout", $data );
386 - $content = ob_get_clean();
387 - } else {
388 - // Hook new @since 4.3.7.
389 - $content = apply_filters( 'learn-press/course-builder/content/layout', '', $menu_current, $data );
390 - }
391 - break;
392 - }
393 -
394 - $output = [
395 - 'wrapper' => '<div id="lp-course-builder-content" class="lp-cb-main">',
396 - 'content' => $content,
397 - 'wrapper_end' => '</div>',
398 - ];
399 -
400 - return Template::combine_components( $output );
401 - }
402 -
403 - /**
404 - * Sidebar footer with "Back to Dashboard" link
405 - *
406 - * @param array $data
407 - *
408 - * @return string
409 - * @since 4.3.0
410 - */
411 - protected function sidebar_footer( array $data = [] ): string {
412 - $userModel = $data['userModel'] ?? false;
413 - if ( ! $userModel instanceof UserModel ) {
414 - return '';
415 - }
416 -
417 - $hide_instructor_access_admin_screen = LP_Settings::is_hide_instructor_access_admin_screen();
418 - $wp_user = new WP_User( $userModel );
419 - $is_instructor = user_can( $wp_user, UserModel::ROLE_INSTRUCTOR );
420 - $dashboard_url = admin_url();
421 -
422 - $footer = [
423 - 'wrapper' => '<div class="lp-cb-sidebar__footer">',
424 - ];
425 -
426 - // Hide "Back to WordPress" for instructors when CB admin mode is on
427 - // Admins always see this link
428 - $hide_back_link = $hide_instructor_access_admin_screen && $is_instructor;
429 -
430 - if ( ! $hide_back_link ) {
431 - $back_to_wp_text = __( 'Back to WordPress', 'learnpress' );
432 -
433 - $footer['back'] = sprintf(
434 - '<a href="%s" class="lp-cb-sidebar__item lp-cb-sidebar__back" title="%s" aria-label="%s">
435 - <span class="dashicons dashicons-wordpress"></span>
436 - <span class="lp-cb-sidebar__item-title">%s</span>
437 - </a>',
438 - esc_url( $dashboard_url ),
439 - esc_attr( $back_to_wp_text ),
440 - esc_attr( $back_to_wp_text ),
441 - esc_html( $back_to_wp_text )
442 - );
443 - }
444 -
445 - $footer['wrapper_end'] = '</div>';
446 -
447 - return Template::combine_components( $footer );
448 - }
449 -
450 - /**
451 - * Render main navigation item (persistent sidebar)
452 - *
453 - * @param string $slug
454 - * @param array $tab_data
455 - *
456 - * @return string
457 - * @since 4..0
458 - */
459 - protected function html_nav_item_main( $slug, $tab_data ) {
460 - $tab_current = CourseBuilder::get_menu_current();
461 - $sub_menu_items = $this->get_nav_item_sub_menu_items( $slug, $tab_data );
462 - $has_sub_menu = ! empty( $sub_menu_items );
463 - $has_active_submenu = $has_sub_menu && ! empty(
464 - array_filter(
465 - $sub_menu_items,
466 - static function ( array $item ): bool {
467 - return ! empty( $item['is_active'] );
468 - }
469 - )
470 - );
471 - $is_active = $slug === $tab_current || ! empty( $has_active_submenu );
472 - $classes = [ 'lp-cb-sidebar__item', $slug ];
473 -
474 - if ( $has_sub_menu ) {
475 - $classes[] = 'has-sub-menu';
476 - }
477 -
478 - if ( $is_active ) {
479 - $classes[] = 'is-active';
480 - }
481 -
482 - if ( $has_sub_menu && $is_active ) {
483 - $classes[] = 'is-expanded';
484 - }
485 -
486 - $icon = isset( $tab_data['icon'] ) ? $tab_data['icon'] : '';
487 - $title = $tab_data['title'];
488 - $link = CourseBuilder::get_tab_link( $slug );
489 - $sub_menu_id = 'lp-cb-sidebar-sub-menu-' . sanitize_html_class( $slug );
490 - $aria_current = $is_active && empty( $has_active_submenu ) ? ' aria-current="page"' : '';
491 -
492 - if ( $has_sub_menu ) {
493 - $content = sprintf(
494 - '<div class="lp-cb-sidebar__item-control">
495 - <a href="%1$s" class="lp-cb-sidebar__item-link" title="%2$s" aria-label="%2$s"%3$s>
496 - %4$s
497 - <span class="lp-cb-sidebar__item-title">%5$s</span>
498 - </a>
499 - <button type="button" class="lp-cb-sidebar__sub-menu-toggle" aria-label="%6$s" aria-expanded="%7$s" aria-controls="%8$s">
500 - <span class="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
501 - </button>
502 - </div>',
503 - esc_url( $link ),
504 - esc_attr( $title ),
505 - $aria_current,
506 - $icon,
507 - esc_html( $title ),
508 - esc_attr( sprintf( __( 'Toggle %s submenu', 'learnpress' ), $title ) ),
509 - $is_active ? 'true' : 'false',
510 - esc_attr( $sub_menu_id )
511 - );
512 - } else {
513 - $content = sprintf(
514 - '<a href="%1$s" class="lp-cb-sidebar__item-link" title="%2$s" aria-label="%2$s"%3$s>
515 - %4$s
516 - <span class="lp-cb-sidebar__item-title">%5$s</span>
517 - </a>',
518 - esc_url( $link ),
519 - esc_attr( $title ),
520 - $aria_current,
521 - $icon,
522 - esc_html( $title )
523 - );
524 - }
525 -
526 - $item = [
527 - 'wrapper' => sprintf( '<li class="%s">', implode( ' ', $classes ) ),
528 - 'content' => $content,
529 - 'wrapper_end' => '</li>',
530 - ];
531 -
532 - if ( $has_sub_menu ) {
533 - $item['content'] .= $this->html_nav_item_sub_menu( $sub_menu_items, $sub_menu_id, $title );
534 - }
535 -
536 - return Template::combine_components( $item );
537 - }
538 -
539 - protected function get_nav_item_sub_menu_items( string $parent_slug, array $tab_data ): array {
540 - $items = [];
541 - if ( ! empty( $tab_data['sub_menu'] ) && is_array( $tab_data['sub_menu'] ) ) {
542 - $items = $this->normalize_nav_sub_menu_items( $parent_slug, $tab_data['sub_menu'] );
543 - }
544 -
545 - return $items;
546 - }
547 -
548 - protected function normalize_nav_sub_menu_items( string $parent_slug, array $sub_menu ): array {
549 - $is_admin = current_user_can( ADMIN_ROLE );
550 - $items = [];
551 -
552 - foreach ( $sub_menu as $key => $item ) {
553 - if ( ! is_array( $item ) || ( ! empty( $item['admin_only'] ) && ! $is_admin ) ) {
554 - continue;
555 - }
556 -
557 - $title = $item['title'] ?? '';
558 - if ( '' === $title ) {
559 - continue;
560 - }
561 -
562 - $slug = ! empty( $item['slug'] ) ? (string) $item['slug'] : ( is_string( $key ) ? $key : sanitize_title( $title ) );
563 - if ( '' === $slug ) {
564 - continue;
565 - }
566 -
567 - $url = $item['url'] ?? '';
568 - if ( '' === $url ) {
569 - $url = CourseBuilder::get_tab_link( $parent_slug, $slug );
570 - }
571 -
572 - if ( '' === $url ) {
573 - continue;
574 - }
575 -
576 - $is_active = false;
577 - if ( $parent_slug === CourseBuilder::get_menu_current() ) {
578 - $is_active = $slug === (string) CourseBuilder::get_item_id();
579 - }
580 -
581 - $items[] = [
582 - 'class' => is_string( $key ) ? $key : sanitize_title( $title ),
583 - 'icon' => $item['icon'] ?? '',
584 - 'is_active' => $is_active,
585 - 'rel' => ! empty( $item['target'] ) && '_blank' === $item['target'] ? 'noopener noreferrer' : '',
586 - 'slug' => $slug,
587 - 'target' => $item['target'] ?? '',
588 - 'title' => $title,
589 - 'url' => $url,
590 - ];
591 - }
592 -
593 - return $items;
594 - }
595 -
596 - protected function html_nav_item_sub_menu( array $sub_menu_items, string $sub_menu_id, string $parent_title = '' ): string {
597 - if ( empty( $sub_menu_items ) ) {
598 - return '';
599 - }
600 -
601 - $content = '';
602 -
603 - foreach ( $sub_menu_items as $item ) {
604 - $target = '' !== $item['target'] ? sprintf( ' target="%s"', esc_attr( $item['target'] ) ) : '';
605 - $rel = '' !== $item['rel'] ? sprintf( ' rel="%s"', esc_attr( $item['rel'] ) ) : '';
606 - $active_class = ! empty( $item['is_active'] ) ? ' is-active' : '';
607 - $aria_current = ! empty( $item['is_active'] ) ? ' aria-current="page"' : '';
608 -
609 - $content .= sprintf(
610 - '<li class="lp-cb-sidebar__sub-menu-item %1$s%2$s"><a href="%3$s" class="lp-cb-sidebar__sub-menu-link"%4$s%5$s%6$s>%7$s<span class="lp-cb-sidebar__sub-menu-title">%8$s</span></a></li>',
611 - esc_attr( sanitize_html_class( $item['class'] ) ),
612 - esc_attr( $active_class ),
613 - esc_url( $item['url'] ),
614 - $target,
615 - $rel,
616 - $aria_current,
617 - $item['icon'],
618 - esc_html( $item['title'] )
619 - );
620 - }
621 -
622 - if ( '' === $content ) {
623 - return '';
624 - }
625 -
626 - return sprintf(
627 - '<ul id="%1$s" class="lp-cb-sidebar__sub-menu" aria-label="%2$s">%3$s</ul>',
628 - esc_attr( $sub_menu_id ),
629 - esc_attr( sprintf( __( '%s submenu', 'learnpress' ), $parent_title ) ),
630 - $content
631 - );
632 - }
633 -
634 - public function html_btn_add_new() {
635 - $tab_current = CourseBuilder::get_menu_current();
636 - $map_title = [
637 - 'courses' => __( 'Course', 'learnpress' ),
638 - 'lessons' => __( 'Lesson', 'learnpress' ),
639 - 'quizzes' => __( 'Quiz', 'learnpress' ),
640 - 'questions' => __( 'Question', 'learnpress' ),
641 - ];
642 -
643 - $map_type = [
644 - 'lessons' => 'lesson',
645 - 'quizzes' => 'quiz',
646 - 'questions' => 'question',
647 - ];
648 -
649 - $title = isset( $map_title[ $tab_current ] ) ? $map_title[ $tab_current ] : '';
650 - $type = isset( $map_type[ $tab_current ] ) ? $map_type[ $tab_current ] : '';
651 - $add_new = 'data-add-new-' . esc_attr( $type );
652 - $template_html = '';
653 - $template_attr = '';
654 -
655 - $btn_add_new = sprintf( '<button %s class="lp-button cb-btn-add-new">', $add_new );
656 - $btn_close = '</button>';
657 -
658 - if ( 'lessons' === $tab_current ) {
659 - $template_id = 'lp-tmpl-builder-popup-lesson-tab-new';
660 - $template_attr = sprintf(
661 - ' data-template="#%1$s" data-popup-type="lesson" data-popup-id="0"',
662 - esc_attr( $template_id )
663 - );
664 - $template_html = sprintf(
665 - '<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>',
666 - esc_attr( $template_id ),
667 - TemplateAJAX::load_content_via_ajax(
668 - [
669 - 'id_url' => 'builder-popup-lesson-tab-new',
670 - 'lesson_id' => 0,
671 - 'html_no_load_ajax_first' => sprintf(
672 - '<div class="lp-builder-popup__loader"><div class="lp-loading-circle"></div><span>%s</span></div>',
673 - esc_html__( 'Loading...', 'learnpress' )
674 - ),
675 - ],
676 - [
677 - 'class' => BuilderPopupTemplate::class,
678 - 'method' => 'render_lesson_popup',
679 - ]
680 - )
681 - );
682 - }
683 -
684 - if ( 'courses' === $tab_current ) {
685 - $btn_add_new = sprintf(
686 - '<a href="%s" class="lp-button cb-btn-add-new">',
687 - esc_url( CourseBuilder::get_link_add_new( 'courses' ) )
688 - );
689 - $btn_close = '</a>';
690 - }
691 -
692 - if ( 'quizzes' === $tab_current ) {
693 - $btn_add_new = sprintf(
694 - '<a href="%s" class="lp-button cb-btn-add-new">',
695 - esc_url( CourseBuilder::get_link_add_new( 'quizzes' ) )
696 - );
697 - $btn_close = '</a>';
698 - }
699 -
700 - if ( 'questions' === $tab_current ) {
701 - $btn_add_new = sprintf(
702 - '<a href="%s" class="lp-button cb-btn-add-new">',
703 - esc_url( CourseBuilder::get_link_add_new( 'questions' ) )
704 - );
705 - $btn_close = '</a>';
706 - }
707 -
708 - $btn = [
709 - 'wrapper' => str_replace( '>', $template_attr . '>', $btn_add_new ),
710 - 'content' => sprintf( '%s %s', __( 'Add New', 'learnpress' ), $title ),
711 - 'wrapper_end' => $btn_close,
712 - 'template' => $template_html,
713 - ];
714 - $btn = apply_filters( 'learn-press/course-builder/button-add-new', $btn, $tab_current, $type );
715 -
716 - return Template::combine_components( $btn );
717 - }
718 -
719 - /**
720 - * Show link to Course Builder in admin bar
721 - */
722 - public function add_admin_bar_menu( $wp_admin_bar ) {
723 - $href = CourseBuilder::get_link_course_builder();
724 - $title = esc_html__( 'Course Builder', 'learnpress' );
725 -
726 - // Check if on frontend single course page
727 - if ( is_singular( LP_COURSE_CPT ) && get_the_ID() ) {
728 - $title = esc_html__( 'Edit with Course Builder', 'learnpress' );
729 - $href = BuilderCourseTemplate::instance()->get_link_edit( get_the_ID() );
730 - }
731 -
732 - // Check if on admin edit course page (post.php or post-new.php)
733 - if ( is_admin() ) {
734 - global $post, $pagenow;
735 - if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) ) {
736 - $post_type = '';
737 - if ( isset( $_GET['post_type'] ) ) {
738 - $post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
739 - } elseif ( isset( $_GET['post'] ) ) {
740 - $post_id = absint( $_GET['post'] );
741 - $post_type = get_post_type( $post_id );
742 - } elseif ( $post && isset( $post->post_type ) ) {
743 - $post_type = $post->post_type;
744 - }
745 -
746 - if ( LP_COURSE_CPT === $post_type ) {
747 - $title = esc_html__( 'Edit with Course Builder', 'learnpress' );
748 - if ( isset( $_GET['post'] ) ) {
749 - $href = BuilderCourseTemplate::instance()->get_link_edit( $_GET['post'] );
750 - } else {
751 - $href = CourseBuilder::get_link_add_new( 'courses' );
752 - }
753 - }
754 - }
755 - }
756 -
757 - $admin_bar_icon = LP_WP_Filesystem::get_icon_svg( 'ico-cb-admin-bar.svg' );
758 -
759 - $wp_admin_bar->add_node(
760 - array(
761 - 'id' => 'lp-course-builder',
762 - 'title' => sprintf(
763 - '<span class="lp-cb-admin-bar-icon">%1$s</span><span class="ab-label">%2$s</span>',
764 - $admin_bar_icon,
765 - $title
766 - ),
767 - 'href' => $href,
768 - )
769 - );
770 - }
771 -}
1 +<?php
2 +/**
3 + * Template hooks Course Builder.
4 + *
5 + * @since 4.3.x
6 + * @version 1.0.0
7 + */
8 +
9 +namespace LearnPress\TemplateHooks\CourseBuilder;
10 +
11 +use Exception;
12 +use LearnPress\CourseBuilder\CourseBuilder;
13 +use LearnPress\Helpers\Singleton;
14 +use LearnPress\Helpers\Template;
15 +use LearnPress\Models\UserModel;
16 +use LearnPress\TemplateHooks\CourseBuilder\Course\BuilderCourseTemplate;
17 +use LearnPress\TemplateHooks\TemplateAJAX;
18 +use LP_Profile;
19 +use LP_Settings;
20 +use LP_WP_Filesystem;
21 +use Throwable;
22 +use WP_User;
23 +
24 +class CourseBuilderTemplate {
25 + use Singleton;
26 +
27 + const MENU_COURSES = 'courses';
28 + const MENU_LESSONS = 'lessons';
29 + const MENU_QUIZZES = 'quizzes';
30 + const MENU_QUESTIONS = 'questions';
31 + const MENU_SETTINGS = 'settings';
32 +
33 + public function init() {
34 + //add_filter( 'lp/rest/ajax/allow_callback', [ $this, 'allow_callback' ] );
35 + add_action( 'learn-press/course-builder/layout', [ $this, 'layout' ] );
36 + // Show link to Course Builder in admin bar
37 + add_action( 'admin_bar_menu', array( $this, 'add_admin_bar_menu' ), 80 );
38 + // Hide admin bar for instructor (not admin)
39 + add_filter( 'show_admin_bar', [ $this, 'hide_admin_bar_for_instructor' ] );
40 + // Dequeue theme styles on Course Builder page (must run during wp_head, not after)
41 + //add_action( 'wp_enqueue_scripts', [ $this, 'dequeue_theme_styles' ], 9999 );
42 + }
43 +
44 + /**
45 + * Hide admin bar for instructor users (not administrators).
46 + *
47 + * @param bool $show_admin_bar
48 + *
49 + * @return bool
50 + * @since 4.3.0
51 + */
52 + public function hide_admin_bar_for_instructor( bool $show_admin_bar ): bool {
53 + // Check enable hide admin bar for instructor
54 + if ( ! LP_Settings::get_option( 'hide_admin_bar_for_instructor', 'no' ) ) {
55 + return $show_admin_bar;
56 + }
57 +
58 + return $show_admin_bar;
59 + }
60 +
61 + /**
62 + * Allow callback for AJAX.
63 + * @use self::render_html_comments
64 + *
65 + * @param array $callbacks
66 + *
67 + * @return array
68 + */
69 + /*public function allow_callback( array $callbacks ): array {
70 + $callbacks[] = get_class( $this ) . ':sidebar';
71 +
72 + return $callbacks;
73 + }*/
74 +
75 + /**
76 + * Layout for Course Builder.
77 + *
78 + * @since 4.3.6
79 + * @version 1.0.0
80 + */
81 + public function layout() {
82 + try {
83 + // Enqueue assets(js,css) for Course Builder
84 + //$this->enqueue_assets();
85 +
86 + // Check permission
87 + $user_id = get_current_user_id();
88 + $userModel = UserModel::find( $user_id, true );
89 + if ( ! $userModel || ! $userModel->is_instructor() ) {
90 + throw new Exception( __( "Sorry, you don't have permission to access Course Builder", 'learnpress' ) );
91 + }
92 +
93 + $data = [
94 + 'userModel' => $userModel,
95 + ];
96 +
97 + $layout = [
98 + 'wrapper' => '<div class="learn-press-course-builder">',
99 + 'header' => $this->html_header( $data ),
100 + 'body' => '<div class="lp-cb-body">',
101 + 'sidebar' => $this->html_sidebar( $data ),
102 + 'content' => $this->html_content( $data ),
103 + 'body_end' => '</div>',
104 + 'wrapper_end' => '</div>',
105 + ];
106 +
107 + echo Template::combine_components( $layout );
108 + } catch ( Throwable $e ) {
109 + Template::print_message(
110 + wp_kses_post( $e->getMessage() ),
111 + 'error'
112 + );
113 + }
114 + }
115 +
116 + /**
117 + * Enqueue scripts, styles and localize data for Course Builder.
118 + *
119 + * @since 4.3.x
120 + * @version 1.0.0
121 + */
122 + protected function enqueue_assets() {}
123 +
124 + /**
125 + * Auto-detect and dequeue all theme/child-theme stylesheets.
126 + * Prevents theme CSS from interfering with Course Builder styles.
127 + *
128 + * Hooked to `wp_enqueue_scripts` at priority 9999 so it runs DURING wp_head(),
129 + * after themes have enqueued their styles but before they are printed.
130 + *
131 + * Only removes styles whose source URL is within the theme or child-theme directory.
132 + * WP core styles, plugin styles, and other assets remain untouched.
133 + *
134 + * @since 4.3.0
135 + * @version 1.0.0
136 + */
137 + /*public function dequeue_theme_styles() {
138 + global $wp_styles, $wp_scripts;
139 +
140 + if ( ! LP_Page_Controller::is_page_course_builder() ) {
141 + return;
142 + }
143 +
144 + $allowed_styles = apply_filters(
145 + 'learn-press/course-builder/allowed-styles',
146 + [
147 + 'dashicons',
148 + 'admin-bar',
149 + 'buttons',
150 + 'media-views',
151 + 'wp-components',
152 + 'wp-block-library',
153 + 'wp-editor',
154 + 'wp-edit-post',
155 + 'wp-block-editor',
156 + 'wp-components',
157 + 'wp-editor',
158 + 'wp-nux',
159 + 'wp-notices',
160 + ]
161 + );
162 +
163 + $allowed_scripts = apply_filters(
164 + 'learn-press/course-builder/allowed-scripts',
165 + [
166 + 'jquery',
167 + 'jquery-core',
168 + 'jquery-migrate',
169 + 'jquery-ui-core',
170 + 'jquery-ui-widget',
171 + 'wp-api-fetch',
172 + 'wp-i18n',
173 + 'wp-components',
174 + 'wp-element',
175 + 'react',
176 + 'react-dom',
177 + 'wp-polyfill',
178 + 'wp-hooks',
179 + 'lodash',
180 + 'moment',
181 + 'heartbeat',
182 + 'wp-data',
183 + 'wp-core-data',
184 + 'wp-url',
185 + 'wp-api',
186 + 'wp-block-editor',
187 + 'wp-blocks',
188 + 'wp-media-utils',
189 + 'wp-compose',
190 + 'regenerator-runtime',
191 + 'wp-a11y',
192 + ]
193 + );
194 +
195 + if ( ! empty( $wp_styles->queue ) ) {
196 + foreach ( $wp_styles->queue as $handle ) {
197 + if ( ! in_array( $handle, $allowed_styles ) && strpos( $handle, 'lp-' ) !== 0 && strpos( $handle, 'learn-press' ) !== 0 && strpos( $handle, 'learnpress' ) !== 0 ) {
198 + wp_dequeue_style( $handle );
199 + }
200 + }
201 + }
202 +
203 + if ( ! empty( $wp_scripts->queue ) ) {
204 + foreach ( $wp_scripts->queue as $handle ) {
205 + if ( ! in_array( $handle, $allowed_scripts ) && strpos( $handle, 'lp-' ) !== 0 && strpos( $handle, 'learn-press' ) !== 0 && strpos( $handle, 'learnpress' ) !== 0 ) {
206 + wp_dequeue_script( $handle );
207 + }
208 + }
209 + }
210 + }*/
211 +
212 + /**
213 + * Header with logo and user profile
214 + *
215 + * @param array $data
216 + *
217 + * @return string
218 + * @throws Exception
219 + * @version 1.0.0
220 + * @since 4.3.6
221 + */
222 + protected function html_header( array $data = [] ): string {
223 + /** @var UserModel $userModel */
224 + $userModel = $data['userModel'] ?? false;
225 + if ( ! $userModel ) {
226 + return '';
227 + }
228 +
229 + $avatar = $userModel->get_avatar_url();
230 + $display_name = $userModel->get_display_name();
231 + $profile = LP_Profile::instance( $userModel->get_id() );
232 + $profile_url = $profile->get_tab_link();
233 + $logout_url = wp_logout_url( home_url() );
234 + $logo_id = absint( LP_Settings::get_option( 'course_builder_logo_id', 0 ) );
235 +
236 + if ( $logo_id ) {
237 + $custom_logo = wp_get_attachment_image(
238 + $logo_id,
239 + 'full',
240 + false,
241 + [
242 + 'class' => 'lp-cb-top-header__logo-image',
243 + 'alt' => __( 'Course Builder', 'learnpress' ),
244 + 'loading' => 'eager',
245 + 'decoding' => 'async',
246 + ]
247 + );
248 + }
249 +
250 + $header = [
251 + 'wrapper' => '<header class="lp-cb-top-header">',
252 + 'logo' => sprintf(
253 + '<div class="lp-cb-top-header__logo">
254 + <a href="%s">%s</a>
255 + </div>',
256 + esc_url( CourseBuilder::get_link_course_builder() ),
257 + $custom_logo ?? LP_WP_Filesystem::get_icon_svg( 'ico-logo-course-builder.svg' ),
258 + ),
259 + 'user' => sprintf(
260 + '<div class="lp-cb-top-header__user">
261 + <div class="lp-cb-top-header__user-avatar">
262 + <img src="%s" class="lp-cb-top-header__user-avatar-image">
263 + <span class="lp-cb-top-header__online-dot"></span>
264 + </div>
265 + <div class="lp-cb-top-header__user-info">
266 + <span class="lp-cb-top-header__user-name">%s</span>
267 + <a href="%s" class="lp-cb-top-header__user-link" target="_blank">%s</a>
268 + </div>
269 + <a href="%s" class="lp-cb-top-header__logout" title="%s">
270 + %s
271 + </a>
272 + </div>',
273 + $avatar,
274 + esc_html( $display_name ),
275 + esc_url( $profile_url ),
276 + __( 'View Profile', 'learnpress' ),
277 + esc_url( $logout_url ),
278 + __( 'Logout', 'learnpress' ),
279 + LP_WP_Filesystem::get_icon_svg( 'ico-logout.svg' ),
280 + ),
281 + 'wrapper_end' => '</header>',
282 + ];
283 +
284 + return Template::combine_components( $header );
285 + }
286 +
287 + /**
288 + * HTML Sidebar
289 + *
290 + * @param array $data
291 + *
292 + * @return string
293 + */
294 + public function html_sidebar( array $data = [] ): string {
295 + $userModel = $data['userModel'] ?? false;
296 + if ( ! $userModel ) {
297 + return '';
298 + }
299 +
300 + $tabs = CourseBuilder::get_menus_arr();
301 + $nav_content = '';
302 + $is_admin = current_user_can( ADMIN_ROLE );
303 +
304 + $tabs = array_filter(
305 + $tabs,
306 + static function ( array $tab ) use ( $is_admin ): bool {
307 + if ( ! empty( $tab['admin_only'] ) && ! $is_admin ) {
308 + return false;
309 + }
310 +
311 + return true;
312 + }
313 + );
314 +
315 + usort(
316 + $tabs,
317 + static function ( array $a, array $b ): int {
318 + $a_priority = isset( $a['priority'] ) && is_numeric( $a['priority'] ) ? (int) $a['priority'] : PHP_INT_MAX;
319 + $b_priority = isset( $b['priority'] ) && is_numeric( $b['priority'] ) ? (int) $b['priority'] : PHP_INT_MAX;
320 +
321 + return $a_priority <=> $b_priority;
322 + }
323 + );
324 +
325 + foreach ( $tabs as $tab ) {
326 + $slug = $tab['slug'];
327 + $nav_item = $this->html_nav_item_main( $slug, $tab );
328 + $nav_content .= $nav_item;
329 + }
330 +
331 + $nav = [
332 + 'wrapper' => '<ul class="lp-cb-sidebar__nav">',
333 + 'content' => $nav_content,
334 + 'wrapper_end' => '</ul>',
335 + ];
336 +
337 + $sidebar_toggle_icon = LP_WP_Filesystem::get_icon_svg( 'ico-cb-sidebar-toggle.svg' );
338 + $toggle = sprintf(
339 + '<button type="button" class="lp-cb-sidebar__toggle" aria-label="%s" title="%s">
340 + %s
341 + </button>',
342 + esc_attr__( 'Toggle Sidebar', 'learnpress' ),
343 + esc_attr__( 'Toggle Sidebar', 'learnpress' ),
344 + $sidebar_toggle_icon
345 + );
346 +
347 + $sidebar = [
348 + 'wrapper' => '<aside id="lp-course-builder-sidebar" class="lp-cb-sidebar">',
349 + 'nav' => Template::combine_components( $nav ),
350 + 'toggle' => $toggle,
351 + 'footer' => $this->sidebar_footer( $data ),
352 + 'wrapper_end' => '</aside>',
353 + ];
354 +
355 + return Template::combine_components( $sidebar );
356 + }
357 +
358 + /**
359 + * HTML main content area
360 + *
361 + * @param array $data
362 + *
363 + * @return string
364 + * @throws Exception
365 + * @version 1.0.1
366 + * @since 4.3.6
367 + */
368 + public function html_content( array $data = [] ): string {
369 + $userModel = $data['userModel'] ?? false;
370 + if ( ! $userModel ) {
371 + return '';
372 + }
373 +
374 + $menu_current = CourseBuilder::get_menu_current();
375 +
376 + //Switch layout display by menu, via model, @since 4.3.7
377 + switch ( $menu_current ) {
378 + case self::MENU_COURSES:
379 + $content = BuilderCourseTemplate::instance()->layout( $data );
380 + break;
381 + default:
382 + if ( has_action( "learn-press/course-builder/{$menu_current}/layout" ) ) {
383 + // Hook old @since 4.3.6.
384 + ob_start();
385 + do_action( "learn-press/course-builder/{$menu_current}/layout", $data );
386 + $content = ob_get_clean();
387 + } else {
388 + // Hook new @since 4.3.7.
389 + $content = apply_filters( 'learn-press/course-builder/content/layout', '', $menu_current, $data );
390 + }
391 + break;
392 + }
393 +
394 + $output = [
395 + 'wrapper' => '<div id="lp-course-builder-content" class="lp-cb-main">',
396 + 'content' => $content,
397 + 'wrapper_end' => '</div>',
398 + ];
399 +
400 + return Template::combine_components( $output );
401 + }
402 +
403 + /**
404 + * Sidebar footer with "Back to Dashboard" link
405 + *
406 + * @param array $data
407 + *
408 + * @return string
409 + * @since 4.3.0
410 + */
411 + protected function sidebar_footer( array $data = [] ): string {
412 + $userModel = $data['userModel'] ?? false;
413 + if ( ! $userModel instanceof UserModel ) {
414 + return '';
415 + }
416 +
417 + $hide_instructor_access_admin_screen = LP_Settings::is_hide_instructor_access_admin_screen();
418 + $wp_user = new WP_User( $userModel );
419 + $is_instructor = user_can( $wp_user, UserModel::ROLE_INSTRUCTOR );
420 + $dashboard_url = admin_url( 'edit.php?post_type=' . LP_COURSE_CPT );
421 +
422 + $footer = [
423 + 'wrapper' => '<div class="lp-cb-sidebar__footer">',
424 + ];
425 +
426 + // Hide "Back to WordPress" for instructors when CB admin mode is on
427 + // Admins always see this link
428 + $hide_back_link = $hide_instructor_access_admin_screen && $is_instructor;
429 +
430 + if ( ! $hide_back_link ) {
431 + $back_to_wp_text = __( 'Back to WordPress', 'learnpress' );
432 +
433 + $footer['back'] = sprintf(
434 + '<a href="%s" class="lp-cb-sidebar__item lp-cb-sidebar__back" title="%s" aria-label="%s">
435 + <span class="dashicons dashicons-wordpress"></span>
436 + <span class="lp-cb-sidebar__item-title">%s</span>
437 + </a>',
438 + esc_url( $dashboard_url ),
439 + esc_attr( $back_to_wp_text ),
440 + esc_attr( $back_to_wp_text ),
441 + esc_html( $back_to_wp_text )
442 + );
443 + }
444 +
445 + $footer['wrapper_end'] = '</div>';
446 +
447 + return Template::combine_components( $footer );
448 + }
449 +
450 + /**
451 + * Render main navigation item (persistent sidebar)
452 + *
453 + * @param string $slug
454 + * @param array $tab_data
455 + *
456 + * @return string
457 + * @since 4..0
458 + */
459 + protected function html_nav_item_main( $slug, $tab_data ) {
460 + $tab_current = CourseBuilder::get_menu_current();
461 + $sub_menu_items = $this->get_nav_item_sub_menu_items( $slug, $tab_data );
462 + $has_sub_menu = ! empty( $sub_menu_items );
463 + $has_active_submenu = $has_sub_menu && ! empty(
464 + array_filter(
465 + $sub_menu_items,
466 + static function ( array $item ): bool {
467 + return ! empty( $item['is_active'] );
468 + }
469 + )
470 + );
471 + $is_active = $slug === $tab_current || ! empty( $has_active_submenu );
472 + $classes = [ 'lp-cb-sidebar__item', $slug ];
473 +
474 + if ( $has_sub_menu ) {
475 + $classes[] = 'has-sub-menu';
476 + }
477 +
478 + if ( $is_active ) {
479 + $classes[] = 'is-active';
480 + }
481 +
482 + if ( $has_sub_menu && $is_active ) {
483 + $classes[] = 'is-expanded';
484 + }
485 +
486 + $icon = isset( $tab_data['icon'] ) ? $tab_data['icon'] : '';
487 + $title = $tab_data['title'];
488 + $link = CourseBuilder::get_tab_link( $slug );
489 + $sub_menu_id = 'lp-cb-sidebar-sub-menu-' . sanitize_html_class( $slug );
490 + $aria_current = $is_active && empty( $has_active_submenu ) ? ' aria-current="page"' : '';
491 +
492 + if ( $has_sub_menu ) {
493 + $content = sprintf(
494 + '<div class="lp-cb-sidebar__item-control">
495 + <a href="%1$s" class="lp-cb-sidebar__item-link" title="%2$s" aria-label="%2$s"%3$s>
496 + %4$s
497 + <span class="lp-cb-sidebar__item-title">%5$s</span>
498 + </a>
499 + <button type="button" class="lp-cb-sidebar__sub-menu-toggle" aria-label="%6$s" aria-expanded="%7$s" aria-controls="%8$s">
500 + <span class="dashicons dashicons-arrow-down-alt2" aria-hidden="true"></span>
501 + </button>
502 + </div>',
503 + esc_url( $link ),
504 + esc_attr( $title ),
505 + $aria_current,
506 + $icon,
507 + esc_html( $title ),
508 + esc_attr( sprintf( __( 'Toggle %s submenu', 'learnpress' ), $title ) ),
509 + $is_active ? 'true' : 'false',
510 + esc_attr( $sub_menu_id )
511 + );
512 + } else {
513 + $content = sprintf(
514 + '<a href="%1$s" class="lp-cb-sidebar__item-link" title="%2$s" aria-label="%2$s"%3$s>
515 + %4$s
516 + <span class="lp-cb-sidebar__item-title">%5$s</span>
517 + </a>',
518 + esc_url( $link ),
519 + esc_attr( $title ),
520 + $aria_current,
521 + $icon,
522 + esc_html( $title )
523 + );
524 + }
525 +
526 + $item = [
527 + 'wrapper' => sprintf( '<li class="%s">', implode( ' ', $classes ) ),
528 + 'content' => $content,
529 + 'wrapper_end' => '</li>',
530 + ];
531 +
532 + if ( $has_sub_menu ) {
533 + $item['content'] .= $this->html_nav_item_sub_menu( $sub_menu_items, $sub_menu_id, $title );
534 + }
535 +
536 + return Template::combine_components( $item );
537 + }
538 +
539 + protected function get_nav_item_sub_menu_items( string $parent_slug, array $tab_data ): array {
540 + $items = [];
541 + if ( ! empty( $tab_data['sub_menu'] ) && is_array( $tab_data['sub_menu'] ) ) {
542 + $items = $this->normalize_nav_sub_menu_items( $parent_slug, $tab_data['sub_menu'] );
543 + }
544 +
545 + return $items;
546 + }
547 +
548 + protected function normalize_nav_sub_menu_items( string $parent_slug, array $sub_menu ): array {
549 + $is_admin = current_user_can( ADMIN_ROLE );
550 + $items = [];
551 +
552 + foreach ( $sub_menu as $key => $item ) {
553 + if ( ! is_array( $item ) || ( ! empty( $item['admin_only'] ) && ! $is_admin ) ) {
554 + continue;
555 + }
556 +
557 + $title = $item['title'] ?? '';
558 + if ( '' === $title ) {
559 + continue;
560 + }
561 +
562 + $slug = ! empty( $item['slug'] ) ? (string) $item['slug'] : ( is_string( $key ) ? $key : sanitize_title( $title ) );
563 + if ( '' === $slug ) {
564 + continue;
565 + }
566 +
567 + $url = $item['url'] ?? '';
568 + if ( '' === $url ) {
569 + $url = CourseBuilder::get_tab_link( $parent_slug, $slug );
570 + }
571 +
572 + if ( '' === $url ) {
573 + continue;
574 + }
575 +
576 + $is_active = false;
577 + if ( $parent_slug === CourseBuilder::get_menu_current() ) {
578 + $is_active = $slug === (string) CourseBuilder::get_item_id();
579 + }
580 +
581 + $items[] = [
582 + 'class' => is_string( $key ) ? $key : sanitize_title( $title ),
583 + 'icon' => $item['icon'] ?? '',
584 + 'is_active' => $is_active,
585 + 'rel' => ! empty( $item['target'] ) && '_blank' === $item['target'] ? 'noopener noreferrer' : '',
586 + 'slug' => $slug,
587 + 'target' => $item['target'] ?? '',
588 + 'title' => $title,
589 + 'url' => $url,
590 + ];
591 + }
592 +
593 + return $items;
594 + }
595 +
596 + protected function html_nav_item_sub_menu( array $sub_menu_items, string $sub_menu_id, string $parent_title = '' ): string {
597 + if ( empty( $sub_menu_items ) ) {
598 + return '';
599 + }
600 +
601 + $content = '';
602 +
603 + foreach ( $sub_menu_items as $item ) {
604 + $target = '' !== $item['target'] ? sprintf( ' target="%s"', esc_attr( $item['target'] ) ) : '';
605 + $rel = '' !== $item['rel'] ? sprintf( ' rel="%s"', esc_attr( $item['rel'] ) ) : '';
606 + $active_class = ! empty( $item['is_active'] ) ? ' is-active' : '';
607 + $aria_current = ! empty( $item['is_active'] ) ? ' aria-current="page"' : '';
608 +
609 + $content .= sprintf(
610 + '<li class="lp-cb-sidebar__sub-menu-item %1$s%2$s"><a href="%3$s" class="lp-cb-sidebar__sub-menu-link"%4$s%5$s%6$s>%7$s<span class="lp-cb-sidebar__sub-menu-title">%8$s</span></a></li>',
611 + esc_attr( sanitize_html_class( $item['class'] ) ),
612 + esc_attr( $active_class ),
613 + esc_url( $item['url'] ),
614 + $target,
615 + $rel,
616 + $aria_current,
617 + $item['icon'],
618 + esc_html( $item['title'] )
619 + );
620 + }
621 +
622 + if ( '' === $content ) {
623 + return '';
624 + }
625 +
626 + return sprintf(
627 + '<ul id="%1$s" class="lp-cb-sidebar__sub-menu" aria-label="%2$s">%3$s</ul>',
628 + esc_attr( $sub_menu_id ),
629 + esc_attr( sprintf( __( '%s submenu', 'learnpress' ), $parent_title ) ),
630 + $content
631 + );
632 + }
633 +
634 + public function html_btn_add_new() {
635 + $tab_current = CourseBuilder::get_menu_current();
636 + $map_title = [
637 + 'courses' => __( 'Course', 'learnpress' ),
638 + 'lessons' => __( 'Lesson', 'learnpress' ),
639 + 'quizzes' => __( 'Quiz', 'learnpress' ),
640 + 'questions' => __( 'Question', 'learnpress' ),
641 + ];
642 +
643 + $map_type = [
644 + 'lessons' => 'lesson',
645 + 'quizzes' => 'quiz',
646 + 'questions' => 'question',
647 + ];
648 +
649 + $title = isset( $map_title[ $tab_current ] ) ? $map_title[ $tab_current ] : '';
650 + $type = isset( $map_type[ $tab_current ] ) ? $map_type[ $tab_current ] : '';
651 + $add_new = 'data-add-new-' . esc_attr( $type );
652 + $template_html = '';
653 + $template_attr = '';
654 +
655 + $btn_add_new = sprintf( '<button %s class="lp-button cb-btn-add-new">', $add_new );
656 + $btn_close = '</button>';
657 +
658 + if ( 'lessons' === $tab_current ) {
659 + $template_id = 'lp-tmpl-builder-popup-lesson-tab-new';
660 + $template_attr = sprintf(
661 + ' data-template="#%1$s" data-popup-type="lesson" data-popup-id="0"',
662 + esc_attr( $template_id )
663 + );
664 + $template_html = sprintf(
665 + '<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>',
666 + esc_attr( $template_id ),
667 + TemplateAJAX::load_content_via_ajax(
668 + [
669 + 'id_url' => 'builder-popup-lesson-tab-new',
670 + 'lesson_id' => 0,
671 + 'html_no_load_ajax_first' => sprintf(
672 + '<div class="lp-builder-popup__loader"><div class="lp-loading-circle"></div><span>%s</span></div>',
673 + esc_html__( 'Loading...', 'learnpress' )
674 + ),
675 + ],
676 + [
677 + 'class' => BuilderPopupTemplate::class,
678 + 'method' => 'render_lesson_popup',
679 + ]
680 + )
681 + );
682 + }
683 +
684 + if ( 'courses' === $tab_current ) {
685 + $btn_add_new = sprintf(
686 + '<a href="%s" class="lp-button cb-btn-add-new">',
687 + esc_url( CourseBuilder::get_link_add_new( 'courses' ) )
688 + );
689 + $btn_close = '</a>';
690 + }
691 +
692 + if ( 'quizzes' === $tab_current ) {
693 + $btn_add_new = sprintf(
694 + '<a href="%s" class="lp-button cb-btn-add-new">',
695 + esc_url( CourseBuilder::get_link_add_new( 'quizzes' ) )
696 + );
697 + $btn_close = '</a>';
698 + }
699 +
700 + if ( 'questions' === $tab_current ) {
701 + $btn_add_new = sprintf(
702 + '<a href="%s" class="lp-button cb-btn-add-new">',
703 + esc_url( CourseBuilder::get_link_add_new( 'questions' ) )
704 + );
705 + $btn_close = '</a>';
706 + }
707 +
708 + $btn = [
709 + 'wrapper' => str_replace( '>', $template_attr . '>', $btn_add_new ),
710 + 'content' => sprintf( '%s %s', __( 'Add New', 'learnpress' ), $title ),
711 + 'wrapper_end' => $btn_close,
712 + 'template' => $template_html,
713 + ];
714 + $btn = apply_filters( 'learn-press/course-builder/button-add-new', $btn, $tab_current, $type );
715 +
716 + return Template::combine_components( $btn );
717 + }
718 +
719 + /**
720 + * Show link to Course Builder in admin bar
721 + */
722 + public function add_admin_bar_menu( $wp_admin_bar ) {
723 + $href = CourseBuilder::get_link_course_builder();
724 + $title = esc_html__( 'Course Builder', 'learnpress' );
725 +
726 + // Check if on frontend single course page
727 + if ( is_singular( LP_COURSE_CPT ) && get_the_ID() ) {
728 + $title = esc_html__( 'Edit with Course Builder', 'learnpress' );
729 + $href = BuilderCourseTemplate::instance()->get_link_edit( get_the_ID() );
730 + }
731 +
732 + // Check if on admin edit course page (post.php or post-new.php)
733 + if ( is_admin() ) {
734 + global $post, $pagenow;
735 + if ( in_array( $pagenow, array( 'post.php', 'post-new.php' ), true ) ) {
736 + $post_type = '';
737 + if ( isset( $_GET['post_type'] ) ) {
738 + $post_type = sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
739 + } elseif ( isset( $_GET['post'] ) ) {
740 + $post_id = absint( $_GET['post'] );
741 + $post_type = get_post_type( $post_id );
742 + } elseif ( $post && isset( $post->post_type ) ) {
743 + $post_type = $post->post_type;
744 + }
745 +
746 + if ( LP_COURSE_CPT === $post_type ) {
747 + $title = esc_html__( 'Edit with Course Builder', 'learnpress' );
748 + if ( isset( $_GET['post'] ) ) {
749 + $href = BuilderCourseTemplate::instance()->get_link_edit( $_GET['post'] );
750 + } else {
751 + $href = CourseBuilder::get_link_add_new( 'courses' );
752 + }
753 + }
754 + }
755 + }
756 +
757 + $admin_bar_icon = LP_WP_Filesystem::get_icon_svg( 'ico-cb-admin-bar.svg' );
758 +
759 + $wp_admin_bar->add_node(
760 + array(
761 + 'id' => 'lp-course-builder',
762 + 'title' => sprintf(
763 + '<span class="lp-cb-admin-bar-icon">%1$s</span><span class="ab-label">%2$s</span>',
764 + $admin_bar_icon,
765 + $title
766 + ),
767 + 'href' => $href,
768 + )
769 + );
770 + }
771 +}