Plugin Directory

Changeset 3371202


Ignore:
Timestamp:
10/01/2025 03:23:44 PM (5 months ago)
Author:
philiasolutions
Message:
  • Bugs fixes and improvements
  • Supports Multi-Vendor & Multi-Inventory (Dokan)
Location:
philia-integration
Files:
31 added
1 deleted
7 edited

Legend:

Unmodified
Added
Removed
  • philia-integration/trunk/includes/cashier/class-cashier-fields.php

    r3368679 r3371202  
    6060        // Enqueue necessary JavaScript for AJAX
    6161        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
     62
     63        // hook for register new seller
     64        add_action('user_register', array( $this, 'dokan_spmv_clone_all_products_for_new_seller' ), 10, 2);
    6265    }
    6366
     
    156159            'philia_cashier_fields_section',
    157160            array( 'field_name' => 'login_image_url' )
     161        );
     162
     163        add_settings_field(
     164            'shop_type',
     165            __( 'Shop Type', 'philia-integration' ),
     166            array( $this, 'shop_type_callback' ),
     167            'philia-cashier-fields',
     168            'philia_cashier_fields_section'
    158169        );
    159170
     
    291302
    292303    /**
     304     * Renders the Shop Type select field on the settings page.
     305     *
     306     * @return void Outputs the HTML for the shop type select field.
     307     */
     308    public function shop_type_callback()
     309    {
     310        $cashier_fields = get_option( 'philia_cashier_fields' );
     311        $value = isset( $cashier_fields['shop_type'] ) ? $cashier_fields['shop_type'] : '';
     312        ?>
     313        <select name="philia_cashier_fields[shop_type]" id="shop-type-field">
     314            <option value=""><?php esc_html_e( 'Select Shop Type', 'philia-integration' ); ?></option>
     315            <option value="single-shop" <?php selected( $value, 'single-shop' ); ?>><?php esc_html_e( 'Single Shop', 'philia-integration' ); ?></option>
     316            <option value="multi-vendor" <?php selected( $value, 'multi-vendor' ); ?>><?php esc_html_e( 'Multi Vendor', 'philia-integration' ); ?></option>
     317            <option value="multi-inventory" <?php selected( $value, 'multi-inventory' ); ?>><?php esc_html_e( 'Multi Inventory', 'philia-integration' ); ?></option>
     318        </select>
     319        <?php
     320    }
     321
     322    /**
    293323     * Enqueues necessary JavaScript files for AJAX actions on the settings page.
    294324     *
     
    313343
    314344    /**
     345     * Automatically clones all base products for a new seller upon registration.
     346     *
     347     * This function hooks into the 'user_register' action. When a new user
     348     * with the 'seller' role is created, it iterates through all published
     349     * products. For each product group (identified by map_id), it creates a
     350     * clone for the new seller, ensuring they only get one copy from each
     351     * unique product line and not from other sellers' clones.
     352     *
     353     * @param int   $user_id  The ID of the newly registered user.
     354     * @param array $userdata The user data array.
     355     */
     356    public function dokan_spmv_clone_all_products_for_new_seller($user_id, $userdata)
     357    {
     358        // 1. Check if the new user is a seller.
     359        if ($userdata['role'] != 'seller' ) {
     360            return;
     361        }
     362
     363        // 2. Ensure Dokan SPMV functionality is active.
     364        if (!class_exists('Dokan_SPMV_Products') || !class_exists('WC_Admin_Duplicate_Product')) {
     365            error_log('Dokan SPMV or WooCommerce is not active. Cloning process aborted for user ID: ' . $user_id);
     366            return;
     367        }
     368
     369        $cashier_settings = get_option('philia_cashier_fields');
     370        $shop_type = $cashier_settings['shop_type'] ?? 'single-shop';
     371        if( $shop_type != 'multi-inventory' ) {
     372            // skip if shop type equal multi-inventory
     373            return;
     374        }
     375
     376        global $wpdb;
     377        $spmv_products_instance = new \Dokan_SPMV_Products();
     378        $dokan_product_map_table = $wpdb->prefix . 'dokan_product_map';
     379
     380        // Get all administrator user IDs
     381        $admins = get_users( array(
     382            'role'   => 'administrator',
     383            'fields' => 'ID',
     384        ) );
     385
     386        // 3. Get all original, published products.
     387        $args = array(
     388            'post_type'      => 'product',
     389            'posts_per_page' => -1,
     390            'post_status'    => 'publish',
     391            'author__in'     => $admins,
     392        );
     393
     394        $all_products_query = new \WP_Query($args);
     395
     396        if (!$all_products_query->have_posts()) {
     397            return;
     398        }
     399
     400        // 4. Loop through each product and clone it for the new seller.
     401        while ($all_products_query->have_posts()) {
     402            $all_products_query->the_post();
     403            $original_product_id = get_the_ID();
     404            $original_product = wc_get_product($original_product_id);
     405
     406            if (!$original_product) {
     407                continue;
     408            }
     409
     410            // Get the map_id to identify the product group.
     411            $map_id = get_post_meta($original_product_id, '_has_multi_vendor', true);
     412
     413            // 5. CRITICAL CHECK: If a map_id exists, check if this seller already has a product in this group.
     414            // This prevents cloning from other sellers' clones and ensures uniqueness.
     415            if ($map_id) {
     416                $existing_clone_count = $wpdb->get_var($wpdb->prepare(
     417                    "SELECT COUNT(*) FROM `{$dokan_product_map_table}` WHERE `map_id` = %d AND `seller_id` = %d",
     418                    $map_id,
     419                    $user_id
     420                ));
     421
     422                if ($existing_clone_count > 0) {
     423                    // This seller already has a version of this product, so skip to the next one.
     424                    continue;
     425                }
     426            }
     427
     428            // A. Duplicate the product post.
     429            $wc_duplicator = new \WC_Admin_Duplicate_Product();
     430            $cloned_product = $wc_duplicator->product_duplicate($original_product);
     431            $cloned_product_id = $cloned_product->get_id();
     432
     433            if (!$cloned_product_id) {
     434                continue;
     435            }
     436
     437            // B. Set the desired status for the new product.
     438            $product_status = apply_filters('dokan_cloned_product_status', dokan_get_new_post_status());
     439
     440            // C. Manage the mapping ID.
     441            $update_product_ids = [];
     442            if (!$map_id) {
     443                // This is the first time this product is being cloned, create a new map_id.
     444                $map_id = $spmv_products_instance->get_next_map_id();
     445                $update_product_ids[] = $original_product_id; // Add original product to the map.
     446            }
     447            $update_product_ids[] = $cloned_product_id; // Add the new clone to the map.
     448
     449            // D. Update the mapping table and product meta.
     450            if ($spmv_products_instance->set_map_id($map_id, $update_product_ids)) {
     451                update_post_meta($original_product_id, '_has_multi_vendor', $map_id);
     452                update_post_meta($cloned_product_id, '_has_multi_vendor', $map_id);
     453            }
     454
     455            // E. Update the cloned product's author and status.
     456            wp_update_post(array(
     457                'ID'          => $cloned_product_id,
     458                'post_author' => $user_id,
     459                'post_status' => $product_status,
     460                'post_title'  => $original_product->get_title(), // Ensure title is consistent.
     461            ));
     462
     463            // F. Update the status in the custom dokan map table as well.
     464            $spmv_products_instance->update_product_status($cloned_product_id, $product_status);
     465
     466            // G. Fire the action hook for compatibility with other functions.
     467            do_action('dokan_spmv_create_clone_product', $cloned_product_id, $original_product_id, $map_id);
     468        }
     469
     470        wp_reset_postdata();
     471    }
     472
     473    /**
    315474     * Callback for the settings section description.
    316475     *
     
    358517        $sanitized_input['logo_url']        = isset( $input['logo_url'] ) ? esc_url_raw( $input['logo_url'] ) : '';
    359518        $sanitized_input['login_image_url'] = isset( $input['login_image_url'] ) ? esc_url_raw( $input['login_image_url'] ) : '';
     519        $sanitized_input['shop_type']   = isset( $input['shop_type'] ) ? sanitize_text_field( $input['shop_type'] ) : '';
    360520        $sanitized_input['barcode_field']   = isset( $input['barcode_field'] ) ? sanitize_text_field( $input['barcode_field'] ) : '';
    361521
  • philia-integration/trunk/includes/cashier/rest-routes/class-cashier-route.php

    r3368487 r3371202  
    4444
    4545        if( empty( $username ) || empty( $password ) ) {
    46             return new \WP_REST_Response(['error' => 'username and password is required.'], 401);
     46            return new \WP_REST_Response(['error' => esc_attr__('username and password is required.', 'philia-integration') ], 401);
    4747        }
    4848   
     
    5151   
    5252        if (is_wp_error($user)) {
    53             return new \WP_REST_Response(['error' => 'Invalid username or password.'], 401);
     53            return new \WP_REST_Response(['error' => esc_attr__('Invalid username or password.', 'philia-integration') ], 401);
    5454        }
    5555   
    5656        // Check user role
    57         if (!in_array('philia_cashier', $user->roles) && !in_array('administrator', $user->roles) && !in_array('shop_manager', $user->roles)) {
    58             return new \WP_REST_Response(['error' => 'Unauthorized role.'], 403);
     57        if (!in_array('seller', $user->roles) && !in_array('philia_woo_cashier', $user->roles) && !in_array('administrator', $user->roles) && !in_array('shop_manager', $user->roles)) {
     58            return new \WP_REST_Response(['error' => esc_attr__('Unauthorized role.', 'philia-integration') ], 403);
    5959        }
    6060   
     
    6666        set_transient('philia_access_token_' . $user->ID, $access_token, $expiration);
    6767        set_transient('philia_access_token_expires_' . $user->ID, $expiration, $expiration);
    68    
    69         return new \WP_REST_Response(['access_token' => $access_token, 'expires_in' => time() + $expiration], 200);
     68
     69        // Get primary role (if multiple roles exist, take the first one)
     70        $user_role = !empty($user->roles) ? $user->roles[0] : '';
     71
     72        // Return token, expiry, and role
     73        return new \WP_REST_Response([
     74            'access_token' => $access_token,
     75            'expires_in'   => time() + $expiration,
     76            'role'         => $user_role
     77        ], 200);
    7078    }
    7179
     
    8088
    8189        if (!is_array($cashier_settings)) {
    82             return new \WP_REST_Response(['error' => 'No cashier fields found.'], 404);
     90            return new \WP_REST_Response(['error' => esc_attr__('No cashier fields found.', 'philia-integration') ], 404);
    8391        }
    8492
     
    100108        $final_cashier_fields['logo_url'] = $cashier_settings['logo_url'];
    101109        $final_cashier_fields['login_image_url'] = $cashier_settings['login_image_url'];
     110        $final_cashier_fields['shop_type'] = $cashier_settings['shop_type'];
    102111       
    103112        return new \WP_REST_Response($final_cashier_fields, 200);
     
    138147        $user_query = new \WP_User_Query([
    139148            'limit' => -1,
    140             'role__in' => ['philia_woo_cashier', 'shop_manager', 'administrator']
     149            'role__in' => ['philia_woo_cashier', 'shop_manager', 'administrator', 'seller']
    141150        ]);
    142151
     
    157166    }
    158167
     168    /**
     169     * Retrieves the user info by the access token if valid and not expired.
     170     *
     171     * @param \WP_REST_Request $request The REST API request object.
     172     * @return \WP_User|false User object if token is valid, false otherwise.
     173     */
     174    public static function get_user_by_token(\WP_REST_Request $request)
     175    {
     176        $headers = $request->get_headers();
     177        $auth_header = isset($headers['authorization']) ? $headers['authorization'][0] : '';
     178
     179        if (empty($auth_header) || strpos($auth_header, 'API-Key ') !== 0) {
     180            return false;
     181        }
     182
     183        $access_token = str_replace('API-Key ', '', $auth_header);
     184        $user_id = self::get_user_id_by_token($access_token);
     185
     186        if (!$user_id) {
     187            return false;
     188        }
     189
     190        return get_userdata($user_id);
     191    }
    159192}
    160193
  • philia-integration/trunk/includes/cashier/rest-routes/class-invoice-route.php

    r3368679 r3371202  
    100100        $page      = $request->get_param('page') ? absint($request->get_param('page')) : 1;
    101101        $page_size = $request->get_param('page_size') ? absint($request->get_param('page_size')) : 10;
     102       
     103        $user_is_seller = 0;
     104        $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     105        if ($user && in_array('seller', (array)$user->roles)) {
     106            $user_is_seller = 1;
     107        }
    102108
    103109        // Build the query arguments. These arguments work for both HPOS and legacy storage.
     
    130136            if (!$order instanceof \WC_Order) {
    131137                continue;
     138            }
     139
     140            // check seller
     141            $seller_ids = dokan_get_seller_id_by_order( $order->get_id() );
     142
     143            // Assuming one seller per order, get the first seller ID
     144            $seller_id = ( is_array( $seller_ids ) ) ? reset( $seller_ids ) : (int) $seller_ids;
     145
     146            if ( $user_is_seller && $user->ID != $seller_id )
     147            {
     148                continue;
     149            }
     150
     151            // get seller info
     152            $store_info = [];
     153            if( function_exists('dokan_get_seller_id_by_order'))
     154            {
     155                // Get the seller ID from the order
     156                $seller_ids = dokan_get_seller_id_by_order( $order->get_id() );
     157
     158                // Assuming one seller per order, get the first seller ID
     159                $seller_id = ( is_array( $seller_ids ) ) ? reset( $seller_ids ) : (int) $seller_ids;
     160
     161                if ( $seller_id )
     162                {
     163                    // Get the seller's user data
     164                    $seller_data = get_userdata( $seller_id );
     165
     166                    // Get the seller's store information
     167                    $store_info = dokan_get_store_info( $seller_id );
     168
     169                    // Retrieve the required information
     170                    $store_info = [
     171                        'seller_first_name' => $seller_data->first_name,
     172                        'seller_last_name' => $seller_data->last_name,
     173                        'store_name' => isset( $store_info['store_name'] ) ? $store_info['store_name'] : '',
     174                        'store_phone' => isset( $store_info['phone'] ) ? $store_info['phone'] : ''
     175                    ];
     176                }
    132177            }
    133178
     
    160205                'note'        => $order->get_customer_note(),
    161206                'products'    => $products,
     207                'store_info'      => ( !empty( $store_info ) ) ? $store_info : null
    162208            ];
    163209        }
     
    238284        $order_subtotal = floatval($order->get_subtotal());
    239285        $order_total_tax = floatval($order->get_total_tax());
     286
     287        // get seller info
     288        $store_info = [];
     289        if( function_exists('dokan_get_seller_id_by_order'))
     290        {
     291            // Get the seller ID from the order
     292            $seller_ids = dokan_get_seller_id_by_order( $order->get_id() );
     293
     294            // Assuming one seller per order, get the first seller ID
     295            $seller_id = ( is_array( $seller_ids ) ) ? reset( $seller_ids ) : (int) $seller_ids;
     296
     297            if ( $seller_id )
     298            {
     299                // Get the seller's user data
     300                $seller_data = get_userdata( $seller_id );
     301
     302                // Get the seller's store information
     303                $store_info = dokan_get_store_info( $seller_id );
     304
     305                // Retrieve the required information
     306                $store_info = [
     307                    'seller_first_name' => $seller_data->first_name,
     308                    'seller_last_name' => $seller_data->last_name,
     309                    'store_name' => isset( $store_info['store_name'] ) ? $store_info['store_name'] : '',
     310                    'store_phone' => isset( $store_info['phone'] ) ? $store_info['phone'] : ''
     311                ];
     312            }
     313        }
    240314
    241315        $data = [
     
    256330            'cashier_fields'  => ($order->get_meta('_cashier_fields')) ? json_decode($order->get_meta('_cashier_fields'), true) : [],
    257331            'used_coupons'    => $used_coupons,
     332            'store_info'      => ( !empty( $store_info ) ) ? $store_info : null
    258333        ];
    259334
     
    391466                    $last_notice = end($notices);
    392467                    if (isset($last_notice['notice'])) {
    393                         $error_message = wc_strip_all_tags($last_notice['notice']);
     468                        $error_message = esc_attr($last_notice['notice']);
    394469                    }
    395470                }
     
    407482    }
    408483
    409 /**
     484    /**
    410485     * Calculate invoice total, discounts, and coupon applications.
    411486     * FINAL VERSION: Uses wc_attribute_label() and resolves all namespace issues.
     
    418493        $global_coupons_param_input = $request->get_param('coupon');
    419494        $customer_id = $request->has_param('user_id') ? intval($request->get_param('user_id')) : 0;
     495        $cashier_settings = get_option('philia_cashier_fields');
     496        $shop_type = $cashier_settings['shop_type'] ?? 'single-shop';
    420497
    421498        if (empty($products_param) || !is_array($products_param)) {
     
    439516                $quantity = absint($product_data['quantity']);
    440517                $cart_item_data = ['philia_pos_index' => $index];
     518
     519                // set vendor for un-selected products
     520                if ( function_exists( 'dokan_get_vendor_by_product' ) )
     521                {
     522                    if( $shop_type === 'multi-inventory' || $shop_type === 'multi-vendor')
     523                    {
     524                        $seller_id = dokan_get_vendor_by_product( $product_id );
     525
     526                        if ( ! $seller_id ) {
     527                            $logged_user_info = Philia_Cashier_REST_Controller::get_user_by_token($request);
     528                            if ( $logged_user_info ) {
     529                                update_post_meta( $product_id, '_dokan_product_author', $logged_user_info->ID );
     530                            }
     531                        }
     532                    }
     533                }
    441534
    442535                $main_product = wc_get_product($product_id);
     
    596689                    'price'           => round($original_price, wc_get_price_decimals()),
    597690                    'quantity'        => $quantity,
     691                    'stock'           => $product_obj->get_stock_quantity(),
    598692                    'totalPrice'      => round($line_total_excl_tax, wc_get_price_decimals()),
    599693                    'totalDiscount'   => round($line_discount_amount, wc_get_price_decimals()),
  • philia-integration/trunk/includes/cashier/rest-routes/class-product-route.php

    r3368679 r3371202  
    116116    public function create_product(\WP_REST_Request $request)
    117117    {
     118        // check permission for access
     119        $cashier_settings = get_option('philia_cashier_fields');
     120        $shop_type = $cashier_settings['shop_type'] ?? 'single-shop';
     121        $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     122
     123        if (!$user) {
     124            return new \WP_Error('invalid_token', 'Invalid access token or user not found.', ['status' => 401]);
     125        }
     126
     127        $user_roles = (array) $user->roles;
     128        $is_admin = in_array('administrator', $user_roles);
     129        $is_seller = in_array('seller', $user_roles);
     130
     131        if (!$is_admin) {
     132            if ($is_seller && $shop_type === 'multi-inventory') {
     133                return new \WP_Error('permission_denied', esc_attr__('Sellers cannot create products in a multi-inventory setup.', 'philia-integration'), ['status' => 403]);
     134            }
     135            if ($is_seller && $shop_type !== 'multi-vendor') {
     136                return new \WP_Error('permission_denied', esc_attr__('Sellers can only create products in a multi-vendor setup.', 'philia-integration'), ['status' => 403]);
     137            }
     138            if (!$is_seller) {
     139                return new \WP_Error('permission_denied', esc_attr__('You do not have permission to create products.', 'philia-integration'), ['status' => 403]);
     140            }
     141        }
     142
    118143        $name          = sanitize_text_field($request->get_param('title'));
    119144        $status_param  = intval($request->get_param('status'));
     
    183208    public function update_product(\WP_REST_Request $request)
    184209    {
     210        $cashier_settings = get_option('philia_cashier_fields');
     211        $shop_type = $cashier_settings['shop_type'] ?? 'single-shop';
     212        $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     213
     214        if (!$user) {
     215            return new \WP_Error('invalid_token', 'Invalid access token or user not found.', ['status' => 401]);
     216        }
     217
    185218        $product_id = intval($request->get_param('id'));
    186219        $product = wc_get_product($product_id);
    187220
    188221        if (!$product) {
    189             return new \WP_Error('not_found', 'Product not found.', ['status' => 404]);
    190         }
    191 
    192         // Update common properties if they are provided in the request
    193         $name = $request->get_param('title');
    194         if ($name !== null) {
    195             $product->set_name(sanitize_text_field($name));
    196         }
    197 
    198         $status_param = $request->get_param('status');
    199         if ($status_param !== null) {
    200             $product->set_status(intval($status_param) === 0 ? 'draft' : 'publish');
    201         }
    202        
    203         $category_ids = $request->get_param('product_cat');
    204         if ($category_ids !== null && is_array($category_ids)) {
    205             $product->set_category_ids(array_map('intval', $category_ids));
    206         }
    207        
    208         $sku = $request->get_param('sku');
    209         if ($sku !== null) {
    210             $product->set_sku(sanitize_text_field($sku));
    211         }
    212 
    213         // --- Product Type Specific Updates ---
    214         if ($product->is_type('variable')) {
    215             $variations_data = $request->get_param('variations');
    216             if ($variations_data !== null && is_array($variations_data)) {
    217                 foreach ($variations_data as $v_data) {
    218                     if (!is_array($v_data) || empty($v_data['id'])) continue;
    219 
    220                     $variation = wc_get_product(intval($v_data['id']));
    221                     if ($variation && $variation->is_type('variation') && $variation->get_parent_id() === $product_id) {
    222                        
    223                         if (isset($v_data['regular_price'])) {
    224                             $variation->set_regular_price(floatval($v_data['regular_price']));
     222            return new \WP_Error('not_found', esc_attr__('Product not found.', 'philia-integration'), ['status' => 404]);
     223        }
     224
     225        $product_author_id = get_post_field('post_author', $product_id);
     226        $user_roles = (array) $user->roles;
     227        $is_admin = in_array('administrator', $user_roles);
     228        $is_seller = in_array('seller', $user_roles);
     229
     230        // check product access only for owner
     231        if (!$is_admin && $product_author_id != $user->ID) {
     232            return new \WP_Error('permission_denied', esc_attr__('You do not have permission to edit this product.', 'philia-inegration'), ['status' => 403]);
     233        }
     234
     235        // update product when user role is seller
     236        if ($is_seller && $shop_type === 'multi-inventory') {
     237            if ($product->is_type('variable')) {
     238                $variations_data = $request->get_param('variations');
     239                if (is_array($variations_data)) {
     240                    foreach ($variations_data as $v_data) {
     241                        if (empty($v_data['id'])) continue;
     242                        $variation = wc_get_product(intval($v_data['id']));
     243                        if ($variation && $variation->get_parent_id() === $product_id) {
     244                            if (isset($v_data['regular_price'])) $variation->set_regular_price(floatval($v_data['regular_price']));
     245                            if (isset($v_data['sale_price'])) $variation->set_sale_price($v_data['sale_price'] !== null ? floatval($v_data['sale_price']) : '');
     246                            if (isset($v_data['stock'])) {
     247                                $variation->set_manage_stock(true);
     248                                $variation->set_stock_quantity(intval($v_data['stock']));
     249                            }
     250                            $variation->save();
    225251                        }
    226                        
    227                         if (isset($v_data['sale_price'])) {
    228                             $sale_price = $v_data['sale_price'] !== null ? floatval($v_data['sale_price']) : '';
    229                             $variation->set_sale_price($sale_price);
     252                    }
     253                }
     254            } else { // simple product
     255                if ($request->get_param('regular_price') !== null) $product->set_regular_price(floatval($request->get_param('regular_price')));
     256                if ($request->get_param('sale_price') !== null) $product->set_sale_price($request->get_param('sale_price') !== '' ? floatval($request->get_param('sale_price')) : '');
     257                if ($request->get_param('stock') !== null) {
     258                    $product->set_manage_stock(true);
     259                    $product->set_stock_quantity(intval($request->get_param('stock')));
     260                }
     261            }
     262            $product->save();
     263            return new \WP_REST_Response(['id' => $product->get_id(), 'message' => 'Product inventory and price updated.'], 200);
     264        }
     265
     266        if ($is_admin || ($is_seller && $shop_type === 'multi-vendor'))
     267        {
     268            // Update common properties if they are provided in the request
     269            $name = $request->get_param('title');
     270            if ($name !== null) {
     271                $product->set_name(sanitize_text_field($name));
     272            }
     273
     274            $status_param = $request->get_param('status');
     275            if ($status_param !== null) {
     276                $product->set_status(intval($status_param) === 0 ? 'draft' : 'publish');
     277            }
     278           
     279            $category_ids = $request->get_param('product_cat');
     280            if ($category_ids !== null && is_array($category_ids)) {
     281                $product->set_category_ids(array_map('intval', $category_ids));
     282            }
     283           
     284            $sku = $request->get_param('sku');
     285            if ($sku !== null) {
     286                $product->set_sku(sanitize_text_field($sku));
     287            }
     288
     289            // --- Product Type Specific Updates ---
     290            if ($product->is_type('variable')) {
     291                $variations_data = $request->get_param('variations');
     292                if ($variations_data !== null && is_array($variations_data)) {
     293                    foreach ($variations_data as $v_data) {
     294                        if (!is_array($v_data) || empty($v_data['id'])) continue;
     295
     296                        $variation = wc_get_product(intval($v_data['id']));
     297                        if ($variation && $variation->is_type('variation') && $variation->get_parent_id() === $product_id) {
     298                           
     299                            if (isset($v_data['regular_price'])) {
     300                                $variation->set_regular_price(floatval($v_data['regular_price']));
     301                            }
     302                           
     303                            if (isset($v_data['sale_price'])) {
     304                                $sale_price = $v_data['sale_price'] !== null ? floatval($v_data['sale_price']) : '';
     305                                $variation->set_sale_price($sale_price);
     306                            }
     307                           
     308                            if (isset($v_data['stock'])) {
     309                                $variation->set_manage_stock(true);
     310                                $variation->set_stock_quantity(intval($v_data['stock']));
     311                            }
     312                           
     313                            $variation->save();
    230314                        }
    231                        
    232                         if (isset($v_data['stock'])) {
    233                             $variation->set_manage_stock(true);
    234                             $variation->set_stock_quantity(intval($v_data['stock']));
    235                         }
    236                        
    237                         $variation->save();
    238315                    }
    239316                }
    240             }
    241         } else { // For simple products
    242             $regular_price = $request->get_param('regular_price');
    243             if ($regular_price !== null) {
    244                 $product->set_regular_price(floatval($regular_price));
    245             }
    246 
    247             $sale_price = $request->get_param('sale_price');
    248             if ($sale_price !== null) {
    249                 $product->set_sale_price($sale_price !== '' ? floatval($sale_price) : '');
    250             }
    251 
    252             $stock = $request->get_param('stock');
    253             if ($stock !== null) {
    254                 $product->set_manage_stock(true);
    255                 $product->set_stock_quantity(intval($stock));
    256             }
    257         }
    258 
    259         $result = $product->save();
    260         if (is_wp_error($result)) {
    261             return new \WP_Error('product_save_failed', 'Failed to save product: ' . $result->get_error_message(), ['status' => 500]);
    262         }
    263 
    264         // --- Optional Updates (Barcode & Image) ---
    265         $barcode = $request->get_param('barcode');
    266         $barcode_field = get_option('philia_cashier_fields')['barcode_field'] ?? '';
    267         if (!empty($barcode_field) && $barcode !== null) {
    268             update_post_meta($product->get_id(), $barcode_field, sanitize_text_field($barcode));
    269         }
    270 
    271         $image = $request->get_file_params()['image'] ?? null;
    272         if ($image && !empty($image['tmp_name'])) {
    273             require_once ABSPATH . 'wp-admin/includes/image.php';
    274             require_once ABSPATH . 'wp-admin/includes/file.php';
    275             require_once ABSPATH . 'wp-admin/includes/media.php';
    276            
    277             $attachment_id = media_handle_upload('image', 0);
    278             if (!is_wp_error($attachment_id)) {
    279                 set_post_thumbnail($product->get_id(), $attachment_id);
    280             }
    281         }
    282 
    283         return new \WP_REST_Response(['id' => $product->get_id(), 'message' => 'Product updated.'], 200);
     317            } else { // For simple products
     318                $regular_price = $request->get_param('regular_price');
     319                if ($regular_price !== null) {
     320                    $product->set_regular_price(floatval($regular_price));
     321                }
     322
     323                $sale_price = $request->get_param('sale_price');
     324                if ($sale_price !== null) {
     325                    $product->set_sale_price($sale_price !== '' ? floatval($sale_price) : '');
     326                }
     327
     328                $stock = $request->get_param('stock');
     329                if ($stock !== null) {
     330                    $product->set_manage_stock(true);
     331                    $product->set_stock_quantity(intval($stock));
     332                }
     333            }
     334
     335            $result = $product->save();
     336            if (is_wp_error($result)) {
     337                return new \WP_Error('product_save_failed', 'Failed to save product: ' . $result->get_error_message(), ['status' => 500]);
     338            }
     339
     340            // --- Optional Updates (Barcode & Image) ---
     341            $barcode = $request->get_param('barcode');
     342            $barcode_field = get_option('philia_cashier_fields')['barcode_field'] ?? '';
     343            if (!empty($barcode_field) && $barcode !== null) {
     344                update_post_meta($product->get_id(), $barcode_field, sanitize_text_field($barcode));
     345            }
     346
     347            $image = $request->get_file_params()['image'] ?? null;
     348            if ($image && !empty($image['tmp_name'])) {
     349                require_once ABSPATH . 'wp-admin/includes/image.php';
     350                require_once ABSPATH . 'wp-admin/includes/file.php';
     351                require_once ABSPATH . 'wp-admin/includes/media.php';
     352               
     353                $attachment_id = media_handle_upload('image', 0);
     354                if (!is_wp_error($attachment_id)) {
     355                    set_post_thumbnail($product->get_id(), $attachment_id);
     356                }
     357            }
     358
     359            return new \WP_REST_Response(['id' => $product->get_id(), 'message' => esc_attr__('Product updated.', 'philia-integration')], 200);
     360        }
     361
     362        return new \WP_Error('permission_denied', 'You do not have permission to update products in this configuration.', ['status' => 403]);
    284363    }
    285364
     
    290369     * @return \WP_REST_Response|\WP_Error JSON response indicating result of deletion.
    291370     */
    292     public function delete_product(\WP_REST_Request $request) {
     371    public function delete_product(\WP_REST_Request $request)
     372    {
     373        $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     374
     375        if (!$user) {
     376            return new \WP_Error('invalid_token', 'Invalid access token or user not found.', ['status' => 401]);
     377        }
     378
    293379        $product_id = intval($request->get_param('id'));
    294380        $product = wc_get_product($product_id);
     
    296382        if (!$product) {
    297383            return new \WP_Error('not_found', 'Product not found.', ['status' => 404]);
     384        }
     385
     386        $product_author_id = get_post_field('post_author', $product_id);
     387        $is_admin = in_array('administrator', (array)$user->roles);
     388
     389        if (!$is_admin && $product_author_id != $user->ID) {
     390            return new \WP_Error('permission_denied', 'You do not have permission to delete this product.', ['status' => 403]);
    298391        }
    299392
     
    316409        $username = absint($request->get_param('username')) > 0 ? intval($request->get_param('username')) : null;
    317410
     411        $cashier_settings = get_option('philia_cashier_fields');
     412        $shop_type = $cashier_settings['shop_type'] ?? 'single-shop';
     413       
    318414        $args = [
    319415            'status' => ['publish'],
     
    321417        ];
    322418
     419        // filter products by seller or owner id
     420        // if ($shop_type === 'multi-vendor' || $shop_type === 'multi-inventory') {
     421        //     $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     422        //     if ($user && !in_array('administrator', (array)$user->roles)) {
     423               
     424        //     }
     425        // }
     426        // get products by owner
     427        $cashier_settings = get_option('philia_cashier_fields');
     428        $shop_type = $cashier_settings['shop_type'] ?? 'single-shop';
     429        if( $shop_type != 'single-shop' )
     430        {
     431            $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     432            $args['author'] = $user->ID;   
     433        }
     434       
    323435        $category_id = $request->get_param('category');
    324436        if (!empty($category_id) && absint($category_id) > 0) {
     
    455567                    'id'                 => $variation->get_id(),
    456568                    'barcode'            => $variation->get_sku(),
    457                     'price'              => $variation->get_price(),
     569                    'regular_price'      => (float) $variation->get_regular_price(),
     570                    'sale_price'         => (float) $variation->get_sale_price(),
    458571                    'discounted_price'   => $variation->get_price(),
    459572                    'visible'            => $variation->is_visible(),
     
    481594            'categories'        => $product->get_category_ids(),
    482595            'category_names'    => $category_names,
    483             'price'             => $product->get_price(),
     596            'regular_price'     => (float) $product->get_regular_price(),
     597            'sale_price'        => (float) $product->get_sale_price(),
    484598            'description'       => $product->get_description(),
    485599            'stock'             => $product->get_stock_quantity(),
     
    498612    public function handle_product_import(\WP_REST_Request $request)
    499613    {
     614        $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     615
     616        // check access for importing product
     617        if (!$user || !in_array('administrator', (array)$user->roles)) {
     618            return new \WP_Error('permission_denied', esc_attr__('You do not have permission to import products.', 'philia-integration'), ['status' => 403]);
     619        }
     620
    500621        $files = $request->get_file_params();
    501622
     
    552673
    553674                set_transient('import_status_' . $import_id, $status, DAY_IN_SECONDS);
    554                 wp_schedule_single_event(time(), 'philia_process_product_import', [$import_id, $file_path]);
     675                wp_schedule_single_event(time(), 'philia_process_product_import', [$import_id, $user->ID, $file_path]);
    555676
    556677                return new \WP_REST_Response([
     
    597718        // Add the new key to the status array
    598719        $status['progress_percentage'] = $percentage;
    599         // --- END: ADDED CODE ---
    600720
    601721        return new \WP_REST_Response($status, 200);
     
    605725     * Main import processor function
    606726     */
    607     public static function run_import_processor($import_id, $file_path)
     727    public static function run_import_processor($import_id, $user_id, $file_path)
    608728    {
    609729        $status = get_transient('import_status_' . $import_id);
     
    645765                    switch (strtolower(trim($row['type'] ?? 'simple'))) {
    646766                        case 'variable':
    647                             $product_id = self::create_or_update_variable_product($row);
     767                            $product_id = self::create_or_update_variable_product($row, $user_id);
    648768                            if (!empty($row['barcode'])) {
    649769                                $variable_products_map[$row['barcode']] = $product_id;
     
    657777                                throw new \Exception("Parent product with barcode '{$parent_barcode}' not found.");
    658778                            }
    659                             self::create_or_update_variation($row, $parent_id);
     779                            self::create_or_update_variation($row, $parent_id, $user_id);
    660780                            break;
    661781                           
    662782                        case 'simple':
    663783                        default:
    664                             self::create_or_update_simple_product($row);
     784                            self::create_or_update_simple_product($row, $user_id);
    665785                            break;
    666786                    }
     
    695815    public function get_popular_products()
    696816    {
     817        $cashier_settings = get_option('philia_cashier_fields');
     818        $shop_type = $cashier_settings['shop_type'] ?? 'single-shop';
     819
    697820        $args = [
    698821            'status' => 'publish',
    699             'limit' => 10, // Change this limit as needed
     822            'limit' => 10,
    700823            'orderby' => 'popularity',
    701824        ];
    702825
     826        // filter products by seller or owner id
     827        if ($shop_type === 'multi-vendor' || $shop_type === 'multi-inventory') {
     828            $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
     829            if ($user && !in_array('administrator', (array)$user->roles)) {
     830                $args['author'] = $user->ID;
     831            }
     832        }
     833
    703834        $products = wc_get_products($args);
     835
    704836        $product_data = array_map(function($product) {
    705837            $image_url = wp_get_attachment_image_src( get_post_thumbnail_id( $product->get_id() ) , 'single-post-thumbnail' );
     
    804936     * @return array
    805937     */
    806     protected function get_attributes( $product ) {
     938    protected function get_attributes( $product )
     939    {
    807940        $attributes = array();
    808941
     
    9771110     * Create or update simple product with attributes
    9781111     */
    979     private static function create_or_update_simple_product(array $row)
     1112    private static function create_or_update_simple_product(array $row, $user_id)
    9801113    {
    9811114        $barcode = sanitize_text_field($row['barcode'] ?? '');
     
    10161149            }
    10171150        }
    1018         // --- END MODIFICATION ---
    10191151       
    10201152        // Stock management
     
    10401172        }
    10411173       
     1174        // Set product author if provided
     1175        if ($new_id) {
     1176            if ($user_id > 0) {
     1177                wp_update_post([
     1178                    'ID'          => $new_id,
     1179                    'post_author' => $user_id
     1180                ]);
     1181            }
     1182        }
     1183       
    10421184        return $new_id;
    10431185    }
     
    10461188     * Create or update variable product
    10471189     */
    1048     private static function create_or_update_variable_product(array $row)
     1190    private static function create_or_update_variable_product(array $row, $user_id)
    10491191    {
    10501192        $barcode = sanitize_text_field($row['barcode'] ?? '');
     
    10771219            self::set_product_barcode($new_id, $barcode);
    10781220        }
     1221
     1222        // Set product author if provided
     1223        if ($new_id) {
     1224            if ($user_id > 0) {
     1225                wp_update_post([
     1226                    'ID'          => $new_id,
     1227                    'post_author' => $user_id
     1228                ]);
     1229            }
     1230        }
    10791231       
    10801232        return $new_id;
     
    10841236     * Create or update product variation
    10851237     */
    1086     private static function create_or_update_variation(array $row, int $parent_id)
     1238    private static function create_or_update_variation(array $row, int $parent_id, int $user_id)
    10871239    {
    10881240        $barcode = sanitize_text_field($row['barcode'] ?? '');
     
    11551307        if ($new_id && !empty($barcode)) {
    11561308            self::set_product_barcode($new_id, $barcode);
     1309        }
     1310
     1311        // Set product author if provided
     1312        if ($new_id) {
     1313            if ($user_id > 0) {
     1314                wp_update_post([
     1315                    'ID'          => $new_id,
     1316                    'post_author' => $user_id
     1317                ]);
     1318            }
    11571319        }
    11581320       
     
    14151577
    14161578add_action( 'rest_api_init', 'Philia_API\register_philia_product_rest_controller' );
    1417 add_action('philia_process_product_import', ['Philia_API\Philia_Product_REST_Controller', 'run_import_processor'], 10, 2);
     1579add_action('philia_process_product_import', ['Philia_API\Philia_Product_REST_Controller', 'run_import_processor'], 10, 3);
  • philia-integration/trunk/includes/cashier/rest-routes/class-reward-route.php

    r3368487 r3371202  
    145145        $reward_id = absint( $request->get_param('reward_id') );
    146146        $username = sanitize_text_field($request->get_param('username') );
     147        $user = Philia_Cashier_REST_Controller::get_user_by_token($request);
    147148
    148149        if( empty( $reward_id ) )
     
    195196                    'post_author' => get_current_user_id(),
    196197                    'post_type'   => 'shop_coupon',
     198                    'post_author' => ( $user->ID  > 0 ) ? $user->ID : ''
    197199                );
    198200
  • philia-integration/trunk/philia-integration.php

    r3368679 r3371202  
    33 * Plugin Name: Philia Integration with WooCommerce
    44 * Description: Sending Users, Products, Categories and invoices from WooCommerce to Philia and Receive Coupons and Cashback from Philia
    5  * Version: 1.8.1
     5 * Version: 1.9
    66 * Author: Philia
    77 * Author URI: https://fa.philia.vip
     
    1717
    1818// Define constants for plugin directory, URL, and table prefix.
    19 define('PHILIA_PLUGIN_VERSION', '1.8.1');
     19define('PHILIA_PLUGIN_VERSION', '1.9');
    2020define('PHILIA_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2121define('PHILIA_PLUGIN_URL', plugin_dir_url(__FILE__));
  • philia-integration/trunk/readme.txt

    r3368843 r3371202  
    44Requires at least: 6.0
    55Tested up to: 6.8
    6 Stable tag: 1.8.1
     6Stable tag: 1.9
    77License: GPL-2.0+
    88License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    4646== Changelog ==
    4747
     48= 1.9 =
     49* Bugs fixes and improvements
     50* Supports Multi-Vendor & Multi-Inventory (Dokan)
     51
    4852= 1.8.1 =
    4953* Bugs fixes and improvements
Note: See TracChangeset for help on using the changeset viewer.