array( 'Divi', defined( 'ET_BUILDER_VERSION' ) || function_exists( 'et_setup_theme' ) ), 'elementor' => array( 'Elementor Pro', defined( 'ELEMENTOR_PRO_VERSION' ) ), 'ninjaforms' => array( 'Ninja Forms', function_exists( 'Ninja_Forms' ) ), 'formidable' => array( 'Formidable Forms', class_exists( 'FrmAppHelper' ) ), 'fluentforms' => array( 'Fluent Forms', defined( 'FLUENTFORM' ) ), 'forminator' => array( 'Forminator', class_exists( 'Forminator' ) ), 'everest' => array( 'Everest Forms', function_exists( 'evf' ) ), 'jetpack' => array( 'Jetpack Forms', class_exists( 'Automattic\\Jetpack\\Forms\\ContactForm\\Contact_Form' ) ), 'kadence' => array( 'Kadence Blocks', defined( 'KADENCE_BLOCKS_VERSION' ) ), 'spectra' => array( 'Spectra', defined( 'UAGB_VER' ) ), 'beaver' => array( 'Beaver Builder', class_exists( 'FLBuilder' ) ), 'wsform' => array( 'WS Form', defined( 'WS_FORM_VERSION' ) ), 'metform' => array( 'MetForm', defined( 'METFORM_VERSION' ) ), 'jetformbuilder' => array( 'JetFormBuilder', function_exists( 'jet_fb_context' ) ), 'happyforms' => array( 'HappyForms', defined( 'HAPPYFORMS_VERSION' ) ), 'weforms' => array( 'weForms', defined( 'WEFORMS_VERSION' ) ), 'otter' => array( 'Otter Blocks', defined( 'OTTER_BLOCKS_BASEFILE' ) ), 'sureforms' => array( 'SureForms', defined( 'SRFM_FILE' ) ), ); } /** Submissions captured during this request, sent on shutdown. */ $GLOBALS['wapadesk_outbox'] = array(); function wapadesk_api_origin() { $s = wapadesk_get_settings(); return '' !== $s['api_url'] ? untrailingslashit( $s['api_url'] ) : 'https://api.wapadesk.com'; } function wapadesk_connection_key() { $s = wapadesk_get_settings(); return isset( $s['connection_key'] ) && preg_match( WAPADESK_KEY_PATTERN, $s['connection_key'] ) ? $s['connection_key'] : ''; } function wapadesk_form_enabled( $plugin, $form_id ) { if ( '' === wapadesk_connection_key() ) { return false; } $s = wapadesk_get_settings(); $enabled = isset( $s['forms'] ) && is_array( $s['forms'] ) ? $s['forms'] : array(); return in_array( $plugin . ':' . $form_id, $enabled, true ) || in_array( $plugin . ':*', $enabled, true ); } /** "your-name" → "Your name" for Contact Form 7, which has no field labels. */ function wapadesk_humanise( $name ) { $words = trim( preg_replace( '/[-_]+/', ' ', preg_replace( '/^your[-_]/', 'your ', (string) $name ) ) ); return '' === $words ? (string) $name : ucfirst( $words ); } /** Strings and lists only, trimmed and capped, as the API expects. */ function wapadesk_clean_value( $value ) { if ( is_array( $value ) ) { $out = array(); foreach ( $value as $v ) { if ( is_scalar( $v ) && '' !== trim( (string) $v ) ) { $out[] = substr( sanitize_textarea_field( (string) $v ), 0, 500 ); } } return array_slice( $out, 0, 50 ); } if ( is_bool( $value ) ) { return $value; } return is_scalar( $value ) ? substr( sanitize_textarea_field( (string) $value ), 0, 5000 ) : ''; } /** * Each form plugin names its field types differently; the API understands a common set. * Email, phone and consent variants are folded into 'email', 'phone' and 'consent'. */ function wapadesk_normalise_type( $type ) { $t = strtolower( (string) $type ); if ( false !== strpos( $t, 'email' ) ) { return 'email'; } if ( preg_match( '/phone|^tel$|_tel$|mobile/', $t ) ) { return 'phone'; } if ( in_array( $t, array( 'gdpr', 'gdpr_agreement', 'gdpr-checkbox', 'gdprcheckbox', 'terms', 'terms_and_condition', 'privacy-policy', 'accept', 'acceptance', 'mf-gdpr-consent', 'consent' ), true ) ) { return 'consent'; } return $t; } function wapadesk_field( $id, $label, $type, $value ) { $type = wapadesk_normalise_type( $type ); if ( 'consent' === $type && ! is_bool( $value ) ) { $value = is_array( $value ) ? ! empty( array_filter( $value ) ) : ( '' !== trim( (string) $value ) && '0' !== (string) $value && 'off' !== strtolower( (string) $value ) ); } return array( 'id' => substr( (string) $id, 0, 64 ), 'label' => substr( wp_strip_all_tags( (string) $label ), 0, 200 ), 'type' => substr( $type, 0, 40 ), 'value' => wapadesk_clean_value( $value ), ); } function wapadesk_capture( $plugin, $form_id, $title, $fields, $submission_id, $page_url = '' ) { $fields = array_values( array_filter( $fields, function ( $f ) { return '' !== $f['id'] && ( is_bool( $f['value'] ) || ! empty( $f['value'] ) ); } ) ); if ( empty( $fields ) ) { return; } $payload = array( 'site' => home_url( '/' ), 'plugin' => $plugin, 'form' => array( 'id' => substr( (string) $form_id, 0, 64 ), 'title' => substr( wp_strip_all_tags( (string) $title ), 0, 120 ), ), 'submissionId' => substr( (string) $submission_id, 0, 128 ), 'fields' => array_slice( $fields, 0, 80 ), 'pluginVersion' => WAPADESK_VERSION, ); if ( is_string( $page_url ) && 0 === strpos( $page_url, 'http' ) ) { $payload['pageUrl'] = substr( esc_url_raw( $page_url ), 0, 2048 ); } $GLOBALS['wapadesk_outbox'][] = $payload; } // ---------- Gravity Forms ---------- add_action( 'gform_after_submission', 'wapadesk_gravityforms', 20, 2 ); function wapadesk_gravityforms( $entry, $form ) { try { if ( ! is_array( $form ) || ! wapadesk_form_enabled( 'gravityforms', rgar( $form, 'id' ) ) ) { return; } $fields = array(); foreach ( (array) rgar( $form, 'fields' ) as $field ) { if ( ! is_object( $field ) ) { continue; } $type = method_exists( $field, 'get_input_type' ) ? $field->get_input_type() : $field->type; $value = method_exists( $field, 'get_value_export' ) ? $field->get_value_export( $entry, $field->id, true ) : rgar( $entry, (string) $field->id ); if ( 'consent' === $type ) { // Ticked consent is stored at ".1" as '1'; the export of the bare id is empty. $value = '1' === (string) rgar( $entry, $field->id . '.1' ); } $fields[] = wapadesk_field( $field->id, $field->label, $type, $value ); } // source_url can be admin-ajax or a REST URL for AJAX forms; then the referring page is better. $page = (string) rgar( $entry, 'source_url' ); if ( '' === $page || false !== strpos( $page, 'admin-ajax.php' ) || false !== strpos( $page, '/wp-json/' ) ) { $page = (string) wp_get_referer(); } wapadesk_capture( 'gravityforms', rgar( $form, 'id' ), rgar( $form, 'title' ), $fields, 'entry-' . rgar( $entry, 'id' ), $page ); } catch ( Throwable $e ) { wapadesk_status_error( 'Gravity Forms: ' . $e->getMessage() ); } } // ---------- Contact Form 7 ---------- add_action( 'wpcf7_mail_sent', 'wapadesk_cf7', 20, 1 ); function wapadesk_cf7( $contact_form ) { try { if ( ! is_object( $contact_form ) || ! class_exists( 'WPCF7_Submission' ) || ! wapadesk_form_enabled( 'cf7', $contact_form->id() ) ) { return; } $submission = WPCF7_Submission::get_instance(); if ( ! $submission ) { return; } $posted = (array) $submission->get_posted_data(); $fields = array(); foreach ( $contact_form->scan_form_tags() as $tag ) { $base = isset( $tag->basetype ) ? $tag->basetype : ''; $name = isset( $tag->name ) ? $tag->name : ''; if ( '' === $name || in_array( $base, array( 'submit', 'quiz', 'captchac', 'captchar', 'file', 'hidden' ), true ) ) { continue; } $type = 'acceptance' === $base ? 'consent' : ( 'tel' === $base ? 'phone' : $base ); $value = isset( $posted[ $name ] ) ? $posted[ $name ] : ''; $value = 'consent' === $type ? ! empty( $value ) : $value; $fields[] = wapadesk_field( $name, wapadesk_humanise( $name ), $type, $value ); } wapadesk_capture( 'cf7', $contact_form->id(), $contact_form->title(), $fields, wp_generate_uuid4(), (string) $submission->get_meta( 'url' ) ); } catch ( Throwable $e ) { wapadesk_status_error( 'Contact Form 7: ' . $e->getMessage() ); } } // ---------- WPForms ---------- add_action( 'wpforms_process_complete', 'wapadesk_wpforms', 20, 4 ); function wapadesk_wpforms( $fields_in, $entry, $form_data, $entry_id ) { try { $form_id = isset( $form_data['id'] ) ? $form_data['id'] : ''; if ( '' === $form_id || ! wapadesk_form_enabled( 'wpforms', $form_id ) ) { return; } $fields = array(); foreach ( (array) $fields_in as $id => $f ) { $type = isset( $f['type'] ) ? $f['type'] : 'text'; $value = isset( $f['value'] ) ? $f['value'] : ''; if ( 'gdpr-checkbox' === $type ) { $type = 'consent'; $value = '' !== (string) $value; } $fields[] = wapadesk_field( $id, isset( $f['name'] ) ? $f['name'] : '', $type, $value ); } $title = isset( $form_data['settings']['form_title'] ) ? $form_data['settings']['form_title'] : ''; wapadesk_capture( 'wpforms', $form_id, $title, $fields, $entry_id ? 'entry-' . $entry_id : wp_generate_uuid4(), (string) wp_get_referer() ); } catch ( Throwable $e ) { wapadesk_status_error( 'WPForms: ' . $e->getMessage() ); } } // ---------- Elementor Pro ---------- add_action( 'elementor_pro/forms/new_record', 'wapadesk_elementor', 20, 2 ); function wapadesk_elementor( $record, $handler ) { try { if ( ! is_object( $record ) || ! wapadesk_form_enabled( 'elementor', '*' ) ) { return; } $fields = array(); foreach ( (array) $record->get( 'fields' ) as $id => $f ) { $type = isset( $f['type'] ) ? $f['type'] : 'text'; $value = isset( $f['raw_value'] ) ? $f['raw_value'] : ( isset( $f['value'] ) ? $f['value'] : '' ); $type = 'acceptance' === $type ? 'consent' : ( 'tel' === $type ? 'phone' : $type ); $value = 'consent' === $type ? ! empty( $value ) && 'off' !== $value : $value; $fields[] = wapadesk_field( $id, isset( $f['title'] ) ? $f['title'] : $id, $type, $value ); } $meta = (array) $record->get( 'meta' ); $page_url = isset( $meta['page_url']['value'] ) ? $meta['page_url']['value'] : (string) wp_get_referer(); $name = (string) $record->get_form_settings( 'form_name' ); $form_id = (string) $record->get_form_settings( 'id' ); wapadesk_capture( 'elementor', '' !== $form_id ? $form_id : sanitize_title( $name ), $name, $fields, wp_generate_uuid4(), is_string( $page_url ) ? $page_url : '' ); } catch ( Throwable $e ) { wapadesk_status_error( 'Elementor: ' . $e->getMessage() ); } } // ---------- Divi contact form module ---------- // Divi's own action after its Contact Form module accepts a submission (Divi 4.13.1 and later): // fields come keyed by field id with 'value' and 'label' only, so the type is inferred here. add_action( 'et_pb_contact_form_submit', 'wapadesk_divi', 20, 3 ); function wapadesk_divi( $fields_in, $et_contact_error, $contact_form_info ) { try { if ( true === $et_contact_error || ! wapadesk_form_enabled( 'divi', '*' ) ) { return; } $fields = array(); foreach ( (array) $fields_in as $id => $f ) { $value = is_array( $f ) && isset( $f['value'] ) ? $f['value'] : ( is_scalar( $f ) ? $f : '' ); $label = is_array( $f ) && ! empty( $f['label'] ) ? $f['label'] : wapadesk_humanise( $id ); $hint = strtolower( $id . ' ' . $label ); $type = 'text'; if ( is_string( $value ) && is_email( trim( $value ) ) ) { $type = 'email'; } elseif ( preg_match( '/phone|mobile|tel\b|telephone|whatsapp/', $hint ) ) { $type = 'phone'; } elseif ( preg_match( '/consent|privacy|gdpr|agree/', $hint ) ) { $type = 'checkbox'; } elseif ( preg_match( '/message|comment|enquiry|inquiry/', $hint ) ) { $type = 'textarea'; } $fields[] = wapadesk_field( $id, $label, $type, $value ); } $info = is_array( $contact_form_info ) ? $contact_form_info : array(); $post_id = isset( $info['post_id'] ) ? (int) $info['post_id'] : (int) get_the_ID(); $number = isset( $info['contact_form_number'] ) ? (string) $info['contact_form_number'] : ''; $page = $post_id ? (string) get_permalink( $post_id ) : (string) wp_get_referer(); $page_t = $post_id ? trim( wp_strip_all_tags( get_the_title( $post_id ) ) ) : ''; $title = '' !== $page_t ? 'Form on ' . $page_t : 'Divi contact form'; wapadesk_capture( 'divi', 'post-' . $post_id . ( '' !== $number ? '-' . $number : '' ), $title, $fields, wp_generate_uuid4(), $page ); } catch ( Throwable $e ) { wapadesk_status_error( 'Divi: ' . $e->getMessage() ); } } // ---------- Sending ---------- add_action( 'shutdown', 'wapadesk_flush_outbox', 99 ); function wapadesk_flush_outbox() { $outbox = isset( $GLOBALS['wapadesk_outbox'] ) ? $GLOBALS['wapadesk_outbox'] : array(); if ( empty( $outbox ) ) { return; } $GLOBALS['wapadesk_outbox'] = array(); // The visitor's response is complete before we talk to Wapadesk. if ( function_exists( 'fastcgi_finish_request' ) ) { fastcgi_finish_request(); } foreach ( $outbox as $payload ) { if ( ! wapadesk_send( $payload, 5 ) ) { wapadesk_enqueue( $payload ); } } } /** * Posts one submission. True when it is done with (sent, or refused for good); * false when it should be retried later. */ function wapadesk_send( $payload, $timeout ) { $key = wapadesk_connection_key(); if ( '' === $key ) { return true; } $res = wp_remote_post( wapadesk_api_origin() . '/v1/inbound/form', array( 'timeout' => $timeout, 'headers' => array( 'Authorization' => 'Bearer ' . $key, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( $payload ), ) ); if ( is_wp_error( $res ) ) { wapadesk_status_error( 'Could not reach Wapadesk: ' . $res->get_error_message() ); return false; } $code = (int) wp_remote_retrieve_response_code( $res ); if ( $code >= 200 && $code < 300 ) { $status = wapadesk_status(); $status['last_sent'] = time(); update_option( WAPADESK_STATUS_OPTION, $status, false ); return true; } $body = json_decode( (string) wp_remote_retrieve_body( $res ), true ); $error = is_array( $body ) && isset( $body['error'] ) ? (string) $body['error'] : 'http_' . $code; wapadesk_status_error( 'Wapadesk answered ' . $code . ' (' . $error . ')' ); // Busy, rate-limited or a server error: try again. Anything else will not get better. return ! ( 409 === $code || 429 === $code || $code >= 500 ); } function wapadesk_enqueue( $payload ) { $queue = get_option( WAPADESK_QUEUE_OPTION, array() ); $queue = is_array( $queue ) ? $queue : array(); if ( count( $queue ) >= WAPADESK_QUEUE_MAX ) { array_shift( $queue ); wapadesk_count_dropped(); } $queue[] = array( 'payload' => $payload, 'attempts' => 1, 'first' => time(), 'next' => time() + 5 * MINUTE_IN_SECONDS, ); update_option( WAPADESK_QUEUE_OPTION, $queue, false ); if ( ! wp_next_scheduled( 'wapadesk_retry' ) ) { wp_schedule_event( time() + 5 * MINUTE_IN_SECONDS, 'wapadesk_fifteen_minutes', 'wapadesk_retry' ); } } add_filter( 'cron_schedules', 'wapadesk_cron_schedules' ); function wapadesk_cron_schedules( $schedules ) { $schedules['wapadesk_fifteen_minutes'] = array( 'interval' => 15 * MINUTE_IN_SECONDS, 'display' => 'Every fifteen minutes (Wapadesk)', ); return $schedules; } add_action( 'wapadesk_retry', 'wapadesk_retry_queue' ); function wapadesk_retry_queue() { $queue = get_option( WAPADESK_QUEUE_OPTION, array() ); $queue = is_array( $queue ) ? $queue : array(); $keep = array(); $now = time(); foreach ( $queue as $item ) { if ( ! is_array( $item ) || empty( $item['payload'] ) ) { continue; } if ( $item['next'] > $now ) { $keep[] = $item; continue; } if ( wapadesk_send( $item['payload'], 10 ) ) { continue; } // Give up after a day: the form plugin still has its own copy and email. if ( $now - (int) $item['first'] > DAY_IN_SECONDS ) { wapadesk_count_dropped(); continue; } $item['attempts'] = (int) $item['attempts'] + 1; $item['next'] = $now + min( 6 * HOUR_IN_SECONDS, 5 * MINUTE_IN_SECONDS * ( 2 ** min( 6, $item['attempts'] - 1 ) ) ); $keep[] = $item; } update_option( WAPADESK_QUEUE_OPTION, $keep, false ); if ( empty( $keep ) ) { wp_clear_scheduled_hook( 'wapadesk_retry' ); } } function wapadesk_status() { $s = get_option( WAPADESK_STATUS_OPTION, array() ); return is_array( $s ) ? $s : array(); } function wapadesk_status_error( $message ) { $s = wapadesk_status(); $s['last_error'] = substr( sanitize_text_field( $message ), 0, 300 ); $s['error_at'] = time(); update_option( WAPADESK_STATUS_OPTION, $s, false ); } function wapadesk_count_dropped() { $s = wapadesk_status(); $s['dropped'] = isset( $s['dropped'] ) ? (int) $s['dropped'] + 1 : 1; update_option( WAPADESK_STATUS_OPTION, $s, false ); } // ---------- Which forms exist ---------- /** * The forms on this site, with whether each sends to Wapadesk and whether it has a consent box. * Titles only; never entries. * * @return array */ function wapadesk_detect_forms() { $out = array(); try { if ( class_exists( 'GFAPI' ) ) { foreach ( (array) GFAPI::get_forms() as $form ) { $consent = false; foreach ( (array) rgar( $form, 'fields' ) as $field ) { if ( is_object( $field ) && 'consent' === $field->type ) { $consent = true; } } $out[] = array( 'plugin' => 'gravityforms', 'id' => (string) rgar( $form, 'id' ), 'title' => (string) rgar( $form, 'title' ), 'hasConsent' => $consent, ); } } if ( class_exists( 'WPCF7_ContactForm' ) ) { foreach ( WPCF7_ContactForm::find() as $form ) { $out[] = array( 'plugin' => 'cf7', 'id' => (string) $form->id(), 'title' => (string) $form->title(), 'hasConsent' => ! empty( $form->scan_form_tags( array( 'type' => array( 'acceptance' ) ) ) ), ); } } $wpforms = null; if ( function_exists( 'wpforms' ) ) { $wpforms = method_exists( wpforms(), 'obj' ) ? wpforms()->obj( 'form' ) : ( isset( wpforms()->form ) ? wpforms()->form : null ); } if ( is_object( $wpforms ) ) { $posts = $wpforms->get( '', array( 'posts_per_page' => 100 ) ); foreach ( is_array( $posts ) ? $posts : array() as $post ) { if ( ! is_object( $post ) ) { continue; } $data = function_exists( 'wpforms_decode' ) ? wpforms_decode( $post->post_content ) : array(); $consent = false; foreach ( isset( $data['fields'] ) ? (array) $data['fields'] : array() as $f ) { if ( isset( $f['type'] ) && 'gdpr-checkbox' === $f['type'] ) { $consent = true; } } $out[] = array( 'plugin' => 'wpforms', 'id' => (string) $post->ID, 'title' => (string) $post->post_title, 'hasConsent' => $consent, ); } } foreach ( wapadesk_all_forms_plugins() as $plugin => $p ) { if ( $p[1] ) { $out[] = array( 'plugin' => $plugin, 'id' => '*', 'title' => 'All ' . $p[0] . ' forms', ); } } } catch ( Throwable $e ) { wapadesk_status_error( 'Listing forms: ' . $e->getMessage() ); } foreach ( $out as $i => $f ) { $out[ $i ]['title'] = substr( wp_strip_all_tags( $f['title'] ), 0, 120 ); $out[ $i ]['enabled'] = wapadesk_form_enabled( $f['plugin'], $f['id'] ); } return array_slice( $out, 0, 100 ); } /** Tells Wapadesk which forms exist and checks the key. Returns the API's answer or a WP_Error. */ function wapadesk_ping() { $key = wapadesk_connection_key(); if ( '' === $key ) { return new WP_Error( 'no_key', __( 'Paste a connection key first.', 'ege-digital-chat-button' ) ); } $res = wp_remote_post( wapadesk_api_origin() . '/v1/inbound/ping', array( 'timeout' => 10, 'headers' => array( 'Authorization' => 'Bearer ' . $key, 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'site' => home_url( '/' ), 'pluginVersion' => WAPADESK_VERSION, 'forms' => wapadesk_detect_forms(), ) ), ) ); if ( is_wp_error( $res ) ) { return $res; } $code = (int) wp_remote_retrieve_response_code( $res ); $body = json_decode( (string) wp_remote_retrieve_body( $res ), true ); if ( 200 !== $code || ! is_array( $body ) ) { $error = is_array( $body ) && isset( $body['error'] ) ? $body['error'] : 'http_' . $code; return new WP_Error( 'wapadesk_' . $error, 'invalid_key' === $error ? __( 'Wapadesk does not recognise this key. Create a new one on the Install page and paste it again.', 'ege-digital-chat-button' ) : sprintf( /* translators: %s: error code */ __( 'Wapadesk answered with an error (%s).', 'ege-digital-chat-button' ), $error ) ); } return $body; } // Report the form list after the settings are saved. add_action( 'update_option_' . WAPADESK_OPTION, 'wapadesk_after_save', 10, 0 ); add_action( 'add_option_' . WAPADESK_OPTION, 'wapadesk_after_save', 10, 0 ); function wapadesk_after_save() { if ( '' !== wapadesk_connection_key() ) { $r = wapadesk_ping(); set_transient( 'wapadesk_ping_result', is_wp_error( $r ) ? array( 'error' => $r->get_error_message() ) : $r, 60 ); } } add_action( 'admin_post_wapadesk_test', 'wapadesk_test_connection' ); function wapadesk_test_connection() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Not allowed.', 'ege-digital-chat-button' ) ); } check_admin_referer( 'wapadesk_test' ); $r = wapadesk_ping(); set_transient( 'wapadesk_ping_result', is_wp_error( $r ) ? array( 'error' => $r->get_error_message() ) : $r, 60 ); wp_safe_redirect( admin_url( 'admin.php?page=wapadesk-account' ) ); exit; } /** The "Contact forms" part of the settings page. */ function wapadesk_forms_settings_section( $s ) { $key = wapadesk_connection_key(); $forms = wapadesk_detect_forms(); $status = wapadesk_status(); $queue = get_option( WAPADESK_QUEUE_OPTION, array() ); $waiting = is_array( $queue ) ? count( $queue ) : 0; $result = get_transient( 'wapadesk_ping_result' ); if ( $result ) { delete_transient( 'wapadesk_ping_result' ); } ?>