PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.8.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.8.2
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.2, at includes/Editors/Elementor.php

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