PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.8.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.8.0
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Editors / Elementor.php

Elementor.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.8.0, at includes/Editors/Elementor.php

1,031 lines 39.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Editors;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit; // Exit if accessed directly
7 }
8
9 use Elementor\Controls_Manager;
10 use Elementor\Api as ElementorAPI;
11 use Elementor\Plugin as ElementorPlugin;
12 use WPDeveloper\BetterDocs\Core\Settings;
13 use WPDeveloper\BetterDocs\Utils\Enqueue;
14 use WPDeveloper\BetterDocs\Editors\Elementor\Helper;
15 use WPDeveloper\BetterDocs\Editors\Elementor\SingleDocs;
16 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\ToC;
17 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\DocAuthor;
18 use WPDeveloper\BetterDocs\Editors\Elementor\DocsArchive;
19 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Title;
20 use WPDeveloper\BetterDocs\Editors\Elementor\Tags\TitleTag;
21 use WPDeveloper\BetterDocs\Editors\Elementor\TemplateSource;
22 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Content;
23 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\DocDate;
24 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Sidebar;
25 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\DocShare;
26 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Feedback;
27 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Basic\FAQ;
28 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Reactions;
29 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Navigation;
30 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\CodeSnippet;
31 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\ArchiveList;
32 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\PrintButton;
33 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Breadcrumbs;
34 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\ReadingTime;
35 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\ArticleSummary;
36 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Basic\SearchForm;
37 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Basic\CategoryBox;
38 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Basic\CategoryGrid;
39 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Basic\CategorySlateLayout;
40 use WPDeveloper\BetterDocs\Editors\Elementor\Conditions\ArchiveCondition;
41 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Tags;
42 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\CategoryArchiveHeader;
43
44 class Elementor extends BaseEditor {
45 protected $is_elementor_active = false;
46 protected $is_elementor_pro_active = false;
47 protected static $is_templates_added = false;
48
49 /**
50 * Elementor plugin class
51 * @var ElementorPlugin
52 */
53 protected $elementor;
54
55 /**
56 * Elementor Helper class
57 * @var Helper
58 */
59 protected $helper;
60
61 public function __construct( Settings $settings, Enqueue $enqueue, Helper $helper ) {
62 parent::__construct( $settings, $enqueue );
63
64 $this->helper = $helper;
65 $this->is_elementor_active = betterdocs()->helper->is_plugin_active( 'elementor/elementor.php' );
66 $this->is_elementor_pro_active = betterdocs()->helper->is_plugin_active( 'elementor-pro/elementor-pro.php' );
67
68 if ( ! $this->is_elementor_active || ! class_exists( ElementorPlugin::class ) ) {
69 return;
70 }
71
72 $this->elementor = ElementorPlugin::instance();
73 }
74
75 public function init() {
76 if ( ! $this->is_elementor_active || $this->elementor == null ) {
77 return;
78 }
79
80 add_action( 'wp_enqueue_scripts', [$this, 'enqueue_scripts'] );
81
82 add_action( 'elementor/elements/categories_registered', [$this, 'register_widget_category'] );
83 add_action( 'elementor/widgets/register', [$this, 'register_basic_widgets'] );
84
85 add_action( 'elementor/editor/before_enqueue_scripts', [$this, 'editor_enqueue_scripts'] );
86 add_filter( 'elementor/editor/localize_settings', [$this, 'promote_pro_elements'] );
87
88 add_filter( 'elementor/theme/need_override_location', [$this, 'override_location'], 10, 2 );
89 add_action( 'betterdocs/elementor/widgets/query', [$this, 'betterdocs_query'], 10, 2 );
90 add_filter( 'betterdocs_base_terms_args', [$this, 'handle_editor_mode_terms_args'], 10, 1 );
91
92 if ( $this->is_elementor_pro_active ) {
93 add_action( 'elementor/dynamic_tags/register', [$this, 'register_basic_tags'] );
94 add_action( 'elementor/widgets/register', [$this, 'register_theme_builder_widgets'] );
95 add_action( 'elementor/theme/register_conditions', [$this, 'register_conditions'] );
96
97 //(Conflict Fix)Solves the issue with plugin - Sticky Header Effects for Elementor(Plugin)
98 if ( ! did_action( 'elementor/documents/register' ) ) {
99 add_action( 'elementor/documents/register', [$this, 'register_documents'] );
100 } else {
101 $this->elementor->documents->register_document_type( 'docs', SingleDocs::get_class_full_name() );
102 $this->elementor->documents->register_document_type( 'doc-archive', DocsArchive::get_class_full_name() );
103 }
104 }
105
106 $this->betterdocs_init();
107 }
108
109 public function editor_enqueue_scripts() {
110 betterdocs()->assets->enqueue( 'betterdocs-el-icon', 'elementor/css/betterdocs-el-icon.css' );
111 }
112
113 public function register_documents( $documents_manager ) {
114 $documents_manager->register_document_type( 'docs', SingleDocs::get_class_full_name() );
115 $documents_manager->register_document_type( 'doc-archive', DocsArchive::get_class_full_name() );
116 }
117
118 public function override_location( $need_override_location, $location ) {
119 if ( is_singular( ['docs'] ) && 'single' === $location ) {
120 $need_override_location = true;
121 }
122
123 return $need_override_location;
124 }
125
126 /**
127 * Query Controls
128 *
129 */
130 public function betterdocs_query( $wb, $taxonomy ) {
131 $wb->start_controls_section(
132 'eael_section_post__filters',
133 [
134 'label' => __( 'Query', 'betterdocs' )
135 ]
136 );
137
138 $default_multiple_kb = $this->multiple_kb_status();
139
140 if ( $default_multiple_kb && $taxonomy != 'knowledge_base' ) {
141 $multiple_kb_terms = $this->get_kb_terms( true, false );
142 $default_slug = count( $multiple_kb_terms ) > 0 ? array_keys( $multiple_kb_terms )[0] : '';
143
144 $wb->add_control(
145 'selected_knowledge_base',
146 [
147 'label' => __( 'Knowledge Bases', 'betterdocs' ),
148 'label_block' => true,
149 'type' => Controls_Manager::SELECT2,
150 'options' => $multiple_kb_terms,
151 'multiple' => false,
152 'default' => '',
153 'select2options' => [
154 'placeholder' => __( 'All Knowledge Base', 'betterdocs' ),
155 'allowClear' => true
156 ]
157 ]
158 );
159 }
160
161 if ( $wb->get_name() === 'betterdocs-category-grid' ) {
162 $wb->add_control(
163 'grid_query_heading',
164 [
165 'label' => __( 'Category Grid', 'betterdocs' ),
166 'type' => Controls_Manager::HEADING
167 ]
168 );
169 }
170
171 $wb->add_control(
172 'include',
173 [
174 'label' => __( 'Include', 'betterdocs' ),
175 'label_block' => true,
176 'type' => Controls_Manager::SELECT2,
177 'options' => $this->helper->get_terms( $taxonomy, 'term_id' ),
178 'multiple' => true,
179 'default' => []
180 ]
181 );
182
183 $wb->add_control(
184 'exclude',
185 [
186 'label' => __( 'Exclude', 'betterdocs' ),
187 'type' => Controls_Manager::SELECT2,
188 'options' => $this->helper->get_terms( $taxonomy, 'term_id' ),
189 'label_block' => true,
190 'post_type' => '',
191 'multiple' => true
192 ]
193 );
194
195 if ( $wb->get_name() === 'betterdocs-category-grid' ) {
196 $wb->add_control(
197 'grid_per_page',
198 [
199 'label' => __( 'Grid Per Page', 'betterdocs' ),
200 'type' => Controls_Manager::NUMBER,
201 'default' => '8'
202 ]
203 );
204 } else if ( $wb->get_name() !== 'betterdocs-sidebar' ) {
205 $wb->add_control(
206 'box_per_page',
207 [
208 'label' => $wb->get_name() == 'betterdocs-tab-view-list'
209 ? __( 'Tabs Per Page', 'betterdocs' )
210 : __( 'Box Per Page', 'betterdocs' ),
211 'type' => Controls_Manager::NUMBER,
212 'default' => '8'
213 ]
214 );
215 }
216
217 $wb->add_control(
218 'offset',
219 [
220 'label' => __( 'Offset', 'betterdocs' ),
221 'type' => Controls_Manager::NUMBER,
222 'default' => '0'
223 ]
224 );
225
226 // difference between betterdocs-sidebar and betterdocs-category-grid is the default order
227 if ( $wb->get_name() === 'betterdocs-sidebar' ) {
228 $wb->add_control(
229 'orderby',
230 [
231 'label' => __( 'Terms Order By', 'betterdocs' ),
232 'type' => Controls_Manager::SELECT,
233 'options' => [
234 'none' => __( 'No order', 'betterdocs' ),
235 'name' => __( 'Name', 'betterdocs' ),
236 'slug' => __( 'Slug', 'betterdocs' ),
237 'term_group' => __( 'Term Group', 'betterdocs' ),
238 'term_id' => __( 'Term ID', 'betterdocs' ),
239 'id' => __( 'ID', 'betterdocs' ),
240 'description' => __( 'Description', 'betterdocs' ),
241 'parent' => __( 'Parent', 'betterdocs' ),
242 'betterdocs_order' => __( 'BetterDocs Order', 'betterdocs' )
243 ],
244 'default' => $this->settings->get( 'terms_orderby', 'betterdocs_order' )
245 ]
246 );
247
248 $wb->add_control(
249 'order',
250 [
251 'label' => __( 'Terms Order', 'betterdocs' ),
252 'type' => Controls_Manager::SELECT,
253 'options' => [
254 'ASC' => 'Ascending',
255 'DESC' => 'Descending'
256 ],
257 'default' => $this->settings->get( 'terms_order', 'ASC' ),
258 'condition' => [
259 'orderby!' => 'betterdocs_order'
260 ]
261
262 ]
263 );
264
265 $wb->add_control(
266 'post_orderby',
267 [
268 'label' => __( 'Docs Order By', 'betterdocs' ),
269 'type' => Controls_Manager::SELECT,
270 'options' => $this->helper->orderby_options(),
271 'default' => 'date'
272 ]
273 );
274
275 $wb->add_control(
276 'post_order',
277 [
278 'label' => __( 'Docs Order', 'betterdocs' ),
279 'type' => Controls_Manager::SELECT,
280 'options' => [
281 'asc' => 'Ascending',
282 'desc' => 'Descending'
283 ],
284 'default' => 'asc',
285 'condition' => [
286 'post_orderby!' => 'betterdocs_order'
287 ]
288 ]
289 );
290 } else {
291 $wb->add_control(
292 'orderby',
293 [
294 'label' => __( 'Order By', 'betterdocs' ),
295 'type' => Controls_Manager::SELECT,
296 'options' => [
297 'none' => __( 'No order', 'betterdocs' ),
298 'name' => __( 'Name', 'betterdocs' ),
299 'slug' => __( 'Slug', 'betterdocs' ),
300 'term_group' => __( 'Term Group', 'betterdocs' ),
301 'term_id' => __( 'Term ID', 'betterdocs' ),
302 'id' => __( 'ID', 'betterdocs' ),
303 'description' => __( 'Description', 'betterdocs' ),
304 'parent' => __( 'Parent', 'betterdocs' ),
305 'betterdocs_order' => __( 'BetterDocs Order', 'betterdocs' )
306 ],
307 'default' => 'name'
308 ]
309 );
310
311 $wb->add_control(
312 'order',
313 [
314 'label' => __( 'Order', 'betterdocs' ),
315 'type' => Controls_Manager::SELECT,
316 'options' => [
317 'ASC' => 'Ascending',
318 'DESC' => 'Descending'
319 ],
320 'default' => 'ASC',
321 'condition' => [
322 'orderby!' => 'betterdocs_order'
323 ]
324
325 ]
326 );
327 }
328
329 if ( $wb->get_name() === 'betterdocs-category-grid' || $wb->get_name() === 'betterdocs-category-slate-layout' ) {
330 $wb->add_control(
331 'grid_posts_query_heading',
332 [
333 'label' => __( 'Grid List Posts', 'betterdocs' ),
334 'type' => Controls_Manager::HEADING,
335 'separator' => 'before'
336 ]
337 );
338
339 $wb->add_control(
340 'post_per_page',
341 [
342 'label' => __( 'Post Per Page', 'betterdocs' ),
343 'type' => Controls_Manager::NUMBER,
344 'default' => '6'
345 ]
346 );
347
348 $wb->add_control(
349 'post_orderby',
350 [
351 'label' => __( 'Order By', 'betterdocs' ),
352 'type' => Controls_Manager::SELECT,
353 'options' => $this->helper->orderby_options(),
354 'default' => 'betterdocs_order'
355 ]
356 );
357
358 $wb->add_control(
359 'post_order',
360 [
361 'label' => __( 'Order', 'betterdocs' ),
362 'type' => Controls_Manager::SELECT,
363 'options' => [
364 'asc' => 'Ascending',
365 'desc' => 'Descending'
366 ],
367 'default' => 'asc',
368 'condition' => [
369 'post_orderby!' => 'betterdocs_order'
370 ]
371 ]
372 );
373 }
374
375 if ( $wb->get_name() === 'betterdocs-category-grid' ) {
376 $wb->add_control(
377 'nested_subcategory',
378 [
379 'label' => __( 'Enable Nested Subcategory', 'betterdocs' ),
380 'type' => Controls_Manager::SWITCHER,
381 'label_on' => __( 'Yes', 'betterdocs' ),
382 'label_off' => __( 'No', 'betterdocs' ),
383 'return_value' => 'true',
384 'default' => false
385 ]
386 );
387
388 $wb->add_control(
389 'subcategory_per_grid',
390 [
391 'label' => __( 'Subcategory Per Grid', 'betterdocs' ),
392 'type' => Controls_Manager::NUMBER,
393 'default' => '2',
394 'condition' => [
395 'nested_subcategory' => 'true'
396 ]
397 ]
398 );
399
400 $wb->add_control(
401 'post_per_subcat',
402 [
403 'label' => __( 'Post Per Subcategory', 'betterdocs' ),
404 'type' => Controls_Manager::NUMBER,
405 'default' => '6',
406 'condition' => [
407 'nested_subcategory' => 'true'
408 ]
409 ]
410 );
411
412 $wb->add_control(
413 'lazy_load',
414 [
415 'label' => __( 'Lazy Load Docs & Subcategories', 'betterdocs' ),
416 'description' => __( 'Render only the active category upfront; fetch the rest on click. Recommended for large knowledge bases when this widget is used as a sidebar.', 'betterdocs' ),
417 'type' => Controls_Manager::SWITCHER,
418 'label_on' => __( 'Yes', 'betterdocs' ),
419 'label_off' => __( 'No', 'betterdocs' ),
420 'return_value' => 'true',
421 'default' => ''
422 ]
423 );
424 }
425
426 if ( $wb->get_name() === 'betterdocs-category-box' ) {
427 $wb->add_control(
428 'nested_subcategory',
429 [
430 'label' => __( 'Nested Subcategory', 'betterdocs' ),
431 'type' => Controls_Manager::SWITCHER,
432 'label_on' => __( 'Yes', 'betterdocs' ),
433 'label_off' => __( 'No', 'betterdocs' ),
434 'return_value' => 'true',
435 'default' => 'true',
436 ]
437 );
438
439 $wb->add_control(
440 'show_dynamic_categories_in_doc_category_template',
441 [
442 'label' => __( 'Show Categories Dynamically', 'betterdocs' ),
443 'type' => Controls_Manager::SWITCHER,
444 'label_on' => __( 'Yes', 'betterdocs' ),
445 'label_off' => __( 'No', 'betterdocs' ),
446 'return_value' => 'true',
447 'default' => 'true',
448 ]
449 );
450
451 $wb->add_control(
452 'show_dynamic_categories_in_doc_category_template_description',
453 [
454 'type' => Controls_Manager::RAW_HTML,
455 'raw' => __( "Note: This option is only applicable for BetterDocs Category Archive page. If enabled, this will show the Categories dynamically in your BetterDocs Category Archive page.", 'betterdocs' ),
456 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info'
457 ]
458 );
459 }
460
461 if ( $wb->get_name() === 'betterdocs-sidebar' ) {
462 $wb->add_control(
463 'nested_subcategory',
464 [
465 'label' => __( 'Nested Subcategory', 'betterdocs' ),
466 'type' => Controls_Manager::SWITCHER,
467 'label_on' => __( 'Yes', 'betterdocs' ),
468 'label_off' => __( 'No', 'betterdocs' ),
469 'return_value' => 'true',
470 'default' => ( $this->settings->get( 'archive_nested_subcategory' ) == 1 ) ? 'true' : ''
471 ]
472 );
473
474 $wb->add_control(
475 'lazy_load',
476 [
477 'label' => __( 'Lazy Load Docs & Subcategories', 'betterdocs' ),
478 'description' => __( 'Render only the active category upfront; fetch the rest on click (or on scroll for the Memphis sidebar layout).', 'betterdocs' ),
479 'type' => Controls_Manager::SWITCHER,
480 'label_on' => __( 'Yes', 'betterdocs' ),
481 'label_off' => __( 'No', 'betterdocs' ),
482 'return_value' => 'true',
483 'default' => ( $this->settings->get( 'archive_lazy_load_descendants' ) == 1 ) ? 'true' : ''
484 ]
485 );
486 }
487
488 if ( $wb->get_name() === 'betterdocs-tab-view-list' ) {
489 $wb->add_control(
490 'tab_posts_query_heading',
491 [
492 'label' => __( 'Tab List Posts', 'betterdocs' ),
493 'type' => Controls_Manager::HEADING,
494 'separator' => 'before'
495 ]
496 );
497
498 $wb->add_control(
499 'tab_box_per_tabs',
500 [
501 'label' => __( 'Box Per Tabs', 'betterdocs' ),
502 'type' => Controls_Manager::NUMBER,
503 'default' => '10'
504 ]
505 );
506
507 $wb->add_control(
508 'post_per_tab',
509 [
510 'label' => __( 'Posts Per Tab', 'betterdocs' ),
511 'type' => Controls_Manager::NUMBER,
512 'default' => '10'
513 ]
514 );
515
516 $wb->add_control(
517 'tab_list_posts_orderby',
518 [
519 'label' => __( 'Posts Order By', 'betterdocs' ),
520 'type' => Controls_Manager::SELECT,
521 'options' => [
522 'name' => __( 'Name', 'betterdocs' ),
523 'slug' => __( 'Slug', 'betterdocs' ),
524 'term_group' => __( 'Term Group', 'betterdocs' ),
525 'term_id' => __( 'Term ID', 'betterdocs' ),
526 'id' => __( 'ID', 'betterdocs' ),
527 'description' => __( 'Description', 'betterdocs' ),
528 'parent' => __( 'Parent', 'betterdocs' ),
529 'betterdocs_order' => __( 'BetterDocs Order', 'betterdocs' )
530 ],
531 'default' => 'name'
532 ]
533 );
534
535 $wb->add_control(
536 'tab_list_order',
537 [
538 'label' => __( 'Order', 'betterdocs' ),
539 'type' => Controls_Manager::SELECT,
540 'options' => [
541 'ASC' => 'Ascending',
542 'DESC' => 'Descending'
543 ],
544 'default' => 'ASC'
545
546 ]
547 );
548
549 $wb->add_control(
550 'nested_subcategory_tab_list',
551 [
552 'label' => __( 'Nested Subcategory', 'betterdocs' ),
553 'type' => Controls_Manager::SWITCHER,
554 'label_on' => __( 'Yes', 'betterdocs' ),
555 'label_off' => __( 'No', 'betterdocs' ),
556 'return_value' => 'true',
557 'default' => 'true'
558 ]
559 );
560
561 $wb->add_control(
562 'nested_posts_per_page',
563 [
564 'label' => __( 'Posts Per Page', 'betterdocs' ),
565 'type' => Controls_Manager::NUMBER,
566 'default' => '-1',
567 'condition' => [
568 'nested_subcategory_tab_list' => 'true'
569 ]
570 ]
571 );
572
573 $wb->add_control(
574 'nested_sub_cat_order',
575 [
576 'label' => __( 'Nested Subcategory Order', 'betterdocs' ),
577 'type' => Controls_Manager::SELECT,
578 'options' => [
579 'ASC' => 'Ascending',
580 'DESC' => 'Descending'
581 ],
582 'default' => 'ASC',
583 'condition' => [
584 'nested_subcategory_tab_list' => 'true'
585 ]
586 ]
587 );
588
589 $wb->add_control(
590 'nested_sub_cat_orderby',
591 [
592 'label' => __( 'Nested Subcategory Order By', 'betterdocs' ),
593 'type' => Controls_Manager::SELECT,
594 'options' => [
595 'none' => __( 'No order', 'betterdocs' ),
596 'name' => __( 'Name', 'betterdocs' ),
597 'slug' => __( 'Slug', 'betterdocs' ),
598 'term_group' => __( 'Term Group', 'betterdocs' ),
599 'term_id' => __( 'Term ID', 'betterdocs' ),
600 'id' => __( 'ID', 'betterdocs' ),
601 'description' => __( 'Description', 'betterdocs' ),
602 'parent' => __( 'Parent', 'betterdocs' ),
603 'betterdocs_order' => __( 'BetterDocs Order', 'betterdocs' )
604 ],
605 'default' => 'name',
606 'condition' => [
607 'nested_subcategory_tab_list' => 'true'
608 ]
609 ]
610 );
611 }
612
613 $wb->end_controls_section();
614 }
615
616 public function multiple_kb_status() {
617 if ( $this->settings->get( 'multiple_kb' ) == 1 ) {
618 return 'true';
619 }
620
621 return '';
622 }
623
624 public function get_kb_terms( $prettify = false, $term_id = true ) {
625 // This list populates the widget "Knowledge Bases" selector — an admin-side
626 // config picker, not a front-end listing. It must show EVERY KB regardless of
627 // whether it currently has content or is access-restricted for the editing
628 // user's role. Previously it ran through terms_query()/get_terms(), so on a
629 // regular page the Pro access-control terms_clauses filter + hide_empty pruned
630 // the list to a subset, while the theme-builder context showed them all.
631 // hide_empty:false keeps empty KBs; suppress_filters:true signals AccessControl
632 // (get_terms_args) to skip its role-based terms_clauses filter, so the picker is
633 // consistent everywhere.
634 $terms = get_terms( [
635 'taxonomy' => 'knowledge_base',
636 'hide_empty' => false,
637 'parent' => 0,
638 'orderby' => 'name',
639 'suppress_filters' => true,
640 ] );
641
642 if ( is_wp_error( $terms ) ) {
643 return [];
644 }
645
646 if ( $prettify ) {
647 $pretty_taxonomies = [];
648
649 foreach ( $terms as $term ) {
650 // Decode HTML entities (e.g. "&amp;" → "&") so the select2 control
651 // shows the real KB name instead of the escaped entity.
652 $pretty_taxonomies[$term_id ? $term->term_id : $term->slug] = wp_specialchars_decode( $term->name, ENT_QUOTES );
653 }
654
655 return $pretty_taxonomies;
656 }
657
658 return $terms;
659 }
660
661 public function register_basic_tags( $module ) {
662 $module->register( new TitleTag );
663 }
664
665 /**
666 * Handle editor mode for terms query args
667 *
668 * @param array $args Terms query arguments
669 * @return array Modified arguments
670 */
671 public function handle_editor_mode_terms_args( $args ) {
672 // Only modify if we're in Elementor editor mode
673 if ( $this->elementor && $this->elementor->editor->is_edit_mode() ) {
674 // Show empty categories in editor mode for better preview
675 $args['hide_empty'] = false;
676 }
677
678 return $args;
679 }
680
681 public function register_basic_widgets( $widgets_manager ) {
682 foreach ( $this->basic_widget_lists() as $value ) {
683 $widgets_manager->register( new $value );
684 }
685 }
686
687 /**
688 *
689 * Mange all widget for single docs
690 *
691 * @return string[]
692 * @since 1.3.0
693 */
694 private function basic_widget_lists() {
695 $widget_arr = [
696 'betterdocs-elementor-search-form' => SearchForm::class,
697 'betterdocs-elementor-category-grid' => CategoryGrid::class,
698 'betterdocs-elementor-category-box' => CategoryBox::class,
699 'betterdocs-elementor-category-slate' => CategorySlateLayout::class,
700 'betterdocs-faq-widget' => FAQ::class,
701 'betterdocs-code-snippet' => CodeSnippet::class
702 ];
703
704 return $widget_arr;
705 }
706
707 public function register_widget_category( $elements_manager ) {
708 $elements_manager->add_category( 'betterdocs-elements', [
709 'title' => __( 'BetterDocs', 'betterdocs' ),
710 'icon' => 'font'
711 ], 1 );
712 }
713
714 public function promote_pro_elements( $config ) {
715 if ( $this->is_pro_active ) {
716 return $config;
717 }
718
719 $promotion_widgets = [];
720
721 if ( isset( $config['promotionWidgets'] ) ) {
722 $promotion_widgets = $config['promotionWidgets'];
723 }
724
725 $combine_array = array_merge( $promotion_widgets, [
726 [
727 'name' => 'betterdocs-multiple-kb',
728 'title' => __( 'BetterDocs Multiple KB', 'betterdocs' ),
729 'icon' => 'betterdocs-icon-category-box',
730 'categories' => '["docs-archive"]'
731 ]
732 ] );
733
734 $config['promotionWidgets'] = $combine_array;
735
736 return $config;
737 }
738
739 public function is_templates() {
740 if ( $this->is_elementor_active && $this->is_elementor_pro_active ) {
741 $post_id = get_the_ID();
742 if ( $this->elementor->editor->is_edit_mode() || (
743 get_post_meta( $post_id, '_elementor_template_type', true )
744 && $this->elementor->documents->get( $post_id )->is_built_with_elementor()
745 ) ) {
746 return true;
747 }
748 }
749
750 return null;
751 }
752
753 public function enqueue_scripts() {
754 $assets = betterdocs()->assets;
755
756 /**
757 * Widget's CSS
758 */
759 $assets->register( 'betterdocs-el-category-grid', 'elementor/css/category-grid.css' );
760 $assets->register( 'betterdocs-el-category-box', 'elementor/css/category-box.css' );
761 $assets->register( 'betterdocs-el-articles-list', 'elementor/css/articles-list.css' );
762 $assets->register( 'betterdocs-el-navigation', 'elementor/css/navigation.css' );
763 $assets->register( 'betterdocs-code-snippet', 'public/css/code-snippet.css' );
764
765 /**
766 * Widget's JS
767 */
768 $assets->register( 'betterdocs-el-category-grid', 'elementor/js/category-grid.js', ['jquery', 'betterdocs-category-toggler'] );
769 $assets->register( 'betterdocs-code-snippet', 'public/js/code-snippet.js', ['jquery'] );
770
771 /**
772 * Localize This In Order To Know If This Block Is Arriving From Betterdocs Templates Or Not
773 */
774 betterdocs()->assets->localize( 'betterdocs-el-category-grid', 'betterdocsCategoryGridConfig', [
775 'is_betterdocs_templates' => betterdocs()->helper->is_templates() ? true : false
776 ] );
777
778 if ( betterdocs()->helper->is_el_templates() == true ) {
779 $assets->enqueue( 'betterdocs-elementor-editor', 'elementor/css/betterdocs-el-edit.css' );
780
781 if ( ! $this->is_pro_active ) {
782 $assets->enqueue( 'betterdocs-elementor-editor', 'elementor/js/editor.js', ['jquery'] );
783 }
784 }
785
786 do_action( 'betterdocs_elementor_enqueue_scripts', $this );
787 }
788
789 public function register_theme_builder_widgets( $widgets_manager ) {
790 foreach ( $this->builder_widget_list() as $value ) {
791 if ( class_exists( $value ) ) {
792 $widgets_manager->register( new $value );
793 }
794 }
795 }
796
797 /**
798 * Mange all widget for single docs
799 *
800 * @return array<string>
801 * @since 1.3.0
802 */
803 public function builder_widget_list() {
804 return apply_filters( 'betterdocs_elementor_pro_widgets', [
805 'betterdocs-elementor-toc' => ToC::class,
806 'betterdocs-elementor-title' => Title::class,
807 'betterdocs-elementor-sidebar' => Sidebar::class,
808 'betterdocs-elementor-content' => Content::class,
809 'betterdocs-elementor-doc-date' => DocDate::class,
810 'betterdocs-elementor-doc-share' => DocShare::class,
811 'betterdocs-elementor-feedback' => Feedback::class,
812 'betterdocs-elementor-reactions' => Reactions::class,
813 'betterdocs-article-summary' => ArticleSummary::class,
814 'betterdocs-elementor-navigation' => Navigation::class,
815 'betterdocs-elementor-breadcrumbs' => Breadcrumbs::class,
816 'betterdocs-elementor-category-archive-list' => ArchiveList::class,
817 'betteredocs-elementor-reading-time' => ReadingTime::class,
818 'betterdocs-print-button' => PrintButton::class,
819 'betterdocs-elementor-doc-author' => DocAuthor::class,
820 'betteredocs-elementor-doc-category-archive-header' => CategoryArchiveHeader::class,
821 'betterdocs-elementor-tags' => Tags::class
822 ] );
823 }
824
825 /**
826 * @param \ElementorPro\Modules\ThemeBuilder\Classes\Conditions_Manager $conditions_manager
827 */
828 public function register_conditions( $conditions_manager ) {
829 $betterdocs_condition = betterdocs()->container->get( ArchiveCondition::class );
830 $conditions_manager->get_condition( 'general' )->register_sub_condition( $betterdocs_condition );
831 }
832
833 public function betterdocs_init() {
834 add_action( 'elementor/init', [$this, 'register_template_instance'], 20 );
835
836 static $is_done = false;
837 if ( defined( 'Elementor\Api::LIBRARY_OPTION_KEY' ) && ! $is_done ) {
838 $is_done = true;
839 add_filter( 'option_' . ElementorAPI::LIBRARY_OPTION_KEY, [$this, 'prepend_categories'] );
840 }
841
842 add_action( 'elementor/ajax/register_actions', [$this, 'modified_ajax_action'], 20 );
843
844 if ( defined( 'ELEMENTOR_VERSION' ) && version_compare( ELEMENTOR_VERSION, '3.10.0', '>=' ) && ! self::$is_templates_added ) {
845 self::$is_templates_added = true;
846 add_filter( 'http_response', [$this, 'http_response_modify_for_bd_template'], 10, 3 );
847 } elseif ( defined( 'ELEMENTOR_VERSION' ) && version_compare( ELEMENTOR_VERSION, '3.5.0', '>=' ) && ! self::$is_templates_added ) {
848 self::$is_templates_added = true;
849 add_filter( 'option_' . ElementorAPI::LIBRARY_OPTION_KEY, [$this, 'added_bd_template'] );
850 }
851 }
852
853 /**
854 * Added BetterDocs template with Elementor core templates list
855 *
856 * @param $response
857 * @param $parsed_args
858 * @param $url
859 *
860 * @return mixed
861 * @since 2.3.5
862 */
863 public function http_response_modify_for_bd_template( $response, $parsed_args, $url ) {
864 if ( $url === \Elementor\TemplateLibrary\Source_Remote::API_TEMPLATES_URL ) {
865 $templates_list = json_decode( wp_remote_retrieve_body( $response ), true );
866 $bd_templates = $this->get_templates();
867
868 if ( ! empty( $bd_templates ) ) {
869 $templates_list = array_merge( (array) $templates_list, (array) $bd_templates );
870 }
871
872 $response['body'] = json_encode( $templates_list );
873 }
874
875 return $response;
876 }
877
878 public function register_template_instance() {
879 $this->elementor->templates_manager->register_source( TemplateSource::class );
880 }
881
882 public function prepend_categories( $library_data ) {
883 $categories = $this->template_categories();
884 if ( ! empty( $categories ) ) {
885 $library_data['types_data']['block']['categories'] = array_merge(
886 $categories,
887 isset( $library_data['types_data']['block']['categories'] ) ? $library_data['types_data']['block']['categories'] : []
888 );
889 }
890 return $library_data;
891 }
892
893 public function template_categories() {
894 return [
895 'Single Docs',
896 'Docs Archive'
897 ];
898 }
899
900 /**
901 * Added BetterDocs template with Elementor core templates list
902 *
903 * added_template
904 *
905 * @param $templates_list array elementor template list
906 *
907 * @return array
908 * @since 2.0.7
909 *
910 */
911 public function added_bd_template( array $templates_list ) {
912 $templates = $this->get_templates();
913
914 if ( ! empty( $templates ) ) {
915 $templates_list['templates'] = array_merge( $templates_list['templates'], $templates );
916 }
917
918 return $templates_list;
919 }
920
921 /**
922 * get_templates
923 *
924 * Get Better docs template list and cache this list
925 *
926 * @return array|mixed
927 * @since 2.0.0
928 */
929 public function get_templates() {
930 $_cache_key = 'betterdocs_template_library_' . betterdocs()->version;
931 $source = new TemplateSource;
932 $local_ids = array_column( $source->get_local_templates(), 'template_id' );
933 $templates = get_transient( $_cache_key );
934
935 if ( ! $templates ) {
936 // Only cache remote templates — local templates are always appended fresh below
937 $all_templates = $source->get_items();
938 $remote_only = array_values( array_filter( $all_templates, function ( $t ) use ( $local_ids ) {
939 return ! in_array( $t['template_id'], $local_ids, true );
940 } ) );
941
942 if ( ! empty( $remote_only ) ) {
943 $remote_only = array_map( function ( $template ) {
944 $template['id'] = $template['template_id'];
945 $template['tmpl_created'] = $template['date'];
946 $template['tags'] = json_encode( $template['tags'] );
947 $template['is_pro'] = $template['isPro'];
948 $template['access_level'] = $template['accessLevel'];
949 $template['popularity_index'] = $template['popularityIndex'];
950 $template['trend_index'] = $template['trendIndex'];
951 $template['has_page_settings'] = $template['hasPageSettings'];
952
953 return $template;
954 }, $remote_only );
955
956 set_transient( $_cache_key, $remote_only, WEEK_IN_SECONDS );
957 $templates = $remote_only;
958 } else {
959 $templates = [];
960 }
961 } else {
962 // Strip any local templates that may have been cached in a previous version
963 $templates = array_values( array_filter( $templates, function ( $t ) use ( $local_ids ) {
964 $id = isset( $t['template_id'] ) ? $t['template_id'] : ( isset( $t['id'] ) ? $t['id'] : '' );
965 return ! in_array( $id, $local_ids, true );
966 } ) );
967 }
968
969 // Always append local templates fresh so they are never stale-cached
970 $local_templates = array_map( function ( $template ) {
971 $template['id'] = $template['template_id'];
972 $template['tmpl_created'] = $template['date'];
973 $template['tags'] = json_encode( $template['tags'] );
974 $template['is_pro'] = $template['isPro'];
975 $template['access_level'] = $template['accessLevel'];
976 $template['popularity_index'] = $template['popularityIndex'];
977 $template['trend_index'] = $template['trendIndex'];
978 $template['has_page_settings'] = $template['hasPageSettings'];
979
980 return $template;
981 }, $source->get_local_templates() );
982
983 return array_merge( $templates, $local_templates );
984 }
985
986 public function modified_ajax_action( $ajax ) {
987 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Elementor's own ajax handler verifies nonce upstream.
988 if ( ! isset( $_REQUEST['actions'] ) ) {
989 return;
990 }
991
992 // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Elementor's ajax handler verifies nonce; raw JSON is structure-validated below.
993 $raw_actions = wp_unslash( $_REQUEST['actions'] );
994 $actions = json_decode( $raw_actions, true );
995 if ( ! is_array( $actions ) ) {
996 return;
997 }
998 $data = false;
999
1000 foreach ( $actions as $id => $action_data ) {
1001 if ( ! isset( $action_data['get_template_data'] ) ) {
1002 $data = $action_data;
1003 }
1004 }
1005
1006 if ( ! $data ) {
1007 return;
1008 }
1009
1010 if ( ! isset( $data['data'] ) ) {
1011 return;
1012 }
1013
1014 $data = $data['data'];
1015
1016 if ( empty( $data['template_id'] ) ) {
1017 return;
1018 }
1019
1020 if ( false === strpos( $data['template_id'], 'betterdocs_' ) ) {
1021 return;
1022 }
1023 $ajax->register_ajax_action( 'get_template_data', [$this, 'get_template_data'] );
1024 }
1025
1026 public function get_template_data( $args ) {
1027 $source = new TemplateSource;
1028 return $source->get_data( $args );
1029 }
1030 }
1031