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

1,042 lines 40.8 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 // Must also carry the lazy-load endpoint. wp_localize_script concatenates
781 // every `var betterdocsCategoryGridConfig = …` for this object name and the
782 // last assignment wins; shipping only is_betterdocs_templates here clobbered
783 // Scripts.php's full config on Elementor pages, leaving ajax_url undefined so
784 // loadLazyBody() bailed and sidebar lazy-load never fired.
785 'ajax_url' => admin_url( 'admin-ajax.php' ),
786 'lazy_load_action' => 'betterdocs_lazy_category_body',
787 ] );
788
789 if ( betterdocs()->helper->is_el_templates() == true ) {
790 $assets->enqueue( 'betterdocs-elementor-editor', 'elementor/css/betterdocs-el-edit.css' );
791
792 if ( ! $this->is_pro_active ) {
793 $assets->enqueue( 'betterdocs-elementor-editor', 'elementor/js/editor.js', ['jquery'] );
794 }
795 }
796
797 do_action( 'betterdocs_elementor_enqueue_scripts', $this );
798 }
799
800 public function register_theme_builder_widgets( $widgets_manager ) {
801 foreach ( $this->builder_widget_list() as $value ) {
802 if ( class_exists( $value ) ) {
803 $widgets_manager->register( new $value );
804 }
805 }
806 }
807
808 /**
809 * Mange all widget for single docs
810 *
811 * @return array<string>
812 * @since 1.3.0
813 */
814 public function builder_widget_list() {
815 return apply_filters( 'betterdocs_elementor_pro_widgets', [
816 'betterdocs-elementor-toc' => ToC::class,
817 'betterdocs-elementor-title' => Title::class,
818 'betterdocs-elementor-sidebar' => Sidebar::class,
819 'betterdocs-elementor-content' => Content::class,
820 'betterdocs-elementor-doc-date' => DocDate::class,
821 'betterdocs-elementor-doc-share' => DocShare::class,
822 'betterdocs-elementor-feedback' => Feedback::class,
823 'betterdocs-elementor-reactions' => Reactions::class,
824 'betterdocs-article-summary' => ArticleSummary::class,
825 'betterdocs-elementor-navigation' => Navigation::class,
826 'betterdocs-elementor-breadcrumbs' => Breadcrumbs::class,
827 'betterdocs-elementor-category-archive-list' => ArchiveList::class,
828 'betteredocs-elementor-reading-time' => ReadingTime::class,
829 'betterdocs-print-button' => PrintButton::class,
830 'betterdocs-elementor-doc-author' => DocAuthor::class,
831 'betteredocs-elementor-doc-category-archive-header' => CategoryArchiveHeader::class,
832 'betterdocs-elementor-tags' => Tags::class
833 ] );
834 }
835
836 /**
837 * @param \ElementorPro\Modules\ThemeBuilder\Classes\Conditions_Manager $conditions_manager
838 */
839 public function register_conditions( $conditions_manager ) {
840 $betterdocs_condition = betterdocs()->container->get( ArchiveCondition::class );
841 $conditions_manager->get_condition( 'general' )->register_sub_condition( $betterdocs_condition );
842 }
843
844 public function betterdocs_init() {
845 add_action( 'elementor/init', [$this, 'register_template_instance'], 20 );
846
847 static $is_done = false;
848 if ( defined( 'Elementor\Api::LIBRARY_OPTION_KEY' ) && ! $is_done ) {
849 $is_done = true;
850 add_filter( 'option_' . ElementorAPI::LIBRARY_OPTION_KEY, [$this, 'prepend_categories'] );
851 }
852
853 add_action( 'elementor/ajax/register_actions', [$this, 'modified_ajax_action'], 20 );
854
855 if ( defined( 'ELEMENTOR_VERSION' ) && version_compare( ELEMENTOR_VERSION, '3.10.0', '>=' ) && ! self::$is_templates_added ) {
856 self::$is_templates_added = true;
857 add_filter( 'http_response', [$this, 'http_response_modify_for_bd_template'], 10, 3 );
858 } elseif ( defined( 'ELEMENTOR_VERSION' ) && version_compare( ELEMENTOR_VERSION, '3.5.0', '>=' ) && ! self::$is_templates_added ) {
859 self::$is_templates_added = true;
860 add_filter( 'option_' . ElementorAPI::LIBRARY_OPTION_KEY, [$this, 'added_bd_template'] );
861 }
862 }
863
864 /**
865 * Added BetterDocs template with Elementor core templates list
866 *
867 * @param $response
868 * @param $parsed_args
869 * @param $url
870 *
871 * @return mixed
872 * @since 2.3.5
873 */
874 public function http_response_modify_for_bd_template( $response, $parsed_args, $url ) {
875 if ( $url === \Elementor\TemplateLibrary\Source_Remote::API_TEMPLATES_URL ) {
876 $templates_list = json_decode( wp_remote_retrieve_body( $response ), true );
877 $bd_templates = $this->get_templates();
878
879 if ( ! empty( $bd_templates ) ) {
880 $templates_list = array_merge( (array) $templates_list, (array) $bd_templates );
881 }
882
883 $response['body'] = json_encode( $templates_list );
884 }
885
886 return $response;
887 }
888
889 public function register_template_instance() {
890 $this->elementor->templates_manager->register_source( TemplateSource::class );
891 }
892
893 public function prepend_categories( $library_data ) {
894 $categories = $this->template_categories();
895 if ( ! empty( $categories ) ) {
896 $library_data['types_data']['block']['categories'] = array_merge(
897 $categories,
898 isset( $library_data['types_data']['block']['categories'] ) ? $library_data['types_data']['block']['categories'] : []
899 );
900 }
901 return $library_data;
902 }
903
904 public function template_categories() {
905 return [
906 'Single Docs',
907 'Docs Archive'
908 ];
909 }
910
911 /**
912 * Added BetterDocs template with Elementor core templates list
913 *
914 * added_template
915 *
916 * @param $templates_list array elementor template list
917 *
918 * @return array
919 * @since 2.0.7
920 *
921 */
922 public function added_bd_template( array $templates_list ) {
923 $templates = $this->get_templates();
924
925 if ( ! empty( $templates ) ) {
926 $templates_list['templates'] = array_merge( $templates_list['templates'], $templates );
927 }
928
929 return $templates_list;
930 }
931
932 /**
933 * get_templates
934 *
935 * Get Better docs template list and cache this list
936 *
937 * @return array|mixed
938 * @since 2.0.0
939 */
940 public function get_templates() {
941 $_cache_key = 'betterdocs_template_library_' . betterdocs()->version;
942 $source = new TemplateSource;
943 $local_ids = array_column( $source->get_local_templates(), 'template_id' );
944 $templates = get_transient( $_cache_key );
945
946 if ( ! $templates ) {
947 // Only cache remote templates — local templates are always appended fresh below
948 $all_templates = $source->get_items();
949 $remote_only = array_values( array_filter( $all_templates, function ( $t ) use ( $local_ids ) {
950 return ! in_array( $t['template_id'], $local_ids, true );
951 } ) );
952
953 if ( ! empty( $remote_only ) ) {
954 $remote_only = array_map( function ( $template ) {
955 $template['id'] = $template['template_id'];
956 $template['tmpl_created'] = $template['date'];
957 $template['tags'] = json_encode( $template['tags'] );
958 $template['is_pro'] = $template['isPro'];
959 $template['access_level'] = $template['accessLevel'];
960 $template['popularity_index'] = $template['popularityIndex'];
961 $template['trend_index'] = $template['trendIndex'];
962 $template['has_page_settings'] = $template['hasPageSettings'];
963
964 return $template;
965 }, $remote_only );
966
967 set_transient( $_cache_key, $remote_only, WEEK_IN_SECONDS );
968 $templates = $remote_only;
969 } else {
970 $templates = [];
971 }
972 } else {
973 // Strip any local templates that may have been cached in a previous version
974 $templates = array_values( array_filter( $templates, function ( $t ) use ( $local_ids ) {
975 $id = isset( $t['template_id'] ) ? $t['template_id'] : ( isset( $t['id'] ) ? $t['id'] : '' );
976 return ! in_array( $id, $local_ids, true );
977 } ) );
978 }
979
980 // Always append local templates fresh so they are never stale-cached
981 $local_templates = array_map( function ( $template ) {
982 $template['id'] = $template['template_id'];
983 $template['tmpl_created'] = $template['date'];
984 $template['tags'] = json_encode( $template['tags'] );
985 $template['is_pro'] = $template['isPro'];
986 $template['access_level'] = $template['accessLevel'];
987 $template['popularity_index'] = $template['popularityIndex'];
988 $template['trend_index'] = $template['trendIndex'];
989 $template['has_page_settings'] = $template['hasPageSettings'];
990
991 return $template;
992 }, $source->get_local_templates() );
993
994 return array_merge( $templates, $local_templates );
995 }
996
997 public function modified_ajax_action( $ajax ) {
998 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Elementor's own ajax handler verifies nonce upstream.
999 if ( ! isset( $_REQUEST['actions'] ) ) {
1000 return;
1001 }
1002
1003 // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Elementor's ajax handler verifies nonce; raw JSON is structure-validated below.
1004 $raw_actions = wp_unslash( $_REQUEST['actions'] );
1005 $actions = json_decode( $raw_actions, true );
1006 if ( ! is_array( $actions ) ) {
1007 return;
1008 }
1009 $data = false;
1010
1011 foreach ( $actions as $id => $action_data ) {
1012 if ( ! isset( $action_data['get_template_data'] ) ) {
1013 $data = $action_data;
1014 }
1015 }
1016
1017 if ( ! $data ) {
1018 return;
1019 }
1020
1021 if ( ! isset( $data['data'] ) ) {
1022 return;
1023 }
1024
1025 $data = $data['data'];
1026
1027 if ( empty( $data['template_id'] ) ) {
1028 return;
1029 }
1030
1031 if ( false === strpos( $data['template_id'], 'betterdocs_' ) ) {
1032 return;
1033 }
1034 $ajax->register_ajax_action( 'get_template_data', [$this, 'get_template_data'] );
1035 }
1036
1037 public function get_template_data( $args ) {
1038 $source = new TemplateSource;
1039 return $source->get_data( $args );
1040 }
1041 }
1042