(int) $lead_id, 'name' => get_the_title( $lead_id ), 'date' => get_post_time( 'c', true, $lead_id ), ); foreach ( array( 'type', 'email', 'phone', 'preferred_time', 'question', 'message', 'page' ) as $key ) { $data[ $key ] = (string) get_post_meta( $lead_id, '_avca_' . $key, true ); } return $data; } /** * Sync one lead (WP-Cron callback). * * @param int $lead_id Lead ID. */ function avcap_sync_lead( $lead_id ) { if ( AVCA_LEAD_POST_TYPE !== get_post_type( $lead_id ) ) { return; } $s = avcap_get_settings(); $lead = avcap_get_lead_data( $lead_id ); $results = array(); $failed = false; if ( ! empty( $s['zoho_enabled'] ) ) { $zoho = avcap_zoho_create_lead( $lead ); if ( is_wp_error( $zoho ) ) { $failed = true; $results[] = 'Zoho: ' . $zoho->get_error_message(); update_post_meta( $lead_id, '_avcap_zoho_error', $zoho->get_error_message() ); } else { $results[] = 'Zoho: OK'; update_post_meta( $lead_id, '_avcap_zoho_id', $zoho ); delete_post_meta( $lead_id, '_avcap_zoho_error' ); } } if ( '' !== $s['webhook_url'] ) { $hook = avcap_send_webhook( $lead ); $failed = $failed || is_wp_error( $hook ); $results[] = is_wp_error( $hook ) ? 'Webhook: ' . $hook->get_error_message() : 'Webhook: OK'; } update_post_meta( $lead_id, '_avcap_sync_status', $failed ? 'error' : 'synced' ); update_post_meta( $lead_id, '_avcap_sync_log', implode( "\n", $results ) ); } add_action( 'avcap_sync_lead', 'avcap_sync_lead' ); /* ------------------------------------------------------------------------- * Zoho CRM. * ------------------------------------------------------------------------- */ /** * Zoho accounts domain for a data centre. * * @param string $dc Data centre code. * @return string */ function avcap_zoho_accounts_url( $dc ) { return 'ca' === $dc ? 'https://accounts.zohocloud.ca' : 'https://accounts.zoho.' . $dc; } /** * Get a Zoho access token (cached for 55 minutes). * * @return array|WP_Error Array with token and api_domain. */ function avcap_zoho_token() { $cached = get_transient( 'avcap_zoho_token' ); if ( is_array( $cached ) && ! empty( $cached['token'] ) ) { return $cached; } $s = avcap_get_settings(); if ( '' === $s['zoho_client_id'] || '' === $s['zoho_secret'] || '' === $s['zoho_refresh'] ) { return new WP_Error( 'avcap_zoho_setup', __( 'Zoho credentials are missing.', 'avi-support-assistant-pro' ) ); } $response = wp_remote_post( avcap_zoho_accounts_url( $s['zoho_dc'] ) . '/oauth/v2/token', array( 'timeout' => 20, 'body' => array( 'grant_type' => 'refresh_token', 'refresh_token' => $s['zoho_refresh'], 'client_id' => $s['zoho_client_id'], 'client_secret' => $s['zoho_secret'], ), ) ); if ( is_wp_error( $response ) ) { return $response; } $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( empty( $data['access_token'] ) ) { $error = isset( $data['error'] ) ? sanitize_text_field( (string) $data['error'] ) : __( 'unknown error', 'avi-support-assistant-pro' ); /* translators: %s: Error from Zoho. */ return new WP_Error( 'avcap_zoho_auth', sprintf( __( 'Zoho sign-in failed: %s', 'avi-support-assistant-pro' ), $error ) ); } $token = array( 'token' => $data['access_token'], 'api_domain' => ! empty( $data['api_domain'] ) ? esc_url_raw( $data['api_domain'] ) : 'https://www.zohoapis.' . $s['zoho_dc'], ); $ttl = isset( $data['expires_in'] ) ? max( 60, (int) $data['expires_in'] - 300 ) : 3300; set_transient( 'avcap_zoho_token', $token, $ttl ); return $token; } /** * Create a Lead in Zoho CRM. * * @param array $lead Lead data. * @return string|WP_Error Zoho record ID or error. */ function avcap_zoho_create_lead( $lead ) { $token = avcap_zoho_token(); if ( is_wp_error( $token ) ) { return $token; } $s = avcap_get_settings(); $parts = preg_split( '/\s+/', trim( $lead['name'] ), 2 ); $first = count( $parts ) > 1 ? $parts[0] : ''; $last = count( $parts ) > 1 ? $parts[1] : $parts[0]; $description = array(); if ( 'callback' === $lead['type'] ) { $description[] = __( 'Callback requested from the website chat.', 'avi-support-assistant-pro' ); } foreach ( array( 'preferred_time' => __( 'Preferred call time', 'avi-support-assistant-pro' ), 'question' => __( 'Question asked', 'avi-support-assistant-pro' ), 'message' => __( 'Message', 'avi-support-assistant-pro' ), 'page' => __( 'Page', 'avi-support-assistant-pro' ), ) as $key => $label ) { if ( '' !== $lead[ $key ] ) { $description[] = $label . ': ' . $lead[ $key ]; } } $record = array( 'Last_Name' => '' !== $last ? $last : __( 'Website visitor', 'avi-support-assistant-pro' ), 'First_Name' => $first, 'Lead_Source' => $s['zoho_source'], 'Description' => implode( "\n", $description ), ); if ( '' !== $lead['email'] ) { $record['Email'] = $lead['email']; } if ( '' !== $lead['phone'] ) { $record['Phone'] = $lead['phone']; } /** * Filter the Zoho CRM lead record before it is sent. * * @param array $record Zoho fields. * @param array $lead Lead data. */ $record = apply_filters( 'avcap_zoho_record', array_filter( $record, 'strlen' ), $lead ); $response = wp_remote_post( untrailingslashit( $token['api_domain'] ) . '/crm/v8/Leads', array( 'timeout' => 20, 'headers' => array( 'Authorization' => 'Zoho-oauthtoken ' . $token['token'], 'Content-Type' => 'application/json', ), 'body' => wp_json_encode( array( 'data' => array( $record ), 'trigger' => array( 'workflow' ), ) ), ) ); if ( is_wp_error( $response ) ) { return $response; } $code = (int) wp_remote_retrieve_response_code( $response ); $data = json_decode( wp_remote_retrieve_body( $response ), true ); if ( 401 === $code ) { delete_transient( 'avcap_zoho_token' ); } $row = isset( $data['data'][0] ) ? $data['data'][0] : array(); if ( isset( $row['code'] ) && 'SUCCESS' === $row['code'] && ! empty( $row['details']['id'] ) ) { return (string) $row['details']['id']; } $message = isset( $row['message'] ) ? $row['message'] : ( isset( $data['message'] ) ? $data['message'] : 'HTTP ' . $code ); if ( isset( $row['details']['api_name'] ) ) { $message .= ' (' . $row['details']['api_name'] . ')'; } return new WP_Error( 'avcap_zoho_insert', sanitize_text_field( $message ) ); } /** * Admin AJAX: test the Zoho connection. */ function avcap_ajax_test_zoho() { check_ajax_referer( 'avcap_test_zoho' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Not allowed.', 'avi-support-assistant-pro' ) ), 403 ); } delete_transient( 'avcap_zoho_token' ); $token = avcap_zoho_token(); if ( is_wp_error( $token ) ) { wp_send_json_error( array( 'message' => $token->get_error_message() ) ); } wp_send_json_success( array( 'message' => __( 'Connected to Zoho CRM. Save your settings, and new leads will be sent automatically.', 'avi-support-assistant-pro' ) ) ); } add_action( 'wp_ajax_avcap_test_zoho', 'avcap_ajax_test_zoho' ); /* ------------------------------------------------------------------------- * Webhook. * ------------------------------------------------------------------------- */ /** * Send a lead to the webhook URL as JSON, signed with HMAC-SHA256 when a secret is set. * * @param array $lead Lead data. * @return true|WP_Error */ function avcap_send_webhook( $lead ) { $s = avcap_get_settings(); $body = wp_json_encode( array( 'event' => 'lead.created', 'site' => home_url( '/' ), 'lead' => $lead, ) ); $headers = array( 'Content-Type' => 'application/json' ); if ( '' !== $s['webhook_secret'] ) { $headers['X-Avi-Signature'] = 'sha256=' . hash_hmac( 'sha256', $body, $s['webhook_secret'] ); } $response = wp_safe_remote_post( $s['webhook_url'], array( 'timeout' => 15, 'headers' => $headers, 'body' => $body, ) ); if ( is_wp_error( $response ) ) { return $response; } $code = (int) wp_remote_retrieve_response_code( $response ); return ( $code >= 200 && $code < 300 ) ? true : new WP_Error( 'avcap_webhook', 'HTTP ' . $code ); } /* ------------------------------------------------------------------------- * Leads list: sync status column. * ------------------------------------------------------------------------- */ /** * Add the CRM column. * * @param array $columns Columns. * @return array */ function avcap_lead_sync_column( $columns ) { $s = avcap_get_settings(); if ( empty( $s['zoho_enabled'] ) && '' === $s['webhook_url'] ) { return $columns; } $date = isset( $columns['date'] ) ? $columns['date'] : null; unset( $columns['date'] ); $columns['avcap_sync'] = __( 'CRM', 'avi-support-assistant-pro' ); if ( $date ) { $columns['date'] = $date; } return $columns; } add_filter( 'manage_avca_lead_posts_columns', 'avcap_lead_sync_column', 20 ); /** * Render the CRM column. * * @param string $column Column. * @param int $post_id Lead ID. */ function avcap_lead_sync_column_content( $column, $post_id ) { if ( 'avcap_sync' !== $column ) { return; } $status = get_post_meta( $post_id, '_avcap_sync_status', true ); $log = (string) get_post_meta( $post_id, '_avcap_sync_log', true ); $labels = array( 'queued' => __( 'Sending…', 'avi-support-assistant-pro' ), 'synced' => __( 'Sent', 'avi-support-assistant-pro' ), 'error' => __( 'Failed', 'avi-support-assistant-pro' ), ); if ( isset( $labels[ $status ] ) ) { echo '' . esc_html( $labels[ $status ] ) . ''; } else { echo '—'; } } add_action( 'manage_avca_lead_posts_custom_column', 'avcap_lead_sync_column_content', 10, 2 );