PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.3
Tutor LMS – eLearning and online course solution v4.0.3
4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / components / Modal.php
tutor / components Last commit date
Constants 3 weeks ago Accordion.php 6 days ago Alert.php 6 days ago AttachmentCard.php 6 days ago Avatar.php 6 days ago Badge.php 6 days ago BaseComponent.php 3 weeks ago Button.php 6 days ago ConfirmationModal.php 6 days ago CourseFilter.php 6 days ago DateFilter.php 6 days ago DropdownFilter.php 6 days ago EmptyState.php 6 days ago FileUploader.php 6 days ago InputField.php 6 days ago Modal.php 6 days ago Nav.php 6 days ago Pagination.php 6 days ago Popover.php 6 days ago PreviewTrigger.php 6 days ago Progress.php 6 days ago SearchFilter.php 6 days ago Sorting.php 6 days ago StarRating.php 6 days ago StarRatingInput.php 6 days ago StatusSelect.php 6 days ago SvgIcon.php 6 days ago Table.php 6 days ago Tabs.php 6 days ago Tooltip.php 6 days ago WPEditor.php 6 days ago
Modal.php
564 lines
1 <?php
2 /**
3 * Tutor Component: Modal
4 *
5 * Provides a fluent builder for rendering modals with Alpine.js integration.
6 *
7 * @package Tutor\Components
8 * @author Themeum
9 * @link https://themeum.com
10 * @since 4.0.0
11 */
12
13 namespace Tutor\Components;
14
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * Modal Component Class.
19 *
20 * ```
21 * // Basic modal with title and body text
22 * Modal::make()
23 * ->id( 'confirm-modal' )
24 * ->title( 'Confirm Submission' )
25 * ->body( '<p>Are you sure you want to submit this form?</p>' )
26 * ->render();
27 *
28 * // Modal with title, subtitle and footer buttons
29 * Modal::make()
30 * ->id( 'confirm-modal' )
31 * ->title( 'Confirm Submission' )
32 * ->subtitle( 'This action cannot be undone.' )
33 * ->body( 'All data will be saved permanently.' )
34 * ->footer_buttons(
35 * Button::make()->label( 'Cancel' )->variant( Variant::SECONDARY )->attr( '@click', "TutorCore.modal.closeModal('confirm-modal')" )->get() .
36 * Button::make()->label( 'Submit' )->variant( Variant::PRIMARY )->get()
37 * )
38 * ->footer_alignment( 'right' )
39 * ->render();
40 *
41 * // Modal loaded from a PHP template file
42 * Modal::make()
43 * ->id( 'course-modal' )
44 * ->title( 'Course Details' )
45 * ->template( get_template_directory() . '/partials/course-modal.php', array( 'course_id' => $course_id ) )
46 * ->render();
47 *
48 * // Non-closeable (headless) modal
49 * Modal::make()
50 * ->id( 'headless-modal' )
51 * ->closeable( false )
52 * ->body( '<h3>Processing payment, please wait...</h3>' )
53 * ->render();
54 *
55 * // Modal open by default (state = 'open')
56 * Modal::make()
57 * ->id( 'welcome-modal' )
58 * ->title( 'Welcome!' )
59 * ->body( 'Please review the onboarding checklist to get started.' )
60 * ->state( 'open' )
61 * ->render();
62 *
63 * // Modal with custom width
64 * Modal::make()
65 * ->id( 'wide-modal' )
66 * ->title( 'Course Builder' )
67 * ->body( '<div x-data="courseBuilder">...</div>' )
68 * ->width( '800px' )
69 * ->render();
70 *
71 * // Modal with title icon
72 * Modal::make()
73 * ->id( 'alert-modal' )
74 * ->title( 'Attention Required' )
75 * ->title_icon( Icon::WARNING )
76 * ->body( 'Your session is about to expire.' )
77 * ->render();
78 *
79 * // Footer buttons aligned left
80 * Modal::make()
81 * ->id( 'info-modal' )
82 * ->title( 'How it works' )
83 * ->body( '<p>Learn more about this feature.</p>' )
84 * ->footer_buttons( Button::make()->label( 'Got it' )->variant( Variant::PRIMARY )->get() )
85 * ->footer_alignment( 'left' )
86 * ->render();
87 *
88 * // Retrieve HTML without echoing
89 * $html = Modal::make()->id( 'my-modal' )->title( 'My Modal' )->body( 'Content' )->get();
90 * ```
91 *
92 * @since 4.0.0
93 */
94 class Modal extends BaseComponent {
95
96 /**
97 * Modal unique ID.
98 *
99 * @since 4.0.0
100 *
101 * @var string
102 */
103 protected $id = '';
104
105 /**
106 * Modal title.
107 *
108 * @since 4.0.0
109 *
110 * @var string
111 */
112 protected $title = '';
113
114 /**
115 * Modal title icon.
116 *
117 * @since 4.0.0
118 *
119 * @var string
120 */
121 protected $title_icon = '';
122
123 /**
124 * Modal title esc func.
125 *
126 * @since 4.0.0
127 *
128 * @var string
129 */
130 protected $title_esc_cb = 'esc_html';
131
132 /**
133 * Modal subtitle.
134 *
135 * @since 4.0.0
136 *
137 * @var string
138 */
139 protected $subtitle = '';
140
141 /**
142 * Modal subtitle esc func.
143 *
144 * @since 4.0.0
145 *
146 * @var string
147 */
148 protected $subtitle_esc_cb = 'esc_html';
149
150 /**
151 * Modal body content or template path.
152 *
153 * @since 4.0.0
154 *
155 * @var string
156 */
157 protected $body = '';
158
159 /**
160 * Modal body content esc callback.
161 *
162 * @since 4.0.0
163 *
164 * @var string
165 */
166 protected $body_esc_cb = 'wp_kses_post';
167
168 /**
169 * Whether body is a template path.
170 *
171 * @since 4.0.0
172 *
173 * @var bool
174 */
175 protected $is_template = false;
176
177 /**
178 * Template data.
179 *
180 * @since 4.0.0
181 *
182 * @var array
183 */
184 protected $template_data = array();
185
186 /**
187 * Footer buttons HTML.
188 *
189 * @since 4.0.0
190 *
191 * @var string
192 */
193 protected $footer_buttons = '';
194
195 /**
196 * Footer alignment (left|center|right).
197 *
198 * @since 4.0.0
199 *
200 * @var string
201 */
202 protected $footer_alignment = 'right';
203
204 /**
205 * Whether modal is closeable.
206 *
207 * @since 4.0.0
208 *
209 * @var bool
210 */
211 protected $closeable = true;
212
213 /**
214 * Initial modal state: 'open' or 'closed'.
215 *
216 * @since 4.0.0
217 *
218 * @var string
219 */
220 protected $state = 'closed';
221
222 /**
223 * Custom width for modal content.
224 *
225 * @since 4.0.0
226 *
227 * @var string
228 */
229 protected $width = '';
230
231 /**
232 * Set modal ID.
233 *
234 * @since 4.0.0
235 *
236 * @param string $id Modal unique ID.
237 *
238 * @return $this
239 */
240 public function id( $id ) {
241 $this->id = sanitize_key( $id );
242 return $this;
243 }
244
245 /**
246 * Set modal title.
247 *
248 * @since 4.0.0
249 *
250 * @param string $title Modal title.
251 * @param string $esc_cb Callable escaping function.
252 *
253 * @return $this
254 */
255 public function title( string $title, $esc_cb = 'esc_html' ) {
256 $this->title = $title;
257 $this->title_esc_cb = $esc_cb;
258 return $this;
259 }
260
261 /**
262 * Set modal title icon.
263 *
264 * @since 4.0.0
265 *
266 * @param string $title_icon the modal title icon name.
267 *
268 * @return $this
269 */
270 public function title_icon( string $title_icon ) {
271 $this->title_icon = $title_icon;
272 return $this;
273 }
274
275 /**
276 * Set modal subtitle.
277 *
278 * @since 4.0.0
279 *
280 * @param string $subtitle Modal subtitle.
281 * @param string $esc_cb Callable escaping function.
282 *
283 * @return $this
284 */
285 public function subtitle( string $subtitle, $esc_cb = 'esc_html' ) {
286 $this->subtitle = $subtitle;
287 $this->subtitle_esc_cb = $esc_cb;
288 return $this;
289 }
290
291 /**
292 * Set modal body content.
293 *
294 * @since 4.0.0
295 *
296 * @param string $body Body content HTML.
297 * @param string $esc_cb Body content HTML.
298 *
299 * @return $this
300 */
301 public function body( $body, $esc_cb = 'wp_kses_post' ) {
302 $this->body = $body;
303 $this->body_esc_cb = $esc_cb;
304 $this->is_template = false;
305 return $this;
306 }
307
308 /**
309 * Set modal body from template path.
310 *
311 * @since 4.0.0
312 *
313 * @param string $path Template file path.
314 * @param array $data Template data.
315 *
316 * @return $this
317 */
318 public function template( $path, $data = array() ) {
319 $this->body = $path;
320 $this->is_template = true;
321 $this->template_data = $data;
322 return $this;
323 }
324
325 /**
326 * Set footer buttons HTML.
327 *
328 * @since 4.0.0
329 *
330 * @param string $buttons Footer buttons HTML.
331 *
332 * @return $this
333 */
334 public function footer_buttons( $buttons ) {
335 $this->footer_buttons = $buttons;
336 return $this;
337 }
338
339 /**
340 * Set footer button alignment.
341 *
342 * @since 4.0.0
343 *
344 * @param string $alignment Alignment (left|center|right).
345 *
346 * @return $this
347 */
348 public function footer_alignment( $alignment ) {
349 $allowed = array( 'left', 'center', 'right' );
350 if ( in_array( $alignment, $allowed, true ) ) {
351 $this->footer_alignment = $alignment;
352 }
353 return $this;
354 }
355
356 /**
357 * Set whether modal is closeable.
358 *
359 * @since 4.0.0
360 *
361 * @param bool $closeable Whether modal can be closed.
362 *
363 * @return $this
364 */
365 public function closeable( $closeable = true ) {
366 $this->closeable = (bool) $closeable;
367 return $this;
368 }
369
370 /**
371 * Set initial modal state (open or closed).
372 *
373 * @since 4.0.0
374 *
375 * @param string $state Either 'open' or 'closed'. Default is 'closed'.
376 *
377 * @return $this
378 */
379 public function state( string $state ) {
380 $this->state = 'open' === $state ? 'open' : 'closed';
381 return $this;
382 }
383
384 /**
385 * Set custom width for modal content.
386 *
387 * @since 4.0.0
388 *
389 * @param string $width Width value (e.g., '354px', '50%').
390 *
391 * @return $this
392 */
393 public function width( $width ) {
394 $this->width = $width;
395 return $this;
396 }
397
398 /**
399 * Render modal header.
400 *
401 * @since 4.0.0
402 *
403 * @return string Header HTML.
404 */
405 protected function render_header() {
406 if ( empty( $this->title ) && empty( $this->subtitle ) ) {
407 return '';
408 }
409
410 $icon = '';
411
412 $title_html = $this->title
413 ? sprintf( '<div class="tutor-modal-title">%s</div>', $this->esc( $this->title, $this->title_esc_cb ) )
414 : '';
415
416 $subtitle_html = $this->subtitle
417 ? sprintf( '<div class="tutor-modal-subtitle">%s</div>', $this->esc( $this->subtitle, $this->subtitle_esc_cb ) )
418 : '';
419
420 if ( ! empty( $this->title_icon ) ) {
421 ob_start();
422 SvgIcon::make()->name( $this->title_icon )->size( 24 )->render();
423 $icon = ob_get_clean();
424
425 return sprintf(
426 '<div class="tutor-modal-header">
427 <div class="tutor-flex tutor-items-center tutor-gap-4">
428 %3$s%1$s
429 </div>
430 %2$s
431 </div>',
432 $title_html,
433 $subtitle_html,
434 $icon,
435 );
436
437 }
438
439 return sprintf(
440 '<div class="tutor-modal-header">%s%s</div>',
441 $title_html,
442 $subtitle_html
443 );
444 }
445
446 /**
447 * Render modal body.
448 *
449 * @since 4.0.0
450 *
451 * @return string Body HTML.
452 */
453 protected function render_body() {
454 if ( empty( $this->body ) ) {
455 return '';
456 }
457
458 $content = '';
459
460 if ( $this->is_template ) {
461 if ( file_exists( $this->body ) ) {
462 ob_start();
463 $data = $this->template_data;
464 include $this->body;
465 $content = ob_get_clean();
466 return $content ? $content : '';
467 }
468 } else {
469 $content = $this->esc( $this->body, $this->body_esc_cb );
470 }
471
472 return sprintf( '<div class="tutor-modal-body">%s</div>', $content );
473 }
474
475 /**
476 * Render modal footer.
477 *
478 * @since 4.0.0
479 *
480 * @return string Footer HTML.
481 */
482 protected function render_footer() {
483 if ( empty( $this->footer_buttons ) ) {
484 return '';
485 }
486
487 $alignment_class = '';
488 if ( 'left' === $this->footer_alignment ) {
489 $alignment_class = ' tutor-justify-start';
490 } elseif ( 'center' === $this->footer_alignment ) {
491 $alignment_class = ' tutor-justify-center';
492 }
493
494 return sprintf(
495 '<div class="tutor-modal-footer%s">%s</div>',
496 esc_attr( $alignment_class ),
497 $this->footer_buttons
498 );
499 }
500
501 /**
502 * Get the modal HTML.
503 *
504 * @since 4.0.0
505 *
506 * @return string HTML output.
507 */
508 public function get(): string {
509 if ( empty( $this->id ) ) {
510 return '';
511 }
512
513 // Build Alpine.js x-data.
514 $alpine_data = array(
515 'id' => $this->id,
516 'initialOpen' => 'open' === $this->state,
517 );
518 if ( ! $this->closeable ) {
519 $alpine_data['isCloseable'] = false;
520 }
521
522 $alpine_json = wp_json_encode( $alpine_data );
523
524 // Build close button.
525 $close_button = $this->closeable
526 ? '<button x-data="tutorIcon({ name: \'cross\', width: 16, height: 16})" x-bind="getCloseButtonBindings()" aria-label="' . esc_attr__( 'Close', 'tutor' ) . '"></button>'
527 : '';
528
529 // Build style attribute for custom width.
530 $style_attr = $this->width
531 ? sprintf( ' style="max-width: %s;"', esc_attr( $this->width ) )
532 : '';
533
534 // Build modal content.
535 $header = $this->render_header();
536 $body = $this->render_body();
537 $footer = $this->render_footer();
538
539 $this->component_string = sprintf(
540 '<div x-data="tutorModal(%s)" x-cloak style="display: none;">
541 <template x-teleport="body">
542 <div x-bind="getModalBindings()">
543 <div x-bind="getBackdropBindings()"></div>
544 <div x-bind="getModalContentBindings()"%s>
545 %s
546 %s
547 %s
548 %s
549 </div>
550 </div>
551 </template>
552 </div>',
553 esc_attr( $alpine_json ),
554 $style_attr,
555 $close_button,
556 $header,
557 $body,
558 $footer
559 );
560
561 return $this->component_string;
562 }
563 }
564