dor_by_product' ) || ! PWSMS()->get_option( 'product_admin_dokan_integration' ) ) { return $empty_array; } // Load general statuses $general_statuses = PWSMS()->get_option( 'product_admin_meta_order_status' ); $prepared_statuses = PWSMS()->prepare_admin_product_status( $general_statuses, false ); // Get vendor $vendor = dokan_get_vendor_by_product( $product_id ); if ( empty( $vendor ) || ! is_object( $vendor ) ) { return $empty_array; } // Get vendor mobile $vendor_mobile = $vendor->get_phone(); if ( ! $this->validate_mobile( $vendor_mobile ) ) { return $empty_array; } return [ 'meta' => 'dokan', 'mobile' => $vendor_mobile, 'statuses' => $prepared_statuses, ]; } public function get_dokan_vendor_name( int $product_id ): string { if ( ! function_exists( 'dokan_get_vendor_by_product' ) || ! PWSMS()->get_option( 'product_admin_dokan_integration' ) ) { return ''; } $vendor = dokan_get_vendor_by_product( $product_id ); if ( empty( $vendor ) || ! is_object( $vendor ) ) { return ''; } return method_exists( $vendor, 'get_shop_name' ) ? $vendor->get_shop_name() : ''; } public function get_user_mobile_meta( $post_id = 0 ) { $meta = 'user'; $empty_array = [ 'meta' => $meta, 'mobile' => '', 'statuses' => '' ]; $data = $this->get_saved_mobile_data( $meta, $post_id, $empty_array ); if ( is_array( $data ) ) { return $data; } // product_admin_user_meta usage from settings (custom meta key) $meta_key = $this->get_option( "product_admin_{$meta}_meta" ); if ( empty( $meta_key ) ) { unset( $empty_array['meta'] ); return $empty_array; } $post_id = intval( $data ); $post = get_post( $post_id ); if ( empty( $post->post_author ) ) { return $empty_array; } return [ 'meta' => $meta, 'mobile' => get_user_meta( $post->post_author, $meta_key, true ), 'statuses' => $this->get_option( 'product_admin_meta_order_status' ), ]; } public function get_saved_mobile_data( $meta, $post_id = 0, $empty_array = [] ) { if ( empty( $post_id ) ) { $screen = get_current_screen(); // Check if we are on the post editing screen if ( empty( $screen ) || 'product' !== $screen->post_type ) { return $empty_array; } $post_id = isset( $_GET['post'] ) ? intval( $_GET['post'] ) : 0; } if ( empty( $post_id ) ) { return $empty_array; } $product = wc_get_product( $post_id ); if ( ! PWSMS()->is_wc_product( $product ) ) { return $empty_array; } $data = $product->get_meta( '_pwoosms_product_admin_meta_' . $meta, true ); if ( ! empty( $data ) ) { return (array) $data;//mobile and statuses that set via admin } return $post_id; } public function get_post_mobile_meta( $post_id = 0 ) { $meta = 'post'; $empty_array = [ 'meta' => $meta, 'mobile' => '', 'statuses' => '' ]; $data = $this->get_saved_mobile_data( $meta, $post_id, $empty_array ); if ( is_array( $data ) ) { return $data; } // product_admin_post_meta usage from settings (custom meta key) $meta_key = $this->get_option( "product_admin_{$meta}_meta" ); if ( empty( $meta_key ) ) { unset( $empty_array['meta'] ); return $empty_array; } $post_id = intval( $data ); $product = wc_get_product( $post_id ); $mobile = $product->get_meta( $meta_key, true ); return [ 'meta' => $meta, 'mobile' => $mobile, 'statuses' => $this->get_option( 'product_admin_meta_order_status' ), ]; } /** * Validate an Iranian mobile number after normalizing it. * * Accept numbers in these formats (based on `modify_mobile()` output): * - Local format: 09121234567 * - International: +989121234567 * * @param string $mobile The raw input mobile number. * * @return bool */ public function validate_mobile( string $mobile = '' ): bool { if ( empty( $mobile ) ) { return false; } // Normalize the input using modify_mobile (cleans, formats) $mobile = $this->modify_mobile( $mobile ); // Match local format: 09121234567 if ( preg_match( '/^09\d{9}$/', $mobile ) ) { return true; } // Match international format: +989121234567 if ( preg_match( '/^\+989\d{9}$/', $mobile ) ) { return true; } // Optionally, allow fallback format: 0 followed by 9 digits // Example: 09121234567 or 09121234567 if ( preg_match( '/^0\d{10}$/', $mobile ) ) { return true; } return false; } /** * Modify and normalize a mobile phone number or an array of numbers. * * - Converts any localized digits (e.g., Persian/Arabic numerals) to English. * - Removes all non-digit characters. * - Converts numbers starting with '+', '00', or nothing into a standardized local format. * - Preserves '+' if the original number began with it. * - Handles an array of numbers recursively. * * @param string|array $mobile A single mobile number or an array of mobile numbers. * * @return string|array Modified mobile number(s) in a normalized format. */ public function modify_mobile( $mobile ) { if ( empty( $mobile ) ) { return is_array( $mobile ) ? [] : ''; } if ( is_array( $mobile ) ) { return array_map( [ $this, __FUNCTION__ ], $mobile ); } // '۰۹۱۲۱۲۳۴۵۶۷' -> '09121234567' $mobile = $this->mobile_english_numbers( $mobile ); // '0912-123-4567' -> '09121234567' $modified = preg_replace( '/\D/is', '', (string) $mobile ); // '+989121234567' -> '+989121234567' if ( substr( $mobile, 0, 1 ) == '+' ) { return '+' . $modified; } // '00989121234567' -> '+989121234567' if ( substr( $modified, 0, 2 ) == '00' ) { return '+' . substr( $modified, 2 ); } // Example: '09121234567' -> '09121234567' if ( substr( $modified, 0, 1 ) == '0' ) { return $modified; } // '9121234567' -> '09121234567' if ( ! empty( $modified ) ) { $modified = '0' . $modified; } // '+9809121234567' -> '09121234567' return str_replace( '+980', '0', $modified ); } public function prepare_admin_product_status( $statuses, $array = true ) { $delimiter = '-sv-'; if ( ! is_array( $statuses ) ) { $statuses = explode( $delimiter, $statuses ); } $statuses = array_map( 'trim', $statuses ); $statuses = array_map( [ $this, 'sanitize_text_field' ], $statuses ); $statuses = array_unique( array_filter( $statuses ) ); // It's necessary for comparison sort( $statuses ); if ( $array ) { return $statuses; } return implode( $delimiter, $statuses ); } public function product_admin_items( $order_products, $product_ids ) { $items = []; if ( empty( $product_ids ) ) { return $items; } if ( is_array( $product_ids ) ) { $product_ids = array_unique( $product_ids ); } else { $product_ids = [ $product_ids ]; } foreach ( $product_ids as $product_id ) { $item_data = $order_products[ $product_id ]; foreach ( (array) $item_data as $item ) { $this->prepare_items( $items, $item ); } } $items['product_ids'] = $product_ids; return $items; } public function order_ID( $order ) { if ( is_numeric( $order ) ) { return intval( $order ); } return $this->order_prop( $order, 'id' ); } public function order_note_metabox( WC_Order $order ) { if ( ! class_exists( 'WC_Meta_Box_Order_Notes' ) ) { return ''; } if ( ! method_exists( 'WC_Meta_Box_Order_Notes', 'output' ) ) { return ''; } ob_start(); WC_Meta_Box_Order_Notes::output( $order ); return ob_get_clean(); } public function SendSMS( $data ) { _doing_it_wrong( __METHOD__, 'SendSMS() is deprecated. Use send_sms() instead.', '6' ); return $this->send_sms( $data ); } public function send_sms( $data ) { $message = ! empty( $data['message'] ) ? esc_textarea( $data['message'] ) : ''; $mobile = ! empty( $data['mobile'] ) ? $data['mobile'] : ''; if ( ! is_array( $mobile ) ) { $mobile = explode( ',', $mobile ); } $mobile = explode( ',', implode( ',', (array) $mobile ) ); $mobile = array_map( 'trim', $mobile ); $mobile = array_filter( $mobile, [ $this, 'validate_mobile' ] ); $mobile = array_unique( array_filter( $mobile ) ); if ( empty( $mobile ) ) { return 'شماره موبایل خالی/نامعتبر است.'; } if ( empty( $message ) ) { return 'متن پیامک خالی است.'; } $gateway_obj = null; try { $gateway_obj = $this->get_sms_gateway(); $gateway_obj->mobile = $mobile; $gateway_obj->message = $message; $result = $gateway_obj->send( $data ); } catch ( Exception $e ) { $result = $e->getMessage(); } if ( ! is_bool( $result ) && ! is_string( $result ) ) { $result = var_export( $result, true ); } $sender_number = ( is_object( $gateway_obj ) && isset( $gateway_obj->senderNumber ) ) ? $gateway_obj->senderNumber : 'نامشخص'; $sender_details = is_object( $gateway_obj ) && method_exists( $gateway_obj, 'name' ) ? "{$gateway_obj->name()} ( {$sender_number} ) " : "درگاه نامشخص ( {$sender_number} )"; Archive::insert_record( [ 'post_id' => ! empty( $data['post_id'] ) ? $data['post_id'] : '', 'type' => ! empty( $data['type'] ) ? $data['type'] : 0, 'reciever' => implode( ',', (array) $mobile ), 'message' => $message, 'sender' => $sender_details, 'result' => $result === true ? '_ok_' : $result, ] ); return $result; } /** * * Return the current active gateway * * @return Gateway * @throws Exception */ public static function get_sms_gateway() { $active_gateway = PWSMS()->get_option( 'sms_gateway' ); if ( ! class_exists( $active_gateway ) || ! is_subclass_of( $active_gateway, Gateway::class ) ) { throw new Exception( 'ابتدا وبسرویس ارسال پیامک را از بخش «پیامک حرفه‌ای ووکامرس ← وبسرویس» انتخاب و پیکربندی کنید.' ); } return new $active_gateway(); } public function get_sms_gateways() { $gateways = []; // Gateways are static as namespace and directory $namespace = 'PW\PWSMS\Gateways'; $dir = PWSMS_DIR . '/src/Gateways'; // Scan the directory for PHP files foreach ( glob( "$dir/*.php" ) as $file ) { $class = basename( $file, '.php' ); // Create Full Qualified Class Name based on file names without .php $fqcn = "$namespace\\$class"; $active_gateway = PWSMS()->get_option( 'sms_gateway' ); if ( empty( $active_gateway ) ) { $active_gateway = Logger::class; } if ( class_exists( $active_gateway ) ) { $active_gateway_id = $active_gateway::id(); } else { // Rollback support on older systems where the raw id of sms gateway got stored $active_gateway_id = $active_gateway; } try { $reflectionClass = new ReflectionClass( $fqcn ); } catch ( Exception $e ) { continue; } // Check if the target class implements GatewayInterface and exclude main classes if ( $reflectionClass->isSubclassOf( Gateway::class ) ) { $id = $fqcn::id(); $name = $fqcn::name(); if ( $id == $active_gateway_id ) { // Migrate sms_gateway to the fully qualified class name $this->update_option( 'sms_main_settings', 'sms_gateway', $fqcn ); } $gateways[ $fqcn ] = $name; } } return apply_filters( 'pwoosms_sms_gateways', $gateways ); } public function update_option( $section, $option, $value ) { $section_settings = get_option( $section ); if ( $section_settings && isset( $section_settings[ $option ] ) ) { $section_settings[ $option ] = $value; update_option( $section, $section_settings ); } } /** * Check if HPOS enabled. */ public function is_wc_order_hpos_enabled() { return function_exists( 'wc_get_container' ) ? wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() : false; } public function extract_last_ten_digits( $phone_number ) { // Remove non-digit characters $phone_number = preg_replace( '/\D/', '', $phone_number ); // Check the length of the cleaned phone number if ( strlen( $phone_number ) < 10 ) { return false; // Not enough digits } // Extract the last 10 digits return substr( $phone_number, - 10 ); } /** * Generate admin orders dashboard link with custom query * * @param string $query * * @return string The admin orders dashboard query link */ public function get_admin_order_dashboard_url( string $query = '' ): string { if ( $this->is_wc_order_hpos_enabled() ) { $dashboard_url = 'admin.php?page=wc-orders&'; } else { $dashboard_url = 'edit.php?post_type=shop_order&'; } $dashboard_url .= $query; return admin_url( $dashboard_url ); } /** * Retrieve a valid Dokan vendor by ID. * * @param int $vendor_id * * @return Vendor|null */ public function get_vendor( int $vendor_id ): ?Vendor { $vendor = dokan()->vendor->get( $vendor_id ); return ( $vendor && $vendor->get_id() ) ? $vendor : null; } /** * Retrieve the vendor's phone number. * * @param Vendor $vendor The vendor object. * * @return string|null The vendor phone number, or null if unavailable. */ public function get_vendor_phone( Vendor $vendor ): ?string { return $vendor->get_phone(); } }