HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/hi.naniguide.com/app/Services/CustomerService.php
<?php

namespace App\Services;

use App\Models\Customer;
use App\Models\CustomerGroup;
use App\Models\DeliveryServer;
use App\Models\Plan;
use App\Models\Setting;
use App\Models\Subscription;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;

class CustomerService
{
    /**
     * Get paginated list of customers.
     */
    public function getPaginated(array $filters = [], int $perPage = 15, string $pageName = 'page'): LengthAwarePaginator
    {
        $query = Customer::query()->with('customerGroups');

        // Apply filters
        if (isset($filters['search'])) {
            $search = $filters['search'];
            $query->where(function ($q) use ($search) {
                $q->where('first_name', 'like', "%{$search}%")
                    ->orWhere('last_name', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%")
                    ->orWhere('company_name', 'like', "%{$search}%");
            });
        }

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (isset($filters['customer_group_id'])) {
            $query->whereHas('customerGroups', function ($q) use ($filters) {
                $q->where('customer_groups.id', $filters['customer_group_id']);
            });
        }

        return $query->latest()->paginate($perPage, ['*'], $pageName);
    }

    /**
     * Create a new customer.
     */
    public function create(array $data): Customer
    {
        // Handle avatar upload
        $avatarPath = null;
        if (isset($data['avatar']) && $data['avatar']->isValid()) {
            $avatarPath = $data['avatar']->store('avatars', 'public');
        }

        $customerData = [
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
            'timezone' => $data['timezone'] ?? 'UTC',
            'language' => $data['language'] ?? 'en',
            'status' => $data['status'] ?? 'pending',
            'phone' => $data['phone'] ?? null,
            'company_name' => $data['company_name'] ?? null,
            'address' => $data['address'] ?? null,
            'city' => $data['city'] ?? null,
            'state' => $data['state'] ?? null,
            'zip_code' => $data['zip_code'] ?? null,
            'country' => $data['country'] ?? null,
            'currency' => $data['currency'] ?? Setting::get('billing_currency', 'USD'),
            'quota' => $data['quota'] ?? 0,
            'max_lists' => $data['max_lists'] ?? ($data['max_lists'] ?? 10),
            'max_subscribers' => $data['max_subscribers'] ?? 0,
            'max_campaigns' => $data['max_campaigns'] ?? 0,
            'expires_at' => isset($data['expires_at']) ? $data['expires_at'] : null,
        ];

        // Add new fields if they exist in the model's fillable array
        // Note: These fields may need to be added to the database migration
        $optionalFields = [
            'birth_date',
            'parent_account',
            'auto_deactivate_at',
            'send_details_via_email',
            'two_factor_auth_enabled',
            'security_notes',
            'monthly_sending_limit',
            'daily_sending_limit',
            'max_campaigns_per_day',
            'welcome_campaign',
            'plan_id',
            'renewal_type',
            'tax_id',
            'marketing_emails',
            'transactional_emails',
            'sms_notifications',
        ];

        foreach ($optionalFields as $field) {
            if (isset($data[$field])) {
                $customerData[$field] = $data[$field];
            }
        }

        // Store avatar path if uploaded
        if ($avatarPath) {
            $customerData['avatar_path'] = $avatarPath;
        }

        // Store complex data as JSON (if columns exist)
        if (isset($data['security']['ip_restrictions']) && is_array($data['security']['ip_restrictions'])) {
            $customerData['ip_restrictions'] = json_encode(array_filter($data['security']['ip_restrictions']));
        }

        if (isset($data['automation']['auto_tagging_rules']) && is_array($data['automation']['auto_tagging_rules'])) {
            $customerData['auto_tagging_rules'] = json_encode($data['automation']['auto_tagging_rules']);
        }

        if (isset($data['preferred_channels']) && is_array($data['preferred_channels'])) {
            $customerData['preferred_channels'] = json_encode($data['preferred_channels']);
        }

        if (isset($data['roles']) && is_array($data['roles'])) {
            $customerData['roles'] = json_encode($data['roles']);
        }

        if (isset($data['billing_address']) && is_array($data['billing_address'])) {
            $customerData['billing_address'] = $data['billing_address'];
        }

        $customer = Customer::create($customerData);

        // Attach customer group (single select)
        $groupId = null;
        if (isset($data['customer_group_id']) && $data['customer_group_id']) {
            $groupId = (int) $data['customer_group_id'];
        } elseif (isset($data['group_id']) && $data['group_id']) {
            $groupId = (int) $data['group_id'];
        } elseif (isset($data['customer_group_ids']) && is_array($data['customer_group_ids']) && !empty($data['customer_group_ids'])) {
            // Legacy support for array format
            $groupId = (int) $data['customer_group_ids'][0];
        }

        if ($groupId) {
            $customer->customerGroups()->sync([$groupId]);
        } else {
            $defaultRoleGroupId = $this->resolveDefaultCustomerRoleGroupId();
            if ($defaultRoleGroupId) {
                $customer->customerGroups()->sync([$defaultRoleGroupId]);
            }
        }

        if (isset($data['allocated_delivery_server_ids']) && is_array($data['allocated_delivery_server_ids'])) {
            $allowedServerIds = DeliveryServer::query()
                ->whereNull('customer_id')
                ->whereIn('id', $data['allocated_delivery_server_ids'])
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->values()
                ->all();

            $customer->allocatedDeliveryServers()->sync($allowedServerIds);
        }

        // Create subscription record when admin assigns a plan
        if (!empty($data['plan_id'])) {
            $this->syncSubscriptionForPlan($customer, $data['plan_id']);
        }

        return $customer->load('customerGroups');
    }

    /**
     * Update an existing customer.
     */
    public function update(Customer $customer, array $data): Customer
    {
        $updateData = [
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'],
            'email' => $data['email'],
            'timezone' => $data['timezone'] ?? $customer->timezone,
            'language' => $data['language'] ?? $customer->language,
            'status' => $data['status'] ?? $customer->status,
            'company_name' => $data['company_name'] ?? null,
            'phone' => $data['phone'] ?? null,
            'address' => $data['address'] ?? null,
            'city' => $data['city'] ?? null,
            'state' => $data['state'] ?? null,
            'zip_code' => $data['zip_code'] ?? null,
            'country' => $data['country'] ?? null,
            'currency' => $data['currency'] ?? $customer->currency,
            'quota' => $data['quota'] ?? $customer->quota,
            'monthly_sending_limit' => $data['monthly_sending_limit'] ?? $customer->monthly_sending_limit,
            'daily_sending_limit' => $data['daily_sending_limit'] ?? $customer->daily_sending_limit,
            'max_lists' => $data['max_lists'] ?? $customer->max_lists,
            'max_subscribers' => $data['max_subscribers'] ?? $customer->max_subscribers,
            'max_campaigns' => $data['max_campaigns'] ?? $customer->max_campaigns,
            'max_campaigns_per_day' => $data['max_campaigns_per_day'] ?? $customer->max_campaigns_per_day,
            'welcome_campaign' => array_key_exists('welcome_campaign', $data) ? (bool) $data['welcome_campaign'] : (bool) $customer->welcome_campaign,
            'plan_id' => $data['plan_id'] ?? $customer->plan_id,
            'renewal_type' => $data['renewal_type'] ?? $customer->renewal_type,
            'expires_at' => isset($data['expires_at']) ? $data['expires_at'] : $customer->expires_at,
        ];

        if (array_key_exists('tax_id', $data)) {
            $updateData['tax_id'] = $data['tax_id'];
        }

        if (isset($data['billing_address']) && is_array($data['billing_address'])) {
            $updateData['billing_address'] = $data['billing_address'];
        }

        if (isset($data['automation']['auto_tagging_rules']) && is_array($data['automation']['auto_tagging_rules'])) {
            $updateData['auto_tagging_rules'] = array_values(array_filter($data['automation']['auto_tagging_rules'], function ($rule) {
                return is_array($rule)
                    && trim((string) ($rule['trigger'] ?? '')) !== ''
                    && trim((string) ($rule['tag'] ?? '')) !== '';
            }));
        } elseif (array_key_exists('automation', $data)) {
            $updateData['auto_tagging_rules'] = [];
        }

        // Update password if provided
        if (!empty($data['password'])) {
            $updateData['password'] = Hash::make($data['password']);
        }

        $customer->update($updateData);

        // Sync customer group (single select)
        $groupId = null;
        if (isset($data['customer_group_id']) && $data['customer_group_id']) {
            $groupId = (int) $data['customer_group_id'];
        } elseif (isset($data['customer_group_ids']) && is_array($data['customer_group_ids']) && !empty($data['customer_group_ids'])) {
            // Legacy support for array format
            $groupId = (int) $data['customer_group_ids'][0];
        }

        if ($groupId !== null) {
            $customer->customerGroups()->sync([$groupId]);
        }

        if (isset($data['allocated_delivery_server_ids']) && is_array($data['allocated_delivery_server_ids'])) {
            $allowedServerIds = DeliveryServer::query()
                ->whereNull('customer_id')
                ->whereIn('id', $data['allocated_delivery_server_ids'])
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->values()
                ->all();

            $customer->allocatedDeliveryServers()->sync($allowedServerIds);
        }

        // Sync subscription record when admin changes the plan
        if (array_key_exists('plan_id', $data)) {
            if (!empty($data['plan_id'])) {
                $this->syncSubscriptionForPlan($customer, $data['plan_id']);
            } else {
                // Admin cleared the plan — cancel any admin-assigned subscription
                $customer->subscriptions()
                    ->where('provider', 'admin')
                    ->whereIn('status', ['active', 'trialing'])
                    ->update(['status' => 'cancelled', 'cancelled_at' => now()]);
            }
        }

        return $customer->load('customerGroups');
    }

    /**
     * Delete a customer.
     */
    public function delete(Customer $customer): bool
    {
        return (bool) DB::transaction(function () use ($customer) {
            $customer->customerGroups()->detach();
            $customer->forceFill([
                'email' => $this->deletedEmail((string) $customer->email, (int) $customer->id),
            ])->save();

            return $customer->delete();
        });
    }

    private function deletedEmail(string $email, int $id): string
    {
        $timestamp = time();

        return "deleted+{$id}+{$timestamp}@mailpurse.invalid";
    }

    private function resolveDefaultCustomerRoleGroupId(): ?int
    {
        $newRegisteredGroupId = Setting::get('new_registered_customer_group_id');
        if ($newRegisteredGroupId && CustomerGroup::query()->whereKey((int) $newRegisteredGroupId)->exists()) {
            return (int) $newRegisteredGroupId;
        }

        $defaultGroupId = Setting::get('default_customer_group_id');
        if ($defaultGroupId && CustomerGroup::query()->whereKey((int) $defaultGroupId)->exists()) {
            return (int) $defaultGroupId;
        }

        $roleGroup = CustomerGroup::query()
            ->orderByRaw('LOWER(name) = ? DESC', ['customer'])
            ->orderBy('id')
            ->first();

        return $roleGroup ? (int) $roleGroup->id : null;
    }

    /**
     * Get all customer groups for select options.
     */
    public function getCustomerGroupsForSelect(): array
    {
        return CustomerGroup::orderBy('name')->pluck('name', 'id')->toArray();
    }

    /**
     * Get timezones for select options.
     */
    public function getTimezones(): array
    {
        return \DateTimeZone::listIdentifiers(\DateTimeZone::ALL);
    }

    /**
     * Get languages for select options.
     */
    public function getLanguages(): array
    {
        return [
            'en' => 'English',
            'es' => 'Spanish',
            'fr' => 'French',
            'de' => 'German',
            'it' => 'Italian',
            'pt' => 'Portuguese',
            'ru' => 'Russian',
            'zh' => 'Chinese',
            'ja' => 'Japanese',
            'ko' => 'Korean',
            'ar' => 'Arabic',
            'hi' => 'Hindi',
        ];
    }

    /**
     * Get plans for select options.
     */
    public function getPlans(): array
    {
        return Plan::query()
            ->where('is_active', true)
            ->orderBy('price')
            ->pluck('name', 'id')
            ->toArray();
    }

    /**
     * Create or update an admin-assigned subscription for the given plan.
     */
    private function syncSubscriptionForPlan(Customer $customer, int|string $planId): void
    {
        $plan = Plan::find((int) $planId);
        if (!$plan) {
            return;
        }

        // Look for an existing admin-assigned subscription
        $subscription = $customer->subscriptions()
            ->where('provider', 'admin')
            ->latest()
            ->first();

        $attrs = [
            'plan_db_id'    => $plan->id,
            'plan_name'     => $plan->name,
            'status'        => 'active',
            'billing_cycle' => $plan->billing_cycle ?? 'monthly',
            'price'         => $plan->price ?? 0,
            'currency'      => $plan->currency ?? 'USD',
            'provider'      => 'admin',
            'starts_at'     => now(),
            'period_start'  => now(),
            'period_end'    => null,
            'ends_at'       => null,
            'auto_renew'    => true,
            'features'      => $plan->features,
            'limits'        => $plan->limits,
        ];

        if ($subscription) {
            $subscription->update($attrs);
        } else {
            $customer->subscriptions()->create($attrs);
        }

        // Sync the customer group from the plan
        if ($plan->customer_group_id) {
            $customer->customerGroups()->syncWithoutDetaching([(int) $plan->customer_group_id]);
        }
    }
}