*/
private $form_values = array(
'message' => '',
'fallback_message' => '',
'phone' => '',
'scheme' => 'sms',
);
/**
* Render page.
*/
public function render(): void {
$this->prepare_page();
$notice = $this->handle_single_submit();
$bulk_notice = $this->handle_bulk_submit();
if ( null === $notice ) {
$notice = $bulk_notice;
}
$tabs = array(
'single' => __( 'Single', 'inteltele' ),
'bulk' => __( 'Bulk', 'inteltele' ),
);
$tab = $this->get_current_tab( 'single', array_keys( $tabs ) );
$this->open_page( __( 'Send message', 'inteltele' ) );
$this->render_tabs( $tabs, $tab, 'inteltele-send' );
$this->render_step_notice();
$this->render_notice( $notice );
if ( 'bulk' === $tab ) {
$this->render_bulk_tab();
} else {
$this->render_single_tab();
}
$this->close_page();
}
/**
* Handle single message submission.
*
* @return array{type:string,messages:string[]}|null
*/
private function handle_single_submit(): ?array {
if ( ! $this->is_post_request() ) {
return null;
}
$action = isset( $_POST['inteltele_action'] ) ? sanitize_key( wp_unslash( $_POST['inteltele_action'] ) ) : '';
if ( 'send_single' !== $action ) {
return null;
}
if ( ! current_user_can( 'inteltele_manage' ) ) {
return array(
'type' => 'error',
'messages' => array( __( 'You do not have permission to send IntelTele messages.', 'inteltele' ) ),
);
}
if ( ! isset( $_POST['inteltele_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['inteltele_nonce'] ) ), 'inteltele_send_single' ) ) {
return array(
'type' => 'error',
'messages' => array( __( 'Security check failed. Please reload the page and try again.', 'inteltele' ) ),
);
}
$message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
$fallback_message = isset( $_POST['fallback_message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['fallback_message'] ) ) : '';
$phone = isset( $_POST['phone'] ) ? $this->normalize_phone( sanitize_text_field( wp_unslash( $_POST['phone'] ) ) ) : '';
$scheme = isset( $_POST['scheme'] ) ? sanitize_key( wp_unslash( $_POST['scheme'] ) ) : 'sms';
if ( isset( $_POST['transliterate'] ) ) {
$message = Transliterator::transliterate( $message );
$fallback_message = Transliterator::transliterate( $fallback_message );
}
$this->form_values = array(
'message' => $message,
'fallback_message' => $fallback_message,
'phone' => $phone,
'scheme' => $scheme,
);
$errors = $this->validate_single_form( $message, $fallback_message, $phone, $scheme );
if ( ! empty( $errors ) ) {
return array(
'type' => 'error',
'messages' => $errors,
);
}
$sender = new MessageSender();
$result = $sender->send_manual( $message, $phone, $scheme, 'viber_sms' === $scheme ? $fallback_message : '' );
return array(
'type' => $result['success'] ? 'success' : 'error',
'messages' => $result['messages'],
);
}
/**
* Validate single send form.
*
* @param string $message Message.
* @param string $fallback_message SMS fallback message.
* @param string $phone Phone.
* @param string $scheme Scheme.
* @return string[]
*/
private function validate_single_form( string $message, string $fallback_message, string $phone, string $scheme ): array {
$errors = array();
if ( '' === trim( $message ) ) {
$errors[] = __( 'Message text is required.', 'inteltele' );
}
$limit = 'sms' === $scheme ? 256 : 1000;
if ( $this->string_length( $message ) > $limit ) {
$errors[] = sprintf(
/* translators: %d: character limit. */
__( 'Message text must not exceed %d characters.', 'inteltele' ),
$limit
);
}
if ( 'viber_sms' === $scheme ) {
if ( '' === trim( $fallback_message ) ) {
$errors[] = __( 'SMS fallback text is required for Viber + SMS.', 'inteltele' );
}
if ( $this->string_length( $fallback_message ) > 256 ) {
$errors[] = __( 'SMS fallback text must not exceed 256 characters.', 'inteltele' );
}
}
if ( strlen( $phone ) < 10 || strlen( $phone ) > 15 ) {
$errors[] = __( 'Recipient phone must contain 10 to 15 digits.', 'inteltele' );
}
if ( ! in_array( $scheme, array( 'sms', 'viber', 'viber_sms' ), true ) ) {
$errors[] = __( 'Selected sending scheme is invalid.', 'inteltele' );
}
return $errors;
}
/**
* Get UTF-8 string length.
*
* @param string $value Value.
*/
private function string_length( string $value ): int {
if ( function_exists( 'mb_strlen' ) ) {
return mb_strlen( $value );
}
return strlen( $value );
}
/**
* Render submit notice.
*
* @param array{type:string,messages:string[]}|null $notice Notice data.
*/
private function render_notice( ?array $notice ): void {
if ( null === $notice ) {
return;
}
$class = 'success' === $notice['type'] ? 'notice-success' : 'notice-error';
echo '
';
foreach ( $notice['messages'] as $message ) {
echo '
' . esc_html( $message ) . '
';
}
echo '
';
}
/**
* Handle bulk campaign submission.
*
* @return array{type:string,messages:string[]}|null
*/
private function handle_bulk_submit(): ?array {
if ( ! $this->is_post_request() ) {
return null;
}
$action = isset( $_POST['inteltele_action'] ) ? sanitize_key( wp_unslash( $_POST['inteltele_action'] ) ) : '';
if ( 'start_bulk_campaign' !== $action ) {
return null;
}
if ( ! current_user_can( 'inteltele_manage' ) ) {
return array(
'type' => 'error',
'messages' => array( __( 'You do not have permission to send IntelTele messages.', 'inteltele' ) ),
);
}
if ( ! isset( $_POST['inteltele_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['inteltele_nonce'] ) ), 'inteltele_start_bulk_campaign' ) ) {
return array(
'type' => 'error',
'messages' => array( __( 'Security check failed. Please reload the page and try again.', 'inteltele' ) ),
);
}
$message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
$fallback_message = isset( $_POST['fallback_message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['fallback_message'] ) ) : '';
$scheme = isset( $_POST['scheme'] ) ? sanitize_key( wp_unslash( $_POST['scheme'] ) ) : 'sms';
$mode = isset( $_POST['recipient_mode'] ) ? sanitize_key( wp_unslash( $_POST['recipient_mode'] ) ) : 'role';
$role = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( $_POST['role'] ) ) : '';
$user_ids = isset( $_POST['user_ids'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['user_ids'] ) ) : array();
$user_search = isset( $_POST['user_search'] ) ? sanitize_text_field( wp_unslash( $_POST['user_search'] ) ) : '';
$order_statuses = isset( $_POST['order_statuses'] ) ? array_map( 'sanitize_key', (array) wp_unslash( $_POST['order_statuses'] ) ) : array();
$order_recipient_keys = isset( $_POST['order_recipient_keys'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['order_recipient_keys'] ) ) : array();
$date_from = isset( $_POST['order_date_from'] ) ? sanitize_text_field( wp_unslash( $_POST['order_date_from'] ) ) : '';
$date_to = isset( $_POST['order_date_to'] ) ? sanitize_text_field( wp_unslash( $_POST['order_date_to'] ) ) : '';
$errors = $this->validate_bulk_form( $message, $fallback_message, $scheme, $mode, $role, $user_ids, $order_statuses );
if ( ! empty( $errors ) ) {
return array(
'type' => 'error',
'messages' => $errors,
);
}
$recipients = $this->get_bulk_recipients( $mode, $role, $user_ids, $user_search, $order_statuses, $date_from, $date_to, $order_recipient_keys );
if ( empty( $recipients ) ) {
return array(
'type' => 'error',
'messages' => array( __( 'No recipients with phone numbers were found.', 'inteltele' ) ),
);
}
$repository = new CampaignRepository();
$campaign_id = $repository->create( get_current_user_id(), $scheme, $message, $recipients, 'viber_sms' === $scheme ? $fallback_message : '' );
if ( 0 === $campaign_id ) {
return array(
'type' => 'error',
'messages' => array( __( 'Campaign could not be created. Please try again.', 'inteltele' ) ),
);
}
$processor = new CampaignProcessor();
$processor->process_batch( $campaign_id );
$campaign = $repository->get( $campaign_id );
return array(
'type' => 'success',
'messages' => array(
__( 'Bulk campaign created.', 'inteltele' ),
$campaign ? sprintf(
/* translators: 1: processed count, 2: total count. */
__( 'Processed %1$d of %2$d recipients.', 'inteltele' ),
(int) $campaign->processed_count,
(int) $campaign->recipients_total
) : '',
),
);
}
/**
* Validate bulk campaign form.
*
* @param string $message Message.
* @param string $scheme Scheme.
* @param string $mode Recipient mode.
* @param string $role Role.
* @param int[] $user_ids User IDs.
* @return string[]
*/
private function validate_bulk_form( string $message, string $fallback_message, string $scheme, string $mode, string $role, array $user_ids, array $order_statuses ): array {
$errors = array();
if ( '' === trim( $message ) ) {
$errors[] = __( 'Message text is required.', 'inteltele' );
}
$message_limit = 'sms' === $scheme ? 256 : 1000;
if ( $this->string_length( $message ) > $message_limit ) {
$errors[] = sprintf(
/* translators: %d: character limit. */
__( 'Message text must not exceed %d characters.', 'inteltele' ),
$message_limit
);
}
if ( 'viber_sms' === $scheme ) {
if ( '' === trim( $fallback_message ) ) {
$errors[] = __( 'SMS fallback text is required for Viber + SMS.', 'inteltele' );
}
if ( $this->string_length( $fallback_message ) > 256 ) {
$errors[] = __( 'SMS fallback text must not exceed 256 characters.', 'inteltele' );
}
}
if ( ! in_array( $scheme, array( 'sms', 'viber', 'viber_sms' ), true ) ) {
$errors[] = __( 'Selected sending scheme is invalid.', 'inteltele' );
}
if ( ! in_array( $mode, array( 'role', 'users', 'users_filtered', 'orders' ), true ) ) {
$errors[] = __( 'Recipient selection mode is invalid.', 'inteltele' );
}
if ( 'role' === $mode && ! array_key_exists( $role, wp_roles()->roles ) ) {
$errors[] = __( 'Selected role is invalid.', 'inteltele' );
}
if ( 'users' === $mode && empty( $user_ids ) ) {
$errors[] = __( 'Select at least one user.', 'inteltele' );
}
if ( 'orders' === $mode && empty( $order_statuses ) ) {
$errors[] = __( 'Select at least one order status.', 'inteltele' );
}
return $errors;
}
/**
* Render single send form.
*/
private function render_single_tab(): void {
echo '';
}
/**
* Render bulk wizard placeholder.
*/
private function render_bulk_tab(): void {
$step = isset( $this->query['step'] ) ? absint( $this->query['step'] ) : 1;
$step = min( max( $step, 1 ), 4 );
echo '';
echo '
' . esc_html( sprintf( /* translators: %d: wizard step number. */ __( 'Step %d', 'inteltele' ), $step ) ) . '
';
if ( 1 === $step ) {
$this->render_bulk_step_one();
} elseif ( 2 === $step ) {
$this->render_bulk_step_two();
} elseif ( 3 === $step ) {
$this->render_bulk_step_three();
} else {
$this->render_bulk_step_four();
}
echo '';
}
/**
* Render bulk step one.
*/
private function render_bulk_step_one(): void {
echo '';
}
/**
* Render bulk step two.
*/
private function render_bulk_step_two(): void {
$mode = isset( $this->query['recipient_mode'] ) ? sanitize_key( $this->query['recipient_mode'] ) : 'role';
if ( 'users' === $mode || 'users_filtered' === $mode ) {
$this->render_user_picker();
return;
}
if ( 'orders' === $mode ) {
$this->render_order_buyers_picker();
return;
}
echo '';
}
/**
* Render bulk step three.
*/
private function render_bulk_step_three(): void {
$mode = isset( $this->query['recipient_mode'] ) ? sanitize_key( $this->query['recipient_mode'] ) : 'role';
$role = isset( $this->query['role'] ) ? sanitize_key( $this->query['role'] ) : '';
$user_ids = isset( $this->query['user_ids'] ) ? array_map( 'absint', (array) $this->query['user_ids'] ) : array();
$user_search = isset( $this->query['user_search'] ) ? sanitize_text_field( $this->query['user_search'] ) : '';
$order_statuses = isset( $this->query['order_statuses'] ) ? array_map( 'sanitize_key', (array) $this->query['order_statuses'] ) : array();
$order_recipient_keys = isset( $this->query['order_recipient_keys'] ) ? array_map( 'sanitize_text_field', (array) $this->query['order_recipient_keys'] ) : array();
$date_from = isset( $this->query['order_date_from'] ) ? sanitize_text_field( $this->query['order_date_from'] ) : '';
$date_to = isset( $this->query['order_date_to'] ) ? sanitize_text_field( $this->query['order_date_to'] ) : '';
$message = isset( $this->query['message'] ) ? sanitize_textarea_field( $this->query['message'] ) : '';
$fallback_message = isset( $this->query['fallback_message'] ) ? sanitize_textarea_field( $this->query['fallback_message'] ) : '';
$scheme = isset( $this->query['scheme'] ) ? sanitize_key( $this->query['scheme'] ) : 'sms';
$this->form_values['message'] = $message;
$this->form_values['fallback_message'] = $fallback_message;
$this->form_values['scheme'] = $scheme;
echo '';
}
/**
* Render bulk step four.
*/
private function render_bulk_step_four(): void {
$mode = isset( $this->query['recipient_mode'] ) ? sanitize_key( $this->query['recipient_mode'] ) : 'role';
$role = isset( $this->query['role'] ) ? sanitize_key( $this->query['role'] ) : '';
$user_ids = isset( $this->query['user_ids'] ) ? array_map( 'absint', (array) $this->query['user_ids'] ) : array();
$user_search = isset( $this->query['user_search'] ) ? sanitize_text_field( $this->query['user_search'] ) : '';
$order_statuses = isset( $this->query['order_statuses'] ) ? array_map( 'sanitize_key', (array) $this->query['order_statuses'] ) : array();
$order_recipient_keys = isset( $this->query['order_recipient_keys'] ) ? array_map( 'sanitize_text_field', (array) $this->query['order_recipient_keys'] ) : array();
$date_from = isset( $this->query['order_date_from'] ) ? sanitize_text_field( $this->query['order_date_from'] ) : '';
$date_to = isset( $this->query['order_date_to'] ) ? sanitize_text_field( $this->query['order_date_to'] ) : '';
$message = isset( $this->query['message'] ) ? sanitize_textarea_field( $this->query['message'] ) : '';
$fallback_message = isset( $this->query['fallback_message'] ) ? sanitize_textarea_field( $this->query['fallback_message'] ) : '';
$scheme = isset( $this->query['scheme'] ) ? sanitize_key( $this->query['scheme'] ) : 'sms';
$recipients = $this->get_bulk_recipients( $mode, $role, $user_ids, $user_search, $order_statuses, $date_from, $date_to, $order_recipient_keys );
$this->form_values['message'] = $message;
$this->form_values['fallback_message'] = $fallback_message;
$this->form_values['scheme'] = $scheme;
echo '';
}
/**
* Build a state-preserving back URL for bulk wizard steps.
*
* @param int $step Step number.
* @param string $mode Recipient mode.
* @param string $role Role.
* @param int[] $user_ids User IDs.
* @param string $user_search User search.
* @param string[] $order_statuses Order statuses.
* @param string[] $order_recipient_keys Selected order recipient keys.
* @param string $date_from Order date from.
* @param string $date_to Order date to.
* @param string $message Message.
* @param string $fallback_message SMS fallback message.
* @param string $scheme Scheme.
*/
private function get_bulk_step_back_url( int $step, string $mode, string $role, array $user_ids, string $user_search, array $order_statuses, array $order_recipient_keys, string $date_from, string $date_to, string $message, string $fallback_message, string $scheme ): string {
$args = array(
'page' => 'inteltele-send',
'tab' => 'bulk',
'step' => $step,
'recipient_mode' => $mode,
'role' => $role,
'user_search' => $user_search,
'user_ids' => $user_ids,
'order_statuses' => $order_statuses,
'order_recipient_keys' => $order_recipient_keys,
'order_date_from' => $date_from,
'order_date_to' => $date_to,
'message' => $message,
'fallback_message' => $fallback_message,
'scheme' => $scheme,
);
return $this->get_page_url( $args );
}
/**
* Render users picker placeholder.
*/
private function render_user_picker(): void {
$search = isset( $this->query['s'] ) ? sanitize_text_field( $this->query['s'] ) : '';
$users = $this->get_users_with_phones( $search );
echo '';
echo '';
}
/**
* Render WooCommerce order buyers picker.
*/
private function render_order_buyers_picker(): void {
$selected_statuses = isset( $this->query['order_statuses'] ) ? array_map( 'sanitize_key', (array) $this->query['order_statuses'] ) : array( 'wc-processing', 'wc-completed' );
$date_from = isset( $this->query['order_date_from'] ) ? sanitize_text_field( $this->query['order_date_from'] ) : '';
$date_to = isset( $this->query['order_date_to'] ) ? sanitize_text_field( $this->query['order_date_to'] ) : '';
$recipients = $this->get_recipients_by_orders( $selected_statuses, $date_from, $date_to );
echo '';
if ( ! isset( $this->query['orders_preview'] ) ) {
return;
}
echo '';
}
/**
* Render SMS fallback message row for Viber + SMS.
*/
private function render_fallback_message_row(): void {
$style = 'viber_sms' === $this->form_values['scheme'] ? '' : 'display:none;';
echo '';
echo ' | ';
echo '';
echo ' ' . esc_html__( 'Used only when the selected scheme is Viber + SMS.', 'inteltele' ) . ' | ';
echo '
';
}
/**
* Render message row.
*/
private function render_message_row(): void {
echo '';
echo ' | ';
echo '';
echo ' ' . esc_html__( 'SMS limit is 256 characters. Viber limit is 1000 characters.', 'inteltele' ) . ' ';
echo ' | ';
echo '
';
}
/**
* Render scheme row.
*/
private function render_scheme_row(): void {
echo '';
echo ' | ';
echo ' | ';
echo '
';
}
/**
* Render phone row.
*
* @param string $label Field label.
*/
private function render_phone_row( string $label ): void {
echo '';
echo ' | ';
echo ' | ';
echo '
';
}
/**
* Render wizard navigation buttons.
*
* @param int $back_step Back step.
*/
private function render_wizard_buttons( int $back_step ): void {
echo '';
echo '' . esc_html__( 'Back', 'inteltele' ) . ' ';
echo '';
echo '
';
}
/**
* Get users with phone numbers.
*
* @param string $search Search.
* @return array>
*/
private function get_users_with_phones( string $search = '' ): array {
$args = array(
'number' => 50,
'orderby' => 'display_name',
'order' => 'ASC',
);
if ( '' !== $search ) {
$args['search'] = '*' . $search . '*';
$args['search_columns'] = array( 'user_login', 'user_nicename', 'display_name', 'user_email' );
}
$users = get_users( $args );
$rows = array();
foreach ( $users as $user ) {
$phone = $this->get_user_phone( (int) $user->ID );
if ( '' === $phone ) {
continue;
}
if ( '' !== $search && false === stripos( $phone, $search ) && false === stripos( $user->display_name, $search ) ) {
continue;
}
$rows[] = array(
'user_id' => (int) $user->ID,
'phone' => $phone,
'name' => (string) $user->display_name,
'role' => $this->format_user_roles( (array) $user->roles ),
);
}
return $rows;
}
/**
* Get recipients for selected bulk mode.
*
* @param string $mode Recipient mode.
* @param string $role Role.
* @param int[] $user_ids User IDs.
* @param string $user_search User search.
* @param string[] $order_statuses Order statuses.
* @param string $date_from Date from.
* @param string $date_to Date to.
* @return array>
*/
private function get_bulk_recipients( string $mode, string $role, array $user_ids, string $user_search, array $order_statuses, string $date_from, string $date_to, array $order_recipient_keys = array() ): array {
if ( 'users' === $mode ) {
return $this->get_recipients_by_user_ids( $user_ids );
}
if ( 'users_filtered' === $mode ) {
return $this->get_recipients_by_filtered_users( $user_search );
}
if ( 'orders' === $mode ) {
$recipients = $this->get_recipients_by_orders( $order_statuses, $date_from, $date_to );
if ( empty( $order_recipient_keys ) ) {
return array();
}
$selected = array();
foreach ( $order_recipient_keys as $recipient_key ) {
$normalized_key = $this->normalize_order_recipient_key( (string) $recipient_key );
if ( '' !== $normalized_key ) {
$selected[ $normalized_key ] = true;
}
}
return array_values(
array_filter(
$recipients,
function ( array $recipient ) use ( $selected ): bool {
return isset( $selected[ $this->get_order_recipient_key( $recipient ) ] );
}
)
);
}
return $this->get_recipients_by_role( $role );
}
/**
* Get recipients by filtered user list.
*
* @param string $search Search.
* @return array>
*/
private function get_recipients_by_filtered_users( string $search = '' ): array {
$args = array(
'number' => 5000,
'orderby' => 'ID',
'order' => 'ASC',
);
if ( '' !== $search ) {
$args['search'] = '*' . $search . '*';
$args['search_columns'] = array( 'user_login', 'user_nicename', 'display_name', 'user_email' );
}
$user_ids = array_map(
static function ( $user ): int {
return (int) $user->ID;
},
get_users( $args )
);
return $this->get_recipients_by_user_ids( $user_ids );
}
/**
* Get recipients by WooCommerce orders.
*
* @param string[] $statuses Order statuses.
* @param string $date_from Date from.
* @param string $date_to Date to.
* @return array>
*/
private function get_recipients_by_orders( array $statuses, string $date_from = '', string $date_to = '' ): array {
if ( ! function_exists( 'wc_get_orders' ) ) {
return array();
}
$statuses = array_values( array_filter( array_map( 'sanitize_key', $statuses ) ) );
if ( empty( $statuses ) || in_array( 'all', $statuses, true ) ) {
$statuses = array_keys( $this->get_order_statuses() );
}
$args = array(
'limit' => 5000,
'status' => array_map(
static function ( string $status ): string {
return 0 === strpos( $status, 'wc-' ) ? substr( $status, 3 ) : $status;
},
$statuses
),
'orderby' => 'date',
'order' => 'DESC',
'return' => 'objects',
);
if ( '' !== $date_from || '' !== $date_to ) {
$args['date_created'] = ( '' === $date_from ? '1970-01-01' : $date_from ) . '...' . ( '' === $date_to ? gmdate( 'Y-m-d' ) : $date_to );
}
$orders = wc_get_orders( $args );
$recipients = array();
$seen = array();
foreach ( $orders as $order ) {
if ( ! is_a( $order, 'WC_Order' ) ) {
continue;
}
$phone = $this->normalize_phone( (string) $order->get_billing_phone() );
if ( '' === $phone || isset( $seen[ $phone ] ) ) {
continue;
}
$seen[ $phone ] = true;
$name = trim( (string) $order->get_billing_first_name() . ' ' . (string) $order->get_billing_last_name() );
$recipients[] = array(
'user_id' => (int) $order->get_user_id(),
'order_id' => (int) $order->get_id(),
'phone' => $phone,
'name' => '' === $name ? (string) $order->get_billing_email() : $name,
'role' => 'woocommerce_buyer',
);
}
return $recipients;
}
/**
* Get stable key for an order-based recipient row.
*
* @param array $recipient Recipient.
*/
private function get_order_recipient_key( array $recipient ): string {
$phone = isset( $recipient['phone'] ) ? (string) $recipient['phone'] : '';
return $this->normalize_order_recipient_key( $phone );
}
/**
* Normalize an order recipient selection key.
*
* @param string $key Selection key.
*/
private function normalize_order_recipient_key( string $key ): string {
if ( false !== strpos( $key, ':' ) ) {
$parts = explode( ':', $key );
$key = (string) end( $parts );
}
return (string) preg_replace( '/\D+/', '', $key );
}
/**
* Get recipients by role.
*
* @param string $role Role.
* @return array>
*/
private function get_recipients_by_role( string $role ): array {
if ( '' === $role || ! array_key_exists( $role, wp_roles()->roles ) ) {
return array();
}
$users = get_users(
array(
'role' => $role,
'number' => 1000,
'orderby' => 'ID',
'order' => 'ASC',
)
);
$recipients = array();
foreach ( $users as $user ) {
$phone = $this->get_user_phone( (int) $user->ID );
if ( '' === $phone ) {
continue;
}
$recipients[] = array(
'user_id' => (int) $user->ID,
'phone' => $phone,
'name' => (string) $user->display_name,
'role' => $role,
);
}
return $recipients;
}
/**
* Get recipients by selected users.
*
* @param int[] $user_ids User IDs.
* @return array>
*/
private function get_recipients_by_user_ids( array $user_ids ): array {
$recipients = array();
foreach ( array_unique( array_filter( array_map( 'absint', $user_ids ) ) ) as $user_id ) {
$user = get_userdata( $user_id );
if ( ! $user ) {
continue;
}
$phone = $this->get_user_phone( $user_id );
if ( '' === $phone ) {
continue;
}
$recipients[] = array(
'user_id' => $user_id,
'phone' => $phone,
'name' => (string) $user->display_name,
'role' => $this->format_user_roles( (array) $user->roles ),
);
}
return $recipients;
}
/**
* Get user phone from common WooCommerce meta fields.
*
* @param int $user_id User ID.
*/
private function get_user_phone( int $user_id ): string {
foreach ( array( 'billing_phone', 'phone', 'shipping_phone' ) as $meta_key ) {
$phone = $this->normalize_phone( (string) get_user_meta( $user_id, $meta_key, true ) );
if ( '' !== $phone ) {
return $phone;
}
}
return '';
}
/**
* Normalize Ukrainian phone numbers to 380XXXXXXXXX where possible.
*
* @param string $phone Raw phone.
*/
private function normalize_phone( string $phone ): string {
$digits = preg_replace( '/\D+/', '', $phone );
$digits = null === $digits ? '' : $digits;
if ( 10 === strlen( $digits ) && '0' === substr( $digits, 0, 1 ) ) {
$digits = '38' . $digits;
}
if ( 11 === strlen( $digits ) && '8' === substr( $digits, 0, 1 ) ) {
$digits = '3' . $digits;
}
if ( strlen( $digits ) < 10 || strlen( $digits ) > 15 ) {
return '';
}
return $digits;
}
/**
* Get unique phones from recipient-like rows.
*
* @param array> $recipients Recipients.
* @return string[]
*/
private function unique_phones_from_recipients( array $recipients ): array {
$phones = array();
foreach ( $recipients as $recipient ) {
if ( empty( $recipient['phone'] ) ) {
continue;
}
$phones[ (string) $recipient['phone'] ] = true;
}
return array_keys( $phones );
}
/**
* Get WooCommerce order statuses.
*
* @return array
*/
private function get_order_statuses(): array {
if ( function_exists( 'wc_get_order_statuses' ) ) {
return array_merge( array( 'all' => __( 'All', 'inteltele' ) ), wc_get_order_statuses() );
}
return array(
'all' => __( 'All', 'inteltele' ),
'wc-pending' => __( 'Pending payment', 'inteltele' ),
'wc-processing' => __( 'Processing', 'inteltele' ),
'wc-completed' => __( 'Completed', 'inteltele' ),
'wc-cancelled' => __( 'Cancelled', 'inteltele' ),
'wc-refunded' => __( 'Refunded', 'inteltele' ),
'wc-failed' => __( 'Failed', 'inteltele' ),
);
}
/**
* Format user roles.
*
* @param string[] $roles Roles.
*/
private function format_user_roles( array $roles ): string {
$wp_roles = wp_roles()->roles;
$labels = array();
foreach ( $roles as $role ) {
$labels[] = isset( $wp_roles[ $role ] ) ? translate_user_role( $wp_roles[ $role ]['name'] ) : $role;
}
return implode( ', ', $labels );
}
/**
* Format scheme.
*
* @param string $scheme Scheme.
*/
private function format_scheme( string $scheme ): string {
$map = array(
'sms' => __( 'SMS', 'inteltele' ),
'viber' => __( 'Viber', 'inteltele' ),
'viber_sms' => __( 'Viber + SMS', 'inteltele' ),
);
return $map[ $scheme ] ?? $scheme;
}
}