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

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

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