*/ private $template_form = array( 'recipient_type' => 'customer', 'order_status' => 'wc-processing', 'scheme' => 'sms', 'message' => '', 'preview_order' => '', ); /** * Constructor. */ public function __construct() { $this->template_repository = new TemplateRepository(); $this->template_renderer = new TemplateRenderer(); } /** * Render page. */ public function render(): void { $this->prepare_page(); $notice = $this->handle_template_submit(); $status_notice = $this->handle_statuses_submit(); if ( null === $notice ) { $notice = $status_notice; } $tabs = array( 'statuses' => __( 'Statuses', 'inteltele' ), 'templates' => __( 'Messages', 'inteltele' ), ); $tab = $this->get_current_tab( 'statuses', array_keys( $tabs ) ); $this->open_page( __( 'WooCommerce', 'inteltele' ) ); $this->render_tabs( $tabs, $tab, 'inteltele-woocommerce' ); $this->render_step_notice(); $this->render_notice( $notice ); if ( 'templates' === $tab ) { $this->render_templates_tab(); } else { $this->render_statuses_tab(); } $this->close_page(); } /** * Handle template form submission. * * @return array{type:string,messages:string[]}|null */ private function handle_template_submit(): ?array { if ( ! $this->is_post_request() ) { return null; } $action = isset( $_POST['inteltele_action'] ) ? sanitize_key( wp_unslash( $_POST['inteltele_action'] ) ) : ''; if ( ! in_array( $action, array( 'save_template', 'preview_template' ), true ) ) { return null; } if ( ! current_user_can( 'inteltele_manage' ) ) { return array( 'type' => 'error', 'messages' => array( __( 'You do not have permission to manage IntelTele templates.', 'inteltele' ) ), ); } if ( ! isset( $_POST['inteltele_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['inteltele_nonce'] ) ), 'inteltele_save_template' ) ) { return array( 'type' => 'error', 'messages' => array( __( 'Security check failed. Please reload the page and try again.', 'inteltele' ) ), ); } $this->template_form = array( 'recipient_type' => isset( $_POST['recipient_type'] ) ? sanitize_key( wp_unslash( $_POST['recipient_type'] ) ) : 'customer', 'order_status' => isset( $_POST['order_status'] ) ? sanitize_key( wp_unslash( $_POST['order_status'] ) ) : 'wc-processing', 'scheme' => isset( $_POST['scheme'] ) ? sanitize_key( wp_unslash( $_POST['scheme'] ) ) : 'sms', 'message' => isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '', 'preview_order' => isset( $_POST['preview_order'] ) ? (string) absint( wp_unslash( $_POST['preview_order'] ) ) : '', ); $errors = $this->validate_template_form(); if ( ! empty( $errors ) ) { return array( 'type' => 'error', 'messages' => $errors, ); } if ( 'preview_template' === $action ) { return null; } $saved = $this->template_repository->save( $this->template_form['recipient_type'], $this->template_form['order_status'], $this->template_form['scheme'], $this->template_form['message'], get_current_user_id() ); if ( $saved ) { $saved = $this->get_settings_repository()->save_wc_template_scheme( $this->template_form['recipient_type'], $this->template_form['order_status'], $this->template_form['scheme'] ); } if ( ! $saved ) { return array( 'type' => 'error', 'messages' => array( __( 'Template could not be saved. Please try again.', 'inteltele' ) ), ); } return array( 'type' => 'success', 'messages' => array( __( 'Saved.', 'inteltele' ) ), ); } /** * Validate template form. * * @return string[] */ private function validate_template_form(): array { $errors = array(); if ( ! in_array( $this->template_form['recipient_type'], array( 'customer', 'admin' ), true ) ) { $errors[] = __( 'Recipient type is invalid.', 'inteltele' ); } if ( ! array_key_exists( $this->template_form['order_status'], $this->get_order_statuses() ) ) { $errors[] = __( 'Order status is invalid.', 'inteltele' ); } if ( ! in_array( $this->template_form['scheme'], array( 'sms', 'viber', 'viber_sms' ), true ) ) { $errors[] = __( 'Selected sending scheme is invalid.', 'inteltele' ); } if ( '' === trim( $this->template_form['message'] ) ) { $errors[] = __( 'Message text is required.', 'inteltele' ); } $limit = 'sms' === $this->template_form['scheme'] ? 256 : 1000; if ( $this->string_length( $this->template_form['message'] ) > $limit ) { $errors[] = sprintf( /* translators: %d: character limit. */ __( 'Message text must not exceed %d characters.', 'inteltele' ), $limit ); } 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. */ 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 '
'; } /** * Render statuses tab. */ private function render_statuses_tab(): void { $statuses = $this->get_order_statuses(); $enabled = $this->get_settings_repository()->get_wc_statuses(); echo '
'; wp_nonce_field( 'inteltele_save_wc_statuses', 'inteltele_nonce' ); echo ''; echo '
'; $this->render_status_column( __( 'Administrator', 'inteltele' ), 'admin', $statuses, $enabled['admin'] ); $this->render_status_column( __( 'Customer', 'inteltele' ), 'customer', $statuses, $enabled['customer'] ); echo '
'; echo '

'; echo '
'; } /** * Render templates tab. */ private function render_templates_tab(): void { $this->hydrate_template_form_from_query(); $saved_template = $this->template_repository->get( $this->template_form['recipient_type'], $this->template_form['order_status'], $this->template_form['scheme'] ); if ( '' === $this->template_form['message'] && $saved_template ) { $this->template_form['message'] = (string) $saved_template->message; } $unknown_variables = TemplateRenderer::find_unknown_variables( $this->template_form['message'] ); $sms_segments = SmsSegments::calculate( $this->template_form['message'] ); $preview = $this->get_template_preview(); echo '
'; wp_nonce_field( 'inteltele_save_template', 'inteltele_nonce' ); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo '

'; echo ' '; echo ''; echo '

'; echo '
'; if ( null !== $preview ) { echo '

' . esc_html__( 'Message preview', 'inteltele' ) . '

'; echo ''; } if ( $saved_template && ! empty( $saved_template->updated_at ) ) { echo '

'; echo esc_html( sprintf( /* translators: 1: date, 2: user ID. */ __( 'Last updated: %1$s by user #%2$d.', 'inteltele' ), (string) $saved_template->updated_at, (int) $saved_template->updated_by ) ); echo '

'; } } /** * Hydrate template form values from GET for switching saved templates. */ private function hydrate_template_form_from_query(): void { if ( $this->is_post_request() ) { return; } $this->template_form['recipient_type'] = isset( $this->query['recipient_type'] ) ? sanitize_key( $this->query['recipient_type'] ) : $this->template_form['recipient_type']; $this->template_form['order_status'] = isset( $this->query['order_status'] ) ? sanitize_key( $this->query['order_status'] ) : $this->template_form['order_status']; $this->template_form['scheme'] = isset( $this->query['scheme'] ) ? sanitize_key( $this->query['scheme'] ) : $this->get_settings_repository()->get_wc_template_scheme( $this->template_form['recipient_type'], $this->template_form['order_status'] ); } /** * Render recent order select. */ private function render_order_select(): void { echo ''; } /** * Get recent WooCommerce orders. * * @return array */ private function get_recent_orders(): array { if ( ! function_exists( 'wc_get_orders' ) ) { return array(); } $orders = wc_get_orders( array( 'limit' => 20, 'orderby' => 'date', 'order' => 'DESC', 'return' => 'objects', ) ); $options = array(); foreach ( $orders as $order ) { if ( ! is_object( $order ) || ! method_exists( $order, 'get_id' ) ) { continue; } $order_id = (int) $order->get_id(); $options[ $order_id ] = sprintf( /* translators: 1: order ID, 2: customer name. */ __( 'Order #%1$d - %2$s', 'inteltele' ), $order_id, method_exists( $order, 'get_formatted_billing_full_name' ) ? (string) $order->get_formatted_billing_full_name() : '' ); } return $options; } /** * Get rendered preview text. */ private function get_template_preview(): ?string { if ( '' === $this->template_form['preview_order'] ) { return null; } if ( ! function_exists( 'wc_get_order' ) ) { return __( 'WooCommerce is not available.', 'inteltele' ); } $order = wc_get_order( absint( $this->template_form['preview_order'] ) ); if ( ! $order ) { return __( 'Selected order was not found.', 'inteltele' ); } return $this->template_renderer->render( $this->template_form['message'], $order ); } /** * Render status checkbox column. * * @param string $title Column title. * @param string $name Input group name. * @param array $statuses Order statuses. * @param string[] $enabled Enabled status keys. */ private function render_status_column( string $title, string $name, array $statuses, array $enabled ): void { echo '
'; echo '' . esc_html( $title ) . ''; foreach ( $statuses as $status_key => $status_label ) { echo ''; } echo '
'; } /** * Handle statuses form. * * @return array{type:string,messages:string[]}|null */ private function handle_statuses_submit(): ?array { if ( ! $this->is_post_request() ) { return null; } $action = isset( $_POST['inteltele_action'] ) ? sanitize_key( wp_unslash( $_POST['inteltele_action'] ) ) : ''; if ( 'save_wc_statuses' !== $action ) { return null; } if ( ! current_user_can( 'inteltele_manage' ) ) { return array( 'type' => 'error', 'messages' => array( __( 'You do not have permission to manage WooCommerce status automation.', 'inteltele' ) ), ); } if ( ! isset( $_POST['inteltele_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['inteltele_nonce'] ) ), 'inteltele_save_wc_statuses' ) ) { return array( 'type' => 'error', 'messages' => array( __( 'Security check failed. Please reload the page and try again.', 'inteltele' ) ), ); } $available = array_keys( $this->get_order_statuses() ); $admin = $this->sanitize_status_selection( isset( $_POST['admin_statuses'] ) ? array_map( 'sanitize_key', (array) wp_unslash( $_POST['admin_statuses'] ) ) : array(), $available ); $customer = $this->sanitize_status_selection( isset( $_POST['customer_statuses'] ) ? array_map( 'sanitize_key', (array) wp_unslash( $_POST['customer_statuses'] ) ) : array(), $available ); if ( empty( $admin ) && empty( $customer ) ) { return array( 'type' => 'error', 'messages' => array( __( 'Select at least one order status.', 'inteltele' ) ), ); } $saved = $this->get_settings_repository()->save_wc_statuses( array( 'admin' => $admin, 'customer' => $customer, ) ); if ( ! $saved ) { return array( 'type' => 'error', 'messages' => array( __( 'Settings could not be saved. Please try again.', 'inteltele' ) ), ); } return array( 'type' => 'success', 'messages' => array( __( 'Saved.', 'inteltele' ) ), ); } /** * Sanitize selected statuses. * * @param mixed $raw Raw values. * @param string[] $available Available statuses. * @return string[] */ private function sanitize_status_selection( $raw, array $available ): array { $values = is_array( $raw ) ? $raw : array(); $result = array(); foreach ( $values as $value ) { $status = sanitize_key( $value ); if ( in_array( $status, $available, true ) ) { $result[] = $status; } } return array_values( array_unique( $result ) ); } /** * Get settings repository. */ private function get_settings_repository(): \IntelTele\Repositories\SettingsRepository { static $repository = null; if ( null === $repository ) { $repository = new \IntelTele\Repositories\SettingsRepository(); } return $repository; } /** * Get WooCommerce statuses or fallback defaults. * * @return array */ private function get_order_statuses(): array { if ( function_exists( 'wc_get_order_statuses' ) ) { return wc_get_order_statuses(); } return array( 'wc-pending' => __( 'Pending payment', 'inteltele' ), 'wc-processing' => __( 'Processing', 'inteltele' ), 'wc-on-hold' => __( 'On hold', 'inteltele' ), 'wc-completed' => __( 'Completed', 'inteltele' ), 'wc-cancelled' => __( 'Cancelled', 'inteltele' ), 'wc-refunded' => __( 'Refunded', 'inteltele' ), 'wc-failed' => __( 'Failed', 'inteltele' ), ); } }