PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / theme-builder / Widgets / Post_Comments.php

Post_Comments.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/theme-builder/Widgets/Post_Comments.php

1,230 lines 34.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Modules\ThemeBuilder\Widgets;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit; // Exit if accessed directly.
7 }
8
9 use Elementor\Controls_Manager;
10 use Elementor\Group_Control_Border;
11 use Elementor\Group_Control_Typography;
12 use Elementor\Plugin;
13 use Elementor\Widget_Base;
14
15 /**
16 * Templately "Post Comments" widget.
17 *
18 * Feature parity with Elementor Pro's Post Comments widget (source: current or
19 * custom post, full native WordPress comment thread — avatars, nested replies,
20 * reply links, pagination and the comment form, plus a "comments are closed"
21 * editor notice). Unlike the Pro widget — which delegates markup and styling to
22 * the active theme via `comments_template()` — this widget renders its own markup
23 * and ships a base skin (matching the Templately Figma design) that is fully
24 * customisable through the Elementor Style tab.
25 */
26 class Post_Comments extends Widget_Base {
27
28 public function get_name() {
29 return 'tl-post-comments';
30 }
31
32 public function get_title() {
33 return esc_html__( 'Post Comments', 'templately' );
34 }
35
36 public function get_icon() {
37 return 'eicon-comments templately-widget-icon';
38 }
39
40 public function get_categories() {
41 return [ 'templately' ];
42 }
43
44 // Hidden from the widget panel by default — surfaced per-document for the
45 // templately_library CPT via ThemeBuilder::elementor_document_config().
46 public function show_in_panel() {
47 return false;
48 }
49
50 public function get_keywords() {
51 return [ 'comments', 'post', 'response', 'form', 'discussion' ];
52 }
53
54 public function get_style_depends() {
55 return [ 'templately-comment-widget' ];
56 }
57
58 public function get_script_depends() {
59 return [ 'comment-reply' ];
60 }
61
62 /* ---------------------------------------------------------------------------
63 * Controls
64 * ------------------------------------------------------------------------- */
65
66 protected function register_controls() {
67 $this->register_content_controls();
68 $this->register_style_controls();
69 }
70
71 protected function register_content_controls() {
72 // ── Comments ──────────────────────────────────────────────────────────
73 $this->start_controls_section(
74 'section_comments',
75 [
76 'label' => esc_html__( 'Comments', 'templately' ),
77 ]
78 );
79
80 $this->add_control(
81 'source_type',
82 [
83 'label' => esc_html__( 'Source', 'templately' ),
84 'type' => Controls_Manager::SELECT,
85 'options' => [
86 'current' => esc_html__( 'Current Post', 'templately' ),
87 'custom' => esc_html__( 'Custom', 'templately' ),
88 ],
89 'default' => 'current',
90 ]
91 );
92
93 $this->add_control(
94 'source_custom',
95 [
96 'label' => esc_html__( 'Search & Select', 'templately' ),
97 'type' => Controls_Manager::SELECT2,
98 'label_block' => true,
99 'options' => $this->get_post_options(),
100 'condition' => [
101 'source_type' => 'custom',
102 ],
103 ]
104 );
105
106 $this->add_control(
107 'heading_text',
108 [
109 'label' => esc_html__( 'Title', 'templately' ),
110 'type' => Controls_Manager::TEXT,
111 'default' => esc_html__( 'Comments', 'templately' ),
112 'separator' => 'before',
113 ]
114 );
115
116 $this->add_control(
117 'title_tag',
118 [
119 'label' => esc_html__( 'Title HTML Tag', 'templately' ),
120 'type' => Controls_Manager::SELECT,
121 'options' => [
122 'h1' => 'H1',
123 'h2' => 'H2',
124 'h3' => 'H3',
125 'h4' => 'H4',
126 'h5' => 'H5',
127 'h6' => 'H6',
128 'div' => 'div',
129 'span' => 'span',
130 'p' => 'p',
131 ],
132 'default' => 'h3',
133 ]
134 );
135
136 $this->add_control(
137 'show_count',
138 [
139 'label' => esc_html__( 'Show Count', 'templately' ),
140 'type' => Controls_Manager::SWITCHER,
141 'label_on' => esc_html__( 'Show', 'templately' ),
142 'label_off' => esc_html__( 'Hide', 'templately' ),
143 'return_value' => 'yes',
144 'default' => 'yes',
145 ]
146 );
147
148 $this->add_control(
149 'count_position',
150 [
151 'label' => esc_html__( 'Count Position', 'templately' ),
152 'type' => Controls_Manager::SELECT,
153 'options' => [
154 'after' => esc_html__( 'After title — Comments (2)', 'templately' ),
155 'before' => esc_html__( 'Before title — 2 Comments', 'templately' ),
156 ],
157 'default' => 'after',
158 'condition' => [
159 'show_count' => 'yes',
160 ],
161 ]
162 );
163
164 $this->add_control(
165 'count_include_replies',
166 [
167 'label' => esc_html__( 'Count Replies', 'templately' ),
168 'description' => esc_html__( 'Include nested replies in the count. When off, only top-level comments are counted (matches the number of visible comment cards).', 'templately' ),
169 'type' => Controls_Manager::SWITCHER,
170 'label_on' => esc_html__( 'Yes', 'templately' ),
171 'label_off' => esc_html__( 'No', 'templately' ),
172 'return_value' => 'yes',
173 'default' => '',
174 'condition' => [
175 'show_count' => 'yes',
176 ],
177 ]
178 );
179
180 $this->add_control(
181 'show_avatar',
182 [
183 'label' => esc_html__( 'Show Avatar', 'templately' ),
184 'type' => Controls_Manager::SWITCHER,
185 'return_value' => 'yes',
186 'default' => 'yes',
187 ]
188 );
189
190 $this->add_control(
191 'avatar_size',
192 [
193 'label' => esc_html__( 'Avatar Size', 'templately' ),
194 'type' => Controls_Manager::NUMBER,
195 'default' => 65,
196 'min' => 16,
197 'max' => 200,
198 'condition' => [
199 'show_avatar' => 'yes',
200 ],
201 ]
202 );
203
204 $this->add_control(
205 'show_reply',
206 [
207 'label' => esc_html__( 'Show Reply Link', 'templately' ),
208 'type' => Controls_Manager::SWITCHER,
209 'return_value' => 'yes',
210 'default' => 'yes',
211 ]
212 );
213
214 $this->add_control(
215 'order',
216 [
217 'label' => esc_html__( 'Order', 'templately' ),
218 'type' => Controls_Manager::SELECT,
219 'options' => [
220 'ASC' => esc_html__( 'Oldest First', 'templately' ),
221 'DESC' => esc_html__( 'Newest First', 'templately' ),
222 ],
223 'default' => 'ASC',
224 ]
225 );
226
227 $this->end_controls_section();
228
229 // ── Comment Form ──────────────────────────────────────────────────────
230 $this->start_controls_section(
231 'section_form',
232 [
233 'label' => esc_html__( 'Comment Form', 'templately' ),
234 ]
235 );
236
237 $this->add_control(
238 'show_form',
239 [
240 'label' => esc_html__( 'Show Form', 'templately' ),
241 'type' => Controls_Manager::SWITCHER,
242 'return_value' => 'yes',
243 'default' => 'yes',
244 ]
245 );
246
247 $this->add_control(
248 'form_title',
249 [
250 'label' => esc_html__( 'Form Title', 'templately' ),
251 'type' => Controls_Manager::TEXT,
252 'default' => esc_html__( 'Leave a comment', 'templately' ),
253 'condition' => [
254 'show_form' => 'yes',
255 ],
256 ]
257 );
258
259 $this->add_control(
260 'form_layout',
261 [
262 'label' => esc_html__( 'Form Layout', 'templately' ),
263 'type' => Controls_Manager::SELECT,
264 'options' => [
265 'inline' => esc_html__( 'Inline — Name & Email side by side', 'templately' ),
266 'stacked' => esc_html__( 'Stacked — one field per row', 'templately' ),
267 ],
268 'default' => 'inline',
269 'selectors' => [
270 '{{WRAPPER}} .tl-post-comments' => '--tl-form-col: {{VALUE}};',
271 ],
272 'selectors_dictionary' => [
273 'inline' => 'calc(50% - 8px)',
274 'stacked' => '100%',
275 ],
276 'condition' => [
277 'show_form' => 'yes',
278 ],
279 ]
280 );
281
282 $this->add_control(
283 'button_text',
284 [
285 'label' => esc_html__( 'Submit Button Text', 'templately' ),
286 'type' => Controls_Manager::TEXT,
287 'default' => esc_html__( 'Post Comment', 'templately' ),
288 'condition' => [
289 'show_form' => 'yes',
290 ],
291 ]
292 );
293
294 $this->end_controls_section();
295 }
296
297 protected function register_style_controls() {
298 // ── Comment Box ───────────────────────────────────────────────────────
299 $this->start_controls_section(
300 'section_style_box',
301 [
302 'label' => esc_html__( 'Comment Box', 'templately' ),
303 'tab' => Controls_Manager::TAB_STYLE,
304 ]
305 );
306
307 $this->add_control(
308 'box_bg_color',
309 [
310 'label' => esc_html__( 'Background Color', 'templately' ),
311 'type' => Controls_Manager::COLOR,
312 'selectors' => [
313 '{{WRAPPER}} .tl-post-comments' => '--tl-comment-box-bg: {{VALUE}};',
314 ],
315 ]
316 );
317
318 $this->add_responsive_control(
319 'box_padding',
320 [
321 'label' => esc_html__( 'Padding', 'templately' ),
322 'type' => Controls_Manager::DIMENSIONS,
323 'size_units' => [ 'px', 'em', 'rem' ],
324 'selectors' => [
325 '{{WRAPPER}} .tl-comment__inner' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
326 ],
327 ]
328 );
329
330 $this->add_responsive_control(
331 'box_radius',
332 [
333 'label' => esc_html__( 'Border Radius', 'templately' ),
334 'type' => Controls_Manager::DIMENSIONS,
335 'size_units' => [ 'px', '%' ],
336 'selectors' => [
337 '{{WRAPPER}} .tl-comment__inner' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
338 ],
339 ]
340 );
341
342 $this->add_group_control(
343 Group_Control_Border::get_type(),
344 [
345 'name' => 'box_border',
346 'selector' => '{{WRAPPER}} .tl-comment__inner',
347 ]
348 );
349
350 $this->add_responsive_control(
351 'box_gap',
352 [
353 'label' => esc_html__( 'Space Between Comments', 'templately' ),
354 'type' => Controls_Manager::SLIDER,
355 'size_units' => [ 'px', 'em', 'rem' ],
356 'range' => [
357 'px' => [ 'min' => 0, 'max' => 100 ],
358 ],
359 'selectors' => [
360 '{{WRAPPER}} .tl-post-comments' => '--tl-comment-gap: {{SIZE}}{{UNIT}};',
361 ],
362 ]
363 );
364
365 $this->end_controls_section();
366
367 // ── Title ─────────────────────────────────────────────────────────────
368 $this->start_controls_section(
369 'section_style_title',
370 [
371 'label' => esc_html__( 'Title', 'templately' ),
372 'tab' => Controls_Manager::TAB_STYLE,
373 ]
374 );
375
376 $this->add_control(
377 'title_color',
378 [
379 'label' => esc_html__( 'Color', 'templately' ),
380 'type' => Controls_Manager::COLOR,
381 'selectors' => [
382 '{{WRAPPER}} .tl-post-comments__title' => 'color: {{VALUE}};',
383 ],
384 ]
385 );
386
387 $this->add_group_control(
388 Group_Control_Typography::get_type(),
389 [
390 'name' => 'title_typography',
391 'selector' => '{{WRAPPER}} .tl-post-comments__title',
392 ]
393 );
394
395 $this->add_responsive_control(
396 'title_spacing',
397 [
398 'label' => esc_html__( 'Spacing', 'templately' ),
399 'type' => Controls_Manager::SLIDER,
400 'size_units' => [ 'px', 'em', 'rem' ],
401 'range' => [
402 'px' => [ 'min' => 0, 'max' => 100 ],
403 ],
404 'selectors' => [
405 '{{WRAPPER}} .tl-post-comments__title' => 'margin-bottom: {{SIZE}}{{UNIT}};',
406 ],
407 ]
408 );
409
410 $this->end_controls_section();
411
412 // ── Avatar ────────────────────────────────────────────────────────────
413 $this->start_controls_section(
414 'section_style_avatar',
415 [
416 'label' => esc_html__( 'Avatar', 'templately' ),
417 'tab' => Controls_Manager::TAB_STYLE,
418 'condition' => [
419 'show_avatar' => 'yes',
420 ],
421 ]
422 );
423
424 $this->add_responsive_control(
425 'avatar_radius',
426 [
427 'label' => esc_html__( 'Border Radius', 'templately' ),
428 'type' => Controls_Manager::DIMENSIONS,
429 'size_units' => [ 'px', '%' ],
430 'selectors' => [
431 '{{WRAPPER}} .tl-comment__avatar img' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
432 ],
433 ]
434 );
435
436 $this->add_responsive_control(
437 'avatar_gap',
438 [
439 'label' => esc_html__( 'Spacing', 'templately' ),
440 'type' => Controls_Manager::SLIDER,
441 'size_units' => [ 'px', 'em', 'rem' ],
442 'range' => [
443 'px' => [ 'min' => 0, 'max' => 100 ],
444 ],
445 'selectors' => [
446 '{{WRAPPER}} .tl-post-comments' => '--tl-comment-avatar-gap: {{SIZE}}{{UNIT}};',
447 ],
448 ]
449 );
450
451 $this->end_controls_section();
452
453 // ── Author ────────────────────────────────────────────────────────────
454 $this->start_controls_section(
455 'section_style_author',
456 [
457 'label' => esc_html__( 'Author Name', 'templately' ),
458 'tab' => Controls_Manager::TAB_STYLE,
459 ]
460 );
461
462 $this->add_control(
463 'author_color',
464 [
465 'label' => esc_html__( 'Color', 'templately' ),
466 'type' => Controls_Manager::COLOR,
467 'selectors' => [
468 '{{WRAPPER}} .tl-comment__author, {{WRAPPER}} .tl-comment__author a' => 'color: {{VALUE}};',
469 ],
470 ]
471 );
472
473 $this->add_group_control(
474 Group_Control_Typography::get_type(),
475 [
476 'name' => 'author_typography',
477 'selector' => '{{WRAPPER}} .tl-comment__author',
478 ]
479 );
480
481 $this->end_controls_section();
482
483 // ── Date / Meta ───────────────────────────────────────────────────────
484 $this->start_controls_section(
485 'section_style_date',
486 [
487 'label' => esc_html__( 'Date', 'templately' ),
488 'tab' => Controls_Manager::TAB_STYLE,
489 ]
490 );
491
492 $this->add_control(
493 'date_color',
494 [
495 'label' => esc_html__( 'Color', 'templately' ),
496 'type' => Controls_Manager::COLOR,
497 'selectors' => [
498 '{{WRAPPER}} .tl-comment__date' => 'color: {{VALUE}};',
499 ],
500 ]
501 );
502
503 $this->add_group_control(
504 Group_Control_Typography::get_type(),
505 [
506 'name' => 'date_typography',
507 'selector' => '{{WRAPPER}} .tl-comment__date',
508 ]
509 );
510
511 $this->end_controls_section();
512
513 // ── Comment Text ──────────────────────────────────────────────────────
514 $this->start_controls_section(
515 'section_style_text',
516 [
517 'label' => esc_html__( 'Comment Text', 'templately' ),
518 'tab' => Controls_Manager::TAB_STYLE,
519 ]
520 );
521
522 $this->add_control(
523 'text_color',
524 [
525 'label' => esc_html__( 'Color', 'templately' ),
526 'type' => Controls_Manager::COLOR,
527 'selectors' => [
528 '{{WRAPPER}} .tl-comment__text' => 'color: {{VALUE}};',
529 ],
530 ]
531 );
532
533 $this->add_group_control(
534 Group_Control_Typography::get_type(),
535 [
536 'name' => 'text_typography',
537 'selector' => '{{WRAPPER}} .tl-comment__text',
538 ]
539 );
540
541 $this->end_controls_section();
542
543 // ── Reply Link ────────────────────────────────────────────────────────
544 $this->start_controls_section(
545 'section_style_reply',
546 [
547 'label' => esc_html__( 'Reply Link', 'templately' ),
548 'tab' => Controls_Manager::TAB_STYLE,
549 'condition' => [
550 'show_reply' => 'yes',
551 ],
552 ]
553 );
554
555 $this->add_control(
556 'reply_color',
557 [
558 'label' => esc_html__( 'Color', 'templately' ),
559 'type' => Controls_Manager::COLOR,
560 'selectors' => [
561 '{{WRAPPER}} .tl-comment__reply a' => 'color: {{VALUE}};',
562 ],
563 ]
564 );
565
566 $this->add_control(
567 'reply_hover_color',
568 [
569 'label' => esc_html__( 'Hover Color', 'templately' ),
570 'type' => Controls_Manager::COLOR,
571 'selectors' => [
572 '{{WRAPPER}} .tl-comment__reply a:hover' => 'color: {{VALUE}};',
573 ],
574 ]
575 );
576
577 $this->add_group_control(
578 Group_Control_Typography::get_type(),
579 [
580 'name' => 'reply_typography',
581 'selector' => '{{WRAPPER}} .tl-comment__reply a',
582 ]
583 );
584
585 $this->end_controls_section();
586
587 // ── Form ──────────────────────────────────────────────────────────────
588 $this->start_controls_section(
589 'section_style_form',
590 [
591 'label' => esc_html__( 'Form', 'templately' ),
592 'tab' => Controls_Manager::TAB_STYLE,
593 'condition' => [
594 'show_form' => 'yes',
595 ],
596 ]
597 );
598
599 $this->add_control(
600 'form_bg_color',
601 [
602 'label' => esc_html__( 'Background Color', 'templately' ),
603 'type' => Controls_Manager::COLOR,
604 'selectors' => [
605 '{{WRAPPER}} .tl-post-comments' => '--tl-form-bg: {{VALUE}};',
606 ],
607 ]
608 );
609
610 $this->add_responsive_control(
611 'form_padding',
612 [
613 'label' => esc_html__( 'Padding', 'templately' ),
614 'type' => Controls_Manager::DIMENSIONS,
615 'size_units' => [ 'px', 'em', 'rem' ],
616 'selectors' => [
617 '{{WRAPPER}} .comment-respond, {{WRAPPER}} .tl-post-comments__form' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
618 ],
619 ]
620 );
621
622 $this->add_control(
623 'form_title_heading',
624 [
625 'label' => esc_html__( 'Form Title', 'templately' ),
626 'type' => Controls_Manager::HEADING,
627 'separator' => 'before',
628 ]
629 );
630
631 $this->add_control(
632 'form_title_color',
633 [
634 'label' => esc_html__( 'Title Color', 'templately' ),
635 'type' => Controls_Manager::COLOR,
636 'selectors' => [
637 '{{WRAPPER}} .comment-reply-title, {{WRAPPER}} .tl-post-comments__form-title' => 'color: {{VALUE}};',
638 ],
639 ]
640 );
641
642 $this->add_group_control(
643 Group_Control_Typography::get_type(),
644 [
645 'name' => 'form_title_typography',
646 'selector' => '{{WRAPPER}} .comment-reply-title, {{WRAPPER}} .tl-post-comments__form-title',
647 ]
648 );
649
650 $this->add_control(
651 'label_heading',
652 [
653 'label' => esc_html__( 'Field Labels', 'templately' ),
654 'type' => Controls_Manager::HEADING,
655 'separator' => 'before',
656 ]
657 );
658
659 $this->add_control(
660 'label_color',
661 [
662 'label' => esc_html__( 'Label Color', 'templately' ),
663 'type' => Controls_Manager::COLOR,
664 'selectors' => [
665 '{{WRAPPER}} .comment-form label' => 'color: {{VALUE}};',
666 ],
667 ]
668 );
669
670 $this->add_group_control(
671 Group_Control_Typography::get_type(),
672 [
673 'name' => 'label_typography',
674 'selector' => '{{WRAPPER}} .comment-form label',
675 ]
676 );
677
678 $this->add_control(
679 'input_heading',
680 [
681 'label' => esc_html__( 'Inputs', 'templately' ),
682 'type' => Controls_Manager::HEADING,
683 'separator' => 'before',
684 ]
685 );
686
687 $this->add_control(
688 'input_bg_color',
689 [
690 'label' => esc_html__( 'Background Color', 'templately' ),
691 'type' => Controls_Manager::COLOR,
692 'selectors' => [
693 '{{WRAPPER}} .tl-post-comments' => '--tl-input-bg: {{VALUE}};',
694 ],
695 ]
696 );
697
698 $this->add_control(
699 'input_text_color',
700 [
701 'label' => esc_html__( 'Text Color', 'templately' ),
702 'type' => Controls_Manager::COLOR,
703 'selectors' => [
704 '{{WRAPPER}} .tl-post-comments' => '--tl-input-color: {{VALUE}};',
705 ],
706 ]
707 );
708
709 $this->add_control(
710 'input_placeholder_color',
711 [
712 'label' => esc_html__( 'Placeholder Color', 'templately' ),
713 'type' => Controls_Manager::COLOR,
714 'selectors' => [
715 '{{WRAPPER}} .tl-post-comments' => '--tl-input-placeholder: {{VALUE}};',
716 ],
717 ]
718 );
719
720 $this->add_control(
721 'input_border_color',
722 [
723 'label' => esc_html__( 'Border Color', 'templately' ),
724 'type' => Controls_Manager::COLOR,
725 'selectors' => [
726 '{{WRAPPER}} .tl-post-comments' => '--tl-input-border-color: {{VALUE}};',
727 ],
728 ]
729 );
730
731 $this->add_responsive_control(
732 'input_radius',
733 [
734 'label' => esc_html__( 'Border Radius', 'templately' ),
735 'type' => Controls_Manager::SLIDER,
736 'size_units' => [ 'px', '%' ],
737 'range' => [
738 'px' => [ 'min' => 0, 'max' => 50 ],
739 ],
740 'selectors' => [
741 '{{WRAPPER}} .tl-post-comments' => '--tl-input-radius: {{SIZE}}{{UNIT}};',
742 ],
743 ]
744 );
745
746 $this->end_controls_section();
747
748 // ── Submit Button ─────────────────────────────────────────────────────
749 $this->start_controls_section(
750 'section_style_button',
751 [
752 'label' => esc_html__( 'Submit Button', 'templately' ),
753 'tab' => Controls_Manager::TAB_STYLE,
754 'condition' => [
755 'show_form' => 'yes',
756 ],
757 ]
758 );
759
760 $this->add_group_control(
761 Group_Control_Typography::get_type(),
762 [
763 'name' => 'button_typography',
764 'selector' => '{{WRAPPER}} .tl-post-comments__submit, {{WRAPPER}} .form-submit input[type="submit"]',
765 ]
766 );
767
768 $this->add_responsive_control(
769 'button_padding',
770 [
771 'label' => esc_html__( 'Padding', 'templately' ),
772 'type' => Controls_Manager::DIMENSIONS,
773 'size_units' => [ 'px', 'em', 'rem' ],
774 'selectors' => [
775 '{{WRAPPER}} .tl-post-comments__submit, {{WRAPPER}} .form-submit input[type="submit"]' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
776 ],
777 ]
778 );
779
780 $this->add_responsive_control(
781 'button_radius',
782 [
783 'label' => esc_html__( 'Border Radius', 'templately' ),
784 'type' => Controls_Manager::SLIDER,
785 'size_units' => [ 'px', '%' ],
786 'range' => [
787 'px' => [ 'min' => 0, 'max' => 50 ],
788 ],
789 'selectors' => [
790 '{{WRAPPER}} .tl-post-comments' => '--tl-btn-radius: {{SIZE}}{{UNIT}};',
791 ],
792 ]
793 );
794
795 $this->start_controls_tabs( 'button_tabs' );
796
797 $this->start_controls_tab(
798 'button_tab_normal',
799 [
800 'label' => esc_html__( 'Normal', 'templately' ),
801 ]
802 );
803
804 $this->add_control(
805 'button_color',
806 [
807 'label' => esc_html__( 'Text Color', 'templately' ),
808 'type' => Controls_Manager::COLOR,
809 'selectors' => [
810 '{{WRAPPER}} .tl-post-comments' => '--tl-btn-color: {{VALUE}};',
811 ],
812 ]
813 );
814
815 $this->add_control(
816 'button_bg_color',
817 [
818 'label' => esc_html__( 'Background Color', 'templately' ),
819 'type' => Controls_Manager::COLOR,
820 'selectors' => [
821 '{{WRAPPER}} .tl-post-comments' => '--tl-btn-bg: {{VALUE}};',
822 ],
823 ]
824 );
825
826 $this->end_controls_tab();
827
828 $this->start_controls_tab(
829 'button_tab_hover',
830 [
831 'label' => esc_html__( 'Hover', 'templately' ),
832 ]
833 );
834
835 $this->add_control(
836 'button_color_hover',
837 [
838 'label' => esc_html__( 'Text Color', 'templately' ),
839 'type' => Controls_Manager::COLOR,
840 'selectors' => [
841 '{{WRAPPER}} .tl-post-comments' => '--tl-btn-color-hover: {{VALUE}};',
842 ],
843 ]
844 );
845
846 $this->add_control(
847 'button_bg_color_hover',
848 [
849 'label' => esc_html__( 'Background Color', 'templately' ),
850 'type' => Controls_Manager::COLOR,
851 'selectors' => [
852 '{{WRAPPER}} .tl-post-comments' => '--tl-btn-bg-hover: {{VALUE}};',
853 ],
854 ]
855 );
856
857 $this->end_controls_tab();
858
859 $this->end_controls_tabs();
860
861 $this->end_controls_section();
862 }
863
864 /* ---------------------------------------------------------------------------
865 * Helpers
866 * ------------------------------------------------------------------------- */
867
868 /**
869 * Build a small option list of recent posts/pages for the "Custom" source.
870 *
871 * @return array<int|string, string>
872 */
873 protected function get_post_options() {
874 $options = [ '' => esc_html__( 'Select a post…', 'templately' ) ];
875
876 $posts = get_posts( [
877 'post_type' => [ 'post', 'page' ],
878 'post_status' => 'publish',
879 'numberposts' => 100,
880 'orderby' => 'date',
881 'order' => 'DESC',
882 ] );
883
884 foreach ( $posts as $post ) {
885 $options[ $post->ID ] = $post->post_title !== '' ? $post->post_title : sprintf( '#%d', $post->ID );
886 }
887
888 return $options;
889 }
890
891 /**
892 * Resolve the post whose comments should be rendered.
893 *
894 * Mirrors the dynamic-preview pattern used by the other Templately theme
895 * builder widgets: in the editor / library preview we fall back to the latest
896 * post so there is something to display.
897 *
898 * @return int
899 */
900 protected function get_target_post_id() {
901 $settings = $this->get_settings_for_display();
902
903 if ( 'custom' === ( $settings['source_type'] ?? 'current' ) && ! empty( $settings['source_custom'] ) ) {
904 return (int) $settings['source_custom'];
905 }
906
907 $is_edit_mode = Plugin::$instance->editor->is_edit_mode();
908 $is_preview = isset( $_GET['templately_library'], $_GET['preview_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
909
910 if ( $is_edit_mode || $is_preview ) {
911 $latest = get_posts( 'post_type=post&numberposts=1' );
912 if ( ! empty( $latest ) ) {
913 return (int) $latest[0]->ID;
914 }
915 }
916
917 return (int) get_the_ID();
918 }
919
920 /* ---------------------------------------------------------------------------
921 * Render
922 * ------------------------------------------------------------------------- */
923
924 protected function render() {
925 $settings = $this->get_settings_for_display();
926 $post_id = $this->get_target_post_id();
927
928 $is_editor = Plugin::$instance->editor->is_edit_mode() || Plugin::$instance->preview->is_preview_mode();
929
930 echo '<div class="tl-post-comments">';
931
932 if ( $is_editor && ! comments_open( $post_id ) ) {
933 // Feature parity with Elementor Pro: notify the editor when comments are off.
934 $this->render_closed_notice();
935 echo '</div>';
936 return;
937 }
938
939 global $post, $wp_query;
940 $original_post = $post;
941 $original_comments = $wp_query->comments ?? null;
942
943 // Establish post context for comment_form() / template tags.
944 $post = get_post( $post_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
945 if ( $post instanceof \WP_Post ) {
946 setup_postdata( $post );
947 }
948
949 $comments = get_comments( [
950 'post_id' => $post_id,
951 'status' => 'approve',
952 'order' => 'ASC', // Threading walker re-orders; top-level order applied below.
953 ] );
954
955 // By default the heading counts only top-level comments so the number
956 // matches the visible comment cards — nested replies are threaded inside a
957 // card, not counted as separate responses. The "Count Replies" control
958 // opts into WordPress' behaviour of counting every approved comment.
959 if ( 'yes' === ( $settings['count_include_replies'] ?? '' ) ) {
960 $count = count( $comments );
961 } else {
962 $count = 0;
963 foreach ( $comments as $_comment ) {
964 if ( 0 === (int) $_comment->comment_parent ) {
965 $count++;
966 }
967 }
968 }
969
970 $this->render_title( $settings, $count );
971
972 if ( ! empty( $comments ) ) {
973 $this->render_list( $settings, $comments );
974 $this->render_pagination();
975 }
976
977 if ( 'yes' === ( $settings['show_form'] ?? 'yes' ) ) {
978 if ( comments_open( $post_id ) ) {
979 // Inside the Elementor editor canvas the form is a preview only —
980 // disable interaction so fields can't be typed into or submitted by
981 // accident. It stays fully functional on the live site.
982 if ( $is_editor ) {
983 echo '<div class="tl-post-comments__form-preview" style="pointer-events:none;" aria-hidden="true" tabindex="-1">';
984 }
985
986 $this->render_form( $settings, $post_id );
987
988 if ( $is_editor ) {
989 echo '</div>';
990 }
991 } else {
992 printf(
993 '<p class="tl-post-comments__notice">%s</p>',
994 esc_html__( 'Comments are closed.', 'templately' )
995 );
996 }
997 }
998
999 // Restore globals.
1000 wp_reset_postdata();
1001 $post = $original_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1002 if ( null !== $original_comments ) {
1003 $wp_query->comments = $original_comments; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1004 }
1005
1006 echo '</div>';
1007 }
1008
1009 protected function render_closed_notice() {
1010 ?>
1011 <div class="elementor-alert elementor-alert-danger" role="alert">
1012 <span class="elementor-alert-title">
1013 <?php echo esc_html__( 'Comments are closed.', 'templately' ); ?>
1014 </span>
1015 <span class="elementor-alert-description">
1016 <?php echo esc_html__( 'Switch on comments from the discussion box on the post edit screen or from the WordPress discussion settings.', 'templately' ); ?>
1017 </span>
1018 </div>
1019 <?php
1020 }
1021
1022 protected function render_title( $settings, $count ) {
1023 $text = $settings['heading_text'] ?? esc_html__( 'Comments', 'templately' );
1024
1025 if ( '' === trim( (string) $text ) ) {
1026 return;
1027 }
1028
1029 $tag = $settings['title_tag'] ?? 'h3';
1030 $tag = \Elementor\Utils::validate_html_tag( $tag );
1031
1032 $title_html = esc_html( $text );
1033
1034 if ( 'yes' === ( $settings['show_count'] ?? 'yes' ) ) {
1035 $position = ( 'before' === ( $settings['count_position'] ?? 'after' ) ) ? 'before' : 'after';
1036
1037 if ( 'before' === $position ) {
1038 // e.g. "2 Comments"
1039 $count_html = sprintf( '<span class="tl-post-comments__title-count">%d</span> ', (int) $count );
1040 $title_html = $count_html . $title_html;
1041 } else {
1042 // e.g. "Comments (2)" — WordPress' standard parenthesised format.
1043 $count_html = sprintf( ' <span class="tl-post-comments__title-count">(%d)</span>', (int) $count );
1044 $title_html = $title_html . $count_html;
1045 }
1046 }
1047
1048 printf(
1049 '<%1$s class="tl-post-comments__title">%2$s</%1$s>',
1050 $tag, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- validated tag.
1051 $title_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- esc_html text + numeric count.
1052 );
1053 }
1054
1055 protected function render_list( $settings, $comments ) {
1056 $this->render_settings = $settings; // Passed to the walker callback.
1057
1058 $max_depth = get_option( 'thread_comments' ) ? (int) get_option( 'thread_comments_depth' ) : 1;
1059
1060 echo '<ol class="tl-post-comments__list">';
1061 wp_list_comments(
1062 [
1063 'callback' => [ $this, 'render_comment' ],
1064 'style' => 'ol',
1065 'max_depth' => $max_depth,
1066 'reverse_top_level' => ( 'DESC' === ( $settings['order'] ?? 'ASC' ) ),
1067 ],
1068 $comments
1069 );
1070 echo '</ol>';
1071 }
1072
1073 /**
1074 * Walker callback — renders a single comment. Opens the list item; the walker
1075 * closes it (and injects nested `.children` before the close).
1076 *
1077 * @param \WP_Comment $comment
1078 * @param array $args
1079 * @param int $depth
1080 */
1081 public function render_comment( $comment, $args, $depth ) {
1082 $settings = $this->render_settings ?? [];
1083 $tag = ( 'div' === $args['style'] ) ? 'div' : 'li';
1084
1085 $classes = comment_class( 'tl-comment', $comment, null, false );
1086 printf( '<%1$s id="comment-%2$s" %3$s>', esc_attr( $tag ), esc_attr( get_comment_ID() ), $classes ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- comment_class output.
1087
1088 echo '<div class="tl-comment__inner">';
1089
1090 if ( 'yes' === ( $settings['show_avatar'] ?? 'yes' ) ) {
1091 $avatar_size = ! empty( $settings['avatar_size'] ) ? (int) $settings['avatar_size'] : 65;
1092 $avatar = get_avatar( $comment, $avatar_size );
1093 if ( $avatar ) {
1094 echo '<div class="tl-comment__avatar">' . $avatar . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_avatar output.
1095 }
1096 }
1097
1098 echo '<div class="tl-comment__body">';
1099
1100 echo '<div class="tl-comment__head">';
1101 printf( '<span class="tl-comment__author">%s</span>', get_comment_author_link( $comment ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP link output.
1102 printf(
1103 '<span class="tl-comment__date">%s</span>',
1104 esc_html( get_comment_date( get_option( 'date_format' ), $comment ) )
1105 );
1106 echo '</div>';
1107
1108 if ( '0' === $comment->comment_approved ) {
1109 printf(
1110 '<p class="tl-comment__moderation">%s</p>',
1111 esc_html__( 'Your comment is awaiting moderation.', 'templately' )
1112 );
1113 }
1114
1115 echo '<div class="tl-comment__text">';
1116 comment_text( $comment );
1117 echo '</div>';
1118
1119 if ( 'yes' === ( $settings['show_reply'] ?? 'yes' ) ) {
1120 $reply = get_comment_reply_link(
1121 array_merge(
1122 $args,
1123 [
1124 'add_below' => 'comment',
1125 'depth' => $depth,
1126 'max_depth' => $args['max_depth'] ?? 5,
1127 'reply_text' => esc_html__( 'Reply', 'templately' ),
1128 ]
1129 ),
1130 $comment
1131 );
1132 if ( $reply ) {
1133 echo '<div class="tl-comment__reply">' . $reply . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP reply link.
1134 }
1135 }
1136
1137 echo '</div>'; // .tl-comment__body
1138 echo '</div>'; // .tl-comment__inner
1139 // Note: the list item is intentionally left open — Walker_Comment closes it.
1140 }
1141
1142 protected function render_pagination() {
1143 if ( ! get_option( 'page_comments' ) || get_comment_pages_count() <= 1 ) {
1144 return;
1145 }
1146
1147 $links = paginate_comments_links(
1148 [
1149 'echo' => false,
1150 'type' => 'list',
1151 'prev_text' => esc_html__( '&laquo; Previous', 'templately' ),
1152 'next_text' => esc_html__( 'Next &raquo;', 'templately' ),
1153 ]
1154 );
1155
1156 if ( $links ) {
1157 echo '<div class="tl-post-comments__pagination">' . $links . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP pagination markup.
1158 }
1159 }
1160
1161 protected function render_form( $settings, $post_id ) {
1162 $form_title = $settings['form_title'] ?? esc_html__( 'Leave a comment', 'templately' );
1163 $button_text = $settings['button_text'] ?? esc_html__( 'Post Comment', 'templately' );
1164
1165 $commenter = wp_get_current_commenter();
1166
1167 $fields = [
1168 'author' => sprintf(
1169 '<p class="comment-form-author"><label for="author">%1$s</label><input id="author" name="author" type="text" placeholder="%2$s" value="%3$s" size="30" /></p>',
1170 esc_html__( 'Name', 'templately' ),
1171 esc_attr__( 'Your name', 'templately' ),
1172 esc_attr( $commenter['comment_author'] )
1173 ),
1174 'email' => sprintf(
1175 '<p class="comment-form-email"><label for="email">%1$s</label><input id="email" name="email" type="email" placeholder="%2$s" value="%3$s" size="30" /></p>',
1176 esc_html__( 'Email', 'templately' ),
1177 esc_attr__( '[email protected]', 'templately' ),
1178 esc_attr( $commenter['comment_author_email'] )
1179 ),
1180 ];
1181
1182 $args = [
1183 'title_reply' => $form_title,
1184 'title_reply_before' => '<h3 class="tl-post-comments__form-title comment-reply-title">',
1185 'title_reply_after' => '</h3>',
1186 'comment_field' => sprintf(
1187 '<p class="comment-form-comment"><label for="comment">%1$s</label><textarea id="comment" name="comment" placeholder="%2$s" rows="4" required></textarea></p>',
1188 esc_html__( 'Message', 'templately' ),
1189 esc_attr__( 'Share your thoughts…', 'templately' )
1190 ),
1191 'fields' => $fields,
1192 'class_submit' => 'tl-post-comments__submit',
1193 'label_submit' => $button_text,
1194 'comment_notes_before' => '',
1195 'comment_notes_after' => '',
1196 ];
1197
1198 // WordPress renders the comment textarea first by default; the Figma design
1199 // places Name/Email above the Message, so reorder the fields for this form.
1200 $reorder = static function ( $fields ) {
1201 $order = [ 'author', 'email', 'url', 'comment', 'cookies' ];
1202 $reordered = [];
1203 foreach ( $order as $key ) {
1204 if ( isset( $fields[ $key ] ) ) {
1205 $reordered[ $key ] = $fields[ $key ];
1206 }
1207 }
1208 // Preserve any custom fields not covered above.
1209 foreach ( $fields as $key => $value ) {
1210 if ( ! isset( $reordered[ $key ] ) ) {
1211 $reordered[ $key ] = $value;
1212 }
1213 }
1214 return $reordered;
1215 };
1216
1217 add_filter( 'comment_form_fields', $reorder, 20 );
1218 comment_form( $args, $post_id );
1219 remove_filter( 'comment_form_fields', $reorder, 20 );
1220 }
1221
1222 /**
1223 * Settings snapshot shared with the walker callback (which receives only the
1224 * comment + walker args, not the widget settings).
1225 *
1226 * @var array
1227 */
1228 protected $render_settings = [];
1229 }
1230