Files
roi-theme/wp-content/plugins/wp-marketing-automations-pro/crm/includes/class-bwfcrm-broadcast-processing.php
root a22573bf0b Commit inicial - WordPress Análisis de Precios Unitarios
- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 21:04:30 -06:00

1203 lines
44 KiB
PHP
Executable File

<?php
/**
* Broadcast Processing Controller Class
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
#[AllowDynamicProperties]
final class BWFCRM_Broadcast_Processing {
/** Constants */
public static $CAMPAIGN_DRAFT = 1;
public static $CAMPAIGN_SCHEDULED = 2;
public static $CAMPAIGN_RUNNING = 3;
public static $CAMPAIGN_COMPLETED = 4;
public static $CAMPAIGN_HALT = 5;
public static $CAMPAIGN_PAUSED = 6;
public static $CAMPAIGN_EMAIL = 1;
public static $CAMPAIGN_SMS = 2;
public static $CAMPAIGN_WHATSAPP = 3;
public $CONTACT_BATCH_COUNT = 75;
public $AS_CALL_TIME;
public $SMS_PER_SECOND_LIMIT = 1;
public $EMAIL_PER_SECOND_LIMIT = 10000;
/** Class Properties */
/** @var BWFCRM_Broadcast_Processing current_broadcast */
private static $ins = null;
private $started_send_mail_at = null;
private $records_processed_in_one_sec = 0;
private $last_engagement_sent = null;
public $start_time = 0;
/** @var array current_broadcast */
private $current_broadcast = null;
private $offset = 0;
private $processed = 0;
private $count = 0;
private $daily_message_limit = 0;
private $daily_achieved_limit = 0;
private $contact_ids = array();
public $stop_broadcast_required = false;
public $stop_broadcast_for_shorten_url = false;
public $default_email_settings = array();
/** Email is supposed to utilise Daily Limit feature for now */
public $supported_daily_limit_broadcast_types = array( 1 );
private $logs = array();
public $failed_cids = array();
public $err_msg = array();
public static function get_instance() {
if ( null === self::$ins ) {
self::$ins = new self();
}
return self::$ins;
}
public function __construct() {
/** Scheduler per Run time in seconds */
$this->AS_CALL_TIME = $this->get_per_call_time();
add_action( 'bwfcrm_broadcast_run_queue', array( $this, 'do_scheduler_work' ) );
}
public function do_scheduler_work() {
/** If Sandbox mode active */
if ( true === BWFAN_Common::is_sandbox_mode_active() ) {
return;
}
if ( true === apply_filters( 'bwfcrm_disable_broadcast_run_queue', false ) ) {
return;
}
$this->default_email_settings = BWFAN_Common::get_global_settings();
/** SMS & EMail limit per second */
$this->EMAIL_PER_SECOND_LIMIT = isset( $this->default_email_settings['bwfan_email_per_second_limit'] ) ? absint( $this->default_email_settings['bwfan_email_per_second_limit'] ) : 15;
$this->EMAIL_PER_SECOND_LIMIT = apply_filters( 'bwfan_broadcast_email_per_second_limit', $this->EMAIL_PER_SECOND_LIMIT );
$this->SMS_PER_SECOND_LIMIT = apply_filters( 'bwfan_broadcast_sms_per_second_limit', $this->SMS_PER_SECOND_LIMIT );
$this->start_time = time();
/** Process scheduled broadcasts, make them running */
$this->process_scheduled_broadcasts();
if ( $this->is_time_exceeded() ) {
return;
}
/** Process running broadcasts */
$this->process_running_broadcasts();
}
public function process_scheduled_broadcasts() {
$scheduled = BWFAN_Model_Broadcast::get_scheduled_broadcasts_to_run();
if ( ! is_array( $scheduled ) || empty( $scheduled ) ) {
return;
}
foreach ( $scheduled as $broadcast ) {
if ( $this->is_time_exceeded() ) {
break;
}
$this->process_single_scheduled_broadcasts( $broadcast );
}
}
public function process_single_scheduled_broadcasts( $broadcast ) {
$args = $this->get_broadcast_query_args( $broadcast );
$parentBroadcast = isset( $broadcast['parent'] ) ? absint( $broadcast['parent'] ) : false;
/** Reset broadcast stats */
delete_transient( 'bwfan_broadcast_stats' );
/** Fetching last contact ID of a broadcast from the given filters */
if ( ! empty( $parentBroadcast ) ) {
$args['props']['limit'] = 1;
$args['props']['offset'] = 0;
$args['props']['grab_totals'] = true;
$args['props']['exclude_unsubs'] = isset( $args['data']['is_promotional'] ) && 1 === intval( $args['data']['is_promotional'] );
$contacts_array = BWFCRM_Core()->campaigns->get_unopen_broadcast_contacts( absint( $parentBroadcast ), $args['props'], OBJECT );
} else {
if ( ! isset( $args['props']['exclude_unsubs'] ) || false === $args['props']['exclude_unsubs'] ) {
$args['props']['exclude_unsubs'] = apply_filters( 'bwfan_force_exclude_unsubscribe_contact', false );
}
$contacts_array = BWFCRM_Contact::get_contacts( '', 0, 1, $args['filters'], $args['props'], OBJECT );
}
$contacts = $contacts_array['contacts'];
$total_contacts = ! empty( $contacts ) && isset( $contacts_array['total_count'] ) ? intval( $contacts_array['total_count'] ) : 0;
$last_contact = is_array( $contacts ) && count( $contacts ) > 0 && $contacts[0] instanceof BWFCRM_Contact ? $contacts[0]->get_id() : 0;
if ( $total_contacts > 0 && absint( $last_contact ) > 0 ) {
/** Increment the last contact ID by 1, to always get the contacts with IDs less than ($last_contact+1) */
$last_contact = absint( $last_contact ) + 1;
$args['data']['content'] = $this->get_or_create_multiple_templates( $args['data']['content'], $args['props']['fetch_base'], true );
BWFAN_Model_Broadcast::update_status_to_run( $broadcast['id'], $last_contact, $args['data'], $total_contacts );
return;
}
$args['data']['halt_reason'] = __( 'No contacts found for this broadcast', 'wp-marketing-automations-pro' );
BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $args['data'] );
}
public function get_or_create_multiple_templates( $emails, $mode = 1, $return_with_contents = false ) {
if ( ! is_array( $emails ) ) {
return array();
}
$result = array();
foreach ( $emails as $email ) {
/** Set template mode 1 - rich, 3 - html 4 - editor */
$template_type = BWFAN_PRO_Common::get_email_template_type( $email['type'] );
$data = array( 'template' => $template_type );
if ( isset( $email['whatsAppImage'] ) && $email['whatsAppImage'] ) {
if ( isset( $email['whatsAppImageSetting'] ) && ! empty( $email['whatsAppImageSetting'] ) ) {
$data = $email['whatsAppImageSetting'];
}
}
$editor_body = isset( $email['editor'] ) && is_array( $email['editor'] ) && isset( $email['editor']['body'] ) ? $email['editor']['body'] : '';
$template_body = isset( $email['type'] ) && 'editor' === $email['type'] ? $editor_body : $email['body'];
$template_body = method_exists( 'BWFAN_Common', 'correct_shortcode_string' ) ? BWFAN_Common::correct_shortcode_string( $template_body, $email['type'] ) : $template_body;
/**
* If a template is a block or 5, then decode the specific blocks in the mail content.
* Decoding the specific blocks before saving the template data for fast execution at the time of sending.
* Replacing the global variables with their values as they are contact-specific
*
* For future versions, if the block is genric, we need to add it in the decode_specific_blocks_before function parameter.
*
* @since 3.5.3
*/
if ( 5 === intval( $template_type ) ) {
include_once BWFAN_PRO_PLUGIN_DIR . '/crm/includes/class-bwfcrm-block-editor.php';
if ( method_exists( 'BWFCRM_Block_Editor', 'decode_specific_blocks_before' ) ) {
$template_body = BWFCRM_Block_Editor::decode_specific_blocks_before( $template_body, [ 'bwfbe_multi_product', 'bwfbe_coupon', 'bwfbe_social_icons' ] );
}
$global_val = class_exists( 'BWFCRM_Block_Editor' ) ? BWFCRM_Block_Editor::$global_settings_var : [];
if ( ! empty( $global_val ) ) {
$global_val_k = array_keys( $global_val );
$global_val_v = array_values( $global_val );
$template_body = str_replace( $global_val_k, $global_val_v, $template_body );
}
}
$template_id = BWFCRM_Core()->conversation->get_or_create_template( $mode, 1 === $mode && isset( $email['subject'] ) ? $email['subject'] : '', $template_body, 'id', $data );
if ( true === $return_with_contents ) {
$email['template_id'] = $template_id;
$result[] = $email;
} else {
$result[] = $template_id;
}
}
return $result;
}
public function process_running_broadcasts() {
$this->set_log( 'processing running broadcast' );
$broadcasts = BWFAN_Model_Broadcast::get_broadcasts_by_status( self::$CAMPAIGN_RUNNING, true );
if ( ! is_array( $broadcasts ) || empty( $broadcasts ) ) {
return;
}
do_action( 'bwfan_before_executing_broadcast', $broadcasts );
$this->set_log( count( $broadcasts ) . ' broadcasts found' );
$this->started_send_mail_at = microtime( true );
foreach ( $broadcasts as $broadcast ) {
/** Set delay if daily limit reached and set daily limit count */
if ( $this->maybe_daily_limit_reached( $broadcast['type'] ) ) {
BWFAN_Model_Broadcast::set_delay( $broadcast['id'] );
continue;
}
/** Store current broadcast for class-wide access */
$this->current_broadcast = $broadcast;
/** Break, if current call request time exceeded */
if ( $this->is_time_exceeded() ) {
break;
}
/** Check if broadcast is valid */
if ( ! $this->is_broadcast_valid() ) {
continue;
}
/**
* Update last_modified to inform subsequent requests
*/
$last_modified_time = current_time( 'mysql', 1 );
BWFAN_Model_Broadcast::update( array( 'last_modified' => $last_modified_time ), array( 'id' => absint( $broadcast['id'] ) ) );
$broadcast['last_modified'] = $last_modified_time;
$this->current_broadcast['last_modified'] = $last_modified_time;
$data = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : array();
$this->processed = absint( $broadcast['processed'] );
$this->offset = absint( $broadcast['offset'] );
$this->count = absint( $broadcast['count'] );
do {
$this->set_log( 'broadcast_id' . $broadcast['id'] . '_before_fetch_contacts' );
$this->fetch_contacts_for_broadcast();
if ( $this->error_while_fetching_contacts() ) {
break;
}
if ( true === $this->stop_broadcast_required ) {
$this->stop_broadcast_required = false;
break;
}
if ( empty( $this->contact_ids ) ) {
BWFAN_Model_Broadcast::update_status_to_complete( $broadcast['id'], $data, $this->processed, $this->count );
self::action_schedule_save_last_sent( $broadcast['id'] );
/** Reset broadcast stats */
delete_transient( 'bwfan_broadcast_stats' );
break;
}
$this->process_single_ongoing_broadcast();
/** Continue to next broadcast, in case needed to end current broadcast process */
if ( true === $this->stop_broadcast_required ) {
$this->stop_broadcast_required = false;
break;
}
if ( $this->is_time_exceeded() ) {
BWFAN_Model_Broadcast::update( array(
'last_modified' => date( 'Y-m-d H:i:s', time() - 6 ),
), array(
'id' => absint( $broadcast['id'] ),
) );
BWFCRM_Common::reschedule_broadcast_action();
}
} while ( ! $this->is_time_exceeded() && ! BWFCRM_Common::memory_exceeded() );
}
$this->set_log( 'broadcasts_loop_end' );
$this->log();
}
public function is_broadcast_valid() {
$broadcast = $this->current_broadcast;
$data = $this->get_current_broadcast_data();
/** SMS Validation */
if ( self::$CAMPAIGN_SMS === absint( $broadcast['type'] ) ) {
$provider = BWFCRM_Common::get_sms_provider_integration();
if ( is_wp_error( $provider ) ) {
$error_message = $provider->get_error_message();
$data['halt_reason'] = ! empty( $error_message ) ? $error_message : __( 'Invalid SMS settings', 'wp-marketing-automations-pro' );
BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );
self::action_schedule_save_last_sent( $broadcast['id'] );
return false;
}
}
/** WhatsApp Validation */
if ( self::$CAMPAIGN_WHATSAPP === absint( $broadcast['type'] ) && false === $this->maybe_validate_whatsapp_settings() ) {
return false;
}
/** Broadcast Content Validation */
$broadcast_content = $data['content'];
if ( empty( $broadcast_content ) || ! is_array( $broadcast_content ) ) {
$data['halt_reason'] = __( 'Broadcast content not found', 'wp-marketing-automations-pro' );
BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );
self::action_schedule_save_last_sent( $broadcast['id'] );
return false;
}
/** Validate broadcast might be running */
if ( true === $this->maybe_broadcast_running() ) {
return false;
}
$this->failed_cids = $data['failed_cids'] ?? [];
$this->err_msg = $data['failed_reason'] ?? [];
return true;
}
public function fetch_contacts_for_broadcast() {
$broadcast = $this->current_broadcast;
$args = $this->get_broadcast_query_args( $broadcast );
$args['props']['end_id'] = absint( $this->offset );
$args['props']['exclude_end_id'] = true;
/** Get Contacts */
$parentBroadcast = isset( $broadcast['parent'] ) ? absint( $broadcast['parent'] ) : false;
if ( ! empty( $parentBroadcast ) ) {
$args['props']['limit'] = $this->CONTACT_BATCH_COUNT;
$args['props']['offset'] = 0;
$args['props']['grab_totals'] = true;
$args['props']['exclude_unsubs'] = isset( $args['data']['is_promotional'] ) && 1 === intval( $args['data']['is_promotional'] );
$contacts_array = BWFCRM_Core()->campaigns->get_unopen_broadcast_contacts( absint( $parentBroadcast ), $args['props'], OBJECT );
} else {
if ( ! isset( $args['props']['exclude_unsubs'] ) || false === $args['props']['exclude_unsubs'] ) {
$args['props']['exclude_unsubs'] = apply_filters( 'bwfan_force_exclude_unsubscribe_contact', false );
}
$contacts_array = BWFCRM_Contact::get_contacts( '', 0, $this->CONTACT_BATCH_COUNT, $args['filters'], $args['props'], OBJECT );
}
if ( ! is_array( $contacts_array ) || ! isset( $contacts_array['contacts'] ) || ! is_array( $contacts_array['contacts'] ) ) {
$this->contact_ids = BWFCRM_Common::crm_error( __( 'Invalid broadcast contact query results', 'wp-marketing-automations-pro' ) );
$this->set_log( 'broadcast_id' . $broadcast['id'] . '_no_query_result' );
return;
}
/** Get IDs From Contacts */
$this->contact_ids = array_map( function ( $contact ) {
return $contact->get_id();
}, $contacts_array['contacts'] );
/** Adding failsafe to avoid multiple broadcast processing on the same contact */
/** getting contacts ids in desc order */
arsort( $this->contact_ids );
/** First id to process is */
$first_id = current( $this->contact_ids );
if ( $first_id >= $this->offset ) {
/** If first contact already processed, empty the contact array */
$this->set_log( 'broadcast_id' . $broadcast['id'] . ' has data but contact already processed' );
$this->stop_broadcast_required = true;
return;
}
$this->set_log( 'broadcast_id' . $broadcast['id'] . ' - IDs: ' . implode( ',', $this->contact_ids ) );
return;
}
protected function get_broadcast_query_args( $broadcast ) {
if ( ! is_array( $broadcast ) || empty( $broadcast ) ) {
return false;
}
$data = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : false;
if ( ! is_array( $data ) && ! empty( $data ) ) {
$data = json_decode( $data, true );
}
$exclude = ! empty( $data ) && isset( $data['exclude'] ) && is_array( $data['exclude'] ) ? $data['exclude'] : array();
$exclude = array_map( 'absint', $exclude );
$parentBroadcast = isset( $broadcast['parent'] ) ? absint( $broadcast['parent'] ) : false;
if ( ! empty( $parentBroadcast ) ) {
return array(
'filters' => array(),
'data' => $data,
'props' => array(
'order' => 'DESC',
'exclude_ids' => $exclude,
'fetch_base' => 1,
),
);
}
$filters = ! empty( $data ) && isset( $data['filters'] ) ? $data['filters'] : array();
$filters['status_is'] = 1;
$mode = isset( $broadcast['type'] ) ? absint( $broadcast['type'] ) : 1;
/** Fetch Base must SMS (Phone Number), if mode is WhatsApp */
$mode = self::$CAMPAIGN_WHATSAPP === $mode ? self::$CAMPAIGN_SMS : $mode;
return array(
'filters' => $filters,
'data' => $data,
'props' => array(
'fetch_base' => $mode,
'order_by' => 'id',
'order' => 'DESC',
'grab_totals' => true,
'exclude_unsubs_lists' => isset( $filters['lists_any'] ),
'exclude_ids' => $exclude,
'include_soft_bounce' => ! empty( $data['includeSoftBounce'] ),
'include_unverified' => ! empty( $data['includeUnverified'] ),
'include_unsubscribe' => empty( $data['is_promotional'] ),
),
);
}
public function error_while_fetching_contacts() {
if ( ! $this->contact_ids instanceof WP_Error ) {
return false;
}
$data = $this->get_current_broadcast_data();
$data['halt_reason'] = $this->contact_ids->get_error_message();
BWFAN_Model_Broadcast::update_status_to_halt( $this->current_broadcast['id'], $data );
self::action_schedule_save_last_sent( $this->current_broadcast['id'] );
return true;
}
public function get_per_second_limit() {
/** Email */
if ( self::$CAMPAIGN_EMAIL === absint( $this->current_broadcast['type'] ) ) {
return absint( $this->EMAIL_PER_SECOND_LIMIT );
}
/** SMS */
if ( self::$CAMPAIGN_SMS === absint( $this->current_broadcast['type'] ) ) {
return absint( $this->SMS_PER_SECOND_LIMIT );
}
/** Not checking WhatsApp here */
return false;
}
public function process_single_ongoing_broadcast() {
$broadcast = $this->current_broadcast;
$data = $this->get_current_broadcast_data();
$per_second_limit = $this->get_per_second_limit();
foreach ( $this->contact_ids as $id ) {
$this->set_log( 'contact#' . $id . '_start' );
if ( $this->is_time_exceeded() || BWFCRM_Common::memory_exceeded() ) {
$this->set_log( 'contact#' . $id . '_time exceeded' );
break;
}
/**
* Fallback, in case previously processed ID gets into loop again,
* where offset must be old, and id must be new.
*/
if ( $this->offset <= $id ) {
$this->set_log( 'contact#' . $id . ' moving to next contact ' . $this->offset . ' = ' . $id );
continue;
}
/**
* If broadcast support daily limit, check that
* currently email type supports daily limit
* if daily limit reached then delay and end current broadcast
*/
$daily_limit_supported = in_array( absint( $broadcast['type'] ), $this->supported_daily_limit_broadcast_types );
if ( $daily_limit_supported && $this->daily_achieved_limit >= $this->daily_message_limit ) {
BWFAN_Model_Broadcast::set_delay( $broadcast['id'] );
$this->stop_broadcast_required = true;
return;
}
/** Hold for one second to complete, if per second limit achieved */
if ( ! empty( $per_second_limit ) && $per_second_limit <= $this->records_processed_in_one_sec ) {
$this->set_log( 'contact#' . $id . '_per_second_limit_reached' );
$this->delay_time();
}
/**
* Check if Smart sending is enabled
* Maybe need to stop broadcast in case contacts processed
* If nothing returned, continue because smart sending is not enabled
* If hold returned, stop the broadcast
*/
$smart_send = $this->maybe_process_smart_send_data( $broadcast['id'], $data, $this->processed, $this->count, absint( $broadcast['type'] ) );
if ( 'hold' === $smart_send ) {
$this->stop_broadcast_required = true;
return;
} elseif ( ! empty( $smart_send ) ) {
$data['winner'] = absint( $smart_send );
$broadcast['data'] = $data;
}
if ( self::$CAMPAIGN_SMS === absint( $broadcast['type'] ) ) {
$this->process_broadcast_contact_sms( $broadcast, $id );
} elseif ( self::$CAMPAIGN_WHATSAPP === absint( $broadcast['type'] ) ) {
$this->whatsapp_delay_time();
$this->process_broadcast_contact_whatsapp( $broadcast, $id );
} else {
$this->process_broadcast_contact_email( $broadcast, $id );
}
if ( true === $this->stop_broadcast_for_shorten_url ) {
return;
}
/** set last engagement time */
$this->last_engagement_sent = microtime( true );
$this->records_processed_in_one_sec ++;
$this->processed ++;
$this->daily_achieved_limit ++;
$this->offset = $id;
if ( count( $this->failed_cids ) > 0 ) {
$data['failed_cids'] = $this->failed_cids;
$data['failed_reason'] = array_values( array_unique( $this->err_msg ) );
$status = count( $this->failed_cids ) >= 20 ? self::$CAMPAIGN_PAUSED : false;
BWFAN_Model_Broadcast::update_broadcast_data_with_status( $broadcast['id'], $data, $status, $this->offset, $this->processed );
/** If the last 10 engagements are failed, stop the broadcast */
if ( count( $this->failed_cids ) >= 20 ) {
$this->set_log( 'contact #' . $id . ' email failed. 20 consecutive failed contacts are: ' . implode( ', ', $this->failed_cids ) );
$this->stop_broadcast_required = true;
}
} else {
// No email failure
if ( isset( $data['failed_cids'] ) ) {
// case when failed_cids is set
unset( $data['failed_cids'] );
if ( isset( $data['failed_reason'] ) ) {
unset( $data['failed_reason'] );
}
BWFAN_Model_Broadcast::update_broadcast_data_with_status( $broadcast['id'], $data, false, $this->offset, $this->processed );
} else {
BWFAN_Model_Broadcast::update_campaign_offsets( $broadcast['id'], $this->offset, $this->processed );
}
}
if ( true === $this->stop_broadcast_required ) {
return;
}
}
}
/**
* @param $broadcast
* @param $contact_id
*
* @return false|void
*/
public function process_broadcast_contact_whatsapp( $broadcast, $contact_id ) {
$contact = new WooFunnels_Contact( '', '', '', absint( $contact_id ) );
if ( empty( $contact->get_id() ) ) {
return;
}
$this->set_log( 'contact#' . $contact_id . '_process_whatsapp_message_start' );
$to = BWFAN_PRO_Common::get_contact_full_number( $contact );
if ( empty( $to ) ) {
$this->set_log( 'contact#' . $contact_id . '_no_phone_number_whatsapp' );
return;
}
$data = $broadcast['data'];
$broadcast_id = absint( $broadcast['id'] );
$broadcast_author_id = absint( $broadcast['created_by'] );
$broadcast_content = $data['content'];
$template_winner = isset( $data['winner'] ) ? absint( $data['winner'] ) : 0;
$chosen_content = $this->get_current_email_template( $broadcast_content, $this->processed, $template_winner );
$message_data = BWFCRM_Core()->conversation->create_campaign_conversation( $contact, $broadcast_id, $chosen_content['template_id'], $broadcast_author_id, BWFCRM_Campaigns::$CAMPAIGN_WHATSAPP );
if ( is_wp_error( $message_data ) ) {
$e_data = $message_data->get_error_data();
if ( is_array( $e_data ) && isset( $e_data['conversation_id'] ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( absint( $e_data['conversation_id'] ), $message_data->get_error_message() );
}
return;
}
if ( ! isset( $message_data['conversation_id'] ) ) {
$this->set_log( 'contact#' . $contact_id . '_conversation_id not found' );
return;
}
$conversation_id = absint( $message_data['conversation_id'] );
$hash_code = isset( $message_data['hash_code'] ) ? $message_data['hash_code'] : '';
$template = isset( $message_data['template'] ) ? $message_data['template'] : '';
$template_data = isset( $message_data['template_data'] ) ? $message_data['template_data'] : array();
$utm_details = isset( $chosen_content['utm'] ) && is_array( $chosen_content['utm'] ) ? $this->get_utm_data_from_campaign_data( $chosen_content['utm'] ) : '';
$message_body = BWFCRM_Core()->conversation->prepare_sms_body( $conversation_id, $contact_id, $hash_code, $template, $utm_details, $broadcast_id );
if ( true === $this->stop_broadcast_for_shorten_url ) {
return false;
}
$textdata = array(
'type' => 'text',
'data' => $message_body,
);
$message = array( $textdata );
if ( ! empty( $template_data ) && isset( $template_data['position'] ) && isset( $template_data['imageURL'] ) && ! empty( $template_data['position'] ) && ! empty( $template_data['imageURL'] ) ) {
$imagedata = array(
'type' => 'image',
'data' => $template_data['imageURL'],
);
if ( $template_data['position'] == 'before' ) {
$message = array( $imagedata, $textdata );
} else {
$message = array( $textdata, $imagedata );
}
}
$response = BWFCRM_Core()->conversation->send_whatsapp_message( $to, $message, $utm_details );
if ( is_array( $response ) && $response['status'] == true ) {
BWFCRM_Core()->conversation->update_conversation_status( $conversation_id, BWFAN_Email_Conversations::$STATUS_SEND );
return;
}
$error_message = __( 'Message could not be sent. ', 'wp-marketing-automations-pro' );
if ( isset( $response['msg'] ) && ! empty( $response['msg'] ) ) {
$error_message = $response['msg'];
}
BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $error_message );
}
public function process_broadcast_contact_sms( $broadcast, $contact_id ) {
$contact = new WooFunnels_Contact( '', '', '', absint( $contact_id ) );
if ( empty( $contact->get_id() ) ) {
return false;
}
$data = $broadcast['data'];
$broadcast_id = absint( $broadcast['id'] );
$broadcast_author_id = absint( $broadcast['created_by'] );
$broadcast_content = $data['content'];
if ( empty( $broadcast_content ) || ! is_array( $broadcast_content ) ) {
return false;
}
$template_winner = isset( $data['winner'] ) ? absint( $data['winner'] ) : 0;
$chosen_content = $this->get_current_email_template( $broadcast_content, $this->processed, $template_winner );
$sms_data = BWFCRM_Core()->conversation->create_campaign_conversation( $contact, $broadcast_id, $chosen_content['template_id'], $broadcast_author_id, BWFCRM_Campaigns::$CAMPAIGN_SMS );
if ( is_wp_error( $sms_data ) ) {
$e_data = $sms_data->get_error_data();
if ( is_array( $e_data ) && isset( $e_data['conversation_id'] ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( absint( $e_data['conversation_id'] ), $sms_data->get_error_message() );
}
return false;
}
if ( ! isset( $sms_data['conversation_id'] ) ) {
return false;
}
$conversation_id = absint( $sms_data['conversation_id'] );
$hash_code = isset( $sms_data['hash_code'] ) ? $sms_data['hash_code'] : '';
$template = isset( $sms_data['template'] ) ? $sms_data['template'] : '';
$utm_details = isset( $chosen_content['utm'] ) && is_array( $chosen_content['utm'] ) ? $this->get_utm_data_from_campaign_data( $chosen_content['utm'] ) : '';
$sms_body = BWFCRM_Core()->conversation->prepare_sms_body( $conversation_id, $contact_id, $hash_code, $template, $utm_details, $broadcast_id );
if ( true === $this->stop_broadcast_for_shorten_url ) {
return false;
}
if ( is_wp_error( $sms_body ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $sms_body->get_error_message() );
return false;
}
$to = BWFAN_PRO_Common::get_contact_full_number( $contact );
if ( empty( $to ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, __( 'No phone number found for this contact: ' . $contact_id, 'wp-marketing-automations-pro' ) );
return false;
}
$send_sms_result = BWFCRM_Common::send_sms( array(
'to' => $to,
'body' => $sms_body
) );
if ( is_wp_error( $send_sms_result ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $send_sms_result->get_error_message() );
return false;
}
return BWFCRM_Core()->conversation->update_conversation_status( $conversation_id, BWFAN_Email_Conversations::$STATUS_SEND );
}
/**
* Get template data for passing it to conversation
*
* @param int $tid
* @param array $raw_template_data
*
* @return array
* @since 3.5.3
*
*/
public function get_template_from_id( $tid = 0, $raw_template_data = [] ) {
if ( empty( $tid ) ) {
return [
'template' => 'editor' === $raw_template_data['type'] ? $raw_template_data['editor']['body'] : $raw_template_data['body'],
'subject' => $raw_template_data['subject'],
'type' => $raw_template_data['type']
];
}
if ( ! empty( $this->current_broadcast['template_data_by_id'][ $tid ] ) ) {
return $this->current_broadcast['template_data_by_id'][ $tid ];
}
$template = BWFAN_Model_Templates::get( $tid );
if ( ! empty( $template ) ) {
$this->current_broadcast['template_data_by_id'][ $tid ] = $template;
return $template;
}
return [
'template' => 'editor' === $raw_template_data['type'] ? $raw_template_data['editor']['body'] : $raw_template_data['body'],
'subject' => $raw_template_data['subject'],
'type' => $raw_template_data['type']
];
}
public function process_broadcast_contact_email( $broadcast, $contact_id ) {
$contact = new WooFunnels_Contact( '', '', '', absint( $contact_id ) );
if ( empty( $contact->get_id() ) ) {
return;
}
$data = $broadcast['data'];
$broadcast_id = absint( $broadcast['id'] );
$broadcast_author_id = absint( $broadcast['created_by'] );
$broadcast_content = $data['content'];
$template_winner = isset( $data['winner'] ) ? absint( $data['winner'] ) : 0;
$chosen_content = $this->get_current_email_template( $broadcast_content, $this->processed, $template_winner );
/**
* Get template data on the basic of template id if exists.
*/
$content_template = $this->get_template_from_id( $chosen_content['template_id'] ?? 0, $chosen_content );
$email_data = BWFCRM_Core()->conversation->create_campaign_conversation( $contact, $broadcast_id, $chosen_content['template_id'], $broadcast_author_id, BWFCRM_Campaigns::$CAMPAIGN_EMAIL, false, $content_template );
if ( true === $this->validate_email_data_error( $email_data ) ) {
return;
}
if ( ! isset( $email_data['conversation_id'] ) ) {
return;
}
$conversation_id = absint( $email_data['conversation_id'] );
$hash_code = isset( $email_data['hash_code'] ) ? $email_data['hash_code'] : '';
$template_type = isset( $chosen_content['type'] ) ? $chosen_content['type'] : '';
$email_subject = isset( $email_data['subject'] ) ? $email_data['subject'] : '';
$template = isset( $email_data['template'] ) ? $email_data['template'] : '';
$pre_header = isset( $chosen_content['preheader'] ) ? $chosen_content['preheader'] : '';
$utm_details = isset( $chosen_content['utm'] ) && is_array( $chosen_content['utm'] ) ? $this->get_utm_data_from_campaign_data( $chosen_content['utm'] ) : '';
$email_subject = BWFCRM_Core()->conversation->prepare_email_subject( $email_subject, $contact_id );
try {
$email_body = BWFCRM_Core()->conversation->prepare_email_body( $conversation_id, $contact_id, $hash_code, $template_type, $template, $pre_header, $utm_details, $broadcast_id );
} catch ( Error $e ) {
/** Update broadcast status to halt if any error comes in email body */
$data['halt_reason'] = __( 'Error in email body: ', 'wp-marketing-automations-pro' ) . $e->getMessage();
BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );
self::action_schedule_save_last_sent( $broadcast['id'] );
die();
}
if ( is_wp_error( $email_body ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $email_body->get_error_message() );
return;
}
$to = $contact->get_email();
if ( ! is_email( $to ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, __( 'No email found for this contact: ' . $contact_id, 'wp-marketing-automations-pro' ) );
return;
}
$headers = array(
'MIME-Version: 1.0',
'From: ' . $data['senders_name'] . ' <' . $data['senders_email'] . '>',
'Content-type: text/html;charset=UTF-8',
);
if ( isset( $data['replyto'] ) && ! empty( $data['replyto'] ) ) {
$headers[] = 'Reply-To: ' . $data['replyto'];
}
/** Set unsubscribe link in header */
$unsubscribe_link = BWFAN_PRO_Common::get_unsubscribe_link( [ 'uid' => $contact->get_uid(), 'broadcast_id' => $broadcast_id ] );
if ( ! empty( $unsubscribe_link ) ) {
$headers[] = "List-Unsubscribe: <$unsubscribe_link>";
$headers[] = "List-Unsubscribe-Post: List-Unsubscribe=One-Click";
}
$this->set_log( 'contact#' . $contact_id . '_before_wp_mail' );
do_action( 'bwfan_before_send_email', $data, $email_body );
$headers = apply_filters( 'bwfan_email_headers', $headers );
$result = $error = false;
try {
/** Process the email */
$result = $this->process_email( $to, $email_subject, $email_body, $headers );
} catch ( Error|Exception $e ) {
$error = $e->getMessage();
}
$this->set_log( 'contact#' . $contact_id . '_after_wp_mail' );
if ( true === $result ) {
BWFCRM_Core()->conversation->update_conversation_status( $conversation_id, BWFAN_Email_Conversations::$STATUS_SEND );
$this->failed_cids = [];
$this->err_msg = [];
return;
}
$error = empty( $error ) ? BWFCRM_Common::$captured_email_failed_message : $error;
$error = ! empty( $error ) ? $error : __( 'Email not sent (Error Unknown)', 'wp-marketing-automations-pro' );
BWFCRM_Core()->conversation->fail_the_conversation( $conversation_id, $error );
$should_pause = apply_filters( 'bwfan_broadcast_should_pause_on_failures', true, $broadcast_id );
$should_pause = is_bool( $should_pause ) ? $should_pause : true;
if ( $should_pause ) {
$this->failed_cids[] = $contact_id;
$this->err_msg[] = $error;
}
BWFCRM_Common::$captured_email_failed_message = null;
}
public function maybe_process_smart_send_data( $broadcast_id, $broadcast_data, $processed, $count, $type = 1 ) {
/** Return false if smart sending not enabled */
$smart_send_settings = is_array( $broadcast_data ) && isset( $broadcast_data['smart_send'] ) ? $broadcast_data['smart_send'] : false;
$is_smart_send_enabled = is_array( $smart_send_settings ) && isset( $smart_send_settings['enable'] ) && true === $smart_send_settings['enable'];
if ( ! $is_smart_send_enabled ) {
return false;
}
/** Return winner if already declared */
$winner = is_array( $broadcast_data ) && isset( $broadcast_data['winner'] ) && ! empty( $broadcast_data['winner'] ) ? absint( $broadcast_data['winner'] ) : false;
if ( $is_smart_send_enabled && ! empty( $winner ) ) {
return absint( $winner );
}
/** Return if smart sending process already over */
$is_smart_send_finished = is_array( $broadcast_data ) && isset( $broadcast_data['smart_sending_done'] ) && 1 === absint( $broadcast_data['smart_sending_done'] );
if ( $is_smart_send_enabled && $is_smart_send_finished ) {
return false;
}
/** Return false, if percent is not achieved yet */
$processed = ! empty( $processed ) ? absint( $processed ) : 0;
$percent = $is_smart_send_enabled && isset( $smart_send_settings['percent'] ) && ! empty( $smart_send_settings['percent'] ) ? absint( $smart_send_settings['percent'] ) : 0;
$percent_achieved = $is_smart_send_enabled ? absint( ( $processed / absint( $count ) ) * 100 ) : 0;
if ( empty( $percent ) || $percent_achieved < $percent ) {
return false;
}
/** Return 'hold', if percent is achieved, and need to wait for the most opens or clicked template */
$hold_broadcast = is_array( $broadcast_data ) && isset( $broadcast_data['hold_broadcast'] );
if ( ! $hold_broadcast ) {
$hours = $is_smart_send_enabled && isset( $smart_send_settings['hours'] ) ? absint( $smart_send_settings['hours'] ) : 0;
BWFAN_Model_Broadcast::hold_broadcast_for_smart_send( $broadcast_id, $hours );
return 'hold';
}
/** Return false, if unable to get the Winner */
$db_winner = BWFAN_Model_Broadcast::get_broadcast_tids_stats( intval( $broadcast_id ), intval( $type ) );
if ( ! is_array( $db_winner ) || empty( $db_winner['winner'] ) ) {
BWFAN_Model_Broadcast::remove_hold_flag_smart_sending( absint( $broadcast_id ) );
return false;
}
/** Return & declare winner */
BWFAN_Model_Broadcast::declare_the_winner_email( intval( $broadcast_id ), $db_winner );
$this->stop_broadcast_required = true;
return intval( $db_winner['winner'] );
}
public function get_current_email_template( $email_content, $processed, $winner_tid ) {
if ( ! empty( $winner_tid ) ) {
foreach ( $email_content as $email ) {
if ( ! is_array( $email ) || ! isset( $email['template_id'] ) || absint( $email['template_id'] ) !== absint( $winner_tid ) ) {
continue;
}
return $email;
}
}
return $email_content[ $processed % count( $email_content ) ];
}
public function get_utm_data_from_campaign_data( $utm_data ) {
$final_data = array();
$utm_keys = array(
'source' => 'utm_source',
'medium' => 'utm_medium',
'name' => 'utm_campaign',
'content' => 'utm_content',
'term' => 'utm_term',
);
foreach ( $utm_data as $key => $datum ) {
if ( ! isset( $utm_keys[ $key ] ) ) {
continue;
}
$final_data[ $utm_keys[ $key ] ] = $datum;
}
return $final_data;
}
/**
* Add gap between whatsapp messages
*/
public function whatsapp_delay_time() {
if ( is_null( $this->last_engagement_sent ) ) {
return;
}
$gap = 1;
$global_settings = BWFAN_Common::get_global_settings();
if ( isset( $global_settings['bwfan_whatsapp_gap_btw_message'] ) && ! empty( $global_settings['bwfan_whatsapp_gap_btw_message'] ) ) {
$gap = floatval( $global_settings['bwfan_whatsapp_gap_btw_message'] );
}
$sleep_time = $gap * 1000 * 1000;
/** Get Remaining sleep time, in Micro-Seconds */
$sleep_time = $sleep_time - ( ( microtime( true ) - $this->last_engagement_sent ) * 1000000 );
if ( $sleep_time > 0 && $sleep_time < 1000000 ) {
usleep( ceil( $sleep_time ) );
}
if ( $sleep_time >= 1000000 ) {
$gap = $sleep_time / 1000000;
sleep( ceil( $gap ) );
}
}
public function delay_time() {
$sleep_time = 1000000 - ( microtime( true ) - $this->started_send_mail_at ) * 1000000;
if ( $sleep_time > 0 ) {
usleep( ceil( $sleep_time ) );
}
$this->records_processed_in_one_sec = 0;
$this->started_send_mail_at = microtime( true );
}
/**
* Check if email daily limit reached
*
* @param int $type 1 email 2 sms 3 whatsapp
*
* @return bool
*/
public function maybe_daily_limit_reached( $type = 1 ) {
if ( empty( $this->default_email_settings ) ) {
$this->default_email_settings = BWFAN_Common::get_global_settings();
}
/** If not email campaign, no limit */
if ( self::$CAMPAIGN_EMAIL !== absint( $type ) ) {
return false;
}
$this->daily_achieved_limit = ( 0 === absint( $this->daily_achieved_limit ) ) ? BWFAN_Model_Engagement_Tracking::get_last_24_hours_conversations_count() : absint( $this->daily_achieved_limit );
$this->daily_message_limit = isset( $this->default_email_settings['bwfan_email_daily_limit'] ) ? absint( $this->default_email_settings['bwfan_email_daily_limit'] ) : 10000;
/** Setting default 10k email daily limit */
$this->daily_message_limit = empty( $this->daily_message_limit ) ? 10000 : $this->daily_message_limit;
return ( $this->daily_achieved_limit >= $this->daily_message_limit );
}
/**
* Set delay in broadcast execution
*
* @param $campaign
* @param string $time
* @param string $in
*/
public function set_delay( $campaign, $time = '10', $in = 'minutes' ) {
$execution_time = date( 'Y-m-d H:i:s', strtotime( "+$time $in", strtotime( $campaign['execution_time'] ) ) );
BWFAN_Model_Broadcast::update( array(
'execution_time' => $execution_time,
'last_modified' => current_time( 'mysql', 1 ),
), array(
'id' => $campaign['id'],
) );
}
public function get_daily_limit_status_array() {
if ( empty( $this->default_email_settings ) ) {
$this->default_email_settings = BWFAN_Common::get_global_settings();
}
return array(
'reached' => $this->maybe_daily_limit_reached(),
'daily_limit' => isset( $this->default_email_settings['bwfan_email_daily_limit'] ) ? absint( $this->default_email_settings['bwfan_email_daily_limit'] ) : 10000,
);
}
public function get_per_call_time() {
if ( defined( 'BWFCRM_BROADCAST_AS_CALL_SECONDS' ) ) {
return absint( BWFCRM_BROADCAST_AS_CALL_SECONDS );
}
return apply_filters( 'bwfan_as_per_call_time', 30 );
}
public function is_time_exceeded() {
return ( time() - $this->start_time ) >= $this->AS_CALL_TIME;
}
public function maybe_validate_twilio_settings() {
$broadcast = $this->current_broadcast;
$data = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : array();
if ( ! BWFCRM_Core()->conversation->is_twilio_connected() ) {
$data['halt_reason'] = __( 'Twilio Connector is not connected, during the schedule time of this campaign', 'wp-marketing-automations-pro' );
BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );
self::action_schedule_save_last_sent( $broadcast['id'] );
return false;
}
$sms_settings = BWFCRM_Core()->conversation->get_twilio_settings();
if ( ! is_array( $sms_settings ) || empty( $sms_settings ) || ! isset( $sms_settings['twilio_no'] ) || ! isset( $sms_settings['account_sid'] ) || ! isset( $sms_settings['auth_token'] ) ) {
$data['halt_reason'] = __( 'Twilio Connector\'s required details are not found. Please re-connect to Twilio connector again', 'wp-marketing-automations-pro' );
BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );
self::action_schedule_save_last_sent( $broadcast['id'] );
return false;
}
return true;
}
public function maybe_validate_whatsapp_settings() {
$broadcast = $this->current_broadcast;
$data = isset( $broadcast['data'] ) && ! empty( $broadcast['data'] ) ? $broadcast['data'] : array();
if ( ! BWFAN_Common::is_whatsapp_services_enabled() ) {
$data['halt_reason'] = __( 'No WhatsApp service connected', 'wp-marketing-automations-pro' );
BWFAN_Model_Broadcast::update_status_to_halt( $broadcast['id'], $data );
self::action_schedule_save_last_sent( $broadcast['id'] );
return false;
}
return true;
}
public function maybe_broadcast_running() {
$broadcast = $this->current_broadcast;
$last_modified = ! empty( $broadcast ) && isset( $broadcast['last_modified'] ) ? $broadcast['last_modified'] : false;
if ( false !== $last_modified ) {
$last_modified = time() - strtotime( $last_modified );
if ( $last_modified <= 5 ) {
return true;
}
}
return false;
}
public function get_current_broadcast_data() {
if ( ! is_array( $this->current_broadcast ) || ! isset( $this->current_broadcast['data'] ) ) {
return array();
}
return $this->current_broadcast['data'];
}
public function validate_email_data_error( $email_data ) {
if ( ! is_wp_error( $email_data ) ) {
return false;
}
$e_data = $email_data->get_error_data();
if ( is_array( $e_data ) && isset( $e_data['conversation_id'] ) ) {
BWFCRM_Core()->conversation->fail_the_conversation( absint( $e_data['conversation_id'] ), $email_data->get_error_message() );
}
return true;
}
public function process_email( $to, $email_subject, $email_body, $headers ) {
BWFCRM_Common::$captured_email_failed_message = null;
return wp_mail( $to, $email_subject, $email_body, $headers );
}
public function set_log( $log ) {
if ( empty( $log ) ) {
return;
}
$this->logs[] = array(
't' => microtime( true ),
'm' => $log,
);
}
protected function log() {
if ( ! is_array( $this->logs ) || 0 === count( $this->logs ) ) {
return;
}
if ( false === apply_filters( 'bwfan_allow_broadcast_logging', BWFAN_PRO_Common::is_log_enabled( 'bwfan_broadcast_logging' ) ) ) {
return;
}
add_filter( 'bwfan_before_making_logs', '__return_true' );
BWFAN_Core()->logger->log( print_r( $this->logs, true ), 'fka-broadcast' );
$this->logs = [];
}
/**
* Schedule action for save last sent
*
* @param $broadcast_id
* @param $delete_key
*
* @return void
*/
public static function action_schedule_save_last_sent( $broadcast_id, $delete_key = true ) {
if ( empty( $broadcast_id ) ) {
return;
}
if ( ! bwf_has_action_scheduled( 'bwfan_broadcast_last_sent', array( $broadcast_id, $delete_key ), 'bwfcrm' ) ) {
bwf_schedule_recurring_action( time(), 60, 'bwfan_broadcast_last_sent', array( $broadcast_id, $delete_key ), 'bwfcrm' );
}
}
}