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

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

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