PluginProbe ʕ •ᴥ•ʔ
Admin Help Docs / trunk
Admin Help Docs vtrunk
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 / support.php
admin-help-docs / inc / tabs Last commit date
css 3 months ago js 3 months ago admin-menu.php 3 months ago documentation.php 3 months ago faq.php 3 months ago import.php 3 months ago settings.php 3 months ago support.php 3 months ago
support.php
530 lines
1 <?php
2 /**
3 * Support Tab Loader
4 */
5
6 namespace PluginRx\AdminHelpDocs;
7
8 if ( ! defined( 'ABSPATH' ) ) exit;
9
10 class Support {
11
12 /**
13 * Form fields for the tab
14 *
15 * @return array
16 */
17 private function form_fields() : array {
18 $user = wp_get_current_user();
19 $first_name = $user->first_name ?? '';
20 $last_name = $user->last_name ?? '';
21 $full_name = $first_name && $last_name ? "$first_name $last_name" : $user->display_name ?? '';
22
23 $fields = [
24 [
25 'name' => 'from_name',
26 'type' => 'text',
27 'label' => __( 'Your Name', 'admin-help-docs' ),
28 'desc' => __( 'Enter your name to include in the support request.', 'admin-help-docs' ),
29 'sanitize' => 'sanitize_text_field',
30 'default' => $full_name,
31 'required' => true,
32 ],
33 [
34 'name' => 'reply_to',
35 'type' => 'text',
36 'label' => __( 'Reply-To Email', 'admin-help-docs' ),
37 'desc' => __( 'The email address where we should send our response.', 'admin-help-docs' ),
38 'sanitize' => 'sanitize_email',
39 'default' => $user->user_email,
40 'required' => true,
41 ],
42 [
43 'name' => 'contact_reason',
44 'type' => 'select',
45 'label' => __( 'Reason for Contact', 'admin-help-docs' ),
46 'desc' => __( 'Select the category that best fits your inquiry.', 'admin-help-docs' ),
47 'choices' => [
48 [
49 'label' => __( '-- Please One ---', 'admin-help-docs' ),
50 'value' => '',
51 ],
52 [
53 'label' => __( 'Technical Support', 'admin-help-docs' ),
54 'value' => __( 'Technical Support', 'admin-help-docs' ),
55 ],
56 [
57 'label' => __( 'Billing Inquiry', 'admin-help-docs' ),
58 'value' => __( 'Billing Inquiry', 'admin-help-docs' ),
59 ],
60 [
61 'label' => __( 'Feature Request', 'admin-help-docs' ),
62 'value' => __( 'Feature Request', 'admin-help-docs' ),
63 ],
64 [
65 'label' => __( 'Other', 'admin-help-docs' ),
66 'value' => __( 'Other', 'admin-help-docs' ),
67 ],
68 ],
69 'sanitize' => 'sanitize_text_field',
70 'required' => true,
71 'has_condition' => true,
72 ],
73 [
74 'name' => 'page_url',
75 'type' => 'text',
76 'label' => __( 'URL of Page with Issue', 'admin-help-docs' ),
77 'desc' => __( 'The URL of the page where you encountered the issue.', 'admin-help-docs' ),
78 'sanitize' => 'sanitize_text_field',
79 'condition' => [ 'contact_reason' => __( 'Technical Support', 'admin-help-docs' ) ],
80 ],
81 [
82 'name' => 'subject',
83 'type' => 'text',
84 'label' => __( 'Subject', 'admin-help-docs' ),
85 'desc' => __( 'A brief summary of your request.', 'admin-help-docs' ),
86 'sanitize' => 'sanitize_text_field',
87 'required' => true,
88 ],
89 [
90 'name' => 'message_body',
91 'type' => 'textarea',
92 'label' => __( 'How can we help?', 'admin-help-docs' ),
93 'desc' => __( 'Please provide as much detail as possible.', 'admin-help-docs' ),
94 'sanitize' => 'sanitize_textarea_field',
95 'required' => true,
96 ],
97 [
98 'name' => 'attachments',
99 'type' => 'file',
100 'label' => __( 'Upload Screenshots or Documents', 'admin-help-docs' ),
101 'desc' => sprintf( __( 'Attach relevant files (JPG, PNG, PDF). Max %dMB.', 'admin-help-docs' ), self::$max_attachment_mb ),
102 'sanitize' => 'sanitize_files',
103 ],
104 ];
105
106 return apply_filters( 'helpdocs_support_form_fields', $fields, $user );
107 } // End form_fields()
108
109
110 /**
111 * Contact info
112 *
113 * @var array
114 */
115 private static $contact_info = [];
116
117
118 /**
119 * Enable or disable logging of support requests
120 *
121 * @var bool
122 */
123 private static $logging_enabled = true;
124
125
126 /**
127 * Quantity of support requests to log
128 *
129 * @var int
130 */
131 private static $logging_qty = 20;
132
133
134 /**
135 * Max attachment size in MB
136 *
137 * @var int
138 */
139 public static $max_attachment_mb = 10;
140
141
142 /**
143 * Date format for logs
144 *
145 * @var string
146 */
147 public static $log_date_format = 'M j, Y g:i a';
148
149
150 /**
151 * The single instance of the class
152 *
153 * @var self|null
154 */
155 private static ?Support $instance = null;
156
157
158 /**
159 * Get the singleton instance
160 *
161 * @return self
162 */
163 public static function instance() : self {
164 return self::$instance ??= new self();
165 } // End instance()
166
167
168 /**
169 * Constructor
170 */
171 private function __construct() {
172
173 // Store the contact info
174 add_action( 'init', [ $this, 'get_support_contact_info' ] );
175
176 // Logging
177 self::$logging_enabled = apply_filters( 'helpdocs_support_logging_enabled', self::$logging_enabled );
178 self::$logging_qty = apply_filters( 'helpdocs_support_log_quantity', self::$logging_qty );
179
180 // Max attachment size
181 self::$max_attachment_mb = apply_filters( 'helpdocs_support_max_attachment_size', self::$max_attachment_mb );
182
183 // Date format for logs
184 self::$log_date_format = apply_filters( 'helpdocs_support_log_date_format', self::$log_date_format );
185
186 // Add the contact info to the header
187 add_action( 'helpdocs_subheader_right', [ $this, 'render_details_box' ] );
188
189 // Handle the AJAX requests
190 add_action( 'wp_ajax_helpdocs_send_support_email', [ $this, 'ajax_process_support_form' ] );
191 add_action( 'wp_ajax_helpdocs_clear_support_logs', [ $this, 'ajax_clear_support_logs' ] );
192
193 } // End __construct()
194
195
196 /**
197 * Get support contact info
198 *
199 * @return array
200 */
201 public function get_support_contact_info() {
202 if ( ! class_exists( Helpers::class ) || ! class_exists( Menu::class ) || ! Menu::is_our_tab( 'support' ) ) {
203 return;
204 }
205
206 self::$contact_info = apply_filters(
207 'helpdocs_support_contact_info',
208 [
209 'name' => sanitize_text_field( get_option( 'helpdocs_contact_name', '' ) ),
210 'emails' => array_map( 'trim', explode( ',', sanitize_text_field( get_option( 'helpdocs_contact_emails', implode( ', ', Helpers::get_all_admin_emails() ) ) ) ) ),
211 'phone' => sanitize_text_field( get_option( 'helpdocs_contact_phone', '' ) ),
212 ]
213 );
214 } // End get_support_contact_info()
215
216
217 /**
218 * Add a details box to the subheader on the support page
219 *
220 * @param string $current_tab The current admin tab
221 * @return void
222 */
223 public function render_details_box( string $current_tab ) {
224 if ( $current_tab !== 'support' ) {
225 return;
226 }
227
228 $results = '<div id="helpdocs-header-support-info">';
229
230 $details = [];
231 foreach ( self::$contact_info as $i => $info ) {
232 if ( empty( $info ) ) {
233 continue;
234 }
235
236 if ( $i === 'emails' ) {
237 continue;
238 }
239
240 $details[] = '<span class="helpdocs-support-' . esc_attr( $i ) . '">' . esc_html( $info ) . '</span>';
241 }
242
243 $results .= implode( ' | ', $details );
244
245 $results .= '</div>';
246
247 $results = apply_filters( 'helpdocs_support_details_box_content', $results, self::$contact_info );
248 echo wp_kses_post( $results );
249 } // End render_details_box()
250
251
252 /**
253 * Render the tab with AJAX support
254 */
255 public function render_tab() {
256 $emails = self::$contact_info[ 'emails' ];
257 if ( empty( $emails ) ) {
258 echo '<p>' . esc_html__( 'No contact email addresses configured. Please add at least one email address in the settings to contact support.', 'admin-help-docs' ) . '</p>';
259 return;
260 }
261
262 $fields = $this->form_fields();
263 $box_label = apply_filters( 'helpdocs_support_form_box_label', __( 'Complete the form below to send a message to support.', 'admin-help-docs' ) );
264 $submit_label = apply_filters( 'helpdocs_support_form_submit_label', __( 'Send Support Request', 'admin-help-docs' ) );
265 ?>
266 <div class="helpdocs-settings-grid">
267 <div class="helpdocs-settings-box">
268 <div class="helpdocs-settings-header">
269 <h2><?php echo esc_html( $box_label ); ?></h2>
270 </div>
271 <div class="helpdocs-settings-body">
272 <?php do_action( 'helpdocs_before_support_form', self::$contact_info ); ?>
273 <form id="helpdocs-support-form" method="post" enctype="multipart/form-data">
274 <?php wp_nonce_field( 'helpdocs_send_support', 'helpdocs_support_nonce' ); ?>
275
276 <div class="helpdocs-form-fields">
277 <?php
278 foreach ( $fields as $index => $field ) {
279 $render_fn = "render_field_{$field[ 'type' ]}";
280
281 if ( method_exists( $this, $render_fn ) ) {
282 $this->{$render_fn}( $field );
283 } elseif ( method_exists( Settings::class, $render_fn ) ) {
284 Settings::instance()->{$render_fn}( $field );
285 }
286 }
287 ?>
288 </div>
289
290 <div class="helpdocs-form-footer">
291 <button type="submit" id="helpdocs-submit-support" class="button button-primary">
292 <?php echo esc_html( $submit_label ); ?>
293 </button>
294 <span class="spinner"></span>
295 </div>
296
297 <div id="helpdocs-support-response" style="margin-top: 15px;"></div>
298 </form>
299 <?php do_action( 'helpdocs_after_support_form', self::$contact_info ); ?>
300 </div>
301 </div>
302 <?php if ( self::$logging_enabled ) : ?>
303 <?php
304 $logs = get_option( 'helpdocs_support_log', [] );
305 $display_class = empty( $logs ) ? ' style="display: none;"' : '';
306 ?>
307 <div id="helpdocs-support-logs" class="helpdocs-settings-box"<?php echo esc_attr( $display_class ); ?>>
308 <div class="helpdocs-settings-header">
309 <h2><?php echo esc_html( __( 'Support Logs', 'admin-help-docs' ) ); ?></h2>
310 <?php if ( Helpers::user_can_edit() ) : ?>
311 <button type="button" id="helpdocs-clear-logs" class="helpdocs-button">
312 <span class="dashicons dashicons-trash"></span>
313 <?php esc_html_e( 'Clear Logs', 'admin-help-docs' ); ?>
314 </button>
315 <?php endif; ?>
316 </div>
317 <div class="helpdocs-settings-body">
318 <p><?php echo esc_html( __( 'Here you can view your most recent support requests.', 'admin-help-docs' ) ); ?></p>
319
320 <table class="helpdocs-logs-table">
321 <thead>
322 <tr>
323 <th><?php esc_html_e( 'Date', 'admin-help-docs' ); ?></th>
324 <th><?php esc_html_e( 'Subject', 'admin-help-docs' ); ?></th>
325 <th><?php esc_html_e( 'Reason', 'admin-help-docs' ); ?></th>
326 <th><?php esc_html_e( 'Sent By', 'admin-help-docs' ); ?></th>
327 </tr>
328 </thead>
329 <tbody id="helpdocs-logs-tbody">
330 <?php foreach ( array_slice( $logs, 0, self::$logging_qty ) as $log ) : ?>
331 <tr>
332 <td class="log-date"><?php echo esc_html( date_i18n( self::$log_date_format, strtotime( $log[ 'date' ] ) ) ); ?></td>
333 <td class="log-subject">
334 <strong><?php echo esc_html( $log[ 'subject' ] ); ?></strong>
335 <?php if ( ! empty( $log[ 'message' ] ) ) : ?>
336 <br><a href="#" class="helpdocs-toggle-message" style="font-size: 11px; text-decoration: none;"><?php esc_html_e( 'View Message', 'admin-help-docs' ); ?></a>
337 <?php endif; ?>
338 </td>
339 <td class="log-reason"><span class="log-badge"><?php echo esc_html( $log[ 'reason' ] ); ?></span></td>
340 <td class="log-user"><?php echo esc_html( $log[ 'user' ] ); ?></td>
341 </tr>
342 <?php if ( ! empty( $log[ 'message' ] ) ) : ?>
343 <tr class="log-message-row" style="display: none;">
344 <td colspan="4">
345 <div class="log-message"><?php echo esc_html( $log[ 'message' ] ); ?></div>
346 <?php if ( ! empty( $log[ 'attachments' ] ) ) : ?>
347 <div class="log-attachments-container">
348 <strong><?php esc_html_e( 'Attachments:', 'admin-help-docs' ); ?></strong> <span class="log-attachments"><?php echo esc_html( implode( ', ', $log[ 'attachments' ] ) ); ?></span>
349 </div>
350 <?php endif; ?>
351 </td>
352 </tr>
353 <?php endif; ?>
354 <?php endforeach; ?>
355 </tbody>
356 </table>
357 </div>
358 </div>
359 <?php endif; ?>
360 </div>
361 <?php
362 } // End render_tab()
363
364
365 /**
366 * Process the support form submission via AJAX
367 */
368 public function ajax_process_support_form() {
369 check_ajax_referer( 'helpdocs_send_support', 'helpdocs_support_nonce' );
370 if ( ! Helpers::user_can_view() ) {
371 wp_send_json_error( __( 'Unauthorized', 'admin-help-docs' ), 403 );
372 }
373
374 $fields = $this->form_fields();
375 $data_to_send = [];
376 $attachments = [];
377 $attachment_names = [];
378 $email_body = "";
379
380 foreach ( $fields as $field ) {
381 $name = $field[ 'name' ];
382 $post_key = 'helpdocs_' . $name;
383
384 $label = $field[ 'label' ] ?? strtoupper( str_replace( '_', ' ', $name ) );
385
386 if ( $field[ 'type' ] === 'file' ) {
387 $file_paths = Settings::instance()->sanitize_files( $name, self::$max_attachment_mb );
388 if ( ! empty( $file_paths ) ) {
389 $attachments = array_merge( $attachments, $file_paths );
390 foreach ( $file_paths as $path ) {
391 $attachment_names[] = basename( $path );
392 }
393 }
394 continue;
395 }
396
397 if ( isset( $_POST[ $post_key ] ) ) {
398 $raw_value = wp_unslash( $_POST[ $post_key ] ); // phpcs:ignore
399
400 if ( $field[ 'type' ] === 'checkbox' ) {
401 $value = Settings::instance()->sanitize_checkbox( $raw_value );
402 } else {
403 $value = Settings::instance()->sanitize_field( $field, $raw_value );
404
405 // If text field is empty, fall back to default
406 if ( $field[ 'type' ] === 'text' && $value === '' && isset( $field[ 'default' ] ) ) {
407 $value = $field[ 'default' ];
408 }
409 }
410 } else {
411 $value = $field[ 'default' ] ?? '';
412 }
413
414 $data_to_send[ $name ] = $value;
415 $trimmed_label = rtrim( (string) $label );
416
417 if ( substr( $trimmed_label, -1 ) === '?' ) {
418 $email_body .= $trimmed_label . "\n" . $value . "\n\n";
419 } else {
420 $email_body .= $trimmed_label . ': ' . $value . "\n\n";
421 }
422 }
423
424 // Add Site Context for better debugging
425 $email_body .= "--- SITE INFO ---\n";
426 $email_body .= "Site URL: " . get_site_url() . "\n";
427 $email_body .= "WP Version: " . get_bloginfo( 'version' ) . "\n";
428
429 $from_email = get_option( 'admin_email' );
430 $from_name = get_bloginfo( 'name' );
431 $to = self::$contact_info[ 'emails' ];
432 $subject = sprintf( '[%s Support] %s', Bootstrap::name(), __( 'New Inquiry', 'admin-help-docs' ) );
433
434 $headers = [
435 'From: ' . $from_name . ' <' . $from_email . '>',
436 'Reply-To: ' . $data_to_send[ 'from_name' ] . ' <' . $data_to_send[ 'reply_to' ] . '>',
437 'Content-Type: text/plain; charset=UTF-8',
438 ];
439
440 $email_args = [
441 'to' => $to,
442 'subject' => $subject,
443 'message' => $email_body,
444 'headers' => $headers,
445 'attachments' => $attachments,
446 ];
447 $email_args = apply_filters( 'helpdocs_support_email_args', $email_args, $data_to_send );
448
449 // dwl( $_POST );
450 // dwl( $email_args );
451
452 $sent = wp_mail( $email_args[ 'to' ], $email_args[ 'subject' ], $email_args[ 'message' ], $email_args[ 'headers' ], $email_args[ 'attachments' ] );
453
454 if ( $sent ) {
455
456 // Log the request if enabled
457 if ( self::$logging_enabled ) {
458 $this->log_support_request( $data_to_send, $attachment_names );
459 }
460
461 // Cleanup temp files
462 foreach ( $attachments as $file ) {
463 if ( file_exists( $file ) ) {
464 wp_delete_file( $file );
465 }
466 }
467
468 wp_send_json_success( [
469 'message' => __( 'Your message has been sent successfully!', 'admin-help-docs' ),
470 'attachments' => $attachment_names
471 ] );
472 } else {
473 wp_send_json_error( __( 'Failed to send message. Please check your SMTP settings.', 'admin-help-docs' ) );
474 }
475 } // End ajax_process_support_form()
476
477
478 /**
479 * Log the request to a site option (keeps last 20)
480 * * @param array $data
481 * * @param array $attachments
482 */
483 private function log_support_request( $data, $attachments = [] ) {
484 $logs = get_option( 'helpdocs_support_log', [] );
485
486 $new_entry = [
487 'date' => current_time( 'mysql' ),
488 'user' => $data[ 'from_name' ] ?? wp_get_current_user()->display_name,
489 'subject' => $data[ 'subject' ] ?? 'No Subject',
490 'reason' => $data[ 'contact_reason' ] ?? 'N/A',
491 'message' => $data[ 'message_body' ] ?? '',
492 'attachments' => $attachments,
493 ];
494
495 array_unshift( $logs, $new_entry );
496 $logs = array_slice( $logs, 0, self::$logging_qty );
497
498 update_option( 'helpdocs_support_log', $logs, false );
499 } // End log_support_request()
500
501
502 /**
503 * Clear Logs (Outside the Form)
504 */
505 public function ajax_clear_support_logs() {
506 check_ajax_referer( 'helpdocs_support_nonce', 'nonce' );
507 if ( ! Helpers::user_can_edit() ) {
508 wp_send_json_error( __( 'You are not authorized to clear the logs.', 'admin-help-docs' ), 403 );
509 }
510
511 $deleted = delete_option( 'helpdocs_support_log' );
512
513 if ( $deleted ) {
514 wp_send_json_success( __( 'Support logs cleared successfully.', 'admin-help-docs' ) );
515 } else {
516 wp_send_json_error( __( 'Failed to clear support logs.', 'admin-help-docs' ) );
517 }
518 } // End ajax_clear_support_logs()
519
520
521 /**
522 * Prevent cloning and unserializing
523 */
524 public function __clone() {}
525 public function __wakeup() {}
526
527 }
528
529
530 Support::instance();