PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.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 / Core / Settings.php

Settings.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.0, at includes/Core/Settings.php

3,222 lines 212.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace WPDeveloper\BetterDocs\Core;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit;
6 }
7
8
9 use WP_Error;
10 use WP_User;
11 use WPDeveloper\BetterDocs\Admin\Builder\GlobalFields;
12 use WPDeveloper\BetterDocs\Admin\Builder\Rules;
13 use WPDeveloper\BetterDocs\AI\ModelRegistry;
14 use WPDeveloper\BetterDocs\AI\ProviderFactory;
15 use WPDeveloper\BetterDocs\REST\AIEdit;
16 use WPDeveloper\BetterDocs\Utils\AIHelper;
17 use WPDeveloper\BetterDocs\Utils\Base;
18 use WPDeveloper\BetterDocs\Utils\Database;
19 use WPDeveloper\BetterDocs\Utils\Helper;
20
21 class Settings extends Base {
22 public $base_key = 'betterdocs_settings';
23
24 /**
25 * Database class
26 * @var Database
27 */
28 protected $database;
29
30 private $deprecated = array();
31
32 private $cannot_be_empty = array(
33 'breadcrumb_doc_title',
34 'docs_slug',
35 'category_slug',
36 'tag_slug',
37 'permalink_structure',
38 'docs_page'
39 );
40
41 public function __construct( Database $database ) {
42 $this->database = $database;
43 $this->deprecated = $this->deprecated_settings();
44
45 add_filter( 'betterdocs_settings_tabs', array( $this, 'import_export_settings' ) );
46 add_filter( 'betterdocs_settings_tabs', array( $this, 'maybe_remove_git_tab' ), 20 );
47
48 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_old' ), 99 );
49 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ), 99 );
50
51 // if ( isset( $_GET['page'] ) && $_GET['page'] === 'betterdocs-settings' && ! has_action( 'betterdocs_settings_header' ) ) {
52 // add_action( 'betterdocs_settings_header', [ $this, 'header' ] );
53 // }
54
55 add_action( 'wp_ajax_betterdocs_dark_mode', array( $this, 'dark_mode' ) );
56 add_filter( 'betterdocs_settings_tab_advance', array( $this, 'hide_roles_management' ), 11, 1 );
57 add_action( 'betterdocs::settings::saved', array( $this, 'fallback_slugs' ), 99, 3 );
58 }
59
60 public function fallback_slugs( $_saved, $_settings, $_old_settings = array() ) {
61 $_default = $this->get_default();
62 foreach ( $this->cannot_be_empty as $key ) {
63 if ( 'docs_page' === $key && ! $_settings[ 'builtin_doc_page' ] && empty( $_settings[ $key ] ) ) {
64 $this->save( 'builtin_doc_page', true );
65 continue;
66 }
67
68 if ( empty( $_settings[ $key ] ) ) {
69 $this->save( $key, $_default[ $key ] );
70 }
71 }
72 }
73
74 /**
75 * Get the settings URL for admin
76 * @return string
77 */
78 public function url() {
79 return esc_url( admin_url( 'admin.php?page=betterdocs-settings' ) );
80 }
81
82 /**
83 * Settings keys holding secret API keys. These are masked before reaching
84 * the browser, stripped for non-admins, and never persisted as their mask.
85 *
86 * Covers the AI Chatbot key plus every content-suite platform key. OpenAI's
87 * is `ai_autowrite_api_key` (see ProviderFactory::key_field_for), hence the
88 * dedupe.
89 *
90 * @return array<int,string>
91 */
92 public static function sensitive_api_key_fields() {
93 $fields = array(
94 ProviderFactory::OPENAI_KEY_FIELD,
95 'ai_chatbot_api_key',
96 );
97 foreach ( array_keys( ModelRegistry::platforms() ) as $platform ) {
98 $fields[] = ProviderFactory::key_field_for( $platform );
99 }
100 // Add-ons (e.g. the AI Chatbot) register their own per-platform keys here
101 // so they are masked in the browser and stripped for non-admins.
102 return apply_filters( 'betterdocs_sensitive_api_key_fields', array_values( array_unique( $fields ) ) );
103 }
104
105 /**
106 * This method is responsible for enqueueing scripts in settings panel
107 *
108 * @param string $hook
109 *
110 * @return void
111 * @since 2.5.0
112 */
113 public function enqueue( $hook ) {
114 if ( ! betterdocs()->is_betterdocs_screen( $hook ) ) {
115 return;
116 }
117
118 wp_enqueue_media();
119 wp_enqueue_script( 'betterdocs-admin' );
120
121 $settings = GlobalFields::normalize( $this->settings_args() );
122
123 // Mask sensitive API keys before they reach the browser. Non-admins still get them stripped entirely below.
124 $sensitive_api_keys = self::sensitive_api_key_fields();
125 foreach ( $sensitive_api_keys as $api_key_field ) {
126 if ( ! empty( $settings[ 'values' ][ $api_key_field ] ) ) {
127 $settings[ 'values' ][ $api_key_field ] = Helper::mask_api_key( $settings[ 'values' ][ $api_key_field ] );
128 }
129 }
130
131 if ( ! current_user_can( 'edit_docs_settings' ) ) {
132 foreach ( $sensitive_api_keys as $api_key_field ) {
133 if ( isset( $settings[ 'values' ][ $api_key_field ] ) ) {
134 unset( $settings[ 'values' ][ $api_key_field ] );
135 }
136 }
137 }
138
139 // Inject raw git settings to bypass get() fallback (e.g., '' -> 'docs')
140 if ( isset( $settings[ 'values' ] ) ) {
141 $settings[ 'values' ][ 'git_provider' ] = $this->get_raw_field( 'git_provider', 'github' );
142 $settings[ 'values' ][ 'git_repository_url' ] = $this->get_raw_field( 'git_repository_url', '' );
143 $settings[ 'values' ][ 'git_branch' ] = $this->get_raw_field( 'git_branch', '' );
144 $settings[ 'values' ][ 'git_docs_directory' ] = $this->get_raw_field( 'git_docs_directory', '' );
145 }
146
147 betterdocs()->assets->localize( 'betterdocs-admin', 'betterdocsAdminSettings', $settings );
148 betterdocs()->assets->enqueue( 'betterdocs-icons', 'admin/btd-icon/style.css' );
149 }
150
151 public function enqueue_old( $hook ) {
152 // FREE & Pro Version Compatibility Check
153 if ( ! betterdocs()->is_betterdocs_screen( $hook ) ) {
154 return;
155 }
156
157 $this->enqueue( 'betterdocs_page_betterdocs-settings' );
158 }
159
160 /**
161 * This method is responsible for printing header in dashboard settings page.
162 *
163 * @param string $hook
164 *
165 * @return void
166 * @since 2.5.0
167 */
168 public function header( $hook ) {
169 if ( 'settings' !== $hook ) {
170 return;
171 }
172
173 betterdocs()->views->get( 'admin/template-parts/settings-header' );
174 betterdocs()->views->get( 'admin/template-parts/settings-header-2' );
175 }
176
177 /**
178 * A list of deprecated settings keys.
179 *
180 * @return array
181 * @since 2.5.0
182 */
183 public function deprecated_settings() {
184 return array();
185 }
186
187 /**
188 * Dynamic migration caller.
189 *
190 * @return void
191 * @since 2.5.0
192 */
193 public function migration( $version ) {
194 if ( $version > 250 ) {
195 for ( $v = 250; $v <= $version; ++$v ) {
196 $_func = "v{$v}";
197 if ( method_exists( $this, $_func ) ) {
198 call_user_func( array( $this, $_func ) );
199 }
200 }
201 }
202 }
203
204 /**
205 * Migration for version 2.5.0
206 *
207 * @return void
208 * @since 2.5.0
209 */
210 public function v250() {
211 if ( $this->get( 'alphabetically_order_term', false ) ) {
212 $this->save( 'terms_orderby', 'name' );
213 }
214
215 if ( $orderby = $this->get( 'alphabetically_order_post', false ) ) {
216 if ( '1' === $orderby ) {
217 $this->save( 'alphabetically_order_post', 'title' );
218 }
219 }
220 }
221
222 /**
223 * A list of default settings data.
224 *
225 * @return array
226 * @since 1.0.0
227 *
228 */
229 public function get_default() {
230 $_default = array(
231 'multiple_kb' => '',
232 'enable_ai_sample_docs' => true,
233 'enable_export_faq' => true,
234 'builtin_doc_page' => true,
235 'breadcrumb_doc_title' => __( 'Docs', 'betterdocs' ),
236 'enable_category_hierarchy_slugs' => false,
237 'docs_slug' => 'docs',
238 'docs_page' => 0,
239 'category_slug' => 'docs-category',
240 'tag_slug' => 'docs-tag',
241 'permalink_structure' => 'docs/',
242 'enable_faq_schema' => false,
243 'live_search' => true,
244 'advance_search' => false,
245 'popular_keyword_limit' => 5,
246 'search_letter_limit' => 3,
247 'search_placeholder' => __( 'Search...', 'betterdocs' ),
248 'search_not_found_text' => __( 'Sorry, no docs were found.', 'betterdocs' ),
249 'search_result_image' => true,
250 'search_modal_search_type' => 'all',
251 'masonry_layout' => true,
252 'docs_list_icon' => array(),
253 'category_title_link' => false,
254 'terms_orderby' => 'betterdocs_order',
255 'alphabetically_order_term' => false,
256 'terms_order' => 'ASC',
257 'alphabetically_order_post' => 'betterdocs_order',
258 'docs_order' => 'ASC',
259 'nested_subcategory' => false,
260 'column_number' => 3,
261 'posts_number' => 10,
262 'post_count' => true,
263 'count_text' => __( 'Docs', 'betterdocs' ),
264 'count_text_singular' => __( 'Doc', 'betterdocs' ),
265 'exploremore_btn' => true,
266 'exploremore_btn_txt' => __( 'Explore More', 'betterdocs' ),
267 'doc_single' => 1,
268 'enable_toc' => true,
269 'toc_title' => __( 'Table of Contents', 'betterdocs' ),
270 'toc_hierarchy' => true,
271 'toc_list_number' => false,
272 'toc_dynamic_title' => false,
273 'enable_sticky_toc' => true,
274 'sticky_toc_offset' => 100,
275 'collapsible_toc_mobile' => false,
276 'supported_heading_tag' => array( '1', '2', '3', '4', '5', '6' ),
277 'enable_post_title' => true,
278 'title_link_ctc' => true,
279 'enable_breadcrumb' => true,
280 'breadcrumb_home_text' => __( 'Home', 'betterdocs' ),
281 'enable_breadcrumb_home_text' => true,
282 'breadcrumb_home_url' => get_home_url(),
283 'enable_breadcrumb_category' => true,
284 'enable_breadcrumb_title' => true,
285 'enable_sidebar_cat_list' => true,
286 'enable_print_icon' => true,
287 'enable_tags' => true,
288 'email_feedback' => true,
289 'feedback_link_text' => __( 'Still stuck? How can we help?', 'betterdocs' ),
290 'reaction_feedback_text' => __( 'Thanks for your feedback', 'betterdocs' ),
291 'feedback_url' => '',
292 'feedback_form_title' => __( 'How can we help?', 'betterdocs' ),
293 'email_address' => get_option( 'admin_email' ),
294 'show_last_update_time' => true,
295 'enable_navigation' => true,
296 'enable_comment' => true,
297 'enable_credit' => false,
298 'enable_archive_sidebar' => true,
299 'archive_nested_subcategory' => true,
300 'archive_enable_pagination' => false,
301 'archive_lazy_load_descendants' => false,
302 'enable_content_restriction' => false,
303 'enable_reporting' => false,
304 'enable_sample_data' => false,
305 'reporting_day' => 'monday',
306 'reporting_email' => get_option( 'admin_email' ),
307 'enable_write_with_ai' => true,
308 'enable_faq_write_with_ai' => true,
309 'enable_glossaries_write_with_ai' => true,
310 'enable_docs_ai_suite' => true,
311 'write_with_ai_model' => 'gpt-4o-mini',
312 'ai_autowrite_api_key' => '',
313 'ai_autowrite_max_token' => 2500,
314 'write_with_ai_instructions' => array(
315 array(
316 'id' => 'default',
317 'title' => __( 'Default/Core', 'betterdocs' ),
318 'content' => WriteWithAI::default_instruction_content()
319 )
320 ),
321 'ai_edit_actions' => AIEdit::default_actions(),
322 'enable_article_summary' => false,
323 'article_summary_model' => 'gpt-4o-mini',
324 'article_summary_max_token' => 1500,
325 // Multi-platform AI (content suite). `ai_platform` selects the active
326 // provider; `ai_model` is the single global model; keys are stored
327 // per platform so switching never loses a saved key. OpenAI reuses
328 // `ai_autowrite_api_key` above — it has always held an OpenAI key,
329 // so nothing needs migrating.
330 'ai_platform' => 'openai',
331 'ai_model' => 'gpt-4o-mini',
332 'ai_api_key_gemini' => '',
333 'ai_api_key_claude' => '',
334 'ai_api_key_deepseek' => '',
335 'ai_api_key_openrouter' => '',
336 'enable_estimated_reading_time' => true,
337 'enable_encyclopedia' => false,
338 'enable_glossaries' => false,
339 'show_glossary_suggestions' => true,
340 'enable_ai_chatbot' => false,
341 'estimated_reading_time_title' => '',
342 'estimated_reading_time_text' => __( 'min read', 'betterdocs' ),
343 'singular_estimated_reading_time_text' => __( 'min read', 'betterdocs' ),
344 'betterdocs_access_control_repeater' => array(),
345 'internal_knowledge_base_type' => 'basic',
346 'betterdocs_access_control_repeater_kb' => array(),
347 'enable_git_integration' => false,
348 /**
349 * MCP master switch. Off by default; the toggle lives on the
350 * BetterDocs → MCP page, not in the settings tree, and writes
351 * through POST betterdocs/v1/settings.
352 *
353 * The key has to be listed here or `get()` cannot see it at all:
354 * it answers `$default` for anything absent from the defaults
355 * array, whatever the stored option holds.
356 *
357 * @since 4.9.0
358 */
359 'enable_mcp' => ''
360 );
361
362 $_default = apply_filters( 'betterdocs_default_settings', $_default );
363 // $_default = apply_filters_deprecated(
364 // 'betterdocs_option_default_settings', [$_default], '2.5.0',
365 // 'betterdocs_default_settings', 'betterdocs_option_default_settings will be removed from v3.5.0.'
366 // );
367
368 return $_default;
369 }
370
371 /**
372 * A list of default settings for pro plugins.
373 *
374 * @return array
375 * @since 2.5.0
376 */
377 public function get_pro_defaults() {
378 return array();
379 }
380
381 public function is_elementor_pro() {
382 return is_plugin_active( 'elementor-pro/elementor-pro.php' );
383 }
384
385 /**
386 * Get full site editor links for docs page.
387 *
388 * @return string
389 * @since 3.5.8
390 */
391 public function gutenberg_link() {
392 if ( betterdocs()->helper->current_theme_is_fse_theme() ) {
393 return admin_url( 'site-editor.php?postType=wp_template&postId=betterdocs/betterdocs//archive-docs' );
394 } else {
395 return 'https://betterdocs.co/docs/betterdocs-provides-full-site-editor-support/';
396 }
397 }
398
399 /**
400 * Get elementor theme builder link for docs page.
401 *
402 * @return string
403 * @since 3.5.8
404 */
405 public function elementor_link() {
406 if ( $this->is_elementor_pro() ) {
407 return admin_url( 'admin.php?page=elementor-app#/site-editor/templates/doc-archive' );
408 } else {
409 return 'https://betterdocs.co/docs/docs-page-with-elementor/';
410 }
411 }
412
413 public function customizer_link() {
414 $query[ 'autofocus[panel]' ] = 'betterdocs_customize_options';
415 $query[ 'return' ] = admin_url( 'edit.php?post_type=docs' );
416 $builtin_doc_page = betterdocs()->settings->get( 'builtin_doc_page' );
417 $docs_slug = betterdocs()->settings->get( 'docs_slug' );
418 $docs_page = betterdocs()->settings->get( 'builtin_doc_page' );
419
420 if ( 1 == $builtin_doc_page && $docs_slug ) {
421 $query[ 'url' ] = site_url( '/' . $docs_slug );
422 } elseif ( 1 != $builtin_doc_page && $docs_page ) {
423 $post_info = get_post( $docs_page );
424 $query[ 'url' ] = site_url( '/' . $post_info->post_name );
425 }
426
427 return add_query_arg( $query, admin_url( 'customize.php' ) );
428 }
429
430 public function design_tab() {
431 $settings = array();
432
433 $settings[ 'gutenberg_link' ] = array(
434 'name' => 'gutenberg_link',
435 'type' => 'action',
436 'action' => 'betterdocs_settings_gutenberg_link',
437 'button' => betterdocs()->helper->current_theme_is_fse_theme() ? __( 'Design with Gutenberg', 'betterdocs' ) : __( 'Learn More', 'betterdocs' ),
438 'url' => $this->gutenberg_link(),
439 'customizer_img' => betterdocs()->assets->icon( 'customizer/gutenberg-preview.png', true ),
440 'priority' => 1
441 );
442
443 $settings[ 'elementor_link' ] = array(
444 'name' => 'elementor_link',
445 'type' => 'action',
446 'action' => 'betterdocs_settings_elementor_link',
447 'button' => $this->is_elementor_pro() ? __( 'Design with Elementor', 'betterdocs' ) : __( 'Learn More', 'betterdocs' ),
448 'url' => $this->elementor_link(),
449 'customizer_img' => betterdocs()->assets->icon( 'customizer/elementor-preview.png', true ),
450 'priority' => 2
451 );
452
453 if ( ! betterdocs()->helper->current_theme_is_fse_theme() ) {
454 $settings[ 'customizer_link' ] = array(
455 'name' => 'customizer_link',
456 'type' => 'action',
457 'action' => 'betterdocs_settings_customizer_link',
458 'button' => __( 'Customize in BetterDocs', 'betterdocs' ),
459 'url' => $this->customizer_link(),
460 'customizer_img' => betterdocs()->assets->icon( 'customizer/customizer-preview.png', true ),
461 'priority' => 3
462 );
463 }
464
465 return $settings;
466 }
467
468 /**
469 * Get All Roles
470 * dynamically
471 *
472 * @return array
473 */
474 public function get_roles() {
475 $roles = wp_roles()->role_names;
476 unset( $roles[ 'subscriber' ] );
477
478 return $roles;
479 }
480
481 /**
482 * Set dark mode
483 *
484 * @return void
485 * @since 1.0.0
486 */
487 public function dark_mode() {
488 if ( ! check_ajax_referer( 'doc_cat_order_nonce', 'nonce', false ) ) {
489 wp_send_json_error();
490 }
491
492 if ( isset( $_POST[ 'mode' ] ) ) {
493 $mode = sanitize_text_field( wp_unslash( $_POST[ 'mode' ] ) );
494 if ( $this->save( 'dark_mode', rest_sanitize_boolean( $mode ) ) ) {
495 wp_send_json_success();
496 }
497 }
498
499 wp_send_json_error();
500 }
501
502 public function get_normalized_value( $key, $value, $default = null ) {
503 $_origin_value = $_value = $value;
504
505 if ( in_array( $_value, array( 'on', 'off', '1', 'false', 'true' ), true ) ) {
506 switch ( $_value ) {
507 case 'on':
508 case 'ON':
509 case '1':
510 case 'true':
511 $_value = true;
512 break;
513 case 'off':
514 case 'OFF':
515 case '':
516 case 'false':
517 $_value = false;
518 break;
519 }
520 }
521
522 $this->type_validation( $_value, $default );
523
524 return $_value;
525 }
526
527 public function get_normalized_values( $values, $default_values = array() ) {
528 if ( empty( $values ) ) {
529 return array();
530 }
531
532 $_settings = array();
533 foreach ( $values as $key => $value ) {
534 $_default_value = isset( $default_values[ $key ] ) ? $default_values[ $key ] : null;
535 $_settings[ $key ] = $this->get_normalized_value( $key, $value, $_default_value );
536 }
537
538 return $_settings;
539 }
540
541 public function get_all( $raw = false ) {
542 $_default_settings = $raw ? array() : array_merge( $this->get_default(), $this->get_pro_defaults() );
543 $_settings = $this->database->get( $this->base_key, $_default_settings );
544
545 return $this->get_normalized_values( $_settings, $_default_settings );
546 }
547
548 public function type_validation( &$value, $defaultValue = null ) {
549 if ( null !== $defaultValue ) {
550 /**
551 * Check if value is not in same type
552 */
553 $_default_type = gettype( $defaultValue );
554
555 if ( ! ( is_scalar( $defaultValue ) && is_scalar( $value ) ) && empty( $value ) ) {
556 $value = $defaultValue;
557 }
558
559 settype( $value, $_default_type );
560 }
561 }
562
563 /**
564 * Get settings value by key
565 *
566 * @param string $key
567 * @param mixed $default
568 * @param bool $get_all
569 *
570 * @return mixed
571 * @since 2.5.0
572 *
573 */
574 public function get( $key, $default = null ) {
575 $_default_settings = array_merge( $this->get_default(), $this->get_pro_defaults() );
576 $_settings = $this->database->get( $this->base_key, $_default_settings );
577
578 $_value = $default;
579 switch ( true ) {
580 // Check if it's a PRO Option
581 case ! isset( $_default_settings[ $key ] ):
582 $_value = $default;
583 break;
584 // Check if it's a FREE Option and not in DB.
585 case ! isset( $_settings[ $key ] ) && isset( $_default_settings[ $key ] ):
586 $_value = null !== $default ? $default : $_default_settings[ $key ];
587 break;
588 // Check if it's a FREE Option
589 case isset( $_settings[ $key ] ) && isset( $_default_settings[ $key ] ):
590 $_value = $_settings[ $key ];
591 break;
592 }
593
594 $_value = $this->get_normalized_value( $key, $_value, isset( $_default_settings[ $key ] ) ? $_default_settings[ $key ] : null );
595
596 if ( gettype( $_value ) === 'string' ) {
597 if ( empty( $_value ) && null != $default ) {
598 $_value = $default;
599 } elseif ( empty( $_value ) && null === $default && isset( $_default_settings[ $key ] ) ) {
600 $_value = $_default_settings[ $key ];
601 }
602 }
603
604 return $_value;
605 }
606
607 public function get_raw_field( $key, $default = null ) {
608 $_settings = $this->database->get( $this->base_key, array() );
609
610 if ( isset( $_settings[ $key ] ) ) {
611 return $this->get_normalized_value( $key, $_settings[ $key ], $default );
612 }
613
614 return $default;
615 }
616
617 public function save( $key, $value ) {
618 $_settings = $this->database->get( $this->base_key, array() );
619 $_settings[ $key ] = $value;
620
621 return $this->database->save( $this->base_key, $_settings );
622 }
623
624 public function save_settings( $settings ) {
625 $existing_plugins = betterdocs()->kbmigration->knowledge_base_plugins();
626 if ( ! current_user_can( 'edit_docs_settings' ) ) {
627 return new WP_Error( 'unauthorized_action', __( 'You don\'t have any rights for saving settings.', 'betterdocs' ) );
628 }
629
630 // The frontend sends the currently-active settings tab as a transport-only hint so
631 // tab-scoped validation (the AI min-token floor below) only runs when that tab is being
632 // saved. It must never be persisted into the settings option.
633 $active_tab = '';
634 if ( array_key_exists( '_active_tab', $settings ) ) {
635 $active_tab = (string) $settings[ '_active_tab' ];
636 unset( $settings[ '_active_tab' ] );
637 }
638
639 // Quickbuilder sends back the full UI-loaded payload. To avoid overwriting values that we
640 // asynchronously updated in the DB via AJAX (like Git integration fields), we strictly remove them
641 // from the incoming save payload so that the freshly loaded `$_old_settings` values persist.
642 $exclude_ajax_keys = array( 'git_provider', 'git_repository_url', 'git_branch', 'git_docs_directory' );
643 foreach ( $exclude_ajax_keys as $key ) {
644 if ( array_key_exists( $key, $settings ) ) {
645 unset( $settings[ $key ] );
646 }
647 if ( isset( $settings[ 'values' ] ) && is_array( $settings[ 'values' ] ) && array_key_exists( $key, $settings[ 'values' ] ) ) {
648 unset( $settings[ 'values' ][ $key ] );
649 }
650 }
651
652 $_old_settings = $this->database->get( $this->base_key, $this->get_default() );
653
654 // The frontend only ever sees masked API keys. A submitted value that still looks like a
655 // mask (contains a run of asterisks — no real key does) means "unchanged": restore the
656 // stored original so a mask string is never persisted. Guarding on the mask SHAPE rather
657 // than strict equality with mask(stored) closes the corruption loop QA hit: once a mask
658 // slips into storage, mask(mask) === mask, so an equality-only guard would faithfully
659 // preserve the corrupted value forever (round-2 follow-up #1). When the stored value is
660 // itself mask-shaped it is unrecoverable — clear it so the UI and the GeoIP/GA4 status
661 // surfaces honestly report a missing key instead of failing downstream with 401s.
662 $sensitive_api_keys = self::sensitive_api_key_fields();
663 foreach ( $sensitive_api_keys as $api_key_field ) {
664 if ( ! isset( $settings[ $api_key_field ] ) ) {
665 continue;
666 }
667 $incoming = trim( (string) $settings[ $api_key_field ] );
668 $stored = isset( $_old_settings[ $api_key_field ] ) ? (string) $_old_settings[ $api_key_field ] : '';
669 $stored_is_mask = '' !== $stored && preg_match( '/\*{4,}/', $stored );
670
671 if ( '' !== $incoming && preg_match( '/\*{4,}/', $incoming ) ) {
672 $settings[ $api_key_field ] = $stored_is_mask ? '' : $stored;
673 }
674 }
675
676 // Minimum-token policy: reject saves where a feature's max_token field is below the
677 // per-model floor defined by AIHelper::get_min_tokens(). The model may be unchanged in
678 // this submission, so fall back to the stored value when it isn't part of the payload.
679 // Both token fields live under the AI Content Suite tab, so enforce the floor only when
680 // that tab is the one being saved — saving any other settings page never trips it (e.g.
681 // old users whose stored value predates the floor). When the active-tab hint is absent
682 // (stale cached JS, the quick-setup wizard, or any programmatic caller) the loop is
683 // skipped and the save is allowed through.
684 $token_pairs = array(
685 array(
686 'tab' => 'tab-betterdocs-ai',
687 'context' => 'write_with_ai',
688 'token_key' => 'ai_autowrite_max_token',
689 'model_key' => 'ai_model',
690 'label' => __( 'Write with AI', 'betterdocs' )
691 ),
692 array(
693 'tab' => 'tab-betterdocs-ai',
694 'context' => 'article_summary',
695 'token_key' => 'article_summary_max_token',
696 'model_key' => 'ai_model',
697 'label' => __( 'AI Doc Summarizer', 'betterdocs' )
698 )
699 );
700 foreach ( $token_pairs as $pair ) {
701 if ( $active_tab !== $pair[ 'tab' ] ) {
702 continue;
703 }
704 if ( ! array_key_exists( $pair[ 'token_key' ], $settings ) ) {
705 continue;
706 }
707 $incoming_tokens = (int) $settings[ $pair[ 'token_key' ] ];
708 $incoming_model = isset( $settings[ $pair[ 'model_key' ] ] )
709 ? $settings[ $pair[ 'model_key' ] ]
710 : ( isset( $_old_settings[ $pair[ 'model_key' ] ] ) ? $_old_settings[ $pair[ 'model_key' ] ] : '' );
711 $min = AIHelper::get_min_tokens( $pair[ 'context' ], $incoming_model );
712 if ( $min > 0 && $incoming_tokens < $min ) {
713 return new WP_Error(
714 'min_tokens_below_threshold',
715 sprintf(
716 /* translators: 1: feature label, 2: minimum tokens, 3: model id */
717 __( '%1$s max tokens must be at least %2$d for %3$s.', 'betterdocs' ),
718 $pair[ 'label' ],
719 $min,
720 $incoming_model
721 )
722 );
723 }
724 }
725
726 // @todo: sanitize the data before inject in DB.
727 $_normalized_settings = $this->get_normalized_values( $settings );
728 if ( $existing_plugins && isset( $_normalized_settings[ 'migration_step' ] ) && true == $_normalized_settings[ 'migration_step' ] ) {
729 betterdocs()->kbmigration->migrate();
730 }
731 $_settings = wp_parse_args( $_normalized_settings, $_old_settings );
732
733 // Check if there are actual changes before saving.
734 // update_option returns false when values serialize identically, which can happen
735 // due to object caching or type normalization even when user made changes.
736 $_has_changes = $_settings != $_old_settings;
737
738 $_saved = $this->database->save( $this->base_key, $_settings );
739
740 do_action_ref_array( 'betterdocs::settings::saved', array( $_saved, $_settings, $_old_settings, &$this ) );
741
742 // Return true if save succeeded OR if there were changes to attempt saving.
743 // This handles cases where update_option returns false due to identical serialization
744 // (e.g., object caching, type coercion during serialization).
745 return $_saved || $_has_changes;
746 }
747
748 public function views( $hook ) {
749 return betterdocs()->views->get( 'admin/settings' );
750 }
751
752 public function save_default_settings() {
753 $_settings = $this->get_all();
754
755 return $this->database->save( $this->base_key, $_settings );
756 }
757
758 public function get_pages() {
759 $_pages = betterdocs()->query->get_posts( array(
760 'post_type' => 'page',
761 'numberposts' => -1,
762 'post_status' => 'publish',
763 'posts_per_page' => -1
764 ) );
765
766 $__pages = array();
767
768 if ( ! empty( $_pages ) ) {
769 $__pages[ 0 ] = __( 'Select a Page', 'betterdocs' );
770 foreach ( $_pages->posts as $page ) {
771 $__pages[ $page->ID ] = esc_html( $page->post_title );
772 }
773 }
774
775 return $__pages;
776 }
777
778 public function settings_args() {
779 $wp_roles = $this->normalize_options( $this->get_roles() );
780
781 $roles_for_ikb = $this->normalize_options( array_merge( array( 'all' => __( 'All logged in users', 'betterdocs' ) ), wp_roles()->role_names ) );
782
783 unset( $roles_for_ikb[ 'administrator' ] );
784
785 $settings = array(
786 'id' => 'betterdocs_settings_metabox_wrapper',
787 'title' => __( 'betterdocs', 'betterdocs' ),
788 'object_types' => array( 'betterdocs' ),
789 'context' => 'normal',
790 'priority' => 'high',
791 'show_header' => false,
792 'tabnumber' => false,
793 'is_pro_active' => betterdocs()->is_pro_active(),
794 'logoURL' => betterdocs()->assets->icon( 'betterdocs-icon.svg', true ),
795 'layout' => 'vertical',
796 'config' => array(
797 'active' => 'tab-general',
798 'sidebar' => true,
799 'title' => false
800 ),
801 'submit' => array(
802 'show' => true,
803 'label' => __( 'Save', 'betterdocs' ),
804 'loadingLabel' => __( 'Saving...', 'betterdocs' ),
805 'class' => 'save-settings',
806 'rules' => Rules::logicalRule( array(
807 Rules::is( 'config.active', 'tab-design', true ),
808 Rules::is( 'config.active', 'tab-shortcodes', true ),
809 Rules::is( 'config.active', 'tab-instant-answer', true ),
810 Rules::is( 'config.active', 'tab-import-export', true ),
811 Rules::is( 'config.active', 'tab-migration', true )
812 ), 'and' )
813 ),
814 'values' => betterdocs()->is_pro_active() ? $this->get_all() : array_merge( $this->get_all(), $this->pro_settings_default_values() ),
815 'tabs' => apply_filters( 'betterdocs_settings_tabs', array(
816 'tab-general' => apply_filters( 'betterdocs_settings_tab_general', array(
817 'id' => 'tab-general',
818 'label' => __( 'General', 'betterdocs' ),
819 'classes' => 'tab-general',
820 'priority' => 10,
821 'fields' => array(
822 'title-general' => apply_filters( 'betterdocs_encyclopedia_settings', array(
823 'name' => 'title-general-tab',
824 'type' => 'section',
825 'label' => __( 'General Settings', 'betterdocs' ),
826 'priority' => 10,
827 'fields' => array(
828 'multiple_kb' => apply_filters( 'betterdocs_multi_kb_settings', array(
829 'name' => 'multiple_kb',
830 'type' => 'toggle',
831 'label' => __( 'Multiple Knowledge Base', 'betterdocs' ),
832 'enable_disable_text_active' => true,
833 'default' => '',
834 'priority' => 1,
835 'is_pro' => true
836 ) ),
837 'builtin_doc_page' => array(
838 'name' => 'builtin_doc_page',
839 'type' => 'toggle',
840 'label' => __( 'Built-in Documentation Page', 'betterdocs' ),
841 'enable_disable_text_active' => true,
842 'default' => 1,
843 'priority' => 2
844 ),
845 'enable_category_hierarchy_slugs' => array(
846 'name' => 'enable_category_hierarchy_slugs',
847 'type' => 'toggle',
848 'label' => __( 'Enable Category Hierarchy Slug', 'betterdocs' ),
849 'enable_disable_text_active' => true,
850 'default' => false,
851 'priority' => 3,
852 'label_subtitle' => __( "Shows Hierarchy Based Permalink Slugs For Doc Categories & Single Doc Page", 'betterdocs' )
853 ),
854 'breadcrumb_doc_title' => array(
855 'name' => 'breadcrumb_doc_title',
856 'type' => 'text',
857 'label' => __( 'Documentation Page Title', 'betterdocs' ),
858 'default' => __( 'Docs', 'betterdocs' ),
859 'priority' => 4,
860 'rules' => Rules::is( 'builtin_doc_page', true )
861 ),
862 'docs_slug' => array(
863 'name' => 'docs_slug',
864 'type' => 'text',
865 'label' => __( 'BetterDocs Root Slug', 'betterdocs' ),
866 'default' => 'docs',
867 'priority' => 5,
868 'rules' => Rules::is( 'builtin_doc_page', true )
869 ),
870 'docs_page' => array(
871 'name' => 'docs_page',
872 'label' => __( 'Docs Page', 'betterdocs' ),
873 'type' => 'select',
874 'default' => 0,
875 'priority' => 6,
876 'search' => true,
877 'options' => $this->normalize_options( $this->get_pages() ),
878 'label_subtitle' => __( 'You will need to insert BetterDocs Shortcode inside the page. This page will be used as docs permalink.', 'betterdocs' ),
879 'rules' => Rules::is( 'builtin_doc_page', false )
880 ),
881
882 'category_slug' => array(
883 'name' => 'category_slug',
884 'type' => 'text',
885 'label' => __( 'Custom Category Slug', 'betterdocs' ),
886 'default' => 'docs-category',
887 'priority' => 7
888 ),
889 'tag_slug' => array(
890 'name' => 'tag_slug',
891 'type' => 'text',
892 'label' => __( 'Custom Tag Slug', 'betterdocs' ),
893 'default' => 'docs-tag',
894 'priority' => 8
895 ),
896 'permalink_structure' => array(
897 'name' => 'permalink_structure',
898 'type' => 'permalink_structure',
899 'label' => __( 'Single Docs Permalink', 'betterdocs' ),
900 'default' => PostType::permalink_structure(),
901 'priority' => 9,
902 'tags' => $this->normalize_options( array(
903 '%doc_category%' => '%doc_category%',
904 '%knowledge_base%' => '%knowledge_base%'
905 ) ),
906 'label_subtitle' => __( 'Make sure to keep Docs Root Slug in the Single Docs Permalink. You are not able to keep it blank. You can use the available tags from below.', 'betterdocs' )
907 ),
908 'enable_glossaries' => array(
909 'name' => 'enable_glossaries',
910 'type' => 'toggle',
911 'label' => __( 'Show Glossary', 'betterdocs' ),
912 'label_subtitle' => __( 'Enable the glossary feature to allow users to look up definitions for terms used within your encyclopedia or glossaries themselves.', 'betterdocs' ),
913 'enable_disable_text_active' => true,
914 'default' => false,
915 'priority' => 10,
916 'is_pro' => true
917 ),
918 'enable_encyclopedia' => array(
919 'name' => 'enable_encyclopedia',
920 'type' => 'toggle',
921 'label' => __( 'Built-in Encyclopedia Page', 'betterdocs' ),
922 'enable_disable_text_active' => true,
923 'default' => false,
924 'priority' => 11,
925 'is_pro' => true
926 ),
927 'enable_faq_schema' => array(
928 'name' => 'enable_faq_schema',
929 'type' => 'toggle',
930 'label' => __( 'FAQ Schema', 'betterdocs' ),
931 'enable_disable_text_active' => true,
932 'default' => '',
933 'priority' => 12
934 ),
935 'analytics_from' => array(
936 'name' => 'analytics_from',
937 'type' => 'select',
938 'label' => __( 'Analytics From', 'betterdocs' ),
939 'options' => $this->normalize_options( array(
940 'everyone' => __( 'Everyone', 'betterdocs' ),
941 'guests' => __( 'Guests Only', 'betterdocs' ),
942 'registered_users' => __( 'Registered Users Only', 'betterdocs' )
943 ) ),
944 'default' => 'everyone',
945 'priority' => 13,
946 'is_pro' => true
947 ),
948 'unique_visitor_count' => array(
949 'name' => 'unique_visitor_count',
950 'type' => 'toggle',
951 'label' => __( 'Unique Visitor Count', 'betterdocs' ),
952 'enable_disable_text_active' => true,
953 'default' => false,
954 'priority' => 14,
955 'is_pro' => true
956 ),
957 'exclude_bot_analytics' => array(
958 'name' => 'exclude_bot_analytics',
959 'type' => 'toggle',
960 'label' => __( 'Exclude Bot Analytics', 'betterdocs' ),
961 'enable_disable_text_active' => true,
962 'default' => false,
963 'priority' => 15,
964 'is_pro' => true
965 )
966
967 )
968 ) )
969 )
970 ) ),
971 'tab-layout' => apply_filters( 'betterdocs_settings_tab_layout', array(
972 'id' => 'tab-layout',
973 'label' => __( 'Layout', 'betterdocs' ),
974 'classes' => 'tab-layout',
975 'priority' => 20,
976 'fields' => array(
977 'title-layout' => array(
978 'name' => 'title-layout-tab',
979 'type' => 'section',
980 'label' => __( 'Layout Settings', 'betterdocs' ),
981 'priority' => 20,
982 'fields' => array(
983 'tab-sidebar-layout' => apply_filters( 'betterdocs_settings_tab_sidebar_layout', array(
984 'id' => 'tab-sidebar-layout',
985 'name' => 'tab_sidebar_layout',
986 'label' => __( 'Layout Settings', 'betterdocs' ),
987 'classes' => 'tab-layout',
988 'type' => 'tab',
989 'active' => 'layout_documentation_page',
990 'completionTrack' => true,
991 'sidebar' => false,
992 'save' => false,
993 'title' => false,
994 'config' => array(
995 'active' => 'layout_documentation_page',
996 'sidebar' => false,
997 'title' => false
998 ),
999 'submit' => array(
1000 'show' => false
1001 ),
1002 'step' => array(
1003 'show' => false
1004 ),
1005 'priority' => 20,
1006 'fields' => array(
1007 'layout_documentation_page' => array(
1008 'id' => 'layout_documentation_page',
1009 'name' => 'layout_documentation_page',
1010 'type' => 'section',
1011 'label' => __( 'Documentation Page', 'betterdocs' ),
1012 'priority' => 1,
1013 'fields' => array(
1014 'tab-nested-layout-1' => array(
1015 'id' => 'tab-nested-layout-1',
1016 'name' => 'tab_nested_layout_1',
1017 'label' => __( 'Documentation Page', 'betterdocs' ),
1018 'classes' => 'tab-nested-layout',
1019 'type' => 'tab',
1020 'active' => 'layout_documentation_page_general',
1021 'completionTrack' => true,
1022 'sidebar' => false,
1023 'save' => false,
1024 'title' => false,
1025 'config' => array(
1026 'active' => 'layout_documentation_page_general',
1027 'sidebar' => false,
1028 'title' => false
1029 ),
1030 'submit' => array(
1031 'show' => false
1032 ),
1033 'step' => array(
1034 'show' => false
1035 ),
1036 'priority' => 1,
1037 'fields' => array(
1038 'layout_documentation_page_general' => array(
1039 'id' => 'layout_documentation_page_general',
1040 'name' => 'layout_documentation_page_general',
1041 'type' => 'section',
1042 'label' => __( 'General', 'betterdocs' ),
1043 'priority' => 1,
1044 'fields' => array(
1045 'docs_list_icon' => array(
1046 'name' => 'docs_list_icon',
1047 'type' => 'media',
1048 'value' => '',
1049 'label' => __( 'Docs List Icon', 'betterdocs' ),
1050 'label_subtitle' => __( 'Upload your own preferred document list icon', 'betterdocs' ),
1051 'priority' => 0
1052 ),
1053 'category_title_link' => array(
1054 'name' => 'category_title_link',
1055 'type' => 'toggle',
1056 'label' => __( 'Category Title Link', 'betterdocs' ),
1057 'label_subtitle' => __( 'This setting is applicable for category grid layout', 'betterdocs' ),
1058 'enable_disable_text_active' => true,
1059 'default' => false,
1060 'priority' => 0
1061 ),
1062 'masonry_layout' => array(
1063 'name' => 'masonry_layout',
1064 'type' => 'toggle',
1065 'label' => __( 'Masonry', 'betterdocs' ),
1066 'enable_disable_text_active' => true,
1067 'default' => 1,
1068 'priority' => 1
1069 ),
1070 'archive_lazy_load_descendants' => array(
1071 'name' => 'archive_lazy_load_descendants',
1072 'type' => 'toggle',
1073 'label' => __( 'Lazy Load Sidebar Docs & Subcategories', 'betterdocs' ),
1074 'label_subtitle' => __( 'Applies to the Sidebar layout wherever it appears — single docs, category archives, and the Sidebar widget/block. Off by default: enable to render only the active category upfront and fetch the rest on click (or on scroll for the Memphis layout), cutting initial HTML size on large knowledge bases.', 'betterdocs' ),
1075 'enable_disable_text_active' => true,
1076 'default' => false,
1077 'priority' => 2
1078 ),
1079 'nested_subcategory' => array(
1080 'name' => 'nested_subcategory',
1081 'type' => 'toggle',
1082 'label' => __( 'Nested Sub Category', 'betterdocs' ),
1083 'enable_disable_text_active' => true,
1084 'default' => '',
1085 'priority' => 3
1086 ),
1087 'column_number' => array(
1088 'name' => 'column_number',
1089 'type' => 'number',
1090 'label' => __( 'Number Of Columns', 'betterdocs' ),
1091 'label_subtitle' => __( 'This setting is not applicable for sleek layout.', 'betterdocs' ),
1092 'default' => 3,
1093 'priority' => 4
1094 ),
1095 'posts_number' => apply_filters( 'betterdocs_posts_number', array(
1096 'name' => 'posts_number',
1097 'type' => 'number',
1098 'label' => __( 'Number Of Docs', 'betterdocs' ),
1099 'label_subtitle' => __( 'This setting is not applicable for handbook layout.', 'betterdocs' ),
1100 'default' => 10,
1101 'priority' => 5
1102 ) ),
1103 'post_count' => array(
1104 'name' => 'post_count',
1105 'type' => 'toggle',
1106 'label' => __( 'Doc Count', 'betterdocs' ),
1107 'enable_disable_text_active' => true,
1108 'default' => 1,
1109 'priority' => 6
1110 ),
1111 'count_text' => array(
1112 'name' => 'count_text',
1113 'type' => 'text',
1114 'label' => __( 'Count Text', 'betterdocs' ),
1115 'default' => __( 'Docs', 'betterdocs' ),
1116 'priority' => 7
1117 ),
1118 'count_text_singular' => array(
1119 'name' => 'count_text_singular',
1120 'type' => 'text',
1121 'label' => __( 'Count Text Singular', 'betterdocs' ),
1122 'default' => __( 'Doc', 'betterdocs' ),
1123 'priority' => 8
1124 ),
1125 'exploremore_btn' => array(
1126 'name' => 'exploremore_btn',
1127 'type' => 'toggle',
1128 'label' => __( 'Explore More Button', 'betterdocs' ),
1129 'enable_disable_text_active' => true,
1130 'default' => true,
1131 'priority' => 9
1132 ),
1133 'exploremore_btn_txt' => array(
1134 'name' => 'exploremore_btn_txt',
1135 'type' => 'text',
1136 'label' => __( 'Explore More Button Text', 'betterdocs' ),
1137 'default' => __( 'Explore More', 'betterdocs' ),
1138 'priority' => 10,
1139 'rules' => Rules::is( 'exploremore_btn', true )
1140 ),
1141 'betterdocs_popular_docs_text' => array(
1142 'name' => 'betterdocs_popular_docs_text',
1143 'type' => 'text',
1144 'label' => __( 'Popular Docs Text', 'betterdocs' ),
1145 'default' => __( 'Popular Docs', 'betterdocs' ),
1146 'priority' => 11,
1147 'is_pro' => true
1148 ),
1149 'betterdocs_popular_docs_number' => array(
1150 'name' => 'betterdocs_popular_docs_number',
1151 'type' => 'number',
1152 'label' => __( 'Popular Docs Number', 'betterdocs' ),
1153 'default' => 10,
1154 'priority' => 12,
1155 'is_pro' => true
1156 )
1157 )
1158 ),
1159 'layout_documentation_page_search' => array(
1160 'id' => 'layout_documentation_page_search',
1161 'name' => 'layout_documentation_page_search',
1162 'type' => 'section',
1163 'label' => __( 'Search', 'betterdocs' ),
1164 'priority' => 2,
1165 'fields' => array(
1166 'live_search' => array(
1167 'name' => 'live_search',
1168 'type' => 'toggle',
1169 'label' => __( 'Live Search', 'betterdocs' ),
1170 'enable_disable_text_active' => true,
1171 'default' => 1,
1172 'priority' => 1
1173 ),
1174 'advance_search' => apply_filters( 'betterdocs_advance_search_settings', array(
1175 'name' => 'advance_search',
1176 'type' => 'toggle',
1177 'label' => __( 'Advanced Search', 'betterdocs' ),
1178 'enable_disable_text_active' => true,
1179 'default' => '',
1180 'priority' => 2,
1181 'is_pro' => true
1182 ) ),
1183 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- legacy public filter name for category-exclude settings field, retained for back-compat.
1184 'child_category_exclude' => apply_filters( 'child_category_exclude', array(
1185 'name' => 'child_category_exclude',
1186 'type' => 'toggle',
1187 'label' => __( 'Exclude Child Terms In Category Search', 'betterdocs' ),
1188 'enable_disable_text_active' => true,
1189 'default' => '',
1190 'priority' => 3,
1191 'is_pro' => true
1192 ) ),
1193 'popular_keyword_limit' => apply_filters( 'betterdocs_popular_keyword_limit_settings', array(
1194 'name' => 'popular_keyword_limit',
1195 'type' => 'number',
1196 'label' => __( 'Minimum amount of Keywords Search', 'betterdocs' ),
1197 'default' => 5,
1198 'priority' => 4,
1199 'is_pro' => true
1200 ) ),
1201 'search_letter_limit' => array(
1202 'name' => 'search_letter_limit',
1203 'type' => 'number',
1204 'label' => __( 'Minimum Character Limit For Search Result', 'betterdocs' ),
1205 'priority' => 5,
1206 'default' => 3
1207 ),
1208 'search_placeholder' => array(
1209 'name' => 'search_placeholder',
1210 'type' => 'text',
1211 'label' => __( 'Search Placeholder', 'betterdocs' ),
1212 'default' => __( 'Search..', 'betterdocs' ),
1213 'priority' => 6
1214 ),
1215 'search_button_text' => apply_filters( 'betterdocs_search_button_text', array(
1216 'name' => 'search_button_text',
1217 'type' => 'text',
1218 'label' => __( 'Search Button Text', 'betterdocs' ),
1219 'priority' => 7,
1220 'default' => __( 'Search', 'betterdocs' )
1221 ) ),
1222 'search_not_found_text' => array(
1223 'name' => 'search_not_found_text',
1224 'type' => 'text',
1225 'label' => __( 'Search Not Found Text', 'betterdocs' ),
1226 'default' => 'Sorry, no docs were found.',
1227 'priority' => 8
1228 ),
1229 'search_result_image' => array(
1230 'name' => 'search_result_image',
1231 'type' => 'toggle',
1232 'label' => __( 'Search Result Image', 'betterdocs' ),
1233 'enable_disable_text_active' => true,
1234 'default' => 1,
1235 'priority' => 9
1236 ),
1237 'kb_based_search' => apply_filters( 'betterdocs_kb_based_search_settings', array(
1238 'name' => 'kb_based_search',
1239 'type' => 'toggle',
1240 'label' => __( 'KB Based Search', 'betterdocs' ),
1241 'enable_disable_text_active' => true,
1242 'default' => '',
1243 'priority' => 10,
1244 'is_pro' => true,
1245 'rules' => Rules::is( 'multiple_kb', true )
1246 ) ),
1247 'search_modal_search_type' => array(
1248 'name' => 'search_modal_search_type',
1249 'type' => 'select',
1250 'label' => __( 'Search Modal Source', 'betterdocs' ),
1251 'label_subtitle' => __( 'Choose the content type to be shown in the search modal. <br> <b>Note:</b> This is only applicable on Modal Search.', 'betterdocs' ),
1252 'default' => 'all',
1253 'options' => $this->normalize_options( array(
1254 'all' => __( 'Both (Docs + FAQs)', 'betterdocs' ),
1255 'docs' => __( 'Docs only', 'betterdocs' ),
1256 'faq' => __( 'FAQs only', 'betterdocs' )
1257 ) ),
1258 'priority' => 11
1259 )
1260 )
1261 ),
1262 'layout_documentation_page_order_by' => array(
1263 'id' => 'layout_documentation_page_order_by',
1264 'name' => 'layout_documentation_page_order_by',
1265 'type' => 'section',
1266 'label' => __( 'Order By', 'betterdocs' ),
1267 'priority' => 3,
1268 'fields' => array(
1269 'terms_orderby' => array(
1270 'name' => 'terms_orderby',
1271 'type' => 'select',
1272 'label' => __( 'Terms Order By', 'betterdocs' ),
1273 'default' => 'betterdocs_order',
1274 'options' => $this->normalize_options( apply_filters( 'betterdocs_terms_orderby_options', array(
1275 'none' => __( 'No order', 'betterdocs' ),
1276 'name' => __( 'Name', 'betterdocs' ),
1277 'slug' => __( 'Slug', 'betterdocs' ),
1278 'term_group' => __( 'Term Group', 'betterdocs' ),
1279 'term_id' => __( 'Term ID', 'betterdocs' ),
1280 'id' => __( 'ID', 'betterdocs' ),
1281 'description' => __( 'Description', 'betterdocs' ),
1282 'parent' => __( 'Parent', 'betterdocs' ),
1283 'betterdocs_order' => __( 'BetterDocs Order', 'betterdocs' )
1284 ) ) ),
1285 'priority' => 1
1286 ),
1287 'terms_order' => array(
1288 'name' => 'terms_order',
1289 'type' => 'select',
1290 'label' => __( 'Terms Order', 'betterdocs' ),
1291 'default' => 'ASC',
1292 'options' => $this->normalize_options( array(
1293 'ASC' => 'Ascending',
1294 'DESC' => 'Descending'
1295 ) ),
1296 'priority' => 3,
1297 'rules' => Rules::includes( 'terms_orderby', 'betterdocs_order', true )
1298 ),
1299 'alphabetically_order_post' => array(
1300 'name' => 'alphabetically_order_post',
1301 'type' => 'select',
1302 'label' => __( 'Docs Order By', 'betterdocs' ),
1303 'default' => 'betterdocs_order',
1304 'options' => $this->normalize_options( array(
1305 'none' => __( 'No order', 'betterdocs' ),
1306 'ID' => __( 'Docs ID', 'betterdocs' ),
1307 'author' => __( 'Docs Author', 'betterdocs' ),
1308 'title' => __( 'Title', 'betterdocs' ),
1309 'date' => __( 'Date', 'betterdocs' ),
1310 'modified' => __( 'Last Modified Date', 'betterdocs' ),
1311 'parent' => __( 'Parent Id', 'betterdocs' ),
1312 'rand' => __( 'Random', 'betterdocs' ),
1313 'comment_count' => __( 'Comment Count', 'betterdocs' ),
1314 'menu_order' => __( 'Menu Order', 'betterdocs' ),
1315 'betterdocs_order' => __( 'BetterDocs Order', 'betterdocs' )
1316 ) ),
1317 'priority' => 4
1318 ),
1319 'docs_order' => array(
1320 'name' => 'docs_order',
1321 'type' => 'select',
1322 'label' => __( 'Docs Order', 'betterdocs' ),
1323 'default' => 'ASC',
1324 'options' => $this->normalize_options( array(
1325 'ASC' => 'Ascending',
1326 'DESC' => 'Descending'
1327 ) ),
1328 'priority' => 5,
1329 'rules' => Rules::includes( 'alphabetically_order_post', 'betterdocs_order', true )
1330 )
1331 )
1332 )
1333 )
1334 )
1335 )
1336 ),
1337 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- legacy public filter name for single-doc setting section, retained for back-compat.
1338 'layout_single_doc' => apply_filters( 'single_doc_setting_section', array(
1339 'id' => 'layout_single_doc',
1340 'name' => 'layout_single_doc',
1341 'type' => 'section',
1342 'label' => __( 'Single Doc', 'betterdocs' ),
1343 'priority' => 2,
1344 'fields' => array(
1345 'tab-nested-layout-2' => array(
1346 'id' => 'tab-nested-layout-2',
1347 'name' => 'tab_nested_layout_2',
1348 'label' => __( 'Single Doc', 'betterdocs' ),
1349 'classes' => 'tab-nested-layout',
1350 'type' => 'tab',
1351 'active' => 'layout_single_doc_general',
1352 'completionTrack' => true,
1353 'sidebar' => false,
1354 'save' => false,
1355 'title' => false,
1356 'config' => array(
1357 'active' => 'layout_single_doc_general',
1358 'sidebar' => false,
1359 'title' => false
1360 ),
1361 'submit' => array(
1362 'show' => false
1363 ),
1364 'step' => array(
1365 'show' => false
1366 ),
1367 'priority' => 20,
1368 'fields' => array(
1369 'layout_single_doc_general' => array(
1370 'id' => 'layout_single_doc_general',
1371 'name' => 'layout_single_doc_general',
1372 'type' => 'section',
1373 'label' => __( 'General', 'betterdocs' ),
1374 'priority' => 5,
1375 'fields' => array(
1376 'enable_post_title' => array(
1377 'name' => 'enable_post_title',
1378 'type' => 'toggle',
1379 'label' => __( 'Doc Title', 'betterdocs' ),
1380 'enable_disable_text_active' => true,
1381 'default' => 1,
1382 'priority' => 1
1383 ),
1384 'enable_sidebar_cat_list' => array(
1385 'name' => 'enable_sidebar_cat_list',
1386 'type' => 'toggle',
1387 'label' => __( 'Sidebar Category List', 'betterdocs' ),
1388 'enable_disable_text_active' => true,
1389 'default' => 1,
1390 'priority' => 2
1391 ),
1392 'enable_print_icon' => array(
1393 'name' => 'enable_print_icon',
1394 'type' => 'toggle',
1395 'label' => __( 'Print Icon', 'betterdocs' ),
1396 'enable_disable_text_active' => true,
1397 'default' => 1,
1398 'priority' => 3
1399 ),
1400 'enable_tags' => array(
1401 'name' => 'enable_tags',
1402 'type' => 'toggle',
1403 'label' => __( 'Tags', 'betterdocs' ),
1404 'enable_disable_text_active' => true,
1405 'default' => 1,
1406 'priority' => 4
1407 ),
1408 'show_last_update_time' => array(
1409 'name' => 'show_last_update_time',
1410 'type' => 'toggle',
1411 'label' => __( 'Last Update Time', 'betterdocs' ),
1412 'enable_disable_text_active' => true,
1413 'default' => 1,
1414 'priority' => 5
1415 ),
1416 'enable_navigation' => array(
1417 'name' => 'enable_navigation',
1418 'type' => 'toggle',
1419 'label' => __( 'Navigation', 'betterdocs' ),
1420 'enable_disable_text_active' => true,
1421 'default' => 1,
1422 'priority' => 6
1423 ),
1424 'enable_comment' => array(
1425 'name' => 'enable_comment',
1426 'type' => 'toggle',
1427 'label' => __( 'Comment', 'betterdocs' ),
1428 'enable_disable_text_active' => true,
1429 'default' => '',
1430 'priority' => 7
1431 ),
1432 'enable_credit' => array(
1433 'name' => 'enable_credit',
1434 'type' => 'toggle',
1435 'label' => __( 'Show Powered by BetterDocs', 'betterdocs' ),
1436 'enable_disable_text_active' => true,
1437 'default' => '',
1438 'priority' => 8
1439 ),
1440 'reaction_feedback_text' => array(
1441 'name' => 'reaction_feedback_text',
1442 'type' => 'text',
1443 'label' => __( 'Reaction Feedback Text', 'betterdocs' ),
1444 'default' => __( 'Thanks for your feedback.', 'betterdocs' ),
1445 'priority' => 9
1446 ),
1447 'enable_estimated_reading_time' => array(
1448 'name' => 'enable_estimated_reading_time',
1449 'type' => 'toggle',
1450 'label' => __( 'Estimated Reading Time', 'betterdocs' ),
1451 'enable_disable_text_active' => true,
1452 'default' => 0,
1453 'priority' => 10
1454 ),
1455 'estimated_reading_time_title' => array(
1456 'name' => 'estimated_reading_time_title',
1457 'type' => 'text',
1458 'label' => __( 'Estimated Reading Time Title', 'betterdocs' ),
1459 'default' => '',
1460 'priority' => 11,
1461 'rules' => Rules::is( 'enable_estimated_reading_time', true )
1462 ),
1463 'estimated_reading_time_text' => array(
1464 'name' => 'estimated_reading_time_text',
1465 'type' => 'text',
1466 'label' => __( 'Estimated Reading Time Text', 'betterdocs' ),
1467 'default' => __( 'min read', 'betterdocs' ),
1468 'priority' => 12,
1469 'rules' => Rules::is( 'enable_estimated_reading_time', true )
1470 ),
1471 'singular_estimated_reading_time_text' => array(
1472 'name' => 'singular_estimated_reading_time_text',
1473 'type' => 'text',
1474 'label' => __( 'Estimated Reading Time Text Singular', 'betterdocs' ),
1475 'default' => __( 'min read', 'betterdocs' ),
1476 'priority' => 13,
1477 'rules' => Rules::is( 'enable_estimated_reading_time', true )
1478 )
1479 )
1480 ),
1481 'layout_single_doc_TOC' => array(
1482 'id' => 'layout_single_doc_TOC',
1483 'name' => 'layout_single_doc_TOC',
1484 'type' => 'section',
1485 'label' => __( 'TOC', 'betterdocs' ),
1486 'priority' => 5,
1487 'fields' => array(
1488 'enable_toc' => array(
1489 'name' => 'enable_toc',
1490 'type' => 'toggle',
1491 'label' => __( 'Table of Contents', 'betterdocs' ),
1492 'enable_disable_text_active' => true,
1493 'default' => 1,
1494 'priority' => 1
1495 ),
1496 'toc_title' => array(
1497 'name' => 'toc_title',
1498 'type' => 'text',
1499 'label' => __( 'TOC Title', 'betterdocs' ),
1500 'default' => __( 'Table of Contents', 'betterdocs' ),
1501 'priority' => 2,
1502 'rules' => Rules::is( 'enable_toc', true )
1503
1504 ),
1505 'toc_hierarchy' => array(
1506 'name' => 'toc_hierarchy',
1507 'type' => 'toggle',
1508 'label' => __( 'TOC Hierarchy', 'betterdocs' ),
1509 'enable_disable_text_active' => true,
1510 'default' => 1,
1511 'priority' => 3,
1512 'rules' => Rules::is( 'enable_toc', true )
1513 ),
1514 'toc_list_number' => array(
1515 'name' => 'toc_list_number',
1516 'type' => 'toggle',
1517 'label' => __( 'TOC List Number', 'betterdocs' ),
1518 'enable_disable_text_active' => true,
1519 'default' => 1,
1520 'priority' => 4,
1521 'rules' => Rules::is( 'enable_toc', true )
1522 ),
1523 'toc_dynamic_title' => array(
1524 'name' => 'toc_dynamic_title',
1525 'type' => 'toggle',
1526 'label' => __( 'Show TOC Title in Anchor Links', 'betterdocs' ),
1527 'enable_disable_text_active' => true,
1528 'default' => 0,
1529 'priority' => 5,
1530 'rules' => Rules::is( 'enable_toc', true )
1531 ),
1532 'enable_sticky_toc' => array(
1533 'name' => 'enable_sticky_toc',
1534 'type' => 'toggle',
1535 'label' => __( 'Sticky TOC', 'betterdocs' ),
1536 'enable_disable_text_active' => true,
1537 'default' => 1,
1538 'priority' => 6,
1539 'rules' => Rules::is( 'enable_toc', true )
1540 ),
1541 'collapsible_toc_mobile' => array(
1542 'name' => 'collapsible_toc_mobile',
1543 'type' => 'toggle',
1544 'label' => __( 'Collapsible TOC on small devices', 'betterdocs' ),
1545 'enable_disable_text_active' => true,
1546 'default' => '',
1547 'priority' => 7,
1548 'rules' => Rules::is( 'enable_toc', true )
1549 ),
1550 'title_link_ctc' => array(
1551 'name' => 'title_link_ctc',
1552 'type' => 'toggle',
1553 'label' => __( 'Title Link Copy To Clipboard', 'betterdocs' ),
1554 'enable_disable_text_active' => true,
1555 'default' => 1,
1556 'priority' => 8
1557 ),
1558 'supported_heading_tag' => array(
1559 'name' => 'supported_heading_tag',
1560 'label' => __( 'TOC Supported Heading Tag', 'betterdocs' ),
1561 'type' => 'checkbox-select',
1562 'multiple' => true,
1563 'priority' => 10,
1564 'default' => array(
1565 '1',
1566 '2',
1567 '3',
1568 '4',
1569 '5',
1570 '6'
1571 ),
1572 'options' => $this->normalize_options( array(
1573 '1' => 'H1',
1574 '2' => 'H2',
1575 '3' => 'H3',
1576 '4' => 'H4',
1577 '5' => 'H5',
1578 '6' => 'H6'
1579 ) ),
1580 'priority' => 9,
1581 'rules' => Rules::is( 'enable_toc', true )
1582 ),
1583 'sticky_toc_offset' => array(
1584 'name' => 'sticky_toc_offset',
1585 'type' => 'number',
1586 'label' => __( 'Content Offset', 'betterdocs' ),
1587 'default' => 100,
1588 'priority' => 10,
1589 'label_subtitle' => __( 'content offset from top on scroll.', 'betterdocs' ),
1590 'rules' => Rules::is( 'enable_toc', true )
1591 )
1592 )
1593 ),
1594 'layout_single_doc_breadcrumb' => array(
1595 'id' => 'layout_single_doc_breadcrumb',
1596 'name' => 'layout_single_doc_breadcrumb',
1597 'type' => 'section',
1598 'label' => __( 'Breadcrumb', 'betterdocs' ),
1599 'priority' => 5,
1600 'fields' => array(
1601 'enable_breadcrumb' => array(
1602 'name' => 'enable_breadcrumb',
1603 'type' => 'toggle',
1604 'label' => __( 'Breadcrumb', 'betterdocs' ),
1605 'enable_disable_text_active' => true,
1606 'default' => 1,
1607 'priority' => 1
1608 ),
1609 'enable_breadcrumb_home_text' => array(
1610 'name' => 'enable_breadcrumb_home_text',
1611 'type' => 'toggle',
1612 'label' => __( 'Enable Breadcrumb Home Text', 'betterdocs' ),
1613 'enable_disable_text_active' => true,
1614 'default' => true,
1615 'priority' => 2,
1616 'rules' => Rules::is( 'enable_breadcrumb', true )
1617 ),
1618 'breadcrumb_home_text' => array(
1619 'name' => 'breadcrumb_home_text',
1620 'type' => 'text',
1621 'label' => __( 'Breadcrumb Home Text', 'betterdocs' ),
1622 'default' => __( 'Home', 'betterdocs' ),
1623 'priority' => 3,
1624 'rules' => Rules::logicalRule(
1625 array(
1626 Rules::is( 'enable_breadcrumb', true ),
1627 Rules::is( 'enable_breadcrumb_home_text', true )
1628 ),
1629 'and'
1630 )
1631 ),
1632 'breadcrumb_home_url' => array(
1633 'name' => 'breadcrumb_home_url',
1634 'type' => 'text',
1635 'label' => __( 'Breadcrumb Home URL', 'betterdocs' ),
1636 'priority' => 4,
1637 'default' => get_home_url(),
1638 'rules' => Rules::is( 'enable_breadcrumb', true )
1639 ),
1640 'enable_breadcrumb_category' => array(
1641 'name' => 'enable_breadcrumb_category',
1642 'type' => 'toggle',
1643 'label' => __( 'Category on Breadcrumb', 'betterdocs' ),
1644 'enable_disable_text_active' => true,
1645 'default' => 1,
1646 'priority' => 5,
1647 'rules' => Rules::is( 'enable_breadcrumb', true )
1648 ),
1649 'enable_breadcrumb_title' => array(
1650 'name' => 'enable_breadcrumb_title',
1651 'type' => 'toggle',
1652 'label' => __( 'Title on Breadcrumb', 'betterdocs' ),
1653 'enable_disable_text_active' => true,
1654 'default' => 1,
1655 'priority' => 6,
1656 'rules' => Rules::is( 'enable_breadcrumb', true )
1657 )
1658 )
1659 ),
1660 'layout_single_doc_email_feedback' => array(
1661 'id' => 'layout_single_doc_email_feedback',
1662 'name' => 'layout_single_doc_email_feedback',
1663 'type' => 'section',
1664 'label' => __( 'Email Feedback', 'betterdocs' ),
1665 'priority' => 5,
1666 'fields' => array(
1667 'email_feedback' => array(
1668 'name' => 'email_feedback',
1669 'type' => 'toggle',
1670 'label' => __( 'Email Feedback', 'betterdocs' ),
1671 'enable_disable_text_active' => true,
1672 'default' => 1,
1673 'priority' => 1
1674 ),
1675 'feedback_link_text' => array(
1676 'name' => 'feedback_link_text',
1677 'type' => 'text',
1678 'label' => __( 'Feedback Link Text', 'betterdocs' ),
1679 'default' => __( 'Still stuck? How can we help?', 'betterdocs' ),
1680 'priority' => 2,
1681 'rules' => Rules::is( 'email_feedback', true )
1682 ),
1683 'feedback_form_title' => array(
1684 'name' => 'feedback_form_title',
1685 'type' => 'text',
1686 'label' => __( 'Feedback Form Title', 'betterdocs' ),
1687 'default' => __( 'Still stuck? How can we help?', 'betterdocs' ),
1688 'priority' => 3,
1689 'rules' => Rules::is( 'email_feedback', true )
1690 ),
1691 'email_address' => array(
1692 'name' => 'email_address',
1693 'type' => 'text',
1694 'label' => __( 'Email Address', 'betterdocs' ),
1695 'default' => get_option( 'admin_email' ),
1696 'priority' => 4,
1697 'label_subtitle' => __( 'The email address where the Feedback form will be sent', 'betterdocs' ),
1698 'rules' => Rules::is( 'email_feedback', true )
1699 ),
1700 'feedback_url' => array(
1701 'name' => 'feedback_url',
1702 'type' => 'text',
1703 'label' => __( 'Feedback URL', 'betterdocs' ),
1704 'default' => '',
1705 'priority' => 5,
1706 'rules' => Rules::is( 'email_feedback', true )
1707 )
1708 )
1709 ),
1710 array(
1711 'id' => 'layout_single_doc_attachments',
1712 'name' => 'layout_single_doc_attachments',
1713 'type' => 'section',
1714 'label' => __( 'Attachments', 'betterdocs' ),
1715 'priority' => 6,
1716 'fields' => apply_filters( 'betterdocs_single_doc_attachments', array(
1717 'show_attachment' => array(
1718 'name' => 'show_attachment',
1719 'type' => 'toggle',
1720 'is_pro' => true,
1721 'priority' => 1,
1722 'label' => __( 'Show Attachment', 'betterdocs' ),
1723 'enable_disable_text_active' => true,
1724 'default' => false
1725 )
1726 ) )
1727 ),
1728 array(
1729 'id' => 'layout_single_doc_related_docs',
1730 'name' => 'layout_single_doc_related_docs',
1731 'type' => 'section',
1732 'label' => __( 'Related Docs', 'betterdocs' ),
1733 'priority' => 7,
1734 'fields' => apply_filters( 'betterdocs_single_doc_related_docs', array(
1735 'show_related_docs' => array(
1736 'name' => 'show_related_docs',
1737 'type' => 'toggle',
1738 'is_pro' => true,
1739 'priority' => 1,
1740 'label' => __( 'Show Related Docs', 'betterdocs' ),
1741 'enable_disable_text_active' => true,
1742 'default' => false
1743 )
1744 ) )
1745 )
1746 )
1747 )
1748 )
1749 ) ),
1750 'layout_archive_page' => array(
1751 'id' => 'layout_archive_page',
1752 'name' => 'layout_archive_page',
1753 'type' => 'section',
1754 'label' => __( 'Archive Page', 'betterdocs' ),
1755 'priority' => 3,
1756 'fields' => array(
1757 'enable_archive_sidebar' => array(
1758 'name' => 'enable_archive_sidebar',
1759 'type' => 'toggle',
1760 'label' => __( 'Sidebar Category List', 'betterdocs' ),
1761 'enable_disable_text_active' => true,
1762 'default' => 1,
1763 'priority' => 31
1764 ),
1765 'archive_nested_subcategory' => array(
1766 'name' => 'archive_nested_subcategory',
1767 'type' => 'toggle',
1768 'label' => __( 'Nested Subcategory', 'betterdocs' ),
1769 'enable_disable_text_active' => true,
1770 'default' => 1,
1771 'priority' => 32
1772 ),
1773 'archive_enable_pagination' => array(
1774 'name' => 'archive_enable_pagination',
1775 'type' => 'toggle',
1776 'label' => __( 'Enable Pagination', 'betterdocs' ),
1777 'enable_disable_text_active' => true,
1778 'default' => false,
1779 'priority' => 33
1780 )
1781 )
1782 )
1783 )
1784 ) )
1785 )
1786 )
1787 )
1788 ) ),
1789 'tab-design' => apply_filters( 'betterdocs_settings_tab_design', array(
1790 'id' => 'tab-design',
1791 'label' => __( 'Design', 'betterdocs' ),
1792 'priority' => 30,
1793 'fields' => array(
1794 'title-design' => array(
1795 'name' => 'title-design-tab',
1796 'type' => 'section',
1797 'label' => __( 'Design', 'betterdocs' ),
1798 'priority' => 30,
1799 'fields' => $this->design_tab()
1800 )
1801 )
1802 ) ),
1803 'tab-shortcodes' => apply_filters( 'betterdocs_settings_tab_shortcodes', array(
1804 'label' => __( 'Shortcodes', 'betterdocs' ),
1805 'id' => 'tab-shortcodes',
1806 'classes' => 'tab-shortcodes',
1807 'priority' => 40,
1808 'fields' => array(
1809 'title-shortcodes' => array(
1810 'name' => 'title-shortcodes-tab',
1811 'type' => 'section',
1812 'label' => __( 'Shortcodes', 'betterdocs' ),
1813 'priority' => 40,
1814 'searchable' => true,
1815 'searchPlaceholder' => __( 'Search for shortcode', 'betterdocs' ),
1816 'searchNotFoundMessage' => '<img src="' . betterdocs()->assets->icon( 'not-found.svg', true ) . '"/><p>' . __( 'No Shortcodes Found with these keywords', 'betterdocs' ) . '</p>',
1817 'fields' => apply_filters( 'betterdocs_shortcode_fields', array(
1818 'search_form' => array(
1819 'name' => 'search_form',
1820 'type' => 'copy-to-clipboard',
1821 'label' => __( 'Search Form', 'betterdocs' ),
1822 'default' => '[betterdocs_search_form]',
1823 'readOnly' => true,
1824 'priority' => 1,
1825 'description' => __( '[betterdocs_search_form placeholder="Search..." heading="Heading" subheading="Subheading" category_search="true" search_button="true" popular_search="true"]', 'betterdocs' ),
1826 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1827 'descriptionCopyable' => true
1828 ),
1829 'feedback_form' => array(
1830 'name' => 'feedback_form',
1831 'type' => 'copy-to-clipboard',
1832 'label' => __( 'Feedback Form', 'betterdocs' ),
1833 'default' => '[betterdocs_feedback_form]',
1834 'readOnly' => true,
1835 'priority' => 2,
1836 'description' => __( '[betterdocs_feedback_form button_text="Send"]', 'betterdocs' ),
1837 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1838 'descriptionCopyable' => true
1839 ),
1840 'category_grid' => array(
1841 'name' => 'category_grid',
1842 'type' => 'copy-to-clipboard',
1843 'label' => __( 'Category Grid- Layout 1', 'betterdocs' ),
1844 'default' => '[betterdocs_category_grid]',
1845 'readOnly' => true,
1846 'priority' => 3,
1847 'description' => __( '[betterdocs_category_grid show_count="true" show_icon="true" masonry="true" column="3" posts_per_page="5" nested_subcategory="true" terms="term_ID, term_ID" terms_orderby="" terms_order="" multiple_knowledge_base="" kb_slug="" title_tag="h2" orderby="" order="" ]', 'betterdocs' ),
1848 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1849 'descriptionCopyable' => true
1850 ),
1851 'category_box' => array(
1852 'name' => 'category_box',
1853 'type' => 'copy-to-clipboard',
1854 'label' => __( 'Category Box- Layout 2', 'betterdocs' ),
1855 'default' => '[betterdocs_category_box]',
1856 'readOnly' => true,
1857 'priority' => 4,
1858 'description' => __( '[betterdocs_category_box orderby="" column="" nested_subcategory="" terms="" terms_orderby="" show_icon="" kb_slug="" title_tag="h2" multiple_knowledge_base="false" border_bottom="false"]', 'betterdocs' ),
1859 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1860 'descriptionCopyable' => true
1861 ),
1862 'category_list' => array(
1863 'name' => 'category_list',
1864 'type' => 'copy-to-clipboard',
1865 'label' => __( 'Category List', 'betterdocs' ),
1866 'default' => '[betterdocs_category_list]',
1867 'readOnly' => true,
1868 'priority' => 5,
1869 'description' => __( '[betterdocs_category_list orderby="" order="" posts_per_page="" nested_subcategory="" terms="" terms_orderby="" terms_order="" kb_slug="" multiple_knowledge_base="false" title_tag="h2"]', 'betterdocs' ),
1870 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1871 'descriptionCopyable' => true
1872 ),
1873 'faq_modern_layout' => array(
1874 'name' => 'faq_modern_layout',
1875 'type' => 'copy-to-clipboard',
1876 'label' => __( 'FAQ Layout - 1', 'betterdocs' ),
1877 'default' => '[betterdocs_faq_list_modern]',
1878 'readOnly' => true,
1879 'priority' => 13,
1880 'description' => __( '[betterdocs_faq_list_modern groups="group_id" class="" group_exclude="group_id" faq_heading="Frequently Asked Questions"]', 'betterdocs' ),
1881 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1882 'descriptionCopyable' => true
1883 ),
1884 'faq_classic_layout' => array(
1885 'name' => 'faq_classic_layout',
1886 'type' => 'copy-to-clipboard',
1887 'label' => __( 'FAQ Layout - 2', 'betterdocs' ),
1888 'default' => '[betterdocs_faq_list_classic]',
1889 'readOnly' => true,
1890 'priority' => 14,
1891 'description' => __( '[betterdocs_faq_list_classic groups="group_id" class="" group_exclude="group_id" faq_heading="Frequently Asked Questions"]', 'betterdocs' ),
1892 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1893 'descriptionCopyable' => true
1894 ),
1895 'betterdocs_faq_list_layout_3' => array(
1896 'name' => 'betterdocs_faq_list_layout_3',
1897 'type' => 'copy-to-clipboard',
1898 'label' => __( 'FAQ Layout - 3', 'betterdocs' ),
1899 'default' => '[betterdocs_faq_list_layout_3 class="faq-doc betterdocs-faq-layout-3"]',
1900 'readOnly' => true,
1901 'priority' => 15,
1902 'description' => __( '[betterdocs_faq_list_layout_3 class="faq-doc betterdocs-faq-layout-3" groups="group_id" class="" group_exclude="group_id" faq_heading="Frequently Asked Questions"]', 'betterdocs' ),
1903 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1904 'descriptionCopyable' => true
1905 ),
1906 'betterdocs_faq_tab' => array(
1907 'name' => 'betterdocs_faq_tab',
1908 'type' => 'copy-to-clipboard',
1909 'label' => __( 'FAQ Layout - 4', 'betterdocs' ),
1910 'default' => '[betterdocs_faq_tab class="faq-doc betterdocs-faq-layout-4"]',
1911 'readOnly' => true,
1912 'priority' => 16,
1913 'description' => __( '[betterdocs_faq_tab class="faq-doc betterdocs-faq-layout-4" groups="group_id" group_exclude="group_id" faq_heading="Frequently Asked Questions"]', 'betterdocs' ),
1914 'descriptionLabel' => __( 'Example with parameters:', 'betterdocs' ),
1915 'descriptionCopyable' => true
1916 )
1917 ) )
1918 )
1919 )
1920 ) ),
1921 'tab-advance-settings' => apply_filters( 'betterdocs_settings_tab_advance', array(
1922 'id' => 'tab-advance-settings',
1923 'label' => __( 'Access & Restrictions', 'betterdocs' ),
1924 'classes' => 'tab-layout',
1925 'priority' => 50,
1926 'fields' => array(
1927 'title_advance_settings' => array(
1928 'name' => 'title-advance-settings-tab',
1929 'type' => 'section',
1930 'label' => __( 'Access & Restrictions', 'betterdocs' ),
1931 'priority' => 50,
1932 'fields' => array(
1933 'tab-advanced-settings' => array(
1934 'id' => 'tab-advanced-settings',
1935 'name' => 'tab-advanced-settings',
1936 'label' => __( 'Access & Restrictions', 'betterdocs' ),
1937 'classes' => 'tab-layout',
1938 'type' => 'tab',
1939 'active' => 'global_role_management',
1940 'completionTrack' => true,
1941 'sidebar' => false,
1942 'save' => false,
1943 'title' => false,
1944 'config' => array(
1945 'active' => '',
1946 'sidebar' => false,
1947 'title' => false
1948 ),
1949 'submit' => array(
1950 'show' => false
1951 ),
1952 'step' => array(
1953 'show' => false
1954 ),
1955 'priority' => 50,
1956 'fields' => array(
1957 'global_role_management' => array(
1958 'id' => 'global_role_management',
1959 'name' => 'global_role_management',
1960 'type' => 'section',
1961 'label' => __( 'Global Role Management', 'betterdocs' ),
1962 'priority' => 1,
1963 'fields' => array(
1964 'article_roles' => array(
1965 'name' => 'article_roles',
1966 'type' => 'checkbox-select',
1967 'label' => __( 'Who Can Write Docs?', 'betterdocs' ),
1968 'priority' => 1,
1969 'multiple' => true,
1970 'search' => true,
1971 'is_pro' => true,
1972 'default' => array( 'administrator' ),
1973 'options' => $wp_roles
1974 ),
1975 'settings_roles' => array(
1976 'name' => 'settings_roles',
1977 'type' => 'checkbox-select',
1978 'label' => __( 'Who Can Edit Settings?', 'betterdocs' ),
1979 'priority' => 2,
1980 'multiple' => true,
1981 'is_pro' => true,
1982 'search' => true,
1983 'default' => array( 'administrator' ),
1984 'options' => $wp_roles
1985 ),
1986 'analytics_roles' => array(
1987 'name' => 'analytics_roles',
1988 'type' => 'checkbox-select',
1989 'label' => __( 'Who Can Check Analytics?', 'betterdocs' ),
1990 'priority' => 3,
1991 'multiple' => true,
1992 'is_pro' => true,
1993 'search' => true,
1994 'default' => array( 'administrator' ),
1995 'options' => $wp_roles
1996 ),
1997 'faq_roles' => array(
1998 'name' => 'faq_roles',
1999 'type' => 'checkbox-select',
2000 'label' => __( 'Who Can Check FAQ Builder?', 'betterdocs' ),
2001 'priority' => 4,
2002 'multiple' => true,
2003 'is_pro' => true,
2004 'search' => true,
2005 'default' => array( 'administrator' ),
2006 'options' => $wp_roles
2007 )
2008
2009 )
2010 ),
2011 'internal_knowledge_base' => array(
2012 'id' => 'internal_knowledge_base',
2013 'name' => 'internal_knowledge_base',
2014 'type' => 'section',
2015 'label' => __( 'Internal Knowledge Base', 'betterdocs' ),
2016 'priority' => 1,
2017 'fields' => apply_filters( 'betterdocs_settings_content_restriction_fields', array(
2018 'enable_content_restriction' => array(
2019 'name' => 'enable_content_restriction',
2020 'type' => 'toggle',
2021 'is_pro' => true,
2022 'priority' => 5,
2023 'label' => __( 'Internal Knowledge Base', 'betterdocs' ),
2024 'enable_disable_text_active' => true,
2025 'default' => array( 'all' )
2026 ),
2027 'internal_knowledge_base_type' => array(
2028 'name' => 'internal_knowledge_base_type',
2029 'type' => 'radio-card',
2030 'label' => __( 'Choose a Rule Type', 'betterdocs' ),
2031 'label_subtitle' => __( 'Choose a rule type: apply access globally (Basic), or configure detailed restrictions (Advanced) for docs, categories, and KBs.', 'betterdocs' ),
2032 'priority' => 6,
2033 'multiple' => true,
2034 'search' => true,
2035 'default' => array( 'all' ),
2036 'placeholder' => __( 'Select any', 'betterdocs' ),
2037 'options' => array(
2038 array(
2039 'label' => 'Basic',
2040 'value' => 'basic'
2041 ),
2042 array(
2043 'label' => 'Advanced',
2044 'value' => 'advanced'
2045 )
2046 ),
2047 'filterValue' => 'all',
2048 'rules' => Rules::is( 'enable_content_restriction', true )
2049 ),
2050 'content_visibility' => array(
2051 'name' => 'content_visibility',
2052 'type' => 'checkbox-select',
2053 'label' => __( 'Restrict Access to', 'betterdocs' ),
2054 'label_subtitle' => __( 'Only selected User Roles will be able to view your Knowledge Base', 'betterdocs' ),
2055 'is_pro' => true,
2056 'priority' => 7,
2057 'multiple' => true,
2058 'search' => true,
2059 'default' => array( 'all' ),
2060 'placeholder' => __( 'Select any', 'betterdocs' ),
2061 'options' => $roles_for_ikb,
2062 'rules' => Rules::logicalRule(
2063 array(
2064 Rules::is( 'enable_content_restriction', true ),
2065 Rules::is( 'internal_knowledge_base_type', 'basic' )
2066 ),
2067 'and'
2068 ),
2069 'filterValue' => 'all'
2070 ),
2071 'restrict_template' => array(
2072 'name' => 'restrict_template',
2073 'type' => 'checkbox-select',
2074 'label' => __( 'Restriction on Docs', 'betterdocs' ),
2075 'label_subtitle' => __( 'Selected Docs pages will be restricted', 'betterdocs' ),
2076 'is_pro' => true,
2077 'priority' => 8,
2078 'multiple' => true,
2079 'search' => true,
2080 'default' => array( 'all' ),
2081 'placeholder' => __( 'Select any', 'betterdocs' ),
2082 'options' => $this->get_texanomy(),
2083 'rules' => Rules::logicalRule(
2084 array(
2085 Rules::is( 'enable_content_restriction', true ),
2086 Rules::is( 'internal_knowledge_base_type', 'basic' )
2087 ),
2088 'and'
2089 ),
2090 'filterValue' => 'all'
2091 ),
2092 'restrict_category' => array(
2093 'name' => 'restrict_category',
2094 'type' => 'checkbox-select',
2095 'label' => __( 'Restriction on Docs Categories', 'betterdocs' ),
2096 'label_subtitle' => __( 'Selected Docs categories will be restricted', 'betterdocs' ),
2097 'is_pro' => true,
2098 'priority' => 9,
2099 'multiple' => true,
2100 'search' => true,
2101 'default' => array( 'all' ),
2102 'placeholder' => __( 'Select any', 'betterdocs' ),
2103 'options' => $this->get_terms( 'doc_category' ),
2104 'rules' => Rules::logicalRule(
2105 array(
2106 Rules::is( 'enable_content_restriction', true ),
2107 Rules::is( 'internal_knowledge_base_type', 'basic' )
2108 ),
2109 'and'
2110 ),
2111 'filterValue' => 'all'
2112 ),
2113 'restricted_redirect_url' => array(
2114 'name' => 'restricted_redirect_url',
2115 'type' => 'text',
2116 'label' => __( 'Redirect URL', 'betterdocs' ),
2117 'label_subtitle' => __( 'Set a custom URL to redirect users without permissions when they try to access internal knowledge base. By default, restricted content will redirect to the "404 not found" page', 'betterdocs' ),
2118 'default' => '',
2119 'placeholder' => 'https://',
2120 'is_pro' => true,
2121 'priority' => 20,
2122 'rules' => Rules::logicalRule(
2123 array(
2124 Rules::is( 'enable_content_restriction', true )
2125 ),
2126 'and'
2127 )
2128 )
2129 ) )
2130 )
2131 )
2132 )
2133 )
2134 )
2135 )
2136 ) ),
2137 'tab-email-reporting' => apply_filters( 'betterdocs_settings_tab_email_reporting', array(
2138 'id' => 'tab-email-reporting',
2139 'label' => __( 'Email Reporting', 'betterdocs' ),
2140 'priority' => 60,
2141 'fields' => array(
2142 'title-email-reporting' => array(
2143 'name' => 'title-email-reporting-tab',
2144 'type' => 'section',
2145 'label' => __( 'Email Reporting', 'betterdocs' ),
2146 'priority' => 60,
2147 'fields' => array(
2148 'enable_reporting' => array(
2149 'name' => 'enable_reporting',
2150 'label' => __( 'Email Reporting', 'betterdocs' ),
2151 'enable_disable_text_active' => true,
2152 'type' => 'toggle',
2153 'priority' => 1,
2154 'default' => 0
2155 ),
2156 'reporting_frequency' => apply_filters( 'betterdocs_reporting_frequency_settings', array(
2157 'name' => 'reporting_frequency',
2158 'type' => 'select',
2159 'label' => __( 'Reporting Frequency', 'betterdocs' ),
2160 'default' => 'betterdocs_weekly',
2161 'priority' => 2,
2162 'is_pro' => true,
2163 'options' => $this->normalize_options( array(
2164 'betterdocs_daily' => __( 'Once Daily', 'betterdocs' ),
2165 'betterdocs_weekly' => __( 'Once Weekly', 'betterdocs' ),
2166 'betterdocs_monthly' => __( 'Once Monthly', 'betterdocs' )
2167 ) ),
2168 'rules' => Rules::is( 'enable_reporting', true )
2169 ) ),
2170 'reporting_day' => array(
2171 'name' => 'reporting_day',
2172 'type' => 'select',
2173 'label' => __( 'Reporting Day', 'betterdocs' ),
2174 'default' => 'monday',
2175 'rules' => Rules::logicalRule( array(
2176 Rules::is( 'enable_reporting', true ),
2177 Rules::is( 'reporting_frequency', 'betterdocs_weekly' )
2178 ), 'and' ),
2179 'priority' => 3,
2180 'options' => $this->normalize_options( array(
2181 'sunday' => __( 'Sunday', 'betterdocs' ),
2182 'monday' => __( 'Monday', 'betterdocs' ),
2183 'tuesday' => __( 'Tuesday', 'betterdocs' ),
2184 'wednesday' => __( 'Wednesday', 'betterdocs' ),
2185 'thursday' => __( 'Thursday', 'betterdocs' ),
2186 'friday' => __( 'Friday', 'betterdocs' ),
2187 'saturday' => __( 'Saturday', 'betterdocs' )
2188 ) ),
2189 'label_subtitle' => __( 'This is only applicable for the “Weekly” report', 'betterdocs' )
2190 ),
2191 'select_reporting_data' => apply_filters( 'betterdocs_select_reporting_data_settings', array(
2192 'name' => 'select_reporting_data',
2193 'type' => 'checkbox-select',
2194 'label' => __( 'Select Reporting Data', 'betterdocs' ),
2195 'priority' => 4,
2196 'multiple' => true,
2197 'options' => $this->normalize_options( array(
2198 'overview' => 'Overview',
2199 'top-docs' => 'Top Docs',
2200 'most-search' => 'Most Searched Keywords'
2201 ) ),
2202 'default' => array( 'overview', 'top-docs', 'most-search' ),
2203 'is_pro' => true,
2204 'rules' => Rules::is( 'enable_reporting', true )
2205 ) ),
2206 'reporting_email' => array(
2207 'name' => 'reporting_email',
2208 'type' => 'text',
2209 'label' => __( 'Reporting Email', 'betterdocs' ),
2210 'default' => get_option( 'admin_email' ),
2211 'priority' => 5,
2212 'rules' => Rules::is( 'enable_reporting', true )
2213 ),
2214 'reporting_subject' => apply_filters( 'betterdocs_reporting_subject_settings', array(
2215 'name' => 'reporting_subject',
2216 'type' => 'textarea',
2217 'label' => __( 'Reporting Email Subject', 'betterdocs' ),
2218 'default' => wp_sprintf( // Translators: %s is replaced with the website name.
2219 __( 'Your Documentation Performance of %s Website', 'betterdocs' ), get_bloginfo( 'name' ) ),
2220 'priority' => 6,
2221 'is_pro' => true,
2222 'rules' => Rules::is( 'enable_reporting', true )
2223 ) ),
2224 'test_report' => array(
2225 'name' => 'test_report',
2226 'label' => __( 'Reporting Test', 'betterdocs' ),
2227 'text' => __( 'Test Report', 'betterdocs' ),
2228 'type' => 'button',
2229 'priority' => 7,
2230 'rules' => Rules::is( 'enable_reporting', true ),
2231 'ajax' => array(
2232 'on' => 'click',
2233 'api' => '/betterdocs/v1/reporting-test',
2234 'data' => array(
2235 'enable_reporting' => '@enable_reporting',
2236 'select_reporting_data' => '@select_reporting_data',
2237 'reporting_subject' => '@reporting_subject',
2238 'reporting_email' => '@reporting_email',
2239 'reporting_day' => '@reporting_day',
2240 'reporting_frequency' => '@reporting_frequency'
2241 ),
2242 'swal' => array(
2243 'text' => __( 'Successfully Sent a Test Report in Your Email.', 'betterdocs' ),
2244 'icon' => 'success',
2245 'autoClose' => 2000
2246 )
2247 )
2248 )
2249 )
2250 )
2251 )
2252 ) ),
2253 'tab-git-sync' => apply_filters( 'betterdocs_settings_tab_github_integration', array(
2254 'id' => 'tab-git-sync',
2255 'label' => __( 'Git Sync', 'betterdocs' ),
2256 'priority' => 60,
2257 'fields' => array(
2258 'title-git-sync' => array(
2259 'name' => 'title-git-sync-tab',
2260 'type' => 'section',
2261 'label' => __( 'Git Sync Settings', 'betterdocs' ),
2262 'priority' => 60,
2263 'fields' => array(
2264 'enable_git_integration' => array(
2265 'name' => 'enable_git_integration',
2266 'type' => 'toggle',
2267 'label' => __( 'Enable Git Sync', 'betterdocs' ),
2268 'enable_disable_text_active' => true,
2269 'default' => false,
2270 'priority' => 1,
2271 'is_pro' => true,
2272 'label_subtitle' => __( 'Enable bidirectional synchronization between BetterDocs and Git repositories', 'betterdocs' )
2273 )
2274 )
2275 )
2276 )
2277 ) ),
2278 'tab-instant-answer' => apply_filters( 'betterdocs_settings_tab_instant_answer', array(
2279 'id' => 'tab-instant-answer',
2280 'name' => 'tab-instant-answer',
2281 'type' => 'section',
2282 'label' => __( 'Instant Answer', 'betterdocs' ),
2283 'save' => false,
2284 'priority' => 70,
2285 'fields' => array(
2286 'title-instant-answer' => array(
2287 'name' => 'title-instant-answer-tab',
2288 'type' => 'section',
2289 'label' => __( 'Instant Answer', 'betterdocs' ),
2290 'priority' => 80,
2291 'save' => false,
2292 'showSubmit' => false,
2293 'fields' => apply_filters( 'betterdocs_instant_answer_fields', array(
2294 'enable_disable_wrapper' => array(
2295 'name' => 'enable_disable_wrapper',
2296 'type' => 'section',
2297 'priority' => 0,
2298 'save' => false,
2299 'fields' => array(
2300 'enable_disable' => array(
2301 'name' => 'enable_disable',
2302 'type' => 'toggle',
2303 'priority' => 100,
2304 'description' => __( 'Enable Instant Answer', 'betterdocs' ),
2305 'enable_disable_text_active' => false,
2306 'default' => false,
2307 'is_pro' => true
2308 )
2309 )
2310 )
2311 ) )
2312 )
2313 )
2314 ) ),
2315 'tab-betterdocs-ai' => array(
2316 'id' => 'tab-betterdocs-ai',
2317 'name' => 'tab-betterdocs-ai',
2318 'type' => 'section',
2319 'label' => __( 'AI Content Suite', 'betterdocs' ),
2320 'priority' => 74,
2321 'fields' => array(
2322 'sections-betterdocs-ai' => array(
2323 'name' => 'sections-betterdocs-ai',
2324 'type' => 'section',
2325 'label' => __( 'AI Content Suite', 'betterdocs' ),
2326 'priority' => 10,
2327 'fields' => array(
2328 'all-betterdocs-ai' => array(
2329 'id' => 'all-tab-betterdocs-ai',
2330 'name' => 'all-tab-betterdocs-ai',
2331 'label' => __( 'AI Content Suite Settings', 'betterdocs' ),
2332 'classes' => 'tab-layout',
2333 'type' => 'tab',
2334 'active' => 'open-ai-settings',
2335 'completionTrack' => true,
2336 'sidebar' => false,
2337 'save' => false,
2338 'title' => false,
2339 'config' => array(
2340 'active' => 'open-ai-settings',
2341 'sidebar' => false,
2342 'title' => false
2343 ),
2344 'submit' => array(
2345 'show' => false
2346 ),
2347 'step' => array(
2348 'show' => false
2349 ),
2350 'priority' => 20,
2351 'fields' => apply_filters(
2352 'betterdocs_migration_tab_sections',
2353 array(
2354 'open-ai-settings' => array(
2355 'id' => 'open-ai-settings',
2356 'name' => 'open-ai-settings',
2357 'type' => 'section',
2358 'label' => __( 'AI Platform & API', 'betterdocs' ),
2359 'priority' => 1,
2360 'fields' => array(
2361 'ai_platform' => array(
2362 'name' => 'ai_platform',
2363 'type' => 'select',
2364 'label' => __( 'AI Platform', 'betterdocs' ),
2365 'label_subtitle' => __( 'Choose which AI platform powers Write with AI, AI Edit, the Doc Summarizer and Quality Score. The model list and API key field below adapt to your choice.', 'betterdocs' ),
2366 'priority' => 1,
2367 'multiple' => false,
2368 'default' => 'openai',
2369 // DeepSeek and OpenRouter are temporarily hidden from the
2370 // dropdown. Filter the options here, NOT ModelRegistry::platforms() —
2371 // sensitive_api_key_fields() loops the registry to mask each
2372 // platform's key field, so trimming the registry would silently
2373 // un-mask those keys. Re-enable later by dropping the array_diff_key.
2374 'options' => GlobalFields::normalize_fields(
2375 array_diff_key( ModelRegistry::platforms(), array_flip( array( 'deepseek', 'openrouter' ) ) )
2376 )
2377 ),
2378 // OpenAI's key field keeps its original name so installs
2379 // that already saved a Write with AI key keep it.
2380 'ai_autowrite_api_key' => array(
2381 'name' => 'ai_autowrite_api_key',
2382 'type' => 'text',
2383 'label' => __( 'OpenAI API Key', 'betterdocs' ),
2384 'label_subtitle' => sprintf( /* translators: %s: documentation URL */ __( 'Check out this <a target="_blank" href="%s">documentation</a> to generate your OpenAI API key.', 'betterdocs' ), esc_url( 'https://betterdocs.co/docs/write-with-ai/' ) ),
2385 'default' => '',
2386 'priority' => 2,
2387 'rules' => Rules::is( 'ai_platform', 'openai' )
2388 ),
2389 'ai_api_key_gemini' => array(
2390 'name' => 'ai_api_key_gemini',
2391 'type' => 'text',
2392 'label' => __( 'Google Gemini API Key', 'betterdocs' ),
2393 'label_subtitle' => sprintf( /* translators: %s: Google AI Studio URL */ __( 'Generate a key from <a target="_blank" href="%s">Google AI Studio</a>.', 'betterdocs' ), esc_url( 'https://aistudio.google.com/app/apikey' ) ),
2394 'default' => '',
2395 'priority' => 2,
2396 'rules' => Rules::is( 'ai_platform', 'gemini' )
2397 ),
2398 'ai_api_key_claude' => array(
2399 'name' => 'ai_api_key_claude',
2400 'type' => 'text',
2401 'label' => __( 'Anthropic Claude API Key', 'betterdocs' ),
2402 'label_subtitle' => sprintf( /* translators: %s: Anthropic Console URL */ __( 'Generate a key from the <a target="_blank" href="%s">Anthropic Console</a>.', 'betterdocs' ), esc_url( 'https://console.anthropic.com/settings/keys' ) ),
2403 'default' => '',
2404 'priority' => 2,
2405 'rules' => Rules::is( 'ai_platform', 'claude' )
2406 ),
2407 'ai_api_key_deepseek' => array(
2408 'name' => 'ai_api_key_deepseek',
2409 'type' => 'text',
2410 'label' => __( 'DeepSeek API Key', 'betterdocs' ),
2411 'label_subtitle' => sprintf( /* translators: %s: DeepSeek platform URL */ __( 'Generate a key from the <a target="_blank" href="%s">DeepSeek platform</a>.', 'betterdocs' ), esc_url( 'https://platform.deepseek.com/api_keys' ) ),
2412 'default' => '',
2413 'priority' => 2,
2414 'rules' => Rules::is( 'ai_platform', 'deepseek' )
2415 ),
2416 'ai_api_key_openrouter' => array(
2417 'name' => 'ai_api_key_openrouter',
2418 'type' => 'text',
2419 'label' => __( 'OpenRouter API Key', 'betterdocs' ),
2420 'label_subtitle' => sprintf( /* translators: %s: OpenRouter keys URL */ __( 'Generate a key from <a target="_blank" href="%s">OpenRouter</a>.', 'betterdocs' ), esc_url( 'https://openrouter.ai/keys' ) ),
2421 'default' => '',
2422 'priority' => 2,
2423 'rules' => Rules::is( 'ai_platform', 'openrouter' )
2424 ),
2425 'ai_model' => array(
2426 'name' => 'ai_model',
2427 'type' => 'platform_model_select',
2428 'label' => __( 'Model', 'betterdocs' ),
2429 'label_subtitle' => __( 'Available models depend on the selected platform.', 'betterdocs' ),
2430 'priority' => 10,
2431 'default' => 'gpt-4o-mini',
2432 'catalogue' => ModelRegistry::catalogue(),
2433 'defaults' => ModelRegistry::defaults()
2434 )
2435 )
2436 ),
2437 'write-with-ai' => array(
2438 'id' => 'write-with-ai',
2439 'name' => 'write-with-ai',
2440 'type' => 'section',
2441 'label' => __( 'Write with AI', 'betterdocs' ),
2442 'priority' => 5,
2443 'fields' => array(
2444 'write-with-ai-subtabs' => array(
2445 'id' => 'write-with-ai-subtabs',
2446 'name' => 'write_with_ai_subtabs',
2447 'label' => __( 'Write with AI', 'betterdocs' ),
2448 'classes' => 'tab-nested-layout',
2449 'type' => 'tab',
2450 'active' => 'wwa-configuration',
2451 'completionTrack' => true,
2452 'sidebar' => false,
2453 'save' => false,
2454 'title' => false,
2455 'config' => array(
2456 'active' => 'wwa-configuration',
2457 'sidebar' => false,
2458 'title' => false
2459 ),
2460 'submit' => array(
2461 'show' => false
2462 ),
2463 'step' => array(
2464 'show' => false
2465 ),
2466 'priority' => 5,
2467 'fields' => array(
2468 'wwa-configuration' => array(
2469 'id' => 'wwa-configuration',
2470 'name' => 'wwa-configuration',
2471 'type' => 'section',
2472 'label' => __( 'Configuration', 'betterdocs' ),
2473 'priority' => 1,
2474 'fields' => array(
2475 'enable_write_with_ai' => array(
2476 'name' => 'enable_write_with_ai',
2477 'type' => 'toggle',
2478 'priority' => 0,
2479 'label' => __( 'Write Docs with AI', 'betterdocs' ),
2480 'label_subtitle' => __( 'Generate AI based Documentation in your Gutenberg Editor', 'betterdocs' ),
2481 'enable_disable_text_active' => true,
2482 'default' => true
2483 ),
2484 'enable_faq_write_with_ai' => array(
2485 'name' => 'enable_faq_write_with_ai',
2486 'type' => 'toggle',
2487 'priority' => 5,
2488 'label' => __( 'Write FAQ with AI', 'betterdocs' ),
2489 'label_subtitle' => __( 'Generate AI based FAQ in your Editor', 'betterdocs' ),
2490 'enable_disable_text_active' => true,
2491 'default' => true
2492 ),
2493 'enable_glossaries_write_with_ai' => array(
2494 'name' => 'enable_glossaries_write_with_ai',
2495 'type' => 'toggle',
2496 'priority' => 6,
2497 'label' => __( 'Write Glossaries with AI', 'betterdocs' ),
2498 'label_subtitle' => __( 'Generate AI based Glossary definitions from the Glossaries admin page', 'betterdocs' ),
2499 'enable_disable_text_active' => true,
2500 'default' => true,
2501 'is_pro' => true
2502 ),
2503 'enable_docs_ai_suite' => array(
2504 'name' => 'enable_docs_ai_suite',
2505 'type' => 'toggle',
2506 'priority' => 7,
2507 'label' => __( 'Enhance Docs with AI', 'betterdocs' ),
2508 'label_subtitle' => __( 'Add BetterDocs AI actions for suggesting categories, tags and excerpts inside the native Docs editor panels.', 'betterdocs' ),
2509 'enable_disable_text_active' => true,
2510 'default' => true
2511 ),
2512 'ai_autowrite_max_token' => array(
2513 'name' => 'ai_autowrite_max_token',
2514 'type' => 'min_token_number',
2515 'label' => __( 'Set Max Tokens', 'betterdocs' ),
2516 'label_subtitle' => sprintf( // translators: %s is a link to more information about token limits.
2517 __( 'Documentation will be generated based on the Token Limits you have set. For more information on Token Limits, you can check out this <a target="_blank" href="%s">link</a>.', 'betterdocs' ), esc_url( 'https://platform.openai.com/account/limits' ) ),
2518 'default' => 2500,
2519 'priority' => 10,
2520 'model_field' => 'ai_model',
2521 'context' => 'write_with_ai',
2522 'min_token_map' => AIHelper::get_min_tokens_map( 'write_with_ai' ),
2523 'classes' => 'wprf-type-text'
2524 )
2525 )
2526 ),
2527 'wwa-personalize' => array(
2528 'id' => 'wwa-personalize',
2529 'name' => 'wwa-personalize',
2530 'type' => 'section',
2531 'label' => __( 'Write AI Personalize', 'betterdocs' ),
2532 'priority' => 5,
2533 'fields' => array(
2534 'write_with_ai_instructions' => array(
2535 'name' => 'write_with_ai_instructions',
2536 'type' => 'wwa_instructions',
2537 'label' => __( 'Instructions', 'betterdocs' ),
2538 'label_subtitle' => __( 'Add reusable instruction sets to steer how BetterDocs AI writes. The "Default/Core" set is always applied; extra sets can be picked in the Write with AI popup.', 'betterdocs' ),
2539 'priority' => 1,
2540 'default' => array(
2541 array(
2542 'id' => 'default',
2543 'title' => __( 'Default/Core', 'betterdocs' ),
2544 'content' => WriteWithAI::default_instruction_content()
2545 )
2546 )
2547 )
2548 )
2549 ),
2550 'edit-ai-personalize' => array(
2551 'id' => 'edit-ai-personalize',
2552 'name' => 'edit-ai-personalize',
2553 'type' => 'section',
2554 'label' => __( 'Edit AI Personalize', 'betterdocs' ),
2555 'priority' => 7,
2556 'fields' => array(
2557 'ai_edit_actions' => array(
2558 'name' => 'ai_edit_actions',
2559 'type' => 'ai_edit_actions',
2560 'label' => __( 'Actions', 'betterdocs' ),
2561 'label_subtitle' => __( 'Choose which Edit with AI actions appear in the editor. Toggle actions on or off, edit each action\'s instruction, or add your own custom actions. All predefined actions are enabled by default.', 'betterdocs' ),
2562 'priority' => 1,
2563 'default' => AIEdit::default_actions()
2564 )
2565 )
2566 )
2567 )
2568 )
2569 )
2570 ),
2571 'article-summary' => array(
2572 'id' => 'article-summary',
2573 'name' => 'article-summary',
2574 'type' => 'section',
2575 'label' => __( 'AI Doc Summarizer', 'betterdocs' ),
2576 'priority' => 10,
2577 'fields' => array(
2578 'enable_article_summary' => array(
2579 'name' => 'enable_article_summary',
2580 'type' => 'toggle',
2581 'priority' => 1,
2582 'label' => __( 'AI Powered Doc Summarizer', 'betterdocs' ),
2583 'label_subtitle' => __( 'Enable AI-powered article summaries on single doc pages. Requires OpenAI API key.', 'betterdocs' ),
2584 'enable_disable_text_active' => true,
2585 'default' => false
2586 ),
2587 'article_summary_max_token' => array(
2588 'name' => 'article_summary_max_token',
2589 'type' => 'min_token_number',
2590 'label' => __( 'Set Max Tokens', 'betterdocs' ),
2591 'label_subtitle' => sprintf( // translators: %s is a link to more information about token limits.
2592 __( 'Single Doc summarizer will be generated based on the token limits you have set. For more information on Token Limits, you can check out this <a target="_blank" href="%s">link</a>.', 'betterdocs' ), esc_url( 'https://platform.openai.com/account/limits' ) ),
2593 'default' => 1500,
2594 'priority' => 5,
2595 'model_field' => 'ai_model',
2596 'context' => 'article_summary',
2597 'min_token_map' => AIHelper::get_min_tokens_map( 'article_summary' ),
2598 'classes' => 'wprf-type-text'
2599 )
2600 )
2601 )
2602 )
2603 )
2604 )
2605 )
2606 )
2607 )
2608 ),
2609 'tab-ai-chatbot' => apply_filters( 'betterdocs_settings_tab_ai_chatbot', array(
2610 'id' => 'tab-ai-chatbot',
2611 'name' => 'tab-ai-chatbot',
2612 'type' => 'section',
2613 'label' => __( 'AI Chatbot', 'betterdocs' ),
2614 'submit' => array(
2615 'show' => false
2616 ),
2617 'save' => false,
2618 'priority' => 75,
2619 'fields' => array(
2620 'title-ai-chatbot-tab' => array(
2621 'name' => 'title-ai-chatbot-tab',
2622 'type' => 'section',
2623 'label' => __( 'AI Chatbot', 'betterdocs' ),
2624 'priority' => 80,
2625 'submit' => array(
2626 'show' => false
2627 ),
2628 'save' => false,
2629 'fields' => apply_filters( 'betterdocs_ai_chatbot_fields', array(
2630 'ai_chatbot_fields' => array(
2631 'name' => 'ai_chatbot_fields',
2632 'type' => 'section',
2633 'priority' => 0,
2634 'fields' => array(
2635 'enable_ai_chatbot' => array(
2636 'name' => 'enable_ai_chatbot',
2637 'label' => __( 'AI Chatbot', 'betterdocs' ),
2638 'description' => __( 'Enable AI Chatbot', 'betterdocs' ),
2639 'type' => 'toggle',
2640 'priority' => 5,
2641 'default' => 0,
2642 'is_pro' => true,
2643 'is_license_active' => false,
2644 'disabled' => ! betterdocs()->is_pro_active() || ! defined( 'BETTERDOCS_CHATBOT_FILE' )
2645 ),
2646 'instant_answer_warning' => array(
2647 'name' => 'instant_answer_warning',
2648 'type' => 'html',
2649 'priority' => 6,
2650 'html' => sprintf(
2651 '<div class="betterdocs-ia-warning" style="margin:8px 0 0;padding:12px 16px;background:#fffaeb;border-left:4px solid #fdb022;border-radius:4px;display:flex;align-items:flex-start;gap:10px;"><span style="font-size:18px;line-height:1;flex-shrink:0;color:#b54708;">&#9888;</span><div style="font-size:13px;color:#202223;line-height:1.5;">%1$s<br><a href="?page=betterdocs-settings&amp;tab=tab-instant-answer" style="display:inline-block;margin-top:6px;color:#5a6bff;text-decoration:none;font-weight:500;">%2$s &rarr;</a></div></div>',
2652 esc_html__( 'Instant Answer is currently disabled. The AI Chatbot won\'t appear on your site until Instant Answer is enabled.', 'betterdocs' ),
2653 esc_html__( 'Enable Instant Answer', 'betterdocs' )
2654 ),
2655 'rules' => Rules::logicalRule( array(
2656 Rules::is( 'enable_ai_chatbot', true ),
2657 Rules::is( 'enable_disable', false ),
2658 ), 'and' ),
2659 )
2660 )
2661 )
2662 ) )
2663 )
2664 )
2665 ) )
2666 ) ),
2667 'TAB_AI_CHATBOT' => get_option( 'enable_ai_chatbot' ),
2668 'CHATBOT_LICENSE' => get_option( 'betterdocs_chatbot_software__license_status' ),
2669 'PRO_ACTIVE' => is_plugin_active( 'betterdocs-pro/betterdocs-pro.php' ),
2670 'PRO_LICENSE' => get_option( 'betterdocs_pro_software__license_status' )
2671 );
2672
2673 $settings = array_merge( $settings, $this->chatbot_active_localize() );
2674
2675 return apply_filters( 'betterdocs_settings_args', $settings );
2676 }
2677
2678 /**
2679 * @return array
2680 */
2681 public function chatbot_active_localize() {
2682 $chatbot_active = is_plugin_active( 'betterdocs-ai-chatbot/betterdocs-ai-chatbot.php' );
2683 $chatbot_license_valid = get_option( 'betterdocs_chatbot_software__license_status' ) === 'valid';
2684 $chatbot_enabled = $this->get( 'enable_ai_chatbot', false );
2685
2686 // AI Search Suggestions are enabled if all conditions are met
2687 $ai_search_suggestions_enabled = betterdocs()->helper->is_ai_chatbot_enabled();
2688
2689 return array(
2690 'CHATBOT_ACTIVE' => $chatbot_active,
2691 'CHATBOT_LICENSE_VALID' => $chatbot_license_valid,
2692 'CHATBOT_ENABLED' => $chatbot_enabled,
2693 'AI_SEARCH_SUGGESTIONS_ENABLED' => $ai_search_suggestions_enabled
2694 );
2695 }
2696
2697 /**
2698 * Call This Function As Helper, When Pro Is Deactivated, To Be Used As Settings Default Values, When Betterdocs Pro Is Deactivated
2699 *
2700 * @return array
2701 */
2702 public function pro_settings_default_values() {
2703 return array(
2704 'multiple_kb' => false,
2705 'enable_glossaries' => false,
2706 'enable_encyclopedia' => false,
2707 'analytics_from' => false,
2708 'unique_visitor_count' => false,
2709 'exclude_bot_analytics' => false,
2710 'show_attachment' => false,
2711 'show_related_docs' => false,
2712 'advance_search' => false,
2713 'child_category_exclude' => false,
2714 'kb_based_search' => false,
2715 'enable_disable' => false,
2716 'enable_content_restriction' => false
2717 );
2718 }
2719
2720 public function import_export_settings( $settings ) {
2721 if ( ! current_user_can( 'import' ) ) {
2722 return $settings;
2723 }
2724
2725 $settings[ 'tab-import-export' ] = apply_filters( 'betterdocs_settings_tab_import_export', array(
2726 'id' => 'tab-import-export',
2727 'name' => 'tab-import-export',
2728 'classes' => 'tab-import-export',
2729 'label' => __( 'Import / Export', 'betterdocs' ),
2730 'priority' => 80,
2731 'fields' => array(
2732 'sections-import-export' => array(
2733 'name' => 'sections-import-export',
2734 'type' => 'section',
2735 'label' => __( 'Import / Export', 'betterdocs' ),
2736 'priority' => 30,
2737 'fields' => array(
2738 'all-tab-import-export' => array(
2739 'id' => 'all-tab-import-export',
2740 'name' => 'all-tab-import-export',
2741 'label' => __( 'Import Export Settings', 'betterdocs' ),
2742 'classes' => 'tab-layout',
2743 'type' => 'tab',
2744 'active' => 'import',
2745 'completionTrack' => true,
2746 'sidebar' => false,
2747 'save' => false,
2748 'title' => false,
2749 'config' => array(
2750 'active' => 'import',
2751 'sidebar' => false,
2752 'title' => false
2753 ),
2754 'submit' => array(
2755 'show' => false
2756 ),
2757 'step' => array(
2758 'show' => false
2759 ),
2760 'priority' => 20,
2761 'fields' => array(
2762 'import' => array(
2763 'id' => 'import',
2764 'name' => 'import',
2765 'type' => 'section',
2766 'label' => __( 'Import', 'betterdocs' ),
2767 'priority' => 1,
2768 'fields' => array(
2769 'import_tab_nested' => array(
2770 'id' => 'import_tab_nested',
2771 'name' => 'import_tab_nested',
2772 'label' => __( 'Import', 'betterdocs' ),
2773 'classes' => 'tab-nested-layout',
2774 'type' => 'tab',
2775 'active' => 'import_docs_nested',
2776 'completionTrack' => true,
2777 'sidebar' => false,
2778 'save' => false,
2779 'title' => false,
2780 'config' => array(
2781 'active' => 'import_docs_nested',
2782 'sidebar' => false,
2783 'title' => false
2784 ),
2785 'submit' => array(
2786 'show' => false
2787 ),
2788 'step' => array(
2789 'show' => false
2790 ),
2791 'priority' => 1,
2792 'fields' => array(
2793 'import_docs_nested' => array(
2794 'id' => 'import_docs_nested',
2795 'name' => 'import_docs_nested',
2796 'type' => 'section',
2797 'label' => __( 'Import Docs', 'betterdocs' ),
2798 'priority' => 1,
2799 'fields' => array(
2800 'import_docs' => array(
2801 'name' => 'import_docs',
2802 'type' => 'importerupload',
2803 'label' => __( 'Import Docs', 'betterdocs' ),
2804 'label_subtitle' => wp_sprintf( // Translators: %1$s is a link to download a sample CSV file.
2805 __( 'To import your Docs, please upload the .xml / .csv file here. <a href="%1$s">Download sample csv file</a>', 'betterdocs' ), betterdocs()->assets->icon( 'BetterDocs-sample-data.csv', true ) ),
2806 'text' => array(
2807 'normal' => __( 'Proceed', 'betterdocs' ),
2808 'saved' => __( 'Proceed', 'betterdocs' ),
2809 'loading' => __( 'Importing...', 'betterdocs' ),
2810 'exists_notice' => __( 'It seems like documentations with same slugs already exist on your website. What would you like to do?', 'betterdocs' )
2811 ),
2812 'ajax' => array(
2813 'on' => 'click',
2814 'api' => '/betterdocs/v1/import-docs',
2815 'swal' => array(
2816 'text' => __( 'Import completed successfully.', 'betterdocs' ),
2817 'icon' => 'success',
2818 'autoClose' => 2000
2819 )
2820 ),
2821 'file_type' => '.xml, .csv',
2822 'priority' => 1
2823 )
2824 )
2825 ),
2826
2827 'import_settings_nested' => array(
2828 'id' => 'import_settings_nested',
2829 'name' => 'import_settings_nested',
2830 'type' => 'section',
2831 'label' => __( 'Import Settings', 'betterdocs' ),
2832 'priority' => 1,
2833 'fields' => array(
2834 'settings_importer' => array(
2835 'name' => 'settings_importer',
2836 'type' => 'settingsuploader',
2837 'label' => __( 'Import Settings', 'betterdocs' ),
2838 'label_subtitle' => __( 'To import BetterDocs Settings, please upload BetterDocs settings you have exported from another website in .json format', 'betterdocs' ),
2839 'reset' => __( 'Change', 'betterdocs' ),
2840 'priority' => 1
2841 ),
2842 'import_settings' => array(
2843 'name' => 'import_settings',
2844 'type' => 'button',
2845 'rules' => Rules::is( 'settings_importer', null, true ),
2846 'text' => array(
2847 'normal' => __( 'Proceed', 'betterdocs' ),
2848 'saved' => __( 'Proceed', 'betterdocs' ),
2849 'loading' => __( 'Importing...', 'betterdocs' )
2850 ),
2851 'ajax' => array(
2852 'on' => 'click',
2853 'api' => '/betterdocs/v1/import-settings',
2854 'data' => array(
2855 'settings' => '@settings_importer'
2856 ),
2857 'swal' => array(
2858 'text' => __( 'Import completed successfully.', 'betterdocs' ),
2859 'icon' => 'success',
2860 'autoClose' => 1000
2861 ),
2862 'reload' => true
2863 ),
2864 'priority' => 2
2865 )
2866 )
2867 )
2868 )
2869 )
2870 )
2871 ),
2872 'export' => array(
2873 'id' => 'export',
2874 'name' => 'export',
2875 'type' => 'section',
2876 'label' => __( 'Export', 'betterdocs' ),
2877 'priority' => 1,
2878 'fields' => array(
2879 'export_tab_nested' => array(
2880 'id' => 'export_tab_nested',
2881 'name' => 'export_tab_nested',
2882 'label' => __( 'Export', 'betterdocs' ),
2883 'classes' => 'tab-nested-layout',
2884 'type' => 'tab',
2885 'active' => 'export_docs_nested',
2886 'completionTrack' => true,
2887 'sidebar' => false,
2888 'save' => false,
2889 'title' => false,
2890 'config' => array(
2891 'active' => 'export_docs_nested',
2892 'sidebar' => false,
2893 'title' => false
2894 ),
2895 'submit' => array(
2896 'show' => false
2897 ),
2898 'step' => array(
2899 'show' => false
2900 ),
2901 'priority' => 1,
2902 'fields' => array(
2903 'export_docs_nested' => array(
2904 'id' => 'export_docs_nested',
2905 'name' => 'export_docs_nested',
2906 'type' => 'section',
2907 'label' => __( 'Export Docs', 'betterdocs' ),
2908 'priority' => 1,
2909 'fields' => apply_filters( 'betterdocs_export_fields', array(
2910 'export_type' => array(
2911 'name' => 'export_type',
2912 'label' => __( 'Select Docs Type', 'betterdocs' ),
2913 'label_subtitle' => __( 'Choose an export type: All Docs, a specific Knowledge Base, or a Doc Category', 'betterdocs' ),
2914 'type' => 'select',
2915 'default' => 'docs',
2916 'priority' => 3,
2917 'search' => true,
2918 'options' => $this->normalize_options( apply_filters( 'betterdocs_export_type_options', array(
2919 'docs' => __( 'Docs', 'betterdocs' ),
2920 'doc_category' => __( 'Docs Category', 'betterdocs' ),
2921 'glossaries' => __( 'Glossaries', 'betterdocs' )
2922 ) ) )
2923 ),
2924 'export_docs' => array(
2925 'name' => 'export_docs',
2926 'type' => 'checkbox-select',
2927 'label' => __( 'Select Docs', 'betterdocs' ),
2928 'label_subtitle' => __( 'Selected docs will be included in the export.', 'betterdocs' ),
2929 'priority' => 4,
2930 'multiple' => true,
2931 'search' => true,
2932 'default' => array( 'all' ),
2933 'placeholder' => __( 'Select any', 'betterdocs' ),
2934 'options' => array_merge( array(
2935 array(
2936 'value' => 'all',
2937 'label' => 'All'
2938 )
2939 ), $this->docs() ),
2940 'filterValue' => 'all',
2941 'rules' => Rules::is( 'export_type', 'docs' )
2942 ),
2943 'export_categories' => array(
2944 'name' => 'export_categories',
2945 'type' => 'checkbox-select',
2946 'label' => __( 'Select Categories', 'betterdocs' ),
2947 'label_subtitle' => __( 'Selected categories and its docs will be included in the export.', 'betterdocs' ),
2948 'priority' => 6,
2949 'multiple' => true,
2950 'search' => true,
2951 'default' => array( 'all' ),
2952 'placeholder' => __( 'Select any', 'betterdocs' ),
2953 'options' => $this->get_terms( 'doc_category', false ),
2954 'filterValue' => 'all',
2955 'rules' => Rules::is( 'export_type', 'doc_category' )
2956 ),
2957 'export_glossaries' => array(
2958 'name' => 'export_glossaries',
2959 'type' => 'checkbox-select',
2960 'label' => __( 'Select Glossaries', 'betterdocs' ),
2961 'label_subtitle' => __( 'Selected glossary terms will be exported.', 'betterdocs' ),
2962 'priority' => 7,
2963 'multiple' => true,
2964 'search' => true,
2965 'default' => array( 'all' ),
2966 'placeholder' => __( 'Select any', 'betterdocs' ),
2967 'options' => $this->get_terms( 'glossaries' ),
2968 'filterValue' => 'all',
2969 'rules' => Rules::is( 'export_type', 'glossaries' )
2970 ),
2971 'file_type' => array(
2972 'name' => 'file_type',
2973 'label' => __( 'Select File Type', 'betterdocs' ),
2974 'label_subtitle' => __( 'Choose a file type', 'betterdocs' ),
2975 'type' => 'select',
2976 'default' => 'xml',
2977 'priority' => 8,
2978 'search' => true,
2979 'options' => $this->normalize_options( apply_filters( 'betterdocs_export_file_type_options', array(
2980 'xml' => __( '.xml', 'betterdocs' ),
2981 'csv' => __( '.csv', 'betterdocs' )
2982 ) ) )
2983 ),
2984 'enable_export_faq' => array(
2985 'name' => 'enable_export_faq',
2986 'type' => 'toggle',
2987 'priority' => 9,
2988 'label' => __( 'Export FAQ', 'betterdocs' ),
2989 'label_subtitle' => __( 'Export FAQ Related Terms & Posts', 'betterdocs' ),
2990 'enable_disable_text_active' => true,
2991 'default' => true,
2992 'rules' => Rules::is( 'export_type', 'glossaries', true )
2993 ),
2994 'export_docs_btn' => array(
2995 'name' => 'export_docs_btn',
2996 'text' => array(
2997 'normal' => __( 'Proceed', 'betterdocs' ),
2998 'saved' => __( 'Proceed', 'betterdocs' ),
2999 'loading' => __( 'Exporting...', 'betterdocs' )
3000 ),
3001 'type' => 'button',
3002 'priority' => 10,
3003 'ajax' => array(
3004 'on' => 'click',
3005 'api' => '/betterdocs/v1/export-docs',
3006 'data' => array(
3007 'export_type' => '@export_type',
3008 'export_docs' => '@export_docs',
3009 'export_kbs' => '@export_kbs',
3010 'export_categories' => '@export_categories',
3011 'export_glossaries' => '@export_glossaries',
3012 'file_type' => '@file_type',
3013 'enable_export_faq' => '@enable_export_faq'
3014 ),
3015 'swal' => array(
3016 'text' => __( 'Exported Successfully.', 'betterdocs' ),
3017 'icon' => 'success',
3018 'autoClose' => 2000
3019 )
3020 )
3021 )
3022 ) )
3023 ),
3024
3025 'export_settings_nested' => array(
3026 'id' => 'export_settings_nested',
3027 'name' => 'export_settings_nested',
3028 'type' => 'section',
3029 'label' => __( 'Export Settings', 'betterdocs' ),
3030 'priority' => 1,
3031 'fields' => array(
3032 'export_settings' => array(
3033 'name' => 'export_settings',
3034 'label' => __( 'Export Settings', 'betterdocs' ),
3035 'label_subtitle' => __( 'Simply click on “Export Settings” button to download your BetterDocs settings in .json format', 'betterdocs' ),
3036 'text' => array(
3037 'normal' => __( 'Export Settings', 'betterdocs' ),
3038 'saved' => __( 'Export Settings', 'betterdocs' ),
3039 'loading' => __( 'Exporting...', 'betterdocs' )
3040 ),
3041 'type' => 'button',
3042 'priority' => 8,
3043 'ajax' => array(
3044 'on' => 'click',
3045 'api' => '/betterdocs/v1/export-settings',
3046 'data' => array(
3047 'betterdocs_settings' => get_option( 'betterdocs_settings' )
3048 ),
3049 'swal' => array(
3050 'text' => __( 'File downloaded successfully.', 'betterdocs' ),
3051 'icon' => 'success',
3052 'autoClose' => 2000
3053 )
3054 )
3055 )
3056 )
3057 )
3058 )
3059 )
3060 )
3061 )
3062 )
3063 )
3064 )
3065 )
3066 )
3067 ) );
3068
3069 return $settings;
3070 }
3071
3072 public function maybe_remove_git_tab( $tabs ) {
3073 $pro_active = is_plugin_active( 'betterdocs-pro/betterdocs-pro.php' );
3074 $pro_has_git = apply_filters( 'betterdocs_pro_has_git_integration', false );
3075
3076 if ( $pro_active && ! $pro_has_git ) {
3077 unset( $tabs[ 'tab-git-sync' ] );
3078 }
3079
3080 return $tabs;
3081 }
3082
3083 public function normalize_options( $options ) {
3084 return GlobalFields::normalize_fields( $options );
3085 }
3086
3087 public function get_texanomy() {
3088 $docs_tax = $this->database->get_cache( 'betterdocs::settings::taxonomies' );
3089
3090 if ( $docs_tax ) {
3091 return $docs_tax;
3092 }
3093
3094 $taxonomies = get_taxonomies( array(
3095 'object_type' => array( 'docs' )
3096 ), 'objects' );
3097
3098 $docs_tax = array(
3099 'all' => 'All Docs Archive',
3100 'docs' => 'Docs Page'
3101 );
3102 foreach ( $taxonomies as $key => $value ) {
3103 $docs_tax[ $key ] = $value->label;
3104 }
3105 unset( $docs_tax[ 'doc_tag' ] );
3106
3107 $docs_tax = $this->normalize_options( $docs_tax );
3108 if ( count( $docs_tax ) > 2 ) {
3109 $this->database->set_cache( 'betterdocs::settings::taxonomies', $docs_tax );
3110 }
3111
3112 return $docs_tax;
3113 }
3114
3115 public function get_terms( $taxonomy, $hide_empty = true ) {
3116 $_cache_key = 'betterdocs::settings::terms::' . trim( $taxonomy );
3117 $docs_tax = $this->database->get_cache( $_cache_key );
3118
3119 if ( $docs_tax ) {
3120 return $docs_tax;
3121 }
3122
3123 $get_terms = get_terms( array(
3124 'taxonomy' => $taxonomy,
3125 'hide_empty' => $hide_empty
3126 ) );
3127
3128 $terms = array(
3129 'all' => 'All'
3130 );
3131
3132 if ( ! empty( $get_terms ) && ! is_wp_error( $get_terms ) ) {
3133 foreach ( $get_terms as $value ) {
3134 if ( isset( $value->slug ) && isset( $value->name ) ) {
3135 $terms[ $value->slug ] = $value->name;
3136 }
3137 }
3138 }
3139
3140 $terms = $this->normalize_options( $terms );
3141 if ( count( $terms ) > 1 ) {
3142 $this->database->set_cache( $_cache_key, $terms );
3143 }
3144
3145 return $terms;
3146 }
3147
3148 public function get_terms_with_ids( $taxonomy ) {
3149 $_cache_key = 'betterdocs::settings::terms::ids' . trim( $taxonomy );
3150 $docs_tax = $this->database->get_cache( $_cache_key );
3151
3152 if ( $docs_tax ) {
3153 return $docs_tax;
3154 }
3155
3156 $get_terms = get_terms( array(
3157 'taxonomy' => $taxonomy,
3158 'hide_empty' => false,
3159 'suppress_filter' => true
3160 ) );
3161
3162 $terms = array();
3163
3164 if ( ! empty( $get_terms ) && ! is_wp_error( $get_terms ) ) {
3165 foreach ( $get_terms as $value ) {
3166 if ( isset( $value->term_id ) && isset( $value->name ) ) {
3167 $terms[ $value->term_id ] = $value->name;
3168 }
3169 }
3170 }
3171
3172 $terms = $this->normalize_options( $terms );
3173 if ( count( $terms ) > 1 ) {
3174 $this->database->set_cache( $_cache_key, $terms );
3175 }
3176
3177 return $terms;
3178 }
3179
3180 /**
3181 * Get all docs
3182 */
3183 public function docs() {
3184 $docs = $this->database->get_cache( 'betterdocs::settings::all_docs' );
3185
3186 if ( $docs ) {
3187 return $docs;
3188 }
3189
3190 $docs = array();
3191
3192 $_docs = get_posts( array(
3193 'post_type' => 'docs',
3194 'numberposts' => -1,
3195 'posts_per_page' => -1
3196 ) );
3197
3198 if ( ! empty( $_docs ) ) {
3199 foreach ( $_docs as $doc ) {
3200 $docs[ $doc->ID ] = betterdocs()->template_helper->kses( $doc->post_title );
3201 }
3202 $docs = GlobalFields::normalize_fields( $docs );
3203 $this->database->set_cache( 'betterdocs::settings::all_docs', $docs );
3204 }
3205
3206 return $docs;
3207 }
3208
3209 public function hide_roles_management( $tabData = array() ) {
3210 global $current_user;
3211
3212 if ( $current_user instanceof WP_User && ! in_array( 'administrator', $current_user->roles ) ) {
3213 unset( $tabData[ 'fields' ][ 'title-advance-settings' ][ 'fields' ][ 'article_roles' ] );
3214 unset( $tabData[ 'fields' ][ 'title-advance-settings' ][ 'fields' ][ 'settings_roles' ] );
3215 unset( $tabData[ 'fields' ][ 'title-advance-settings' ][ 'fields' ][ 'analytics_roles' ] );
3216 unset( $tabData[ 'fields' ][ 'title-advance-settings' ][ 'fields' ][ 'faq_roles' ] );
3217 }
3218
3219 return $tabData;
3220 }
3221 }
3222