PluginProbe ʕ •ᴥ•ʔ
Admin Help Docs / 2.0.3
Admin Help Docs v2.0.3
2.0.3 2.0.2 2.0.1.1 trunk 1.4.3.2 2.0.0 2.0.0.1 2.0.0.2 2.0.1
admin-help-docs / inc / tabs / settings.php
admin-help-docs / inc / tabs Last commit date
css 21 hours ago js 21 hours ago admin-menu.php 21 hours ago documentation.php 21 hours ago faq.php 21 hours ago import.php 21 hours ago migrate.php 21 hours ago settings.php 21 hours ago support.php 21 hours ago
settings.php
1268 lines
1 <?php
2 /**
3 * Settings Tab Loader
4 */
5
6 namespace PluginRx\AdminHelpDocs;
7
8 if ( ! defined( 'ABSPATH' ) ) exit;
9
10 class Settings {
11
12
13 /**
14 * Returns all settings boxes.
15 */
16 public static function setting_boxes() : array {
17 $boxes = [
18 'interface' => __( 'Interface', 'admin-help-docs' ),
19 'content_output' => __( 'Content & Output', 'admin-help-docs' ),
20 'access_control' => __( 'Access Control', 'admin-help-docs' ),
21 'advanced' => __( 'Advanced', 'admin-help-docs' ),
22 ];
23
24 return apply_filters( 'helpdocs_settings_boxes', $boxes );
25 } // End setting_boxes()
26
27
28 /**
29 * Returns all settings fields.
30 */
31 public static function setting_fields( $defaults_only = false ) : array {
32 $themes = [];
33 foreach ( Colors::themes() as $key => $theme ) {
34 $themes[ $key ] = $theme[ 'label' ];
35 }
36 $themes[ 'custom' ] = __( 'Custom (Use the color pickers below to customize)', 'admin-help-docs' );
37
38 $top_placements = [];
39 foreach ( self::top_placements() as $key => $label ) {
40 $top_placements[] = [
41 'value' => $key,
42 'label' => $label,
43 ];
44 }
45
46 $legacy_cap = get_option( 'helpdocs_user_view_cap' );
47 $current_type = get_option( 'helpdocs_user_view_type' );
48 $view_default = ( false !== $legacy_cap && false === $current_type ) ? 'capability' : 'role';
49
50 $fields = [
51 // Interface
52 [
53 'name' => 'menu_title',
54 'label' => __( 'Menu Title', 'admin-help-docs' ),
55 'type' => 'text',
56 'sanitize' => 'sanitize_text_field',
57 'box' => 'interface',
58 'default' => 'Help Docs',
59 ],
60 [
61 'name' => 'dashicon',
62 'label' => __( 'Menu Icon', 'admin-help-docs' ) . ' — <a id="view-dashicons-link" href="https://developer.wordpress.org/resource/dashicons/#editor-help" target="_blank">View Dashicons <span class="dashicons dashicons-external"></span></a>',
63 'type' => 'select',
64 'choices' => Helpers::get_dashicons(),
65 'sanitize' => 'sanitize_text_field',
66 'box' => 'interface',
67 'default' => 'editor-help',
68 ],
69 [
70 'name' => 'menu_position',
71 'label' => __( 'Menu Position', 'admin-help-docs' ),
72 'desc' => __( '1 = Above Dashboard, 2 = Under Dashboard, 999 = Bottom, etc.', 'admin-help-docs' ),
73 'type' => 'number',
74 'sanitize' => 'absint',
75 'box' => 'interface',
76 'default' => Menu::$default_menu_position,
77 ],
78 [
79 'name' => 'page_title',
80 'label' => __( 'Page Title', 'admin-help-docs' ),
81 'type' => 'text',
82 'sanitize' => 'sanitize_text_field',
83 'box' => 'interface',
84 'default' => Bootstrap::name(),
85 ],
86 [
87 'name' => 'logo',
88 'label' => __( 'Page Logo', 'admin-help-docs' ),
89 'desc' => __( 'Preferred size: 100x100 pixels. Accepted formats: jpg | jpeg | png | webp', 'admin-help-docs' ),
90 'type' => 'text',
91 'sanitize' => 'sanitize_text_field',
92 'box' => 'interface',
93 'default' => Helpers::get_default_logo_url(),
94 ],
95 [
96 'name' => 'doc_logo',
97 'label' => __( 'Help Doc Logo', 'admin-help-docs' ),
98 'desc' => __( 'You can also change the logo on all of the help docs, which may differ from the page logo if the background is contrasted. For example a light logo on dark background at the top of the page, and a dark logo on a light background on the help docs themselves. You can also disable the help doc logo from the Content & Output settings on the right. The preferred size of the image is 100x100 pixels. Accepted formats: jpg | jpeg | png | webp', 'admin-help-docs' ),
99 'type' => 'text',
100 'sanitize' => 'sanitize_text_field',
101 'box' => 'interface',
102 'default' => Helpers::get_default_logo_url(),
103 ],
104 [
105 'name' => 'themes',
106 'label' => __( 'Color Theme', 'admin-help-docs' ),
107 'type' => 'select',
108 'choices' => $themes,
109 'sanitize' => 'sanitize_text_field',
110 'box' => 'interface',
111 'default' => 'custom',
112 ],
113 [
114 'name' => 'contact_form',
115 'label' => __( 'Enable Support Contact Form', 'admin-help-docs' ),
116 'desc' => sprintf(
117 /* translators: %s is the current site admin email address. */
118 __( 'Adds a simple contact form within the Help Docs menu, allowing clients to reach out for support directly. Emails will be sent from %s. This feature utilizes wp_mail() for delivery. To ensure reliable inbox placement, using a dedicated provider like WP Mail SMTP (with Brevo) is advised, along with WP Mail Logging for tracking sent messages.', 'admin-help-docs' ),
119 '<strong>' . esc_html( get_option( 'admin_email' ) ) . '</strong>'
120 ),
121 'type' => 'checkbox',
122 'sanitize' => 'sanitize_checkbox',
123 'box' => 'interface',
124 'default' => false,
125 'has_condition' => true,
126 ],
127 [
128 'name' => 'contact_name',
129 'label' => __( 'Support Contact Name', 'admin-help-docs' ),
130 'desc' => __( 'Enter the name that clients can use to reach you. This will be displayed on the contact form page. Can be a business or personal name.', 'admin-help-docs' ),
131 'type' => 'text',
132 'sanitize' => 'sanitize_text_field',
133 'box' => 'interface',
134 'default' => '',
135 'condition' => [ 'contact_form' => true ],
136 ],
137 [
138 'name' => 'contact_emails',
139 'label' => __( 'Support Contact Email(s)', 'admin-help-docs' ),
140 'desc' => __( 'Enter the email address(es) that will receive messages from the contact form. Separate multiple emails with commas.', 'admin-help-docs' ),
141 'type' => 'text',
142 'sanitize' => 'sanitize_text_field',
143 'box' => 'interface',
144 'default' => implode( ', ', Helpers::get_all_admin_emails() ),
145 'condition' => [ 'contact_form' => true ],
146 ],
147 [
148 'name' => 'contact_phone',
149 'label' => __( 'Support Contact Phone Number', 'admin-help-docs' ),
150 'desc' => __( 'Enter a phone number that clients can use to reach you. This will be displayed on the contact form page.', 'admin-help-docs' ),
151 'type' => 'text',
152 'sanitize' => 'sanitize_text_field',
153 'box' => 'interface',
154 'default' => '',
155 'condition' => [ 'contact_form' => true ],
156 ],
157 [
158 'name' => 'admin_bar',
159 'label' => __( 'Enable Admin Bar Menu for Backend', 'admin-help-docs' ),
160 'type' => 'checkbox',
161 'sanitize' => 'sanitize_checkbox',
162 'box' => 'interface',
163 'default' => false,
164 ],
165 [
166 'name' => 'admin_bar_frontend',
167 'label' => __( 'Enable Admin Bar Menu for Frontend', 'admin-help-docs' ),
168 'type' => 'checkbox',
169 'sanitize' => 'sanitize_checkbox',
170 'box' => 'interface',
171 'default' => false,
172 ],
173 [
174 'name' => 'replace_dashboard',
175 'label' => __( 'Replace WordPress Dashboard with a Help Doc', 'admin-help-docs' ),
176 'desc' => __( 'Replace the default WordPress dashboard with a custom help docs page. To add docs, set their locations to "WordPress Dashboard (Replaces Dashboard Entirely)". Warning: Replacing the WordPress dashboard entirely may affect other plugins and functionality. No other widgets or dashboard elements will be displayed.', 'admin-help-docs' ),
177 'type' => 'checkbox',
178 'sanitize' => 'sanitize_checkbox',
179 'box' => 'interface',
180 'default' => false,
181 ],
182 [
183 'name' => 'dashboard_toc',
184 'label' => __( 'Enable Dashboard Table of Contents Widget', 'admin-help-docs' ),
185 'desc' => __( 'Adds a dashboard widget with a table of contents for the docs on the Main Documentation Page.', 'admin-help-docs' ),
186 'type' => 'checkbox',
187 'sanitize' => 'sanitize_checkbox',
188 'box' => 'interface',
189 'default' => false,
190 ],
191 [
192 'name' => 'gutenberg_editor',
193 'label' => __( 'Use Gutenberg Editor', 'admin-help-docs' ),
194 'desc' => __( 'Adds support for the Gutenberg editor for the documentation. Default is the classic editor.', 'admin-help-docs' ),
195 'type' => 'checkbox',
196 'sanitize' => 'sanitize_checkbox',
197 'box' => 'interface',
198 'default' => false,
199 ],
200
201 // Content & Output
202 [
203 'name' => 'admin_bar_include_content',
204 'label' => __( 'Include Doc Content in Admin Bar', 'admin-help-docs' ),
205 'desc' => __( 'Includes a snippet of the document content in the admin bar.', 'admin-help-docs' ),
206 'type' => 'checkbox',
207 'sanitize' => 'sanitize_checkbox',
208 'box' => 'content_output',
209 'default' => false,
210 ],
211 [
212 'name' => 'default_doc',
213 'label' => __( 'Default Document on Main Docs Page', 'admin-help-docs' ),
214 'type' => 'select',
215 'choices' => Helpers::get_main_helpdoc_options(),
216 'sanitize' => 'sanitize_text_field',
217 'box' => 'content_output',
218 ],
219 [
220 'name' => 'hide_doc_meta',
221 'label' => __( 'Hide Document Meta on Main Docs Page', 'admin-help-docs' ),
222 'desc' => __( 'Includes created and last modified dates and authors.', 'admin-help-docs' ),
223 'type' => 'checkbox',
224 'sanitize' => 'sanitize_checkbox',
225 'box' => 'content_output',
226 'default' => false,
227 ],
228 [
229 'name' => 'auto_htoc',
230 'label' => __( 'Auto-Generate Table of Contents on Main Docs Page', 'admin-help-docs' ),
231 'desc' => __( 'Automatically generate a table of contents from headings (H2–H6) at the top of each documentation page.', 'admin-help-docs' ),
232 'type' => 'checkbox',
233 'sanitize' => 'sanitize_checkbox',
234 'box' => 'content_output',
235 'default' => false,
236 ],
237 [
238 'name' => 'curly_quotes',
239 'label' => __( 'Disable Curly Quotes on Main Docs Page', 'admin-help-docs' ),
240 'desc' => __( 'WP automatically converts straight quotes (") to curly quotes (”), which makes sharing code difficult.', 'admin-help-docs' ),
241 'type' => 'checkbox',
242 'sanitize' => 'sanitize_checkbox',
243 'box' => 'content_output',
244 'default' => false,
245 ],
246 [
247 'name' => 'enqueue_frontend_styles',
248 'label' => __( 'Use Frontend Stylesheets on Main Docs Page', 'admin-help-docs' ),
249 'desc' => __( 'Adds support for your frontend styles in the backend by enqueueing them while on the Main Docs Page.', 'admin-help-docs' ),
250 'type' => 'checkbox',
251 'sanitize' => 'sanitize_checkbox',
252 'box' => 'content_output',
253 'default' => false,
254 ],
255 [
256 'name' => 'main_docs_css',
257 'label' => __( 'Main Docs Page CSS', 'admin-help-docs' ),
258 'desc' => __( 'Add CSS here to style the Main Documentation Page specifically. You must use CSS selectors.', 'admin-help-docs' ),
259 'type' => 'textarea',
260 'sanitize' => 'sanitize_custom_css',
261 'box' => 'content_output',
262 'default' => '',
263 ],
264 [
265 'name' => 'include_logo_on_docs',
266 'label' => __( 'Include Logo on Help Docs', 'admin-help-docs' ),
267 'desc' => __( 'Includes the site logo on individual help docs that are not on the Main Docs Page.', 'admin-help-docs' ),
268 'type' => 'checkbox',
269 'sanitize' => 'sanitize_checkbox',
270 'box' => 'content_output',
271 'default' => true,
272 ],
273 [
274 'name' => 'top_location_type',
275 'label' => __( 'Top Location Placement', 'admin-help-docs' ),
276 'type' => 'select',
277 'choices' => $top_placements,
278 'sanitize' => 'sanitize_key',
279 'box' => 'content_output',
280 'default' => 'admin_notices',
281 ],
282 [
283 'name' => 'footer_left',
284 'label' => __( 'Left Footer Text', 'admin-help-docs' ),
285 'desc' => __( 'Supports HTML. Default: "Thank you for creating with WordPress."', 'admin-help-docs' ),
286 'type' => 'textarea',
287 'sanitize' => 'wp_kses_post',
288 'box' => 'content_output',
289 'default' => '<span id="footer-thankyou">Thank you for creating with <a href="https://wordpress.org/">WordPress</a>.</span>',
290 ],
291 [
292 'name' => 'footer_right',
293 'label' => __( 'Right Footer Text', 'admin-help-docs' ),
294 'desc' => __( 'Use <code>{version}</code> to display the current WordPress version', 'admin-help-docs' ),
295 'type' => 'textarea',
296 'sanitize' => 'wp_kses_post',
297 'box' => 'content_output',
298 'default' => 'Version {version}',
299 ],
300
301 // Access & Permissions
302 [
303 'name' => 'api',
304 'label' => __( 'Allow Public by Default', 'admin-help-docs' ),
305 'type' => 'checkbox',
306 'sanitize' => 'sanitize_checkbox',
307 'box' => 'access_control',
308 'default' => false,
309 ],
310 [
311 'name' => 'api_key',
312 'label' => __( 'Public Access API Key (Optional)', 'admin-help-docs' ),
313 'desc' => __( 'If you enable "Allow Public" above or on individual docs, you can optionally require an API key for access. This adds a layer of security by ensuring that only users with the key can import your documentation. To use, generate a key here and enter it into the import on your other site where you are importing the docs. Leave empty to allow public access without a key.', 'admin-help-docs' ),
314 'type' => 'api_key',
315 'sanitize' => 'sanitize_text_field',
316 'box' => 'access_control',
317 'default' => false,
318 ],
319 [
320 'name' => 'user_view_type',
321 'label' => __( 'Requirement Type to View Docs', 'admin-help-docs' ),
322 'type' => 'select',
323 'choices' => [
324 'capability' => __( 'Capability', 'admin-help-docs' ),
325 'role' => __( 'Role', 'admin-help-docs' ),
326 ],
327 'sanitize' => 'sanitize_text_field',
328 'box' => 'access_control',
329 'default' => $view_default,
330 'has_condition' => true,
331 ],
332 [
333 'name' => 'user_view_cap',
334 'label' => __( 'Default Capability Required to View Docs', 'admin-help-docs' ) . ' — <a href="https://wordpress.org/documentation/article/roles-and-capabilities/" target="_blank">View a list of capabilities. <span class="dashicons dashicons-external"></span></a>',
335 'desc' => __( 'Use <code>manage_options</code> for admins only. You can also override this setting on a per-document basis.', 'admin-help-docs' ),
336 'type' => 'text',
337 'sanitize' => 'sanitize_text_field',
338 'box' => 'access_control',
339 'default' => 'manage_options',
340 'condition' => [ 'user_view_type' => 'capability' ],
341 ],
342 [
343 'name' => 'view_roles',
344 'label' => __( 'Additional Default Roles Required to View Docs', 'admin-help-docs' ),
345 'desc' => __( 'Admins can view all docs regardless of this setting. You can also override this setting on a per-document basis.', 'admin-help-docs' ),
346 'type' => 'checkboxes',
347 'choices' => Helpers::get_role_options(),
348 'sanitize' => 'sanitize_checkboxes',
349 'box' => 'access_control',
350 'condition' => [ 'user_view_type' => 'role' ],
351 ],
352 [
353 'name' => 'user_edit_type',
354 'label' => __( 'Requirement Type to Edit Docs', 'admin-help-docs' ),
355 'type' => 'select',
356 'choices' => [
357 'capability' => __( 'Capability', 'admin-help-docs' ),
358 'role' => __( 'Role', 'admin-help-docs' ),
359 ],
360 'sanitize' => 'sanitize_text_field',
361 'box' => 'access_control',
362 'default' => 'role',
363 'has_condition' => true,
364 ],
365 [
366 'name' => 'user_edit_cap',
367 'label' => __( 'Capability Required to Edit Docs', 'admin-help-docs' ) . ' — <a href="https://wordpress.org/documentation/article/roles-and-capabilities/" target="_blank">View a list of capabilities. <span class="dashicons dashicons-external"></span></a>',
368 'desc' => __( 'Use <code>manage_options</code> for admins only.', 'admin-help-docs' ),
369 'type' => 'text',
370 'sanitize' => 'sanitize_text_field',
371 'box' => 'access_control',
372 'default' => 'manage_options',
373 'condition' => [ 'user_edit_type' => 'capability' ],
374 ],
375 [
376 'name' => 'edit_roles',
377 'label' => __( 'Additional Roles Required to Edit Docs', 'admin-help-docs' ),
378 'desc' => __( 'Admins can edit all docs regardless of this setting.', 'admin-help-docs' ),
379 'type' => 'checkboxes',
380 'choices' => Helpers::get_role_options(),
381 'sanitize' => 'sanitize_checkboxes',
382 'box' => 'access_control',
383 'condition' => [ 'user_edit_type' => 'role' ],
384 ],
385
386 // Advanced
387 [
388 'name' => 'remove_on_uninstall',
389 'label' => __( 'Remove All Plugin Data on Uninstall', 'admin-help-docs' ),
390 'desc' => __( 'Deletes all plugin settings and documentation permanently when the plugin is deleted.', 'admin-help-docs' ),
391 'type' => 'checkbox',
392 'sanitize' => 'sanitize_checkbox',
393 'box' => 'advanced',
394 'default' => false,
395 ],
396 [
397 'name' => 'flush_cache',
398 'label' => __( 'System Cache Management', 'admin-help-docs' ),
399 'type' => 'html',
400 'box' => 'advanced',
401 'content' => '<button type="button" id="helpdocs-flush-cache" class="helpdocs-button">' . __( 'Clear Stored Data', 'admin-help-docs' ) . '</button>',
402 ],
403 [
404 'name' => 'upload_download_colors',
405 'label' => __( 'Upload/Download Colors', 'admin-help-docs' ),
406 'desc' => __( 'Export your current color settings as a JSON file for backup or transfer to another site. You can also import color settings from a JSON file exported from this plugin. Note: Importing settings will overwrite your current color settings.', 'admin-help-docs' ),
407 'type' => 'html',
408 'box' => 'advanced',
409 'content' => '<button type="button" id="helpdocs-download-colors-btn" class="helpdocs-button">' . __( 'Download Colors', 'admin-help-docs' ) . '</button> <div id="helpdocs-upload-colors-button"><label for="helpdocs-upload-colors"><span class="helpdocs-button">' . __( 'Upload Colors', 'admin-help-docs' ) . '</span></label><input type="file" id="helpdocs-upload-colors" name="helpdocs-upload-colors" accept=".json"></div> <div id="helpdocs-upload-colors-filename"></div>',
410 ],
411 [
412 'name' => 'upload_download_settings',
413 'label' => __( 'Upload/Download Settings', 'admin-help-docs' ),
414 'desc' => __( 'Export your current settings as a JSON file for backup or transfer to another site. You can also import settings from a JSON file exported from this plugin. Note: Importing settings will overwrite your current settings.', 'admin-help-docs' ),
415 'type' => 'html',
416 'box' => 'advanced',
417 'content' => '<button type="button" id="helpdocs-download-settings-btn" class="helpdocs-button">' . __( 'Download Settings', 'admin-help-docs' ) . '</button> <div id="helpdocs-upload-button"><label for="helpdocs-upload-settings"><span class="helpdocs-button">' . __( 'Upload Settings', 'admin-help-docs' ) . '</span></label><input type="file" id="helpdocs-upload-settings" name="helpdocs-upload-settings" accept=".json"></div> <div id="helpdocs-upload-filename"></div>',
418 ],
419 [
420 'name' => 'reset_settings',
421 'label' => __( 'Reset Settings', 'admin-help-docs' ),
422 'desc' => __( 'This will not delete any documentation, but it will reset all settings to their defaults.', 'admin-help-docs' ),
423 'type' => 'html',
424 'box' => 'advanced',
425 'content' => '<button type="button" id="helpdocs-reset-colors" class="helpdocs-button">' . __( 'Reset All Colors', 'admin-help-docs' ) . '</button> <button type="button" id="helpdocs-reset-settings" class="helpdocs-button">' . __( 'Reset All Settings', 'admin-help-docs' ) . '</button>',
426 ],
427 ];
428
429 // Colors
430 $color_fields = [];
431 $colors = Colors::defaults();
432 foreach ( $colors as $key => $color ) {
433 if ( strpos( $key, 'subheader' ) !== false ) {
434 continue;
435 }
436 $color_fields[] = [
437 'name' => 'color_' . $key,
438 'label' => $color[ 'label' ],
439 'type' => 'color',
440 'sanitize' => 'sanitize_text_field',
441 'box' => 'interface',
442 'default' => $color[ 'color' ],
443 ];
444 }
445
446 $themes_index = false;
447 foreach ( $fields as $index => $field ) {
448 if ( isset( $field[ 'name' ] ) && 'themes' === $field[ 'name' ] ) {
449 $themes_index = $index;
450 break;
451 }
452 }
453
454 if ( false !== $themes_index ) {
455 array_splice( $fields, $themes_index + 1, 0, $color_fields );
456 } else {
457 $fields = array_merge( $fields, $color_fields );
458 }
459
460 // Only get the name, type and default for each field
461 if ( $defaults_only ) {
462 foreach ( $fields as $index => $field ) {
463 $fields[ $index ] = [
464 'name' => $field[ 'name' ],
465 'type' => $field[ 'type' ],
466 'default' => $field[ 'default' ] ?? null,
467 ];
468 }
469
470 }
471
472 return apply_filters( 'helpdocs_settings_fields', $fields );
473 } // End setting_fields()
474
475
476 /**
477 * Returns available placements for top docs.
478 *
479 * @return array
480 */
481 public static function top_placements() : array {
482 return [
483 'admin_notices' => __( 'Just Above Page Title', 'admin-help-docs' ),
484 'in_admin_header' => __( 'Very Top of Page (Above All Content)', 'admin-help-docs' ),
485 ];
486 } // End top_placements()
487
488
489 /**
490 * The single instance of the class
491 *
492 * @var self|null
493 */
494 private static ?Settings $instance = null;
495
496
497 /**
498 * Get the singleton instance
499 *
500 * @return self
501 */
502 public static function instance() : self {
503 return self::$instance ??= new self();
504 } // End instance()
505
506
507 /**
508 * Constructor
509 */
510 private function __construct() {
511 add_action( 'helpdocs_subheader_left', [ $this, 'render_save_reminder' ] );
512 add_action( 'wp_ajax_helpdocs_save_settings', [ $this, 'ajax_save_settings' ] );
513 add_action( 'wp_ajax_helpdocs_flush_cache', [ $this, 'ajax_flush_cache' ] );
514 } // End __construct()
515
516
517 /**
518 * Render a reminder to save settings after changes are made
519 *
520 * @param string $current_tab The current active tab
521 */
522 public function render_save_reminder( $current_tab ) {
523 if ( $current_tab === 'settings' ) {
524 echo '<span id="helpdocs-save-reminder">' . esc_html__( 'Remember to click "Save" after making changes to your settings.', 'admin-help-docs' ) . '</span>';
525 }
526 } // End render_save_reminder()
527
528
529 /**
530 * Render the tab
531 */
532 public function render_tab() {
533 $fields = $this->setting_fields();
534 $boxes = [];
535 $box_labels = $this->setting_boxes();
536
537 // Group fields by box
538 foreach ( $fields as $field ) {
539 $boxes[ $field[ 'box' ] ][] = $field;
540 }
541
542 echo '<div class="helpdocs-settings-grid">';
543 foreach ( $boxes as $box_id => $fields ) {
544 echo '<div class="helpdocs-settings-box">';
545 echo '<div class="helpdocs-settings-header">';
546 echo '<h2>' . esc_html( $box_labels[ $box_id ] ?? 'Unknown Box' ) . '</h2>';
547 echo '</div>';
548 echo '<div class="helpdocs-settings-body">';
549
550 $in_color_group = false;
551 foreach ( $fields as $index => $field ) {
552 $is_color = ( $field[ 'type' ] === 'color' );
553 $render_fn = "render_field_{$field[ 'type' ]}";
554
555 if ( $is_color && ! $in_color_group ) {
556 echo '<div class="helpdocs-color-grid">';
557 $in_color_group = true;
558 }
559
560 if ( ! $is_color && $in_color_group ) {
561 echo '</div>';
562 $in_color_group = false;
563 }
564
565 if ( method_exists( $this, $render_fn ) ) {
566 $this->{$render_fn}( $field );
567 }
568
569 $is_last = ( $index === array_key_last( $fields ) );
570
571 if ( $is_last && $in_color_group ) {
572 echo '</div>';
573 $in_color_group = false;
574 }
575 }
576 echo '</div></div>';
577 }
578 echo '</div>';
579 } // End render_tab()
580
581
582 /**
583 * Get the value of a field, falling back to the default if not set
584 *
585 * @param string $field_name The name of the field
586 * @return mixed The value of the field or its default
587 */
588 public static function get_field_value( $field_name ) {
589 $option = get_option( "helpdocs_{$field_name}", null );
590
591 if ( null !== $option ) {
592 return $option;
593 }
594
595 $all_fields = self::setting_fields();
596 foreach ( $all_fields as $field ) {
597 if ( $field[ 'name' ] === $field_name ) {
598 return $field[ 'default' ] ?? '';
599 }
600 }
601
602 return '';
603 } // End get_field_value()
604
605
606 /**
607 * Sanitize a field value based on the field's specified sanitize callback
608 *
609 * @param array $field The field definition array
610 * @param mixed $value The value to sanitize
611 * @return mixed The sanitized value
612 */
613 public function sanitize_field( $field, $value ) {
614 $sanitize_callback = $field[ 'sanitize' ] ?? null;
615
616 if ( $sanitize_callback && is_callable( [ $this, $sanitize_callback ] ) ) {
617 return $this->$sanitize_callback( $value );
618 } elseif ( is_callable( $sanitize_callback ) ) {
619 return $sanitize_callback( $value );
620 } else {
621 return sanitize_text_field( $value );
622 }
623 } // End sanitize_field()
624
625
626 /**
627 * Render a field label with optional description tooltip
628 *
629 * @param array $field The field definition array
630 * @param string $for Optional ID of the associated input for the label's "for" attribute
631 */
632 public function render_field_label( $field, $for = '' ) {
633 ?>
634 <label <?php echo $for ? 'for="' . esc_attr( $for ) . '"' : ''; ?> class="helpdocs-field-label">
635 <?php echo wp_kses_post( $field[ 'label' ] ); ?>
636
637 <?php if ( ! empty( $field[ 'required' ] ) ) : ?>
638 <span class="helpdocs-required-field" title="<?php esc_attr_e( 'Required', 'admin-help-docs' ); ?>">*</span>
639 <?php endif; ?>
640
641 <?php if ( ! empty( $field[ 'desc' ] ) ) : ?>
642 <span class="helpdocs-tooltip">
643 <span class="dashicons dashicons-editor-help"></span>
644 <span class="helpdocs-tooltip-text"><?php echo wp_kses_post( $field[ 'desc' ] ); ?></span>
645 </span>
646 <?php endif; ?>
647 </label>
648 <?php
649 } // End render_field_label()
650
651
652 /**
653 * Render a text field
654 *
655 * @param array $field The field definition array
656 */
657 public function render_field_text( $field ) {
658 $value = $this->get_field_value( $field[ 'name' ] );
659
660 // If the value is empty/null, use the default if provided
661 if ( ( is_null( $value ) || $value === '' ) && isset( $field[ 'default' ] ) ) {
662 $value = $field[ 'default' ];
663 }
664
665 $value = $this->sanitize_field( $field, $value );
666
667 $conditional_class = '';
668 $data_condition_field = '';
669 $data_condition_value = '';
670
671 if ( ! empty( $field[ 'condition' ] ) ) {
672 $condition_field = key( $field[ 'condition' ] );
673 $expected_value = current( $field[ 'condition' ] );
674 $target_value = $this->get_field_value( $condition_field );
675
676 if ( $target_value != $expected_value ) {
677 $conditional_class = ' condition-hide';
678 }
679
680 $data_condition_field = $condition_field;
681 $data_condition_value = $expected_value;
682 }
683
684 $required = ! empty( $field[ 'required' ] ) ? ' required' : '';
685 ?>
686 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>"
687 class="helpdocs-field<?php echo esc_attr( $conditional_class ); ?>"
688 <?php if ( $data_condition_field && $data_condition_value ) : ?>
689 data-condition-field="<?php echo esc_attr( $data_condition_field ); ?>"
690 data-condition-value="<?php echo esc_attr( $data_condition_value ); ?>"
691 <?php endif; ?>>
692 <?php $this->render_field_label( $field, $field[ 'name' ] ); ?>
693 <input type="text"
694 id="<?php echo esc_attr( $field[ 'name' ] ); ?>"
695 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>"
696 value="<?php echo esc_attr( $value ); ?>"
697 <?php echo esc_attr( $required ); ?>
698 placeholder="<?php echo isset( $field[ 'default' ] ) ? esc_attr( $field[ 'default' ] ) : ''; ?>">
699 </div>
700 <?php
701 } // End render_field_text()
702
703
704 /**
705 * Render a textarea field
706 *
707 * @param array $field The field definition array
708 */
709 public function render_field_textarea( $field ) {
710 $value = $this->get_field_value( $field[ 'name' ] );
711 $value = $this->sanitize_field( $field, $value );
712
713 $conditional_class = '';
714 $data_condition_field = '';
715 $data_condition_value = '';
716
717 if ( ! empty( $field[ 'condition' ] ) ) {
718 $condition_field = key( $field[ 'condition' ] );
719 $expected_value = current( $field[ 'condition' ] );
720 $target_value = $this->get_field_value( $condition_field );
721
722 if ( $target_value != $expected_value ) {
723 $conditional_class = ' condition-hide';
724 }
725
726 $data_condition_field = $condition_field;
727 $data_condition_value = $expected_value;
728 }
729
730 $required = ! empty( $field[ 'required' ] ) ? ' required' : '';
731
732 $field_id = str_starts_with( $field[ 'name' ], 'footer' ) ? 'helpdocs_' . $field[ 'name' ] : $field[ 'name' ];
733 ?>
734 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>"
735 class="helpdocs-field<?php echo esc_attr( $conditional_class ); ?>"
736 <?php if ( $data_condition_field && $data_condition_value ) : ?>
737 data-condition-field="<?php echo esc_attr( $data_condition_field ); ?>"
738 data-condition-value="<?php echo esc_attr( $data_condition_value ); ?>"
739 <?php endif; ?>>
740 <?php $this->render_field_label( $field, $field[ 'name' ] ); ?>
741 <textarea id="<?php echo esc_attr( $field_id ); ?>"
742 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>"
743 <?php echo esc_attr( $required ); ?>
744 placeholder="<?php if ( isset( $field[ 'default' ] ) ) { echo esc_attr( $field[ 'default' ] ); } ?>"><?php echo esc_textarea( $value ); ?></textarea>
745 </div>
746 <?php
747 } // End render_field_textarea()
748
749
750 /**
751 * Render a color field
752 *
753 * @param array $field The field definition array
754 */
755 public function render_field_color( $field ) {
756 $value = Colors::get( str_replace( 'color_', '', $field[ 'name' ] ) );
757 $value = $this->sanitize_field( $field, $value );
758 ?>
759 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>" class="helpdocs-field color-picker-field">
760 <label for="<?php echo esc_attr( $field[ 'name' ] ); ?>">
761 <?php echo esc_html( $field[ 'label' ] ); ?>
762 </label>
763 <input type="color"
764 id="<?php echo esc_attr( $field[ 'name' ] ); ?>"
765 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>"
766 value="<?php echo esc_attr( $value ); ?>">
767 </div>
768 <?php
769 } // End render_field_color()
770
771
772 /**
773 * Render a select field
774 *
775 * @param array $field The field definition array, which should include a 'choices' key with an array of options
776 */
777 public function render_field_select( $field ) {
778 $option = get_option( "helpdocs_{$field[ 'name' ]}", null );
779
780 $value = ( null === $option || '' === $option )
781 ? ( $field[ 'default' ] ?? '' )
782 : $option;
783
784 $value = $this->sanitize_field( $field, $value );
785
786 // Strip dashicons
787 $value = str_replace( 'dashicons-', '', $value );
788
789 $has_condition = isset( $field[ 'has_condition' ] ) && $field[ 'has_condition' ];
790 $conditional_class = $has_condition ? ' has-condition' : '';
791
792 $required = ! empty( $field[ 'required' ] ) ? ' required' : '';
793 ?>
794 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>" class="helpdocs-field">
795 <?php $this->render_field_label( $field, $field[ 'name' ] ); ?>
796 <select id="<?php echo esc_attr( $field[ 'name' ] ); ?>" name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>" class="<?php echo esc_attr( $conditional_class ); ?>"<?php echo esc_attr( $required ); ?>>
797 <?php
798 if ( isset( $field[ 'choices' ] ) && is_array( $field[ 'choices' ] ) ) {
799
800 foreach ( $field[ 'choices' ] as $key => $choice ) {
801
802 if ( is_array( $choice ) ) {
803 $option_value = $choice[ 'value' ] ?? '';
804 $option_label = $choice[ 'label' ] ?? '';
805 } else {
806 $option_value = is_string( $key ) ? $key : $choice;
807 $option_label = $choice;
808 }
809 ?>
810 <option value="<?php echo esc_attr( $option_value ); ?>" <?php selected( $value, $option_value ); ?>>
811 <?php echo esc_html( $option_label ); ?>
812 </option>
813 <?php
814 }
815 }
816 ?>
817 </select>
818 </div>
819 <?php
820 } // End render_field_select()
821
822
823 /**
824 * Render a checkbox field
825 *
826 * @param array $field The field definition array
827 */
828 public function render_field_checkbox( $field ) {
829 $option = get_option( "helpdocs_{$field[ 'name' ]}", null );
830
831 $value = ( null === $option )
832 ? ( $field[ 'default' ] ?? '' )
833 : $option;
834
835 $value = $this->sanitize_field( $field, $value );
836
837 $has_condition = isset( $field[ 'has_condition' ] ) && $field[ 'has_condition' ];
838 $conditional_class = $has_condition ? ' has-condition' : '';
839
840 $required = ! empty( $field[ 'required' ] ) ? ' required' : '';
841 ?>
842 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>" class="helpdocs-field helpdocs-field-checkbox<?php echo esc_attr( $conditional_class ); ?>">
843 <label class="helpdocs-checkbox-label">
844 <input type="checkbox"
845 id="<?php echo esc_attr( $field[ 'name' ] ); ?>"
846 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>"
847 value="1" <?php checked( $value, '1' ); ?> <?php echo esc_attr( $required ); ?>>
848 <span><?php echo esc_html( $field[ 'label' ] ); ?></span>
849 <?php if ( ! empty( $field[ 'desc' ] ) ) : ?>
850 <span class="helpdocs-tooltip">
851 <span class="dashicons dashicons-editor-help"></span>
852 <span class="helpdocs-tooltip-text"><?php echo wp_kses_post( $field[ 'desc' ] ); ?></span>
853 </span>
854 <?php endif; ?>
855 </label>
856 </div>
857 <?php
858 } // End render_field_checkbox()
859
860
861 /**
862 * Sanitize checkbox
863 *
864 * @param mixed $value The value to sanitize
865 * @return string '1' if checked, '' if not
866 */
867 public function sanitize_checkbox( $value ) {
868 return ( ( $value == '1' || $value == 'yes' ) ? '1' : '' );
869 } // End sanitize_checkbox()
870
871
872 /**
873 * Render a checkboxes field
874 *
875 * @param array $field The field definition array, which should include a 'choices' key with an array of options
876 */
877 public function render_field_checkboxes( $field ) {
878 $values = (array) $this->get_field_value( $field[ 'name' ] );
879 $values = (array) $this->sanitize_field( $field, $values );
880
881 $conditional_class = '';
882 $data_condition_field = '';
883 $data_condition_value = '';
884
885 if ( ! empty( $field[ 'condition' ] ) ) {
886 $condition_field = key( $field[ 'condition' ] );
887 $expected_value = current( $field[ 'condition' ] );
888 $target_value = $this->get_field_value( $condition_field );
889
890 if ( $target_value != $expected_value ) {
891 $conditional_class = ' condition-hide';
892 }
893
894 $data_condition_field = $condition_field;
895 $data_condition_value = $expected_value;
896 }
897 ?>
898 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>"
899 class="helpdocs-field<?php echo esc_attr( $conditional_class ); ?>"
900 <?php if ( $data_condition_field && $data_condition_value ) : ?>
901 data-condition-field="<?php echo esc_attr( $data_condition_field ); ?>"
902 data-condition-value="<?php echo esc_attr( $data_condition_value ); ?>"
903 <?php endif; ?>>
904 <?php $this->render_field_label( $field, $field[ 'name' ] ); ?>
905 <div class="helpdocs-checkboxes">
906 <?php
907 if ( isset( $field[ 'choices' ] ) && is_array( $field[ 'choices' ] ) ) {
908 foreach ( $field[ 'choices' ] as $choice ) :
909 $key = is_array( $choice ) ? ( $choice[ 'value' ] ?? '' ) : ( $choice[ 0 ] ?? '' );
910 $label = is_array( $choice ) ? ( $choice[ 'label' ] ?? '' ) : ( $choice[ 1 ] ?? '' );
911 ?>
912 <label>
913 <input type="checkbox"
914 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>[]"
915 value="<?php echo esc_attr( $key ); ?>" <?php checked( in_array( $key, $values, true ) || in_array( $key, array_keys( $values ), true ) ); ?>>
916 <?php echo esc_html( $label ); ?>
917 </label>
918 <?php
919 endforeach;
920 }
921 ?>
922 </div>
923 </div>
924 <?php
925 } // End render_field_checkboxes()
926
927
928 /**
929 * Sanitize checkboxes
930 *
931 * @param mixed $values The values to sanitize, expected to be an array of selected keys
932 * @return array An array of sanitized values that are valid choices
933 */
934 private function sanitize_checkboxes( $values ) {
935 if ( ! is_array( $values ) ) {
936 return [];
937 }
938 return array_map( 'strval', $values );
939 } // End sanitize_checkboxes()
940
941
942 /**
943 * Render a file upload field
944 *
945 * @param array $field The field definition array
946 */
947 public function render_field_file( $field ) {
948 $conditional_class = '';
949 $data_condition_field = '';
950 $data_condition_value = '';
951
952 if ( ! empty( $field[ 'condition' ] ) ) {
953 $condition_field = key( $field[ 'condition' ] );
954 $expected_value = current( $field[ 'condition' ] );
955 $target_value = $this->get_field_value( $condition_field );
956
957 if ( $target_value != $expected_value ) {
958 $conditional_class = ' condition-hide';
959 }
960
961 $data_condition_field = $condition_field;
962 $data_condition_value = $expected_value;
963 }
964
965 // Allow file types to be filterable
966 $default_types = '.jpg,.jpeg,.png,.pdf,.zip';
967 $accepted_types = apply_filters( 'helpdocs_support_file_types', $default_types, $field );
968
969 $required = ! empty( $field[ 'required' ] ) ? ' required' : '';
970 ?>
971 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>"
972 class="helpdocs-field helpdocs-field-file<?php echo esc_attr( $conditional_class ); ?>"
973 <?php if ( $data_condition_field && $data_condition_value ) : ?>
974 data-condition-field="<?php echo esc_attr( $data_condition_field ); ?>"
975 data-condition-value="<?php echo esc_attr( $data_condition_value ); ?>"
976 <?php endif; ?>>
977 <?php $this->render_field_label( $field, $field[ 'name' ] ); ?>
978 <input type="file"
979 id="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>" // Added prefix to ID
980 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>[]"
981 accept="<?php echo esc_attr( $accepted_types ); ?>"
982 <?php echo esc_attr( $required ); ?>
983 multiple>
984 </div>
985 <?php
986 } // End render_field_file()
987
988
989 /**
990 * Sanitize and process file uploads
991 *
992 * @param string $field_name The name of the file input
993 * @return array List of local file paths for wp_mail attachments
994 */
995 public function sanitize_files( $field_name, $max_total_mb = 10 ) {
996 if ( empty( $_FILES[ "helpdocs_$field_name" ] ) ) { // phpcs:ignore
997 return [];
998 }
999
1000 $files = $_FILES[ "helpdocs_$field_name" ]; // phpcs:ignore
1001 $attachments = [];
1002 $total_size = 0;
1003 $max_total_size = $max_total_mb * 1024 * 1024; // Convert MB to bytes
1004
1005 if ( is_array( $files[ 'name' ] ) ) {
1006 // First Pass: Check total size
1007 foreach ( $files[ 'size' ] as $size ) {
1008 $total_size += $size;
1009 }
1010
1011 if ( $total_size > $max_total_size ) {
1012 wp_send_json_error( sprintf( __( 'The total size of attachments exceeds %dMB.', 'admin-help-docs' ), $max_total_mb ) );
1013 }
1014
1015 // Second Pass: Process uploads
1016 foreach ( $files[ 'name' ] as $key => $value ) {
1017 if ( $files[ 'name' ][ $key ] ) {
1018 $file = [
1019 'name' => $files[ 'name' ][ $key ],
1020 'type' => $files[ 'type' ][ $key ],
1021 'tmp_name' => $files[ 'tmp_name' ][ $key ],
1022 'error' => $files[ 'error' ][ $key ],
1023 'size' => $files[ 'size' ][ $key ],
1024 ];
1025
1026 require_once( ABSPATH . 'wp-admin/includes/file.php' );
1027
1028 $upload_overrides = [ 'test_form' => false ];
1029 $movefile = wp_handle_upload( $file, $upload_overrides );
1030
1031 if ( $movefile && ! isset( $movefile[ 'error' ] ) ) {
1032 $attachments[] = $movefile[ 'file' ];
1033 }
1034 }
1035 }
1036 }
1037
1038 return $attachments;
1039 } // End sanitize_files()
1040
1041
1042 /**
1043 * Render an API Key field with Generate, Copy, and Clear actions
1044 * * @param array $field The field definition array
1045 */
1046 public function render_field_api_key( $field ) {
1047 $value = $this->get_field_value( $field[ 'name' ] );
1048
1049 if ( ( is_null( $value ) || $value === '' ) && isset( $field[ 'default' ] ) ) {
1050 $value = $field[ 'default' ];
1051 }
1052
1053 $value = $this->sanitize_field( $field, $value );
1054
1055 $conditional_class = '';
1056 $data_condition_field = '';
1057 $data_condition_value = '';
1058
1059 if ( ! empty( $field[ 'condition' ] ) ) {
1060 $condition_field = key( $field[ 'condition' ] );
1061 $expected_value = current( $field[ 'condition' ] );
1062 $target_value = $this->get_field_value( $condition_field );
1063
1064 if ( $target_value != $expected_value ) {
1065 $conditional_class = ' condition-hide';
1066 }
1067
1068 $data_condition_field = $condition_field;
1069 $data_condition_value = $expected_value;
1070 }
1071 ?>
1072 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>"
1073 class="helpdocs-field<?php echo esc_attr( $conditional_class ); ?>"
1074 <?php if ( $data_condition_field && $data_condition_value ) : ?>
1075 data-condition-field="<?php echo esc_attr( $data_condition_field ); ?>"
1076 data-condition-value="<?php echo esc_attr( $data_condition_value ); ?>"
1077 <?php endif; ?>>
1078
1079 <?php $this->render_field_label( $field, $field[ 'name' ] ); ?>
1080
1081 <div class="helpdocs-api-key-wrapper">
1082 <div id="helpdocs_api_key_display" class="helpdocs-api-key-box <?php echo $value ? 'has-key' : 'no-key'; ?>">
1083 <?php echo $value ? esc_html( $value ) : '<em>' . esc_html__( 'No API Key Generated', 'admin-help-docs' ) . '</em>'; ?>
1084 </div>
1085
1086 <input type="hidden"
1087 id="<?php echo esc_attr( $field[ 'name' ] ); ?>"
1088 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>"
1089 value="<?php echo esc_attr( $value ); ?>">
1090
1091 <div class="helpdocs-api-key-actions">
1092 <button type="button" class="helpdocs-button helpdocs-generate-api-key"><?php esc_html_e( 'Generate API Key', 'admin-help-docs' ); ?></button>
1093 <button type="button" class="helpdocs-button helpdocs-copy-api-key" <?php echo ! $value ? 'disabled' : ''; ?>><?php esc_html_e( 'Copy', 'admin-help-docs' ); ?></button>
1094 <button type="button" class="helpdocs-button helpdocs-clear-api-key" <?php echo ! $value ? 'disabled' : ''; ?>><?php esc_html_e( 'Clear', 'admin-help-docs' ); ?></button>
1095 </div>
1096 </div>
1097 </div>
1098 <?php
1099 } // End render_field_api_key()
1100
1101
1102 /**
1103 * Sanitize custom CSS input
1104 *
1105 * @param string $css The CSS input to sanitize
1106 * @return string The sanitized CSS
1107 */
1108 public function sanitize_custom_css( $css ) {
1109 $css = wp_unslash( $css );
1110 $css = wp_strip_all_tags( $css );
1111 return $css;
1112 } // End sanitize_custom_css()
1113
1114
1115 /**
1116 * Render an HTML field (for custom content like buttons)
1117 *
1118 * @param array $field The field definition array, which should include a 'content' key with the HTML to render
1119 */
1120 public function render_field_html( $field ) {
1121 ?>
1122 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>" class="helpdocs-field helpdocs-field-html">
1123 <label>
1124 <?php echo wp_kses_post( $field[ 'label' ] ); ?>
1125 <?php if ( ! empty( $field[ 'desc' ] ) ) : ?>
1126 <span class="helpdocs-tooltip">
1127 <span class="dashicons dashicons-editor-help"></span>
1128 <span class="helpdocs-tooltip-text"><?php echo wp_kses_post( $field[ 'desc' ] ); ?></span>
1129 </span>
1130 <?php endif; ?>
1131 </label>
1132 <div class="helpdocs-html-content">
1133 <?php
1134 $allowed_html = wp_kses_allowed_html( 'post' );
1135
1136 $allowed_html[ 'button' ] = [
1137 'type' => true,
1138 'id' => true,
1139 'class' => true,
1140 ];
1141 $allowed_html[ 'input' ] = [
1142 'type' => true,
1143 'id' => true,
1144 'name' => true,
1145 'accept' => true,
1146 'style' => true,
1147 'value' => true,
1148 'class' => true,
1149 ];
1150 $allowed_html[ 'label' ][ 'for' ] = true;
1151
1152 echo wp_kses( ( $field[ 'content' ] ?? '' ), $allowed_html );
1153 ?>
1154 </div>
1155 </div>
1156 <?php
1157 } // End render_field_html()
1158
1159
1160 /**
1161 * AJAX handler to save settings
1162 */
1163 public function ajax_save_settings() {
1164 check_ajax_referer( 'helpdocs_settings_nonce', 'nonce' );
1165 if ( ! current_user_can( Helpers::admin_role() ) ) {
1166 wp_send_json_error( 'Insufficient permissions.' );
1167 }
1168
1169 $fields = self::setting_fields();
1170
1171 $colors_to_save = [];
1172 $errors = [];
1173
1174 foreach ( $fields as $field ) {
1175 $name = $field[ 'name' ];
1176 $post_key = 'helpdocs_' . $name;
1177
1178 if ( strpos( $name, 'color_' ) === 0 ) {
1179 $key = str_replace( 'color_', '', $name );
1180 if ( isset( $_POST[ 'settings' ][ $post_key ] ) ) {
1181 $raw_value = wp_unslash( $_POST[ 'settings' ][ $post_key ] ); // phpcs:ignore
1182 $colors_to_save[ $key ] = $this->sanitize_field( $field, $raw_value );
1183 }
1184 continue;
1185 }
1186
1187 if ( isset( $_POST[ 'settings' ][ $post_key ] ) ) {
1188 // Sanitize below based on field type, but first get the raw value for checkboxes
1189 $raw_value = wp_unslash( $_POST[ 'settings' ][ $post_key ] ); // phpcs:ignore
1190 } else {
1191 // If it's a checkbox group and nothing is checked, default to empty array
1192 if ( $field[ 'type' ] === 'checkboxes' ) {
1193 $raw_value = [];
1194 } elseif ( $field[ 'type' ] === 'checkbox' ) {
1195 $raw_value = 0;
1196 } else {
1197 continue;
1198 }
1199 }
1200
1201 // Determine type and sanitize accordingly
1202 if ( $field[ 'type' ] === 'checkbox' ) {
1203 $value = $this->sanitize_checkbox( $raw_value );
1204 } elseif ( $field[ 'type' ] === 'checkboxes' ) {
1205 $value = $this->sanitize_checkboxes( (array) $raw_value );
1206 } elseif ( $field[ 'sanitize' ] === 'sanitize_custom_css' ) {
1207 $value = $this->sanitize_custom_css( $raw_value );
1208 } else {
1209 $value = $this->sanitize_field( $field, $raw_value );
1210
1211 // If text field is empty, fall back to default
1212 if ( $field[ 'type' ] === 'text' && $value === '' && isset( $field[ 'default' ] ) ) {
1213 $value = $field[ 'default' ];
1214 }
1215 }
1216
1217 $updated = update_option( $post_key, $value );
1218 if ( $updated === false && get_option( $post_key ) != $value ) {
1219 $errors[] = $name;
1220 }
1221 }
1222
1223 // Before saving all colors, update the CSS variable storage if needed
1224 Colors::convert_color_storage();
1225
1226 if ( ! empty( $colors_to_save ) ) {
1227 $updated_colors = update_option( 'helpdocs_colors', $colors_to_save );
1228 if ( $updated_colors === false && get_option( 'helpdocs_colors' ) !== $colors_to_save ) {
1229 $errors[] = 'colors';
1230 }
1231 }
1232
1233 // Flush the cache so changes show immediately
1234 Helpers::flush_permissions_cache();
1235
1236 if ( empty( $errors ) ) {
1237 wp_send_json_success();
1238 } else {
1239 wp_send_json_error( 'Failed to save: ' . implode( ', ', $errors ) );
1240 }
1241 } // End ajax_save_settings()
1242
1243
1244 /**
1245 * AJAX handler to flush system cache
1246 */
1247 public function ajax_flush_cache() {
1248 check_ajax_referer( 'helpdocs_settings_nonce', 'nonce' );
1249 if ( ! Helpers::user_can_edit() ) {
1250 wp_send_json_error( __( 'You do not have permission to perform this action.', 'admin-help-docs' ) );
1251 }
1252
1253 Helpers::flush_location_cache();
1254
1255 wp_send_json_success( __( 'Cache cleared successfully.', 'admin-help-docs' ) );
1256 } // End ajax_flush_cache()
1257
1258
1259 /**
1260 * Prevent cloning and unserializing
1261 */
1262 public function __clone() {}
1263 public function __wakeup() {}
1264
1265 }
1266
1267
1268 Settings::instance();