false, 'show_ui' => false, 'label' => 'Chatbot support reports', 'supports' => array( 'title', 'editor' ), 'capabilities' => array( 'create_posts' => 'do_not_allow' ), 'map_meta_cap' => true, ) ); } public static function menu() { add_submenu_page( 'replyvane', 'Support', 'Support', 'manage_options', self::SLUG, array( __CLASS__, 'render' ) ); } public static function destination() { $email = self::constant_value( array( 'REPLYVANE_REPORTS_EMAIL', 'CHAT_SUPPORT_REPORTS_EMAIL', 'RHC_FAQ_SUPPORT_EMAIL' ) ); return (string) apply_filters( 'replyvane_reports_email', $email ); } public static function webhook() { $hook = self::constant_value( array( 'REPLYVANE_REPORTS_WEBHOOK', 'CHAT_SUPPORT_REPORTS_WEBHOOK', 'RHC_FAQ_SUPPORT_WEBHOOK' ) ); return (string) apply_filters( 'replyvane_reports_webhook', $hook ); } /** First non-empty value among wp-config.php constants (current name first, then names used by earlier builds). */ private static function constant_value( array $names ) { foreach ( $names as $name ) { if ( defined( $name ) && constant( $name ) ) { return (string) constant( $name ); } } return ''; } private static function url( array $args = array() ) { return add_query_arg( array_merge( array( 'page' => self::SLUG ), $args ), admin_url( 'admin.php' ) ); } /** Technical details that help fix problems. No visitor or customer data. */ public static function system_info() { global $wpdb, $wp_version; $theme = wp_get_theme(); $plugins = array(); foreach ( (array) get_option( 'active_plugins', array() ) as $file ) { $data = get_file_data( WP_PLUGIN_DIR . '/' . $file, array( 'Name' => 'Plugin Name', 'Version' => 'Version' ) ); $plugins[] = ( $data['Name'] ? $data['Name'] : $file ) . ( $data['Version'] ? ' ' . $data['Version'] : '' ); } $kb = Replyvane_Knowledge::stats(); $lines = array( 'Site' => home_url(), 'Plugin version' => REPLYVANE_VERSION, 'WordPress' => $wp_version . ( is_multisite() ? ' (multisite)' : '' ), 'PHP' => PHP_VERSION, 'Database' => $wpdb->db_version(), 'Theme' => $theme->get( 'Name' ) . ' ' . $theme->get( 'Version' ), 'Permalinks' => get_option( 'permalink_structure' ) ? get_option( 'permalink_structure' ) : 'Plain', 'WP-Cron' => defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ? 'Disabled' : 'Enabled', 'Answer engine' => Replyvane_AI::enabled() ? Replyvane_AI::label() : ( 'wp_ai' === Replyvane_Plugin::get( 'engine' ) ? 'WordPress AI chosen, but not available (needs WordPress 7.0+)' : 'Built-in' ), 'Live chat' => Replyvane_Plugin::get( 'live_enabled' ) ? 'On' : 'Off', 'Who sees the chat' => Replyvane_Plugin::admins_only() ? 'Administrators only' : 'Everyone', 'Pages read' => $kb['pages'] . ' (last full scan: ' . ( $kb['last_indexed'] ? $kb['last_indexed'] : 'never' ) . ')', 'FAQ questions' => count( Replyvane_Store::items() ), 'Active plugins' => implode( ', ', $plugins ), ); $out = ''; foreach ( $lines as $k => $v ) { $out .= $k . ': ' . $v . "\n"; } return trim( $out ); } /** Saves attachments in a folder that can't be opened from the web. */ private static function store_files() { $saved = array(); if ( empty( $_FILES['files']['name'] ) || ! is_array( $_FILES['files']['name'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return $saved; } require_once ABSPATH . 'wp-admin/includes/file.php'; $dir_filter = function ( $dirs ) { $dirs['subdir'] = '/replyvane-support'; $dirs['path'] = $dirs['basedir'] . '/replyvane-support'; $dirs['url'] = $dirs['baseurl'] . '/replyvane-support'; return $dirs; }; add_filter( 'upload_dir', $dir_filter ); $base = wp_upload_dir(); wp_mkdir_p( $base['path'] ); if ( ! file_exists( $base['path'] . '/.htaccess' ) ) { file_put_contents( $base['path'] . '/.htaccess', "Require all denied\nDeny from all\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions file_put_contents( $base['path'] . '/index.php', " 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp', 'pdf' => 'application/pdf', 'txt|log' => 'text/plain', ); $files = $_FILES['files']; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput $count = min( self::MAX_FILES, count( $files['name'] ) ); for ( $i = 0; $i < $count; $i++ ) { if ( empty( $files['tmp_name'][ $i ] ) || UPLOAD_ERR_OK !== (int) $files['error'][ $i ] || (int) $files['size'][ $i ] > self::MAX_BYTES ) { continue; } $one = array( 'name' => wp_unique_filename( $base['path'], wp_generate_password( 8, false ) . '-' . sanitize_file_name( $files['name'][ $i ] ) ), 'type' => $files['type'][ $i ], 'tmp_name' => $files['tmp_name'][ $i ], 'error' => $files['error'][ $i ], 'size' => $files['size'][ $i ], ); $result = wp_handle_upload( $one, array( 'test_form' => false, 'mimes' => $mimes, ) ); if ( ! empty( $result['file'] ) ) { $saved[] = $result['file']; } } remove_filter( 'upload_dir', $dir_filter ); return $saved; } public static function handle() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'You are not allowed to do this.' ); } check_admin_referer( 'replyvane_support' ); $types = array( 'suggestion' => 'Suggestion', 'bug' => 'Bug report', 'question' => 'Question', ); $type = isset( $_POST['type'] ) ? sanitize_key( wp_unslash( $_POST['type'] ) ) : ''; $type = isset( $types[ $type ] ) ? $type : 'suggestion'; $subject = isset( $_POST['subject'] ) ? mb_substr( sanitize_text_field( wp_unslash( $_POST['subject'] ) ), 0, 150 ) : ''; $details = isset( $_POST['details'] ) ? mb_substr( sanitize_textarea_field( wp_unslash( $_POST['details'] ) ), 0, 8000 ) : ''; $steps = isset( $_POST['steps'] ) ? mb_substr( sanitize_textarea_field( wp_unslash( $_POST['steps'] ) ), 0, 4000 ) : ''; $severity = isset( $_POST['severity'] ) && in_array( $_POST['severity'], array( 'low', 'medium', 'high' ), true ) ? sanitize_key( $_POST['severity'] ) : 'medium'; $info = ! empty( $_POST['include_info'] ) ? self::system_info() : ''; if ( '' === $subject || '' === $details ) { set_transient( 'replyvane_support_msg_' . get_current_user_id(), array( 'error', 'Please fill in a subject and a description.' ), 120 ); wp_safe_redirect( self::url() ); exit; } $files = self::store_files(); $user = wp_get_current_user(); $id = wp_insert_post( array( 'post_type' => self::POST_TYPE, 'post_status' => 'publish', 'post_title' => $subject, 'post_content' => $details, ) ); update_post_meta( $id, 'type', $type ); update_post_meta( $id, 'severity', 'bug' === $type ? $severity : '' ); update_post_meta( $id, 'steps', $steps ); update_post_meta( $id, 'system_info', $info ); update_post_meta( $id, 'files', $files ); // Email to the developer's support inbox. $site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ); $lines = array( $types[ $type ] . ' from ' . $site . ' (' . home_url() . ')', 'Sent by: ' . $user->display_name . ' <' . $user->user_email . '>', ); if ( 'bug' === $type ) { $lines[] = 'How serious: ' . ucfirst( $severity ); } $lines[] = ''; $lines[] = 'Subject: ' . $subject; $lines[] = ''; $lines[] = $details; if ( '' !== $steps ) { $lines[] = ''; $lines[] = 'Steps to reproduce:'; $lines[] = $steps; } if ( '' !== $info ) { $lines[] = ''; $lines[] = '--- System information ---'; $lines[] = $info; } $text = implode( "\n", $lines ); $sent_mail = false; $to = self::destination(); if ( is_email( $to ) ) { $headers = is_email( $user->user_email ) ? array( 'Reply-To: ' . $user->display_name . ' <' . $user->user_email . '>' ) : array(); $sent_mail = wp_mail( $to, '[Replyvane Chat Support] ' . $types[ $type ] . ': ' . $subject . ' (' . $site . ')', $text, $headers, $files ); } $sent_hook = false; $hook = self::webhook(); if ( $hook && wp_http_validate_url( $hook ) ) { $res = wp_remote_post( $hook, array( 'timeout' => 15, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( array( 'type' => $type, 'severity' => 'bug' === $type ? $severity : '', 'subject' => $subject, 'details' => $details, 'steps' => $steps, 'system_info' => $info, 'site' => home_url(), 'reporter' => array( 'name' => $user->display_name, 'email' => $user->user_email, ), 'files' => array_map( 'basename', $files ), ) ), ) ); $sent_hook = ! is_wp_error( $res ) && (int) wp_remote_retrieve_response_code( $res ) < 300; } $status = $sent_mail || $sent_hook ? 'sent' : 'saved'; update_post_meta( $id, 'status', $status ); set_transient( 'replyvane_support_msg_' . get_current_user_id(), 'sent' === $status ? array( 'success', 'Thank you! Your ' . strtolower( $types[ $type ] ) . ' was sent to the plugin\'s support team. Replies come to ' . $user->user_email . '.' ) : array( 'warning', 'Your ' . strtolower( $types[ $type ] ) . ' was saved on this site, but it could not be sent because no support address is set up or the email failed. See the note on this page.' ), 120 ); wp_safe_redirect( self::url() ); exit; } public static function render() { $view = isset( $_GET['report'] ) ? absint( $_GET['report'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification $msg = get_transient( 'replyvane_support_msg_' . get_current_user_id() ); if ( $msg ) { delete_transient( 'replyvane_support_msg_' . get_current_user_id() ); } echo '
%s
Help us improve the chatbot. Send a suggestion, report a problem, or ask a question. Screenshots help a lot with problems.
No support address is set up yet, so reports are saved on this site only. The plugin's developer sets it in replyvane-chat-support.php (REPLYVANE_REPORTS_EMAIL).
Nothing yet.
'; return; } echo '| Date | Type | Subject | Status |
|---|---|---|---|
| %s | %s | %s | %s |
Report not found.
'; return; } $steps = get_post_meta( $id, 'steps', true ); $info = get_post_meta( $id, 'system_info', true ); $files = (array) get_post_meta( $id, 'files', true ); echo '' . esc_html( get_the_date( 'M j, Y g:i a', $r ) . ' · ' . ( 'sent' === get_post_meta( $id, 'status', true ) ? 'Sent to support' : 'Saved here only' ) ) . '
'; echo '