PHP 7.4.33
Preview: UserRepository.php Size: 128.96 KB
/var/www/multi-event-cfp.bitkit.dk/httpdocs/app/Repositories/UserRepository.php
<?php


namespace App\Repositories;


use App\Exports\ExportUsersClass;
use App\Jobs\SendSpeakerTerms;
use App\Models\Abstracts;
use App\Models\AbstractUser;
use App\Models\AmendSpeakerTerms;
use App\Models\Event;
use App\Models\EventUser;
use App\Models\File;
use App\Models\Presenter;
use App\Models\SpeakerNotes;
use App\Models\User;
use App\Support\Entity;
use App\Support\Query;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
use App\Jobs\SendUserCreateMail;
use App\Jobs\SendAdminResetPasswordMail;
use App\Jobs\SendLoginInfoMail;
use App\Jobs\SendUserVerificationFieldsChangedMail;
use App\Jobs\SendUserFilesDownloadLinkEmail;
use Laravel\Sanctum\NewAccessToken;
use Maatwebsite\Excel\Facades\Excel;
use App\Imports\ImportUserTemplate;
use App\Imports\UsersImport;
use App\Models\Presentation;
use App\Models\PresentationReview;
use App\Models\Score;
use App\Models\Session;
use App\Models\SessionUser;
use App\Models\Slot;
use App\Models\SlotUser;
use App\Jobs\SendSessionUpdatedMail;
use Illuminate\Support\Str;
use App\Rules\DateRule;
use App\Rules\SessionDateRule;
use Exception;
use ZipArchive;
use URL;
use Intervention\Image\Facades\Image;
use Carbon\Carbon;

class UserRepository extends Repository
{
    protected $userFields = ["verification_status", "biography", "avatar", "company_logo", "company", "mobile", "job_title", "type"];
    public function __construct($event = null)
    {
        $this->for($event);
    }

    public function model(): User
    {
        return new User;
    }

    public function query($as = null)
    {
        $model = $this->model();
        return $model->newQuery();
    }

    /**
     * @param $admins
     * @param Event|null $event
     * @return Entity
     */
    public function createEventAdmin($admins, Event $event = null): Entity
    {
        $newUsers = [];
        $newUserPasswords = [];
        $existingUsers = [];
        if (!$event)
            $event = Event::find($this->for[0]);
        if (!$event)
            validationErrorResponse(['Event not found']);
        foreach ($admins as $admin) {
            $email = $admin['email'];
            $developerOptions = isset($admin['developer_options']) ? $admin['developer_options'] : false;
            $allUserAPIAccess = isset($admin['all_user_api_access']) ? $admin['all_user_api_access'] : false;
            $userExportAccess = isset($admin['user_export_access']) ? $admin['user_export_access'] : false;
            $abstractExportAccess = isset($admin['abstract_export_access']) ? $admin['abstract_export_access'] : false;
            $presentationFilesDownloadAccess = isset($admin['presentation_files_download_access']) ? $admin['presentation_files_download_access'] : false;
            $adminUser = User::whereEmail($email)->first();
            if (!$adminUser) {
                $adminPassword = randomStringGenerator(8);
                $adminUser = User::create([
                    'email' => $email,
                    'password' => Hash::make($adminPassword),
                    'valid' => true
                ]);
                $adminUser->assignRole('user');
                $newUsers[] = $email;
                $newUserPasswords[] = $adminPassword;
            } else {
                if (!$adminUser->hasRole('user'))
                    $adminUser->assignRole('user');
                $existingUsers[] = $email;
            }

            $eventUser = $this->attachUserToEvent($event, $adminUser, 'event_admin');
            $eventUser->developer_options = $developerOptions;
            $eventUser->all_user_api_access = $allUserAPIAccess;
            $eventUser->user_export_access = $userExportAccess;
            $eventUser->abstract_export_access = $abstractExportAccess;
            $eventUser->presentation_files_download_access = $presentationFilesDownloadAccess;
            $eventUser->access_level = ($admin['access_level'] ?? null) === 'full' ? 'full' : 'limited';
            $eventUser->vip_categories = ($admin['access_level'] ?? null) === 'limited' ? ($admin['vip_categories'] ?? []) : null;
            $eventUser->save();
        }

        return entity([
            'new_users' => $newUsers,
            'new_user_passwords' => $newUserPasswords,
            'existing_users' => $existingUsers
        ]);
    }

    public function removeEventUser($eventId, $emails, $role = null): bool
    {
        foreach ($emails as $email) {
            $admin = User::whereEmail($email)->first();
            $eventUser = getEventUser($eventId, $admin->id);
            if (!$role) {
                $eventUser->syncRoles([]);
            } else if ($eventUser->hasRole($role)) {
                $eventUser->removeRole($role);
            }
            if (!$eventUser->hasAnyRole())
                $eventUser->delete();
        }

        return true;
    }

    public function checkEventUserExists(Event $event, $email, $role)
    {
        $user = User::whereEmail($email)->first();
        if (!$user)
            return response([
                'status' => true,
                'user_status' => 0, // new user
                'message' => 'User not in the platform'
            ]);

        $eventUser = getEventUser($event, $user);
        if (!$eventUser || !$eventUser->hasRole($role))
            return response([
                'status' => true,
                'user_status' => 1, // user found not registered for the event
                'message' => 'User exists but not registered for event'
            ]);

        return response([
            'status' => true,
            'user_status' => 2, //user already registered for event
            'message' => "User already registered for event"
        ]);
    }

    public function attachUserToEvent(Event $event, User $user, $role = null): ?EventUser
    {
        $event->users()->attach($user->id);
        $eventUser = getEventUser($event->id, $user->id);
        if ($role)
            $eventUser->assignRole($role);
        return $eventUser;
    }

    public function attachUserToAbstract(Abstracts $abstract, User $user, $role = null): AbstractUser
    {
        $abstract->users()->attach($user->id);
        $abstractUser = getAbstractUser($abstract->id, $user->id);
        if ($role)
            $abstractUser->assignRole($role);
        return $abstractUser;
    }

    /**
     * Track user profile modification
     */
    public function trackUserModification(User $user, $modifiedBy = null, $eventId = null): void
    {
        $user->last_modified_by = $modifiedBy;
        $user->last_modified_at = now();
        $user->last_modified_event_id = $eventId;
    }

    /**
     * Track who marked the user as VIP
     * Only sets vip_marked_by when profile_type changes to a VIP type
     */
    public function trackVipMarkedBy(User $user, array $changes): void
    {
        if (array_key_exists('profile_type', $changes) && in_array($user->profile_type, VIP_PROFILE_TYPES)) {
            $user->vip_marked_by = authUser()->id;
        }
    }


    public function createUser($input)
    {
        // form type
        $formType = isset($input['form_type']) ? $input['form_type'] : false;

        // common rules
        $commonRules = [
            'first_name' => ['required', 'string', 'max:255'],
            'last_name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'cc_emails' => ['string', 'nullable'],
            'do_not_send_emails' => ['boolean'],
        ];
        // additional rules
        $additionalRules = [
            'password' => ['required', 'string', 'min:8', 'confirmed'],
            'job_title' => ['required', 'string', 'max:255'],
            'company' => ['required', 'string', 'max:255'],
            'country' => ['required', 'string', 'max:255'],
            'mobile' => ['required', 'string', 'max:255'],
            'dob' => [new DateRule],
            'nationality' => ['string', 'max:255', 'nullable'],
            'emirates_id' => ['regex:/^[0-9]{15}$/', 'nullable'],
            'passport_first_last_name' => ['string', 'max:255', 'nullable'],
            'salesforce_opportunity_id' => ['string', 'size:18', 'regex:/^[a-zA-Z0-9]{18}$/', 'nullable'],
        ];

        // Merge rules based on the form type
        $rules = $formType === 'minimal' ? $commonRules : array_merge($commonRules, $additionalRules);
        // validation
        $validator = Validator::make($input, $rules);
        // return error response
        if ($validator->fails())
            validationErrorResponse($validator->errors());
        // password
        $input['password'] = Hash::make($input['password']);
        // create user with input
        $user = User::create($input);
        // set valid true
        $user->valid = true;
        // auth user
        $authUser = authUser();
        // set created by
        $user->created_by = $authUser ? $authUser->id : null;
        // set new user true
        $user->new_user = true;
        // save user
        $user->save();
        // assign role
        $user->assignRole('user');
        return $user;
    }

    /**
     * @param Request $request
     * @param User $user
     * @return User
     */
    public function updateUser($input, User $user, $request, $modifiedBy = null, $eventId = null): User
    {
        $input = cleanInput($input);
        $validator = Validator::make($input, [
            'first_name' => ['required', 'string', 'max:255'],
            'last_name' => ['required', 'string', 'max:255'],
            'job_title' => ['required', 'string', 'max:255'],
            'company' => ['required', 'string', 'max:255'],
            'country' => ['required', 'string', 'max:255'],
            'mobile' => ['required', 'string', 'max:255'],
            'password' => ['string', 'min:8', 'confirmed'],
            'dob' => [new DateRule],
            'nationality' => ['string', 'max:255', 'nullable'],
            'passport_number' => ['string', 'max:255', 'nullable'],
            // 15 digits only, numeric
            'emirates_id' => ['regex:/^[0-9]{15}$/', 'nullable'],
            'passport_first_last_name' => ['string', 'max:255', 'nullable'],
        ]);

        if ($validator->fails())
            validationErrorResponse($validator->errors());

        if ($input['password']) {
            $password = Hash::make($input['password']);
            $input = array_merge($input, [
                'password' => $password,
                'password_confirmation' => $password
            ]);
        } else {
            unset($input['password']);
            unset($input['password_confirmation']);
        }

        unset($input['id']);
        $input['emirates_id'] = isset($input['emirates_id']) && $input['emirates_id'] !== '' ? $input['emirates_id'] : null;
        $user->fill($input);

        //keep changes field lists
        $changes = $user->getDirty();
        $user->new_user = false;
        
        // Track modification if users table data changed
        if (!empty($changes)) {
            $this->trackUserModification($user, $modifiedBy, $eventId);
        }
        
        $user->save();

        $imageFiles = $request->allFiles();
        $removeAvatar = $request->get('avatar') === "null" && $user->avatar;
        $removeCompanyLogo = $request->get('company_logo') === "null" && $user->company_logo;

        // Get the changed fields using the common function
        $changeFields = $this->getChangedFields($changes, $imageFiles, $removeAvatar, $removeCompanyLogo);

        // verification changes for change fields
        if ($changeFields) {
            // published session status changing
            $this->publishedSessionStatusUpdate($user);

            $this->userVerificationFieldsChanges($changeFields, $user);
        }

        return $user;
    }

    /**
     * @param User $user
     * @param false $avatar
     * @param false $companyLogo
     * @return User
     */
    public function removeUserImages(User $user, $avatar = false, $companyLogo = false, $userSecondaryImage = false): User
    {
        if ($avatar) {
            $avatar = $user->avatar;
            if ($avatar) {
                $file = File::find($avatar['file_id']);
                if ($file) {
                    // Get the thumbnail image name
                    $thumbnailImageName = $this->getThumbnailImageName($file);

                    // Check if the original image exists before deleting it
                    if (Storage::exists($file->filepath . '/' . $file->save_name)) {
                        Storage::delete($file->filepath . '/' . $file->save_name);
                    }

                    // Check if the thumbnail image exists before deleting it
                    if (Storage::exists($file->filepath . '/' . $thumbnailImageName)) {
                        Storage::delete($file->filepath . '/' . $thumbnailImageName);
                    }

                    // Delete the file record from the database
                    $file->delete();
                }
                $user->avatar = null;
            }
        }
        if ($companyLogo) {
            $companyLogo = $user->company_logo;
            if ($companyLogo) {
                $file = File::find($companyLogo['file_id']);
                if ($file) {
                    Storage::delete($file->filepath . '/' . $file->save_name);
                    $file->delete();
                }
                $user->company_logo = null;
            }
        }
        if ($userSecondaryImage) {
            $userSecondaryImage = $user->user_secondary_image;
            if ($userSecondaryImage) {
                $file = File::find($userSecondaryImage['file_id']);
                if ($file) {
                    Storage::delete($file->filepath . '/' . $file->save_name);
                    $file->delete();
                }
                $user->user_secondary_image = null;
            }
        }

        $user->save();
        return $user;
    }

    public function updateUserImages($imageFiles, User $user, $modifiedBy = null, $eventId = null): User
    {
        $hasChanges = false;

        if (isset($imageFiles['avatar'])) {
            $file = $imageFiles['avatar'];
            $currentAvatar = $user->avatar;
            $avatarFile = $user->saveFile(
                $file,
                $currentAvatar['file_id'] ?? null,
                "{$user->id}/images/",
                'user.avatar',
                'avatar'
            );

            $currentAvatar = new Entity($currentAvatar);

            $avatar = $avatarFile->getFileObject($currentAvatar);
            // save thumbnail image - smaller version of actual image
            $this->saveThumbnailImage($file, $avatar, $user->id);

            $user->avatar = $avatar;
            $hasChanges = true;
        }

        if (isset($imageFiles['company_logo'])) {
            $file = $imageFiles['company_logo'];
            $currentLogo = $user->company_logo;
            $logoFile = $user->saveFile(
                $file,
                $currentLogo['file_id'] ?? null,
                "{$user->id}/images/",
                'user.company_logo',
                'company_logo',
            );

            $currentLogo = new Entity($currentLogo);

            $companyLogo = $logoFile->getFileObject($currentLogo);

            $user->company_logo = $companyLogo;
            $hasChanges = true;
        }
        if (isset($imageFiles['user_secondary_image'])) {

            $file = $imageFiles['user_secondary_image'];
            $currentUserSecondaryImage = $user->user_secondary_image;
            $logoFile = $user->saveFile(
                $file,
                $currentUserSecondaryImage['file_id'] ?? null,
                "{$user->id}/images/",
                'user.user_secondary_image',
                'user_secondary_image',
            );

            $currentUserSecondaryImage = new Entity($currentUserSecondaryImage);

            $userSecondaryImage = $logoFile->getFileObject($currentUserSecondaryImage);

            $user->user_secondary_image = $userSecondaryImage;
            $hasChanges = true;
        }

        if ($hasChanges) {
            $this->trackUserModification($user, $modifiedBy, $eventId);
        }

        $user->save();
        return $user;
    }

    public function saveThumbnailImage($uploadFile, $avatar, $userId)
    {
        // Get extension
        $extension = $avatar['extension'];
        // Get the base name without the extension
        $baseName = pathinfo($avatar['save_name'], PATHINFO_FILENAME);
        // thumbnail image name
        $thumbnailImageName = "{$baseName}_thumbnail.{$extension}";

        // Define the full thumbnail save path based on avatar's existing path
        $thumbnailDirectory = $avatar['filepath'];
        // thumbnail path
        $thumbnailRelativePath = "{$thumbnailDirectory}{$thumbnailImageName}";

        // Create thumbnail using the Intervention Image library
        $image = Image::make($uploadFile->getRealPath());

        // Resize the image to 500x500
        $image->resize(500, 500, function ($constraint) {
            $constraint->aspectRatio(); // Maintain aspect ratio
            $constraint->upsize();      // Prevent upsizing if the image is smaller than 500x500
        });

        // Generate image stream
        $imageStream = $image->stream($avatar['extension'], 85);

        Storage::put($thumbnailRelativePath, $imageStream->__toString(), 'public');

        // Generate the public path for the thumbnail
        $thumbnailPath = User::FILE_PUBLIC_PATH . "{$userId}/images/{$thumbnailImageName}";

        // Update the saved file's thumbnail path in the database
        $savedFile = File::find($avatar['file_id']);
        if ($savedFile) {
            $savedFile->thumbnail_path = $thumbnailPath;
            $savedFile->save();
        }

        // Update the avatar object with the new thumbnail path
        $avatar['thumbnail_path'] = $thumbnailPath . "?v=" . md5(Carbon::now());

        return $avatar; // Return updated avatar for further use if needed
    }

    //handling uploading files by user
    public function handleUserFiles(Request $request, $data, User $user, $modifiedBy = null, $eventId = null)
    {
        $hasChanges = false;

        // handling uploaded files
        if ($request->hasFile('passport_images')) {

            $passportFiles = $request->file('passport_images');

            foreach ($passportFiles as $file) {
                $fileObj = $user->saveFile(
                    $file,
                    null,
                    "{$user->id}/files/passportImages/",
                    "user.passport_images.file",
                    null,
                    true
                );
                $passportFileContents[] = $fileObj->getFileObject();
            }
            $passportImages = array_values(array_filter(array_merge($data['passport_images'], $passportFileContents)));
            $user->passport_images = $passportImages;
            $hasChanges = true;
            $user->save();
        } else {
            // update user data with updated data
            if (isset($data['passport_images'])) {
                $passportImagesData = $data['passport_images'];
                if ($user->passport_images != $passportImagesData) {
                    $user->passport_images = $passportImagesData;
                    $hasChanges = true;
                    $user->save();
                }
            }
        }
        if ($request->hasFile('emirates_id_images')) {
            $emiratesIdFiles = $request->file('emirates_id_images');
            foreach ($emiratesIdFiles as $file) {
                $fileObj = $user->saveFile(
                    $file,
                    null,
                    "{$user->id}/files/emiratesIdImages/",
                    "user.emirates_id_images.file",
                    null,
                    true
                );
                $emiratesIdFileContents[] = $fileObj->getFileObject();
            }
            $emiratesIdImages = array_values(array_filter(array_merge($data['emirates_id_images'], $emiratesIdFileContents)));
            $user->emirates_id_images = $emiratesIdImages;
            $hasChanges = true;
            $user->save();
        } else {
            // update user data with updated data
            if (isset($data['emirates_id_images'])) {
                $emiratesImageData = $data['emirates_id_images'];
                if ($user->emirates_id_images != $emiratesImageData) {
                    $user->emirates_id_images = $emiratesImageData;
                    $hasChanges = true;
                    $user->save();
                }
            }
        }
        if (isset($data['removed_files'])) {
            //handling removed files
            $removedFiles = $data['removed_files'];

            if (isset($removedFiles)) {
                $fileIds = $removedFiles;
                foreach ($fileIds as $fileId) {

                    $this->deleteFile($fileId);
                }
                $hasChanges = true;
            }
        }
        
        if ($hasChanges) {
            $this->trackUserModification($user, $modifiedBy, $eventId);
        }
        
        return $hasChanges;
    }

    public function selectColumns(): array
    {
        return [
            'users.id',
            'users.first_name',
            'users.last_name',
            'users.company',
            'users.nationality',
            'users.passport_number',
            'users.country',
            DB::raw('concat(users.first_name," ",users.last_name) as name'),
            DB::raw('CASE WHEN users.do_not_send_emails = 0 THEN users.email ELSE users.cc_emails END as send_mail'),
            'users.email',
            'users.do_not_send_emails',
            'users.cc_emails',
            DB::raw('CASE WHEN users.salutation = "Prefer not to say" THEN NULL ELSE users.salutation END as salutation'),
            'users.job_title',
            'users.mobile',
            'users.profile_type',
            'users.dietary_requirements',
            'users.last_modified_by',
            'users.last_modified_at',
            'users.last_modified_event_id',
            DB::raw('(SELECT CONCAT(u2.first_name, " ", u2.last_name) FROM users u2 WHERE u2.id = users.last_modified_by) as last_modified_by_name'),
            DB::raw('(SELECT u2.email FROM users u2 WHERE u2.id = users.last_modified_by) as last_modified_by_email'),
            DB::raw('(SELECT e.event_name FROM events e WHERE e.id = users.last_modified_event_id) as last_modified_event_name'),
        ];
    }

    public function applyEventScope(Builder $query, $arguments): Builder
    {
        if ($arguments->event) {
            $query->join('event_user as eu', function ($join) use ($arguments) {
                $join->on('eu.user_id', '=', 'users.id')
                    ->where('eu.event_id', '=', $arguments->event->id);
            })->addSelect([
                DB::raw("eu.verification_status as verification_status"),
                DB::raw("eu.type as type"),
                DB::raw("eu.user_added_via as user_added_via")
            ])->groupBy('eu.id')->whereIn('eu.id', function ($subquery) use ($arguments) {
                $subquery->select(DB::raw('MIN(id)'))
                    ->from('event_user')
                    ->where('event_id', $arguments->event->id)
                    ->groupBy('user_id');
            });
        }
        return $query;
    }

    public function applyRoleScope(Builder $query, $arguments): Builder
    {
        if ($arguments->event_role) {
            $eventRole = $arguments->event_role;
            $query->join('model_has_roles as mhs', 'mhs.model_id', '=', 'eu.id')
                ->where('mhs.model_type', '=', EventUser::class)
                ->join('roles as r', 'r.id', '=', 'mhs.role_id')
                ->where('r.name', '=', $eventRole);

            if ($eventRole == 'event_submitter') {
                $query->leftJoin('abstracts as abstracts', function ($join) use ($arguments) {
                    $join->on('abstracts.user_id', '=', 'users.id')
                        ->where('abstracts.event_id', '=', $arguments->event->id);
                })->addSelect([
                            DB::raw("SUM(if(abstracts.id is not null, 1, 0)) as abstract_count")
                        ])->groupBy('users.id');
            }

            if ($eventRole == 'event_reviewer') {
                $query->addSelect(['eu.event_profile_data']);
                $query->addSelect('eu.id as event_user_id');

                if (!$arguments->presentation_id) {
                    $query->leftJoin('scores as s', function ($join) use ($arguments) {
                        $join->on('s.user_id', '=', 'users.id')
                            ->where('s.event_id', '=', $arguments->event->id);
                    })->addSelect([
                        DB::raw("SUM(if(s.id is not null, 1, 0)) as score_count"),
                        DB::raw("REPLACE(REPLACE(REPLACE(JSON_EXTRACT(eu.event_profile_data, '$.category[*].value'), '[', ''), ']', ''), '\"', '\'') as category"),
                        DB::raw("REPLACE(REPLACE(REPLACE(JSON_EXTRACT(eu.event_profile_data, '$.subcategory[*].value'), '[', ''), ']', ''), '\"', '\'') as subcategory")
                    ])->groupBy('users.id');
                }

                if ($arguments->presentation_id ?? false) {

                    // presentation
                    $presentation = Presentation::find($arguments->presentation_id);

                    if ($presentation->abstract_id) {
                        // abstract
                        $abstract = Abstracts::find($presentation->abstract_id);

                        // category
                        $category_field = array_values(array_filter(
                            $abstract->data->toArray(),
                            fn ($settings) => $settings['field_id'] == "category"
                        ));

                        // searching with category value
                        if ($category_field[0]['value']) {
                            $search = strtolower($category_field[0]['value']);
                            // $special_char = '–'; // en dash

                            // $special_char_pos = mb_strpos($search, $special_char);
                            // if ($special_char_pos !== false) {
                            //     mb_internal_encoding("UTF-8");
                            //     $search = mb_substr($search, $special_char_pos + mb_strlen($special_char));
                            //     $search = trim($search);
                            // }

                            $special_chars = ['–', '/']; // en dash and forward slash

                            foreach ($special_chars as $special_char) {
                                $special_char_pos = mb_strpos($search, $special_char);
                                if ($special_char_pos !== false) {
                                    mb_internal_encoding("UTF-8");
                                    $search = mb_substr($search, $special_char_pos + mb_strlen($special_char));
                                    $search = trim($search);
                                    // Break after processing the first found special character
                                    break;
                                }
                            }
                            $query->where(DB::raw("lower(REPLACE(REPLACE(REPLACE(JSON_EXTRACT(eu.event_profile_data, '$.category[*].value'), '[', ''), ']', ''), '\"', '\''))"), 'like', "%{$search}%");
                        }

                    }

                    $query->leftJoin('presentation_reviews as pr', function ($join) use ($arguments) {
                        $join->on('pr.reviewer_id', '=', 'users.id')
                            ->where('pr.event_id', '=', $arguments->event->id)
                            ->where('pr.presentation_id', '=', $arguments->presentation_id);
                    })->addSelect([
                        DB::raw("(CASE WHEN pr.id is null THEN 0 ELSE 1 END) as assigned")
                    ]);

                    $query->leftJoin('presentation_reviews as prs', function ($join) use ($arguments) {
                        $join->on('prs.reviewer_id', '=', 'users.id')
                            ->where('prs.event_id', '=', $arguments->event->id);
                    })->addSelect([
                        DB::raw("SUM(CASE WHEN prs.id is null THEN 0 ELSE 1 END) as assigned_count")
                    ]);
                }
            }
        }

        return $query;
    }

    public function applyOrder(Builder $query, Query $arguments): Builder
    {

        if (!$arguments->sort)
            return $query;


        $userColumns = [
            'id' => 'users.id',
            'first_name' => 'users.first_name',
            'last_name' => 'users.last_name',
            'email' => 'users.email',
            'name' => 'name',
            'abstract_count' => 'abstract_count',
            'score_count' => 'score_count',
            'category' => 'category',
            'subcategory' => 'subcategory',
            'avatar' => 'avatar',
            'biography' => 'biography',
            'company_logo' => 'company_logo',
            'mobile' => 'mobile',
            'slot_identifiers' => 'slot_identifiers',
            'agreement_status' => 'agreement_status',
            'speaker_added_via' => 'speaker_added_via',
            'verification_status' => 'verification_status',
            'type' => 'type',
        ];

        $sorts = is_array($arguments->sort) ? json_decode(json_encode($arguments->sort), FALSE) : json_decode($arguments->sort, true);

        // order by event user order field
        if (empty($sorts) && $arguments->orderSort) {
            $query->orderBy('eu.order', 'asc');
            return $query;
        }

        foreach ($sorts as $sort => $method) {
            if (array_key_exists($sort, $userColumns)) {
                $query->orderBy($sort, $method);
            }
        }

        return $query;
    }

    public function applyAbstractScope(Builder $query, $arguments)
    {
        if ($arguments->abstract_ids) {
            $abstractIds = array_map('intval', $arguments->abstract_ids);
            $abstractUsers = $arguments->abstract_users;
            $query->whereIn('users.id', function ($query) use ($abstractIds, $abstractUsers) {
                $query->select('au.user_id')
                    ->from('abstract_user as au')
                    ->whereIn('au.abstracts_id', $abstractIds);
                if ($abstractUsers) {
                    $query->join('model_has_roles as mhs', 'mhs.model_id', '=', 'au.id')
                        ->where('mhs.model_type', '=', AbstractUser::class)
                        ->join('roles as r', 'r.id', '=', 'mhs.role_id')
                        ->whereIn('r.name', $abstractUsers);
                }
            })->groupBy('users.id')->distinct();
        }
    }

    public function applySearch(Builder $query, $arguments): Builder
    {
        if ($arguments->search) {
            $userColumns = [
                'id' => 'users.id',
                'first_name' => 'users.first_name',
                'last_name' => 'users.last_name',
                'email' => 'users.email',
                'name' => DB::raw('concat(users.first_name," ",users.last_name)'),
            ];
            $search = $arguments->search;
            $search = strtolower($search);

            foreach ($userColumns as $userColumn) {
                $query->orHaving(DB::raw("lower($userColumn)"), 'like', "%{$search}%");
            }
            if ($arguments->event_role == 'event_reviewer') {
                $query->orHaving(DB::raw("lower(REPLACE(REPLACE(REPLACE(JSON_EXTRACT(eu.event_profile_data, '$.category[*].value'), '[', ''), ']', ''), '\"', '\''))"), 'like', "%{$search}%");
                $query->orHaving(DB::raw("lower(REPLACE(REPLACE(REPLACE(JSON_EXTRACT(eu.event_profile_data, '$.subcategory[*].value'), '[', ''), ']', ''), '\"', '\''))"), 'like', "%{$search}%");
            }
            if ($arguments->confirmed_speakers ?? false) {
                $query->orHaving("slot_identifiers", 'like', "%{$arguments->search}%");
                $query->orHaving("agreement_status", 'like', "%{$search}%");
                $query->orHaving("speaker_added_via", 'like', "%{$search}%");
            }
        }

        // Add logic to include the last note added for confirmed speakers
        if ($arguments->confirmed_speakers ?? false) {
            $query->addSelect([
                DB::raw("(SELECT note FROM speaker_notes WHERE speaker_notes.speaker_id = users.id AND speaker_notes.event_id = {$arguments->event->id} ORDER BY created_at DESC LIMIT 1) as last_note_added")
            ]);
        }

        return $query;
    }

    public function applyContactRestrictionScope(Builder $query, $arguments): Builder
    {
        if (!($arguments->include_contact_restriction ?? false)) {
            return $query;
        }

        $authUser = authUser();
        $authEventUser = $arguments->event ? getEventUser($arguments->event, $authUser) : null;

        if ($authEventUser && $authEventUser->access_level === 'limited') {
            $allowedCategories = $authEventUser->vip_categories ?? [];
            $vipTypes = implode("','", VIP_PROFILE_TYPES);
            if (!empty($allowedCategories)) {
                $placeholders = implode(',', array_fill(0, count($allowedCategories), '?'));
                $query->addSelect([DB::raw("CASE WHEN users.profile_type IN ('{$vipTypes}') AND users.profile_type NOT IN ({$placeholders}) THEN 1 ELSE 0 END as contact_restricted")])
                    ->addBinding($allowedCategories, 'select');
            } else {
                $query->addSelect([DB::raw("CASE WHEN users.profile_type IN ('{$vipTypes}') THEN 1 ELSE 0 END as contact_restricted")]);
            }
        } else {
            $query->addSelect([DB::raw("0 as contact_restricted")]);
        }

        return $query;
    }

    public function applyScope(Builder $query, $arguments): Builder
    {
        $this->applyEventScope($query, $arguments);

        $this->applyRoleScope($query, $arguments);

        $this->applyOrder($query, $arguments);

        $this->applySearch($query, $arguments);

        $this->applyAbstractScope($query, $arguments);

        $this->applyPresentationScope($query, $arguments);

        $this->applySpeakerScope($query, $arguments);

        $this->applyFilter($query, $arguments);

        $this->applyFullData($query, $arguments);

        $this->applyContactRestrictionScope($query, $arguments);

        return $query;
    }

    public function applyFullData(Builder $query, $arguments): Builder
    {
        if (($arguments->full_data ?? false) && ($arguments->full_data == 'true' || $arguments->full_data === true)) {
            $query->addSelect([
                //'users.do_not_send_emails',
                //'users.job_title',
                //'users.country',
                //'users.phone',
                //'users.mobile',
                'users.fax',
                'users.po_box',
                'users.street',
                'users.city',
                'users.state',
                'users.post_code',
                'users.linkedin_link',
                'users.dietary_requirements',
                'users.industry_code',
                //'users.company',
                //'users.company_address',
                'users.biography',
                'users.avatar',
                'users.company_logo',
                'users.checkboxes',
                //'users.login_email_count',
                //'eu.event_profile_data',
                //'eu.confirmed_speaker',
                'eu.speaker_added_via',
                'eu.agreement_status',
                'eu.agreement_details',
                'users.created_by',
                'users.created_at',
                'users.updated_at',
             ]);
        }
        return $query;
    }

    public function applyFilter(Builder $query, $arguments): Builder
    {
        if (!$arguments->filter)
            return $query;

        $filter = is_array($arguments->filter) ? json_decode(json_encode($arguments->filter), FALSE) : json_decode($arguments->filter);

        if ($filter->biography ?? false) {
            $values = $filter->biography;
            $this->getFilteredQuery($query, $values, 'biography');
        }

        if ($filter->avatar ?? false) {
            $values = $filter->avatar;
            $this->getFilteredQuery($query, $values, 'avatar');
        }

        if ($filter->company_logo ?? false) {
            $values = $filter->company_logo;
            $this->getFilteredQuery($query, $values, 'company_logo');
        }

        if ($filter->mobile ?? false) {
            $values = $filter->mobile;
            $this->getFilteredQuery($query, $values, 'mobile');
        }

        if ($filter->company ?? false) {
            $values = $filter->company;
            $this->getFilteredQuery($query, $values, 'company');
        }

        if ($filter->job_title ?? false) {
            $values = $filter->job_title;
            $this->getFilteredQuery($query, $values, 'job_title');
        }

        if ($filter->verification_status ?? false) {
            $values = $filter->verification_status;
            $this->getFilteredQuery($query, $values, 'verification_status');
        }

        if ($filter->confirmed_speaker ?? false) {
            $values = $filter->confirmed_speaker;
            $this->getFilteredQuery($query, $values, 'confirmed_speaker');
        }

        if ($filter->type ?? false) {
            $values = $filter->type;
            $this->getFilteredQuery($query, $values, 'type');
        }

        if ($filter->agreement_status ?? false) {
            $values = $filter->agreement_status;
            $this->getFilteredQuery($query, $values, 'agreement_status');
        }

        if ($arguments->confirmed_speakers ?? false) {
            if ($filter->day ?? false) {
                $values = $filter->day;
                $this->getFilteredQuery($query, $values, 'start_date');
            }
            if ($filter->sessions ?? false) {
                $values = $filter->sessions;
                $this->getFilteredQuery($query, $values, 'id');
            }
        }

        // Filter based on last synced date
        if ($filter->last_synced_date ?? false) {
            $query->where('users.updated_at', '>=', $filter->last_synced_date);
        }

        // event role
        $eventRole = $arguments->event_role;

        if ($eventRole == 'event_submitter') {
            // abstract repository
            $abstractRepository = new AbstractsRepository();
            // Convert the Eloquent builder to a query builder
            $queryBuilder = $query->getQuery();
            // filter using submitter abstracts data
            $abstractRepository->applyFilter($queryBuilder, $arguments, true);
        }

        return $query;
    }

    public function applySpeakerScope(Builder $query, $arguments)
    {
       if (isset($arguments->confirmed_speakers) && $arguments->confirmed_speakers !== 'false') {
            $query->where('eu.confirmed_speaker', '=', true)
                ->leftJoin('slot_users as su', 'su.user_id', '=', 'users.id')
                ->leftJoin('session_users as seu', 'seu.user_id', '=', 'users.id')
                ->leftJoin('sessions as sess', function ($query) use ($arguments) {
                $query->on('seu.session_id', '=', 'sess.id')
                    ->where('sess.event_id', '=', $arguments->event->id);
            })->leftJoin('slots', function ($query) use ($arguments) {
                $query->on('su.slot_id', '=', 'slots.id')
                    ->where('slots.event_id', '=', $arguments->event->id);
            })->addSelect([
                DB::raw('GROUP_CONCAT(DISTINCT slots.identifier) as slot_identifiers'),
                DB::raw('eu.order as speaker_order')
            ]);
            if (!($arguments->full_data ?? false) || $arguments->full_data == 'false' || $arguments->full_data === false) {
                $query->addSelect([
                    'users.biography',
                    'users.avatar',
                    'users.company_logo',
                    'eu.agreement_status',
                    'eu.speaker_added_via',
                    'eu.verified_data',
                    'eu.previously_verified_data',
                    'users.passport_first_last_name',
                    'users.dob',
                    'users.uae_resident',
                    'users.emirates_id',
                    'users.passport_images',
                    'users.emirates_id_images',
                    //'eu.type',
                ]);

                $query->with([
                    'presentations' => function ($query) use ($arguments) {
                        $query->select(
                            'presentations.id',
                            'presentations.abstract_id',
                            'presentations.submission_status',
                            'presentations.written_paper_status'
                        )->where('presentations.event_id', '=', $arguments->event->id);
                    },
                    'sessions' => function ($query) use ($arguments) {
                        $query->select(
                            'sessions.id',
                            'sessions.title',
                            'sessions.start_date',
                            'sessions.end_date',
                            'sessions.type',
                            'sessions.category',
                            'sessions.session_categories'
                        )->where('sessions.event_id', '=', $arguments->event->id);
                    },
                    'slots' => function ($query) use ($arguments) {
                        $query->select(
                            'slots.id',
                            'slots.session_id',
                        )->where('slots.event_id', '=', $arguments->event->id);
                    }, 'slots.session',
                ]);
            }
        } elseif ($arguments->confirmed_speakers === 'false') {
             $query->where(function($q) {
                $q->where('eu.confirmed_speaker', '=', false)
                  ->orWhereNull('eu.confirmed_speaker');
            });
        }
    }

    public function applyPresentationScope(Builder $query, $arguments)
    {
        if ($arguments->presentation_ids) {
            $presentationIds = array_map('intval', $arguments->presentation_ids);
            $presentationUsers = $arguments->presentation_users;

            $query->where(function ($query) use ($presentationUsers, $presentationIds) {
                foreach ($presentationUsers as $presentationUser) {
                    if ($presentationUser == 'submitter') {
                        $query->orWhereIn('users.id', function ($query) use ($presentationIds) {
                            $query->select('p.user_id')
                                ->from('presentations as p')
                                ->whereIn('p.id', $presentationIds);
                        });
                    }

                    if ($presentationUser == 'presenter') {
                        $query->orWhereIn('users.id', function ($query) use ($presentationIds) {
                            $query->select('ps.user_id')
                                ->from('presenters as ps')
                                ->whereIn('ps.presentation_id', $presentationIds);
                        });
                    }

                    if ($presentationUser == 'reviewer') {
                        $query->orWhereIn('users.id', function ($query) use ($presentationIds) {
                            $query->select('pr.reviewer_id')
                                ->from('presentation_reviews as pr')
                                ->whereIn('pr.presentation_id', $presentationIds);
                        });
                    }
                }
            });
            $query->groupBy('users.id');
        }
    }


    public function scope($arguments, $callback = null)
    {
        //get query builder
        $query = $this->query()
            ->select($this->selectColumns());

        //applying different scopes based on the arguments
        $this->applyScope($query, $arguments);

        //resolve if there is any callback functions available
        return $this->resolve($query, $arguments, $callback);
    }

    public function listing(Request $request, $paginate)
    {
        //building arguments
        $arguments = $this->arguments($request);

        //building query
        $query = $this->scope($arguments);

        return ($paginate && $arguments->paging != 'All') ? $query->paginate($arguments->paging, ['*'], 'page', $arguments->page) : $query->get();
    }

    public function getSpeakerCount(Event $event): int
    {
        return $event->users()
            ->where('event_user.confirmed_speaker', '=', true)
            ->count();
    }

    public function getEventUserCount(Event $event): array
    {
        $submitters = $event->users('event_submitter')->count();
        $reviewers = $event->users('event_reviewer')->count();
        $admins = $event->users('event_admin')->count();
        $coChairs = $event->users('event_co_chair')->count();
        $speakers = $event->speakers()->count();

        return [
            'submitter_count' => $submitters,
            'reviewer_count' => $reviewers,
            'admin_count' => $admins,
            'co_chair_count' => $coChairs,
            'confirmed_speakers' => $speakers
        ];
    }

    public function createOrUpdate($data, Event $event, $modifiedBy = null, $eventId = null): array
    {
        // roles
        $roles = $data['roles'] ?? false;
        // form type
        $formType = isset($data['form_type']) ? $data['form_type'] : false;
        if (!$roles)
            validationErrorResponse(['Roles are missing']);
        // id
        $id = $data['id'] ?? null;
        // user
        $user = User::find($id);
        if (!$user) {
            if (!isset($data['password_confirmation']) || empty($data['password_confirmation'])) {
                $password = randomStringGenerator(10);
                $data = array_merge($data, [
                    'password' => $password,
                    'password_confirmation' => $password
                ]);
            }
            $user = null;
            $existedUser = false;
            // checking the user is already exist when the post request coming from directly in API
            if(isset($data['email'])){
                // get the user
                $user = User::where('email', '=', $data['email'])->first();
                $existedUser = true;
            }
            if(!$user){
                // create user
                $user = $this->createUser($data);
                // track vip marked by on create
                if (in_array($user->profile_type, VIP_PROFILE_TYPES)) {
                    $user->vip_marked_by = authUser()->id;
                    $user->save();
                }
            }
            // attach user to th event
            $eventUser = $this->attachUserToEvent($event, $user);
            if (in_array('event_submitter', $data['roles'])) {
            // type
            $eventUser->type = $data['type'] ?? null;
            }
            // user added via
            $eventUser->user_added_via = $formType == 'minimal' ? "MinimalForm" : "Form";
            if($existedUser){
                // sync roles
                $eventUser->syncRoles($roles);
            }else{
                // assign roles
                $eventUser->assignRole($roles);
            }
            if ($data['send_login'] ?? false) {
                // send email to user with password
                dispatch(new SendUserCreateMail($event, $user, 'new', $roles, $data['password_confirmation'] ? $data['password_confirmation'] : $password));
            }
        }
        else {
            // common rules
            $commonRules = [
                'first_name' => ['required', 'string', 'max:255'],
                'last_name' => ['required', 'string', 'max:255'],
                'cc_emails' => ['string', 'nullable'],
                'do_not_send_emails' => ['boolean'],
                'profile_type' => ['string', 'nullable'],
            ];

            // additional rules
            $additionalRules = [
                'job_title' => ['required', 'string', 'max:255'],
                'company' => ['required', 'string', 'max:255'],
                'country' => ['required', 'string', 'max:255'],
                'mobile' => ['required', 'string', 'max:255'],
                'password' => ['string', 'min:8', 'confirmed'],
                'salutation' => ['string', 'nullable'],
                'phone' => ['string', 'nullable'],
                'fax' => ['string', 'nullable'],
                'po_box' => ['string', 'nullable'],
                'street' => ['string', 'nullable'],
                'city' => ['string', 'nullable'],
                'state' => ['string', 'nullable'],
                'post_code' => ['string', 'nullable'],
                'linkedin_link' => ['string', 'nullable'],
                'industry_code' => ['string', 'nullable'],
                'company_address' => ['string', 'nullable'],
                'company_country' => ['string', 'nullable'],
                'biography' => ['string', 'nullable'],
                'dietary_requirements' => ['string', 'nullable'],
                'nationality' => ['string', 'nullable'],
                'passport_number' => ['string', 'nullable'],
                'emirates_id' => ['regex:/^[0-9]{15}$/', 'nullable'],
                'passport_first_last_name' => ['string', 'nullable'],
                'user_secondary_image' => ['array', 'nullable'],
                'avatar' => ['array', 'nullable'],
                'dob' => [new DateRule],
                'company_logo' => ['array', 'nullable'],
                'send_login' => ['boolean', 'nullable'],
                'uae_resident' => ['boolean', 'nullable'],
            ];

            // Merge rules based on the form type
            $rules = $formType === 'minimal' ? $commonRules : array_merge($commonRules, $additionalRules);
            // validation
            $validator = Validator::make($data, $rules);
            // user data
            $userData = $validator->validated();

            if (isset($userData['password'])) {
                $password = Hash::make($userData['password']);
                $userData = array_merge($userData, [
                    'password' => $password,
                    'password_confirmation' => $password
                ]);
            }

            $userData['emirates_id'] = isset($userData['emirates_id']) && $userData['emirates_id'] !== '' ? $userData['emirates_id'] : null;
            $user->fill($userData);
            //keep changes field lists
            $changes = $user->getDirty();
            // is it new user
            $user->new_user = false;
            
            // Track modification if users table data changed
            if (!empty($changes)) {
                $this->trackUserModification($user, $modifiedBy, $eventId);
                $this->trackVipMarkedBy($user, $changes);
            }
            
            $user->save();

            // Get the changed fields using the common function
            $changeFields = $this->getChangedFields($changes);

            // verification changes for change fields
            if ($changeFields) {

                // published session status changing
                $this->publishedSessionStatusUpdate($user);

                // user verification fields changes
                $this->userVerificationFieldsChanges($changeFields, $user);
            }

            $eventUser = getEventUser($event, $user);
            if ($eventUser) {
                $new_confirmed_speaker = $data['confirmed_speaker'] ?? null;
                if ($eventUser->confirmed_speaker && !$new_confirmed_speaker) {
                    // Get the order of the speaker being removed
                    $removedSpeakerOrder = $eventUser->order;

                    // Decrement the order for speakers with an order greater than the removed speaker's order
                    EventUser::where('event_id', $event->id)
                        ->where('confirmed_speaker', 1)
                        ->whereNotNull('confirmed_speaker')
                        ->where('order', '>', $removedSpeakerOrder)
                        ->decrement('order');

                    // set the order of the removed speaker to null
                    $eventUser->order = null;
                }
                // type
                $eventUser->type = $data['type'] ?? null;
            }
            if (!$eventUser) {
                $eventUser = $this->attachUserToEvent($event, $user);
                // type
                $eventUser->type = $data['type'] ?? null;
                // user added via
                $eventUser->user_added_via = "Form";
            }
            $eventUser->syncRoles($roles);

            if ($userData['send_login'] ?? false) {
                //send email to user
                dispatch(new SendUserCreateMail($event, $user, 'existing', $roles));
            }
        }

        return [$user, $eventUser];
    }

    /**
     * Get the list of changed fields from the user's data, including image file checks.
     *
     * @param  array  $changes
     * @param  array  $imageFiles (optional)
     * @param  bool   $removeAvatar (optional)
     * @param  bool   $removeCompanyLogo (optional)
     * @return array
     */
    public function getChangedFields($changes, $imageFiles = [], $removeAvatar = false, $removeCompanyLogo = false)
    {
        // Initialize an empty array to store the changed fields
        $changeFields = [];

        // 1. Check regular fields (e.g., 'mobile', 'biography', 'job_title')
        $changeFields = array_merge($changeFields, $this->checkRegularFields($changes));

        // 2. Check image-related fields (e.g., 'avatar', 'company_logo') based on changes and file uploads
        $changeFields = array_merge($changeFields, $this->checkImageFields($changes, $imageFiles, $removeAvatar, $removeCompanyLogo));

        return $changeFields;
    }

    /**
     * Check if any regular fields (e.g., 'mobile', 'biography', 'job_title') have been changed.
     *
     * @param  array  $changes
     * @return array
     */
    public function checkRegularFields($changes)
    {
        // List of fields to check
        $fieldsToCheck = ['salutation','first_name','last_name','mobile', 'biography', 'job_title'];

        // Filter out fields that have changed
        return array_filter($fieldsToCheck, function($field) use ($changes) {
            return array_key_exists($field, $changes);
        });
    }

    /**
     * Check if any image-related fields (e.g., 'avatar', 'company_logo') have been changed, including file uploads or removals.
     *
     * @param  array  $changes
     * @param  array  $imageFiles
     * @param  bool   $removeAvatar
     * @param  bool   $removeCompanyLogo
     * @return array
     */
    public function checkImageFields($changes, $imageFiles, $removeAvatar, $removeCompanyLogo)
    {
        $imageFields = [];

        // Check for 'avatar' change based on changes or file upload/removal
        if (array_key_exists('avatar', $changes) || isset($imageFiles['avatar']) || $removeAvatar) {
            $imageFields[] = 'avatar';
        }

        // Check for 'company_logo' change based on changes or file upload/removal
        if (array_key_exists('company_logo', $changes) || isset($imageFiles['company_logo']) || $removeCompanyLogo) {
            $imageFields[] = 'company_logo';
        }

        return $imageFields;
    }

    /**
     * published session status changing
     * @param $field
     */
    public function publishedSessionStatusUpdate($user)
    {
        // session published status updating based on session users
        $sessionUsers = SessionUser::all()->where('user_id', '=', $user->id);
        $sessionIds = [];

        foreach ($sessionUsers as $sessionUser) {

            $session = Session::find($sessionUser->session_id);

            $eventUsers = EventUser::all()->where('user_id', '=', $user->id)
                ->where('event_id', '=', $session->event_id);

            foreach ($eventUsers as $eventUser) {
                if ($eventUser->verification_status) {
                    if ($session->published_status) {
                        $session->published_status = false;
                        $session->save();
                    }

                    dispatch(new SendSessionUpdatedMail($session, $eventUser, 'speaker'));
                }
                array_push($sessionIds, $session->id);
            }
        }

        // session published status updating based on slot users
        $slotUsers = SlotUser::select('slots.session_id as session_id')
            ->join('slots', 'slots.id', '=', 'slot_users.slot_id')
            ->where('slot_users.user_id', '=', $user->id)->distinct()
            ->get();

        foreach ($slotUsers as $slotUser) {

            $session = Session::find($slotUser->session_id);

            $eventUsers = EventUser::all()->where('user_id', '=',  $user->id)
                ->where('event_id', '=', $session->event_id);

            foreach ($eventUsers as $eventUser) {
                if ($eventUser->verification_status) {
                    if ($session->published_status) {
                        $session->published_status = false;
                        $session->save();
                    }

                    if (!in_array($session->id, $sessionIds)) {
                        dispatch(new SendSessionUpdatedMail($session, $eventUser, 'slot user'));
                    }
                }
            }
        }
    }

    /**
     * user verification fields changes
     * @param $field
     */
    public function userVerificationFieldsChanges($changeFields, $user)
    {
        // event users
        $eventUsers = EventUser::all()->where('user_id', '=', $user->id);

        foreach ($eventUsers as $eventUser) {
            if ($eventUser['verification_data']) {
                $verificationData = $eventUser['verification_data'];
                foreach ($changeFields as $field) {
                    $verificationData[$field] = false;
                }
                $verificationData['verify_all'] = false;
                $eventUser['verification_data'] =  $verificationData;

                if ($eventUser->verified_data && $eventUser->verification_status) {
                    //send mail to event admins
                    dispatch(new SendUserVerificationFieldsChangedMail($user, $eventUser->event, $changeFields));
                }

                $eventUser['verification_status'] =  false;
                $eventUser->save();
            }
        }
        return true;
    }

    public function deleteUser(User $user, EventUser $eventUser, $removeRole)
    {

        $sessionUserCount = SessionUser::whereUserId($user->id)->whereEventId($eventUser->event->id)
            ->count();

        if ($sessionUserCount > 0)
            return response([
                'status' => false,
                'message' => 'Cannot delete the user. The user is added to a session'
            ]);

        $sessionsSlotUserCount = SlotUser::whereUserId($user->id)->whereEventId($eventUser->event->id)
            ->count();

        if ($sessionsSlotUserCount > 0)
            return response([
                'status' => false,
                'message' => 'Cannot delete the user. The user is added to a slot'
            ]);

        if ($removeRole == 'event_submitter') {

            $abstractUserCount = AbstractUser::where('abstract_user.user_id', '=', $user->id)
                ->join('abstracts as a', 'abstract_user.abstracts_id', '=', 'a.id')
                ->where('a.user_id', '!=', $user->id)
                ->where('a.event_id', '=', $eventUser->event->id)
                ->count();

            if ($abstractUserCount)
                return response([
                    'status' => false,
                    'message' => 'Cannot delete the user. The user is added in an abstract'
                ]);

            $presentersCount = Presenter::where('presenters.user_id', '=', $user->id)
                ->join('presentations as p', 'presenters.presentation_id', '=', 'p.id')
                ->join('abstracts as a', 'p.abstract_id', '=', 'a.id')
                ->where('a.user_id', '!=', $user->id)
                ->where('presenters.event_id', '=', $eventUser->event->id)
                ->count();

            if ($presentersCount > 0)
                return response([
                    'status' => false,
                    'message' => 'Cannot delete the user. The user is added to a presentation as a presenter'
                ]);


            $reviewersCount = PresentationReview::where('presentation_reviews.reviewer_id', '=', $user->id)
                ->join('presentations as p', 'presentation_reviews.presentation_id', '=', 'p.id')
                ->join('abstracts as a', 'p.abstract_id', '=', 'a.id')
                ->where('a.user_id', '!=', $user->id)
                ->where('presentation_reviews.event_id', '=', $eventUser->event->id)
                ->count();

            if ($reviewersCount > 0)
                return response([
                    'status' => false,
                    'message' => 'Cannot delete the user. The user is added to a presentation as a reviewer'
                ]);

            $abstracts = $user->abstracts($eventUser->event->id)->get();

            if ($abstracts) {
                $abstractInSlotCount = 0;
                $presentationInSlotCount = 0;
                foreach ($abstracts as $abstract) {
                    $slotCount = Slot::whereAbstractId($abstract->id)
                        ->count();
                    if ($slotCount > 0)
                        $abstractInSlotCount = +1;
                    $presentation = Presentation::whereAbstractId($abstract->id)->first();
                    if ($presentation) {
                        $slotCount = Slot::wherePresentationId($presentation->id)
                            ->count();
                        if ($slotCount > 0) {
                            $presentationInSlotCount = +1;
                        }
                    }
                }

                if ($abstractInSlotCount <= 0 && $presentationInSlotCount <= 0) {
                    foreach ($abstracts as $abstract) {

                        $presentation = Presentation::whereAbstractId($abstract->id)->first();
                        if ($presentation) {
                            $presentationRepo = new PresentationRepository();
                            $presentationRepo->deletePresentation($presentation);
                        }

                        //detaching abstract users
                        $abstractUsers = AbstractUser::whereAbstractsId($abstract->id)->get();
                        foreach ($abstractUsers as $abstractUser) {
                            $abstractUser->delete();
                        }
                        $event = $abstract->event;

                        //deleting abstract files
                        $fileFields = getField($event->form_settings, 'file', 'type');
                        $data = $abstract->data->toArray();
                        foreach ($fileFields as $fileField) {
                            $dataField = getField($data, $fileField['field_id']);
                            $dataField = array_shift($dataField);
                            $values = $dataField['value'] ?? [];
                            $values = $values != "" ? $values : [];
                            foreach ($values as $value) {
                                $fileId = $value['file_id'];
                                $this->deleteFile($fileId);
                            }
                        }

                        //deleting abstract scores
                        $scores = Score::whereAbstractId($abstract->id)->get();
                        $scoreRepo = new ScoreRepository();
                        foreach ($scores as $score) {
                            $scoreRepo->delete($score);
                        }

                        Storage::deleteDirectory('private/media/abstracts/' . $abstract->id);

                        $abstract->delete();
                    }
                }
                else {
                    return response([
                        'status' => false,
                        'message' => 'Cannot delete the user. Some abstracts or presentation created by this user is added to a slot'
                    ]);
                }
            }
        }
        else if ($removeRole == 'event_reviewer' || $removeRole == 'event_co_chair' || $removeRole == 'event_admin') {
            if ($eventUser->hasRole($removeRole)) {


                $abstractUserCount = AbstractUser::where('abstract_user.user_id', '=', $user->id)
                    ->join('abstracts as a', 'abstract_user.abstracts_id', '=', 'a.id')
                    ->where('a.event_id', '=', $eventUser->event->id)
                    ->count();

                if ($abstractUserCount  > 0)
                    return response([
                        'status' => false,
                        'message' => 'Cannot delete the user. The user is added in an abstract'
                    ]);

                $presentersCount = Presenter::whereUserId($user->id)->whereEventId($eventUser->event->id)
                    ->count();

                if ($presentersCount > 0)
                    return response([
                        'status' => false,
                        'message' => 'Cannot delete the user. The user is added to a presentation as a presenter'
                    ]);

                $reviewersCount = PresentationReview::whereReviewerId($user->id)->whereEventId($eventUser->event->id)
                    ->count();

                if ($reviewersCount > 0)
                    return response([
                        'status' => false,
                        'message' => 'Cannot delete the user. The user is added to a presentation as a reviewer'
                    ]);

                $scores = Score::where('user_id', '=', $user->id)->whereEventId($eventUser->event->id)->get();
                if ($scores) {
                    $scoreRepo = new ScoreRepository();
                    foreach ($scores as $score) {
                        $scoreRepo->delete($score);
                    }
                }
            }
        }

        //delete speaker notes
        $speakerNotes = SpeakerNotes::where(function($query) use ($user) {
            $query->where('user_id', '=', $user->id)
                  ->orWhere('speaker_id', '=', $user->id);
        })
        ->whereEventId($eventUser->event->id)
        ->get();

        if ($speakerNotes) {
            foreach ($speakerNotes as $speakerNote) {
                if($speakerNote->files){
                    foreach($speakerNote->files as $speakerFile){
                        $file = File::find($speakerFile['file_id']);
                        $file->delete();
                    }
                }
                $speakerNote->delete();
            }
        }

        $eventUser->removeRole($removeRole);

        //delete event user
        if ($eventUser->roles()->count() == 0)
        {
            if ($eventUser->confirmed_speaker) {
                // Get the order of the speaker being removed
                $removedSpeakerOrder = $eventUser->order;

                // Decrement the order for speakers with an order greater than the removed speaker's order
                EventUser::where('event_id', $eventUser->event->id)
                    ->where('confirmed_speaker', 1)
                    ->whereNotNull('confirmed_speaker')
                    ->where('order', '>', $removedSpeakerOrder)
                    ->decrement('order');

                // set the order of the removed speaker to null
                $eventUser->order = null;
            }
            $eventUser->delete();
        }


        $events = $user->events()->count();
        if ($events == 0) {
            //deleting the entire user
            if (!$user->hasRole('super_admin')) {
                $this->removeUserImages($user, true, true, true);
                $user->delete();
            }
        }

        return response([
            'status' => true,
            'message' => 'Successfully removed user'
        ]);
    }

    public function resetPassword(Request $request)
    {
        $userId = $request->get('id');
        $user = User::find($userId);
        $event = $request->get('event');
        $hasEventSlug = false;

        if (!$event) {
            $eventSlug = $request->get('event_slug');
            $event = Event::where('slug_name', '=', $eventSlug)->first();
            $hasEventSlug = true;
        }

        if (!$user) {
            return response([
                'status' => false,
                'message' => 'User not found'
            ]);
        }

        else {
            $password = randomStringGenerator(10);
            $user->password = Hash::make($password);
            $user->save();

            //send mail to user with new password
            dispatch(new SendAdminResetPasswordMail($user, $event, $password, $hasEventSlug));

            return response([
                'status' => true,
                'message' => "Password reset successfully"
            ]);
        }
    }

    public function sendLoginInfo(Request $request)
    {
        $userId = $request->get('id');
        $user = User::find($userId);
        $event = $request->get('event');

        if (!$user) {
            return response([
                'status' => false,
                'message' => 'User not found'
            ]);
        } else {
            $password = randomStringGenerator(10);
            $user->password = Hash::make($password);
            $user->save();

            //send mail to user with new password
            dispatch(new SendLoginInfoMail($user, $event, $password));

            return response([
                'status' => true,
                'message' => "Email Sent Successfully"
            ]);
        }
    }

    public function importUserTemplate($form_settings, $dtcm_fields_enabled, $short_form_terms_option)
    {

        //user headings
        $heading =
            [
                'Email',
                'Do Not Send Emails',
                'Password',
                'Password Confirmation',
                'Cc Emails',
                'Salutation',
                'First Name',
                'Last Name',
                'Job Title',
                'Linkedin Link',
                'Country',
                'Street',
                'Town/City',
                'State/Province',
                'Zip Code',
                'Mobile Number',
                'Phone Number',
                'Fax',
                'Company Name',
                'Company Address',
                'Company Country',
                'Industry Area',
                'Biography',
                'Roles',
                'Send Login information',
            ];


        $category_field = array_values(
            array_filter(
                $form_settings->toArray(),
                fn($settings) => $settings['field_id'] == "category"
            )
        );

        $sub_category_field = array_values(
            array_filter(
                $form_settings->toArray(),
                fn($settings) => $settings['field_id'] == "subcategory",
            )
        );


        $event_profile_data_headings = [
            $category_field[0]['label'],
            'Excluded Companies',
            'Presentation submission',
            'Maximum number of presentations',
            'Confirmed Speaker',
            'Type',
        ];

        // if short term option not enabled
        if(!$short_form_terms_option){
            $short_form_option = [
                'Send Adobe Contract',
            ];
            $event_profile_data_headings = array_merge($event_profile_data_headings, $short_form_option);
        }


        //event profile data headings
        if ($sub_category_field[0]['enabled']) {

            $event_profile_data_headings = array_merge(
                array_slice($event_profile_data_headings, 0, 1),
                array($sub_category_field[0]['label']),
                array_slice($event_profile_data_headings, 1)
            );
        }

        $heading = array_merge($heading, $event_profile_data_headings);


        if ($dtcm_fields_enabled) {

            $DTCM_form_fields = [
                'DOB',
                'Nationality',
                'Profile Type'
            ];
            $heading = array_merge($heading, $DTCM_form_fields);

        } else {
            // Add Profile Type even when DTCM is disabled
            $heading[] = 'Profile Type';
        }

        $excel = Excel::download(new ImportUserTemplate([$heading], $category_field, $sub_category_field, $short_form_terms_option), 'userTemplate.xlsx');

        return $excel;
    }

    public function importUser($request, $event)
    {
        try {
            $userFile = $request->allFiles();
            //saving to local storage
            $userFile['file']->store('import');

            Excel::import(new UsersImport($event), $userFile['file']);

            return response([
                'status' => true,
                'message' => "User Imported Successfully"
            ]);
        }
        catch (\Maatwebsite\Excel\Validators\ValidationException $e) {
            $failures = $e->failures();
            return response([
                'status' => false,
                'failures' => $failures
            ]);
        }
    }

    public function fetchAgreementStatus(EventUser $eventUser): EventUser
    {

        $event = $eventUser->event;
        $generalSettings = $event->general_settings;
        $accessToken = $generalSettings['speaker_terms_prefill']['access_token'];

        $adobeSign = getAdobeSign($accessToken);
        if (!$eventUser->agreement_details)
            return $eventUser;
        $agreementDetails = $eventUser->agreement_details->toArray();
        $agreementId = $agreementDetails['id'];
        $response = $adobeSign->getAgreement($agreementId);
        $responseContent = $response->getBody()->getContents();
        $responseContent = json_decode($responseContent, true);
        $eventUser->agreement_details = $responseContent;
        $eventUser->agreement_status = $responseContent['status'];
        $eventUser->save();

        return $eventUser;
    }

    public function downloadAgreement(EventUser $eventUser): array
    {

        $event = $eventUser->event;
        $generalSettings = $event->general_settings;
        $accessToken = $generalSettings['speaker_terms_prefill']['access_token'];

        $adobeSign = getAdobeSign($accessToken);
        $agreementDetails = $eventUser->agreement_details->toArray();
        if (!$agreementDetails)
            validationErrorResponse(['Agreement not found']);
        $agreementId = $agreementDetails['id'];
        $response = $adobeSign->getAgreementCombinedDocument($agreementId);
        $headers = $response->getHeaders();
        $streamFileData = $response->getBody()->getContents();
        $filename = "Speaker terms - " . $eventUser->user->name;
        $headers['Content-Disposition'] = ['attachment;filename=' . $filename . '.pdf;'];
        return [$headers, $streamFileData];
    }

    public function remindAgreementToSign(EventUser $eventUser)
    {
        $agreementStatus = $eventUser->agreement_status;
        if (!$agreementStatus) {
            dispatch(new SendSpeakerTerms([$eventUser]));
        }
        else {
            $agreementStatuses = ["SIGNED", "CANCELLED", "EXPIRED"];
            if (!in_array($agreementStatus, $agreementStatuses)){
                $agreementDetails = $eventUser->agreement_details->toArray();
                $agreementId = $agreementDetails['id'];

                // Check if the user's email has changed since the agreement was created
                $currentEmail = $eventUser->user->email;
                // Try to get the email from agreement details (adjust the key if needed)
                $agreementEmail = $agreementDetails['participantSetsInfo'][0]['memberInfos'][0]['email'] ?? null;

                if ($agreementEmail && $agreementEmail !== $currentEmail) {
                    // Email has changed, regenerate agreement
                    // Optionally, you may want to cancel the old agreement here
                    $eventUser->agreement_details = null;
                    $eventUser->agreement_status = null;
                    $eventUser->save();
                    dispatch(new SendSpeakerTerms([$eventUser]));
                    return;
                }

                $participantId = $agreementDetails['participantSetsInfo'][0]['memberInfos'][0]['id'];
                $reminderData = [
                    "recipientParticipantIds" => [
                        $participantId
                    ],
                    "status" => 'ACTIVE',
                ];
                $event = $eventUser->event;
                $generalSettings = $event->general_settings;
                $accessToken = $generalSettings['speaker_terms_prefill']['access_token'];

                $adobeSign = getAdobeSign($accessToken);

                $adobeSign->sendAgreementReminder($agreementId, $reminderData);
            }
        }
    }

    public function cancelAgreement(EventUser $eventUser, Event $event)
    {
        // agreement status
        $agreementStatus = $eventUser->agreement_status;
        // agreement details
        $agreementDetails = $eventUser->agreement_details;
        if (!$agreementStatus && $agreementDetails) {
            return response([
                'status' => false,
                'message' => "No agreement found"
            ]);
        }
        else {
            // new cancelled status set
            $eventUser->agreement_status = 'CANCELLED';
            // set agreement details null
            $eventUser->agreement_details = null;
            // Retrieve event profile data
            $profileData = $eventUser->event_profile_data;

            // Modify send adobe contract
            if(isset($profileData['send_adobe_contract'])) {
                $profileData['send_adobe_contract'] = false; // Modify the array directly
            }
            // Update event profile data
            $eventUser->event_profile_data = $profileData;
            // save event user
            $eventUser->save();
        }
    }

    public function generateToken($name = 'API'): NewAccessToken
    {
        $user = authUser();
        $tokens = $user->tokens()->get();
        if ($tokens->count() >= 5)
            validationErrorResponse(['Only 5 tokens are allowed per user']);

        $token = $user->createToken($name);
        $token->accessToken->plain_text_token = $token->plainTextToken;
        $token->accessToken->save();
        return $token;
    }

    /**
     * @param Builder $query
     * @param $values
     * @param $field
     * @return void
     */
    public function getFilteredQuery(Builder $query, $values, $field): void
    {

        $query->where(function ($query) use ($values, $field) {
            foreach ($values as $value) {
                           if ($value == 'set')
                                 $query->orWhere(function ($query) use ($field) {
                                    $query->whereNotNull("users.$field")
                                        ->Where("users.$field", '!=', "");
                                });
                            else if ($value == 'not_set')
                                $query->orWhereNull("users.$field")
                                    ->orWhere("users.$field", '=', "");
                            else if ($value == 'verified')
                                $query->orWhere("eu.$field", '=', true);
                            else if ($value == 'not_verified')
                                $query->orWhere("eu.$field", '=', false);
                            else if ($value == 'strategic' || $value == 'technical' || $value == 'strategic,technical')
                                $query->orWhere("eu.$field", '=', $value);
                            else if ($value == 'yes' || $value == 'no'){
                                $query->orWhere("eu.$field", '=', $value == 'yes' ? 1 : 0)
                                ->orWhere($value == 'no' ? "eu.$field" : null, null);
                            }
                            else if ($value == 'SIGNED' || $value == 'DOCUMENTS_NOT_YET_PROCESSED' || $value == 'OUT_FOR_SIGNATURE' || $value == 'CANCELLED')
                                $query->orWhere("eu.$field", '=', $value);
                            else if ($value == 'NULL')
                                $query->orWhere("eu.$field");
                            else if ($field == 'start_date') {
                                $query->where(function ($query) use ($value) {
                                    $query->whereRaw('? between sess.start_date and sess.end_date', [$value]);
                                    $query->orWhere('sess.start_date', 'LIKE', '%' . $value . '%');
                                    $query->orWhere('slots.start_date', 'LIKE', '%' . $value . '%');
                                }
                                );
                            }
                            else if ($field == 'id') {
                                $query->orWhere("sess.$field", '=', $value);
                                $query->orWhere("slots.session_id", '=', $value);
                            }
                            else
                                $query->orWhere("users.$field", '=', $value);
                        }
                    });
    }

    /**
     * user verification
     * @param $request
     */
    public function userVerification($request)
    {
        $userId = $request->get('id');
        $user = User::find($userId);
        if (!$user)
            return response([
                'status' => false,
                'message' => 'User not found'
            ]);

        $event = $request->get('event');
        $eventUser = getEventUser($event, $user);
        if (!$eventUser)
            return response([
                'status' => false,
                'message' => 'Event User not found'
            ]);


        $verificationData = $request->get('user_verification_data');
        $loginUser = authUser();
        $verifiedUserAvatar = null;
        $verifiedUserCompanyLogo = null;

        if ($user->avatar)
            $verifiedUserAvatar = $this->verificationFilesHandling($user->avatar, $user, $eventUser, $verificationData['avatar'], 'avatar');

        if ($user->company_logo)
            $verifiedUserCompanyLogo = $this->verificationFilesHandling($user->company_logo, $user, $eventUser, $verificationData['company_logo'], 'company_logo');

        // Get original verification_data from database BEFORE making changes
        $previousVerificationData = $eventUser->verification_data ?? [];

        // Get changed fields by comparing new verification_data with original verification_data
        $changedFields = [];
        if ($eventUser->verified_data) {
            foreach ($verificationData as $field => $isVerified) {
                // Skip verify_all field from changed_fields tracking
                if ($field === 'verify_all') continue;

                $previousValue = $previousVerificationData[$field] ?? true;
                if ($isVerified !== $previousValue) {
                    $changedFields[] = $field;
                }
            }
            $this->pushPreviousVerification($eventUser, $eventUser->verified_data, $previousVerificationData, $changedFields);
        }

        // Now update event user with new verification_data
        $eventUser->verification_data = $verificationData;

        $eventUser->verified_data = [
            'id' => $user->id,
            'avatar' => $verificationData['avatar'] ? $verifiedUserAvatar : null,
            'company_logo' => $verificationData['company_logo'] ? $verifiedUserCompanyLogo : null,
            'mobile' => $verificationData['mobile'] ? $user->mobile : null,
            'biography' => $verificationData['biography'] ? $user->biography : null,
            'job_title' => $verificationData['job_title'] ? $user->job_title : null,
            'salutation' => $verificationData['salutation'] ? $user->salutation : null,
            'first_name' => $verificationData['first_name'] ? $user->first_name : null,
            'last_name' => $verificationData['last_name'] ? $user->last_name : null,
            'verified_by' => $loginUser->email,
            'verification_date' => date('Y-m-d H:i:s', time())
        ];

        $eventUser->verification_status = true;
        $eventUser->verification_date = date('Y-m-d H:i:s', time());
        $eventUser->verified_by = $loginUser->id;

        // event user save
        $eventUser->save();
        // user updates for view
        $user->verification_data =  $eventUser->verification_data;
        $user->verified_data = $eventUser->verified_data;
        $user->verification_status = $eventUser->verification_status;
        $user->verification_date = $eventUser->verification_date;
        $user->verified_by = $eventUser->verified_by;

        // published session's status changed to false
        $this->publishedSessionsChange($user);

        return response([
            'status' => true,
            'user' => $user
        ]);
    }

    /**
     * Push previous verification data to the event user
     *
     * @param EventUser $eventUser
     * @param array $verifiedData
     * @param array $previousVerificationData
     * @param array $changedFields
     */
    private function pushPreviousVerification($eventUser, $verifiedData, $previousVerificationData, $changedFields = [])
    {
        // Initialize or update the previously verified data
        $history = (array) $eventUser->previously_verified_data ?? [];
        // If data is provided, add it to the history
        if ($verifiedData) {
            $newEntry = $verifiedData;
            // Exclude avatar and company_logo data from history
            unset($newEntry['avatar'], $newEntry['company_logo']);
            $newEntry['verified_at'] = $eventUser->verification_date;
            // Get verifier user email from verified_by user ID
            $verifier = \App\Models\User::find($eventUser->verified_by);
            $newEntry['verified_by'] = $verifier ? $verifier->email : null;
            $newEntry['changed_fields'] = $changedFields;
            $newEntry['verification_data'] = $previousVerificationData;
            array_unshift($history, $newEntry);
        }
        // Limit the history to the last 5 entries
        $history = array_slice($history, 0, 5);

        // Add order to each entry (1 = most recent, 5 = oldest)
        foreach ($history as $index => $entry) {
            $history[$index]['previous_verified_data_order'] = $index + 1;
        }

        // Update the event user with the new history
        $eventUser->previously_verified_data = $history;
    }


    public function publishedSessionsChange($user)
    {
        // session users
        $sessionUsers = SessionUser::whereUserId($user->id)->get();
        // slot users
        $slotUsers = SlotUser::select('slots.session_id as session_id')
            ->join('slots', 'slots.id', '=', 'slot_users.slot_id')
            ->where('slot_users.user_id', '=', $user->id)->distinct()
            ->get();

        if ($sessionUsers) {
            foreach ($sessionUsers as $sessionUser) {
                // session
                $session = Session::find($sessionUser->session_id);
                if ($session->published_status) {
                    $session->published_status = false;
                    $session->save();
                }
            }

        }
        if ($slotUsers) {
            foreach ($slotUsers as $slotUser) {
                // session
                $session = Session::find($slotUser->session_id);

                // session
                if ($session->published_status) {
                    $session->published_status = false;
                    $session->save();
                }
            }
        }

        return true;
    }

    /**
     * handling user files
     * @param $userFile
     * @param $user
     * @param $eventUser
     * @param $fieldVerificationStatus
     * @param $type
     */
    public function verificationFilesHandling($userFile, $user, $eventUser, $fieldVerificationStatus, $type)
    {
        // delete previous files
        $this->deletePreviousFiles($eventUser, $type);

        if ($fieldVerificationStatus) {
            $userPreviousFile = $userFile['public_path'];
            // update file paths
            $this->updateFilePaths($userFile, $user, $eventUser);

            $thumbnailImageName = null;
            $thumbnailPath = null;

            if ($type === 'avatar') {
                $thumbnailImageName = $this->getThumbnailImageName($userFile);
                $thumbnailPath = $this->getThumbnailPath($eventUser, $thumbnailImageName);
            }

            // create and save file record
            $file = $this->createFileRecord($userFile, $eventUser, $type, $thumbnailPath);

            // move file to new location
            Storage::putFileAs($userFile['filepath'], $userPreviousFile, $userFile['save_name'], 'public');

            // handle avatar thumbnail
            if ($type === 'avatar') {
                $this->createAndSaveThumbnail($file, $thumbnailImageName);
                $userFile['thumbnail_path'] = $thumbnailPath . "?v=" . md5(Carbon::now());
            }

            // save file ID to userFile
            $userFile['file_id'] = $file->id;
        }

        return $userFile;
    }

    // Private helper methods
    private function deletePreviousFiles($eventUser, $type)
    {
        if ($eventUser->verified_data && isset($eventUser->verified_data[$type])) {
            $filePath = $eventUser->verified_data[$type]['filepath'];
            $saveName = $eventUser->verified_data[$type]['save_name'];

            // Check if the original image exists before deleting it
            if (Storage::exists($filePath . '/' . $saveName)) {
                Storage::delete($filePath . '/' . $saveName);
            }

            if ($type === 'avatar') {
                $baseName = pathinfo($saveName, PATHINFO_FILENAME);
                $thumbnailImageName = "{$baseName}_thumbnail.{$eventUser->verified_data[$type]['extension']}";

                // Check if the thumbnail image exists before deleting it
                if (Storage::exists($filePath . '/' . $thumbnailImageName)) {
                    Storage::delete($filePath . '/' . $thumbnailImageName);
                }
            }

            // Check if the file record exists in the database before deleting it
            $file = File::find($eventUser->verified_data[$type]['file_id']);
            if ($file) {
                $file->delete();
            }
        }
    }

    private function updateFilePaths(&$userFile, $user, $eventUser)
    {
        $userFile['filepath'] = str_replace("users/{$user->id}", "verifiedUsers/{$eventUser->id}", $userFile['filepath']);
        $userFile['public_path'] = str_replace("users/{$user->id}", "verifiedUsers/{$eventUser->id}", $userFile['public_path']);
        $userFile['value'] = str_replace("users/{$user->id}", "verifiedUsers/{$eventUser->id}", $userFile['value']);
    }

    private function getThumbnailImageName($userFile)
    {
        $baseName = pathinfo($userFile['save_name'], PATHINFO_FILENAME);
        return "{$baseName}_thumbnail.{$userFile['extension']}";
    }

    private function getThumbnailPath($eventUser, $thumbnailImageName)
    {
        return User::FILE_VERIFIED_PUBLIC_PATH . "{$eventUser->id}/images/{$thumbnailImageName}";
    }

    private function createFileRecord($userFile, $eventUser, $type, $thumbnailPath)
    {
        $file = new File([
            'model_id' => $eventUser->id,
            'model' => 'App\Models\EventUser',
            'filename' => $userFile['filename'],
            'filepath' => $userFile['filepath'],
            'extension' => $userFile['extension'],
            'type' => "image",
            'context' => "eventUser.$type",
            'public' => 1
        ]);

        $file->public_path = $userFile['public_path'];
        $file->thumbnail_path = $thumbnailPath;
        $file->save_name = $userFile['save_name'];
        $file->save();

        return $file;
    }

    private function createAndSaveThumbnail($file, $thumbnailImageName)
    {
        $imagePath = storage_path("app/{$file->filepath}{$file->save_name}");
        $image = Image::make($imagePath);

        // Resize the image to 500x500
        $image->resize(500, 500, function ($constraint) {
            $constraint->aspectRatio(); // Maintain aspect ratio
            $constraint->upsize();      // Prevent upsizing if the image is smaller than 500x500
        });

        // thumbnail path
        $thumbnailRelativePath = "{$file->filepath}{$thumbnailImageName}";

        // Generate image stream
        $imageStream = $image->stream($file->extension, 85);

        Storage::put($thumbnailRelativePath, $imageStream->__toString(), 'public');
    }

    public function exportUsers($users, $event, $generalSettings, $confirmedSpeaker)
    {
        // Check if DTCM fields are enabled
        $dtcmFieldsEnabled = $generalSettings['dtcm_form_fields_section']['dtcm_form_fields'] ?? false;

        // Check if Amend Speaker Terms feature is enabled
        $amendSpeakerTermsEnabled = $generalSettings['amend_speaker_terms_section']['enabled'] ?? false;

        // Get Amend Speaker Terms checkboxes
        $amendSpeakerTermsOptions = $generalSettings['amend_speaker_terms_section']['checkboxes'] ?? [];

        // Generate heading row
        $heading = $this->generateHeading($confirmedSpeaker, $amendSpeakerTermsEnabled, $dtcmFieldsEnabled, $amendSpeakerTermsOptions);

        $excelData = [$heading];

        // Loop through users
        foreach ($users as $userObj) {
            $user = User::find($userObj->id);
            $eventUser = getEventUser($event->id, $user->id);
            $eventRoles = $eventUser->roles->pluck('name')->toArray();

            // Get avatar and company logo URLs
            $headShotLogo = $user->avatar ? config('app.url') . '/' . $user->avatar['public_path'] : '';
            $companyLogo = $user->company_logo ? config('app.url') . '/' . $user->company_logo['public_path'] : '';

            // Base user fields
            $data = $this->getUserBaseData($user, $event, $eventUser, $eventRoles, $headShotLogo, $companyLogo);

            // Append speaker-specific fields if confirmed speaker
            if ($confirmedSpeaker) {
                $data += $this->getSpeakerData($user, $event, $eventUser, $amendSpeakerTermsEnabled, $amendSpeakerTermsOptions);
            }

            // Append DTCM-specific fields if enabled
            if ($dtcmFieldsEnabled) {
                $data += $this->getDtcmData($user);
            }

            $excelData[] = $data;
        }

        // Export the Excel file
        return Excel::download(new ExportUsersClass($excelData, count($heading)), 'users.xlsx');
    }

    /**
     * Generate Excel header row
     */
    private function generateHeading($confirmedSpeaker, $amendSpeakerTermsEnabled, $dtcmFieldsEnabled, $amendSpeakerTermsOptions)
    {
        $heading = [
            'User ID', 'Salutation', 'First Name', 'Last Name', 'Email', 'Send Email or not', 'CC Emails',
            'Job Title', 'LinkedIn Profile Link', 'Country', 'Street', 'Town/City', 'State/Province', 'Zip Code',
            'Mobile Number', 'Phone Number', 'Fax', 'Company Name', 'Company Country',
            'HeadShot URL', 'Company Logo URL', 'Industry Area', 'Biography', 'Type',
            'Is Confirmed Speaker?', 'Is Reviewer?', 'Is Submitter?'
        ];

        if ($confirmedSpeaker) {
            $heading = array_merge($heading, [
                'Speaker Term Status', 'Last Note Added', 'Last Note Attachment'
            ]);

            if ($amendSpeakerTermsEnabled) {
                $heading[] = 'Amend Speaker Term';
            }
        }

        if ($dtcmFieldsEnabled) {
            $heading = array_merge($heading, [
                'First and Last Name As Stated On Passport', 'Date of birth', 'Nationality',
                'Passport Number', 'Emirates ID'
            ]);
        }

        return $heading;
    }

    /**
     * Base user data row
     */
    private function getUserBaseData($user, $event, $eventUser, $eventRoles, $headShotLogo, $companyLogo)
    {
        // Get auth user for access check
        $authUser = authUser();
        $authEventUser = getEventUser($event, $authUser);

        // Check if contact should be masked
        $maskContact = shouldRestrictContact($user, $authEventUser);
        $maskedValue = '********';

        return [
            'id' => $user->id,
            'salutation' => $user->salutation ?? '',
            'first_name' => $user->first_name ?? '',
            'last_name' => $user->last_name ?? '',
            'email' => $maskContact ? $maskedValue : ($user->email ?? ''),
            'do_not_send_emails' => $user->do_not_send_emails ? 'Do not send' : 'Send',
            'cc_emails' => $user->cc_emails ?? '',
            'job_title' => $user->job_title ?? '',
            'linkedin_link' => $user->linkedin_link ?? '',
            'country' => $user->country ?? '',
            'street' => $user->street ?? '',
            'city' => $user->city ?? '',
            'state' => $user->state ?? '',
            'post_code' => $user->post_code ?? '',
            'mobile' => $maskContact ? $maskedValue : ($user->mobile ?? ''),
            'phone' => $maskContact ? $maskedValue : ($user->phone ?? ''),
            'fax' => $user->fax ?? '',
            'company' => $user->company ?? '',
            'company_country' => $user->company_country ?? '',
            'avatar' => $headShotLogo,
            'company_logo' => $companyLogo,
            'industry_code' => $user->industry_code ?? '',
            'biography' => $user->biography ?? '',
            'type' => ucfirst($eventUser->type == 'strategic,technical' ? 'strategic and technical' : $eventUser->type),
            'is_confirmed_speaker' => $eventUser->confirmed_speaker ? 'Yes' : 'No',
            'is_reviewer' => in_array('event_reviewer', $eventRoles) ? 'Yes' : 'No',
            'is_submitter' => in_array('event_submitter', $eventRoles) ? 'Yes' : 'No',
        ];
    }

    /**
     * Confirmed speaker-related data
     */
    private function getSpeakerData($user, $event, $eventUser, $amendSpeakerTermsEnabled, $amendSpeakerTermsOptions)
    {
        $data = [];

        // Get latest note
        $latestNote = SpeakerNotes::where('speaker_id', $user->id)
            ->where('event_id', $event->id)
            ->latest('created_at')
            ->first();

        $attachments = [];
        if ($latestNote && $latestNote->files) {
            foreach ($latestNote->files as $file) {
                $attachments[] = URL::temporarySignedRoute(
                    'downloadAttachment',
                    now()->addHour(),
                    ['file_id' => $file['file_id']]
                );
            }
        }

        $data += [
            'speaker_term_status' => $eventUser->agreement_status ?? '',
            'last_note_added' => $latestNote->note ?? '',
            'notes_attachments' => !empty($attachments) ? implode(', ', $attachments) : null,
        ];

        // Get selected checkboxes if enabled
        if ($amendSpeakerTermsEnabled) {
            $amendTerms = AmendSpeakerTerms::where('speaker_id', $user->id)
                ->where('event_id', $event->id)
                ->first();

                $data['selected_checkboxes'] = $amendTerms && $amendTerms->selected_checkboxes
                ? implode(', ', array_filter(array_map(function ($checkboxId) use ($amendSpeakerTermsOptions) {
                    $checkbox = collect($amendSpeakerTermsOptions)->firstWhere('id', $checkboxId);
                    return $checkbox['label'] ?? null;
                }, $amendTerms->selected_checkboxes)))
                : null;

        }

        return $data;
    }

    /**
     * DTCM-related data
     */
    private function getDtcmData($user)
    {
        return [
            'passport_first_last_name' => $user->passport_first_last_name ?? '',
            'dob' => $user->dob ? date('d/m/Y', strtotime($user->dob)) : '',
            'nationality' => $user->nationality ?? '',
            'passport_number' => $user->passport_number ?? '',
            'emirates_id' => $user->emirates_id ?? '',
        ];
    }


    /**
     * getScheduleMailData()
     *
     * @return array
     */
    public function getScheduleMailData(object $data):Array{
        $filter     = json_decode($data->filter, 1);
        $to         = [];
        $email      = [];
        // storing recipients
        if(isset($filter['recipients'])){
            foreach($filter['recipients'] as $recipients){
                $to[] = $recipients['email'];
            }
        }
        // getting all users if there is no user selected
        if(!isset($filter['users']) || count($filter['users']) <= 0){
            $filter['users'] = $this->getAllUsersByEmailType('User');
        }

        $formatedFilter = [];
        foreach($this->userFields as $fields){
            $arrayColumn = (isset($filter[$fields])) ? $filter[$fields] : [];
            $formatedFilter[$fields] = array_column($arrayColumn, 'value');
        }
        $event   = Event::find($data->event_id);
        $request = new Request();
        $sendList = [];
        foreach($filter['users'] as $user){
            switch($user['value']){
                case "Submitters":
                    $event_role = 'event_submitter';
                    break;
                case "Admins":
                    $event_role = 'event_admin';
                    break;
                case "Reviewers":
                    $event_role = 'event_reviewer';
                    break;
                case "Co-chairs":
                    $event_role = 'event_co_chair';
                    break;
            }
            $request->replace([
                'paging' => 'All',
                'sort' => '{}',
                'filter' => json_encode($formatedFilter),
                'role' => 'event_admin',
                'event' => $event,
                'event_role' => $event_role
            ]);

            $userController = app(\App\Http\Controllers\UserController::class);
            $result = $userController->list($request, false);
            $resultArray = json_decode($result->getContent(), true);
            $sendList = array_merge($sendList, $resultArray['users']['data']);
        }
        // making unique array
        $temp = array_unique(array_column($sendList, 'email'));
        $sendListUnique = array_intersect_key($sendList, $temp);

        $variables = config('schedule_mail_settings')->User->variables;

        $valueToCheck = "Reviewers";

        if (in_array($valueToCheck, array_column($filter['users'], "value"))) {

            $variables = collect($variables);

            $newVariables = collect([
                (object) [
                    'label' => 'Assigned Abstract Count',
                    'value' => 'assigned_abstract_count',
                ],
                (object) [
                    'label' => 'Score Count',
                    'value' => 'score_count',
                ],
                (object) [
                    'label' => 'Remaining Score Count',
                    'value' => 'remaining_score_count',
                ],
            ]);

            $variables = $variables->concat($newVariables);
        }

        foreach ($sendListUnique as $key => $res) {

            $body = $this->replaceMacros($res, $data, $variables, $event->event_name);

            $substring = '{remaining_score_count}';

            $valuesToCheck = ["Submitters", "Admins", "Co-chairs"];
            if (
                (
                    in_array($valueToCheck, array_column($filter['users'], "value")) &&
                    strpos($data->body, $substring) &&
                    isset($res['remaining_score_count']) &&
                    $res['remaining_score_count'] > 0
                )
                ||
                (
                    !strpos($data->body, $substring)
                )
                ||
                (
                strpos($data->body, $substring) &&
                count(array_intersect($valuesToCheck, array_column($filter['users'], "value"))) > 0 &&
                (isset($res['remaining_score_count']) ? $res['remaining_score_count'] > 0 : true)
                )
            ) {
                $event = Event::find($data->event_id);
                $email[] = [
                    'to' => $to,
                    'bcc' => $res['send_mail'],
                    'subject' => $data->subject,
                    'body' => $body,
                    'attachments' => $data->attachments,
                    'event' => $event,
                    'event_id' => $data->event_id,
                    'id' => $data->id,
                ];
            }
        }
        return $email;
    }
    /**
     * Replace macros
     *
     * @return string
     */
    public function replaceMacros(array $row, object $data, $variables, string $event_name): String {
        $body               = $data->body;
        foreach($variables as $key => $var){
            if($var->value == 'event_name')
                continue;
            $replace = @$row[$var->value];
            if($var->value == 'user_id')
                $replace = $row['id'];
            if($var->value == "verification_status"){
                if($row['verification_status']){
                    $replace = "Verified";
                }else{
                    $replace = "Not Verified";
                }
            }
            $body  = Str::replace("{".$var->value."}", $replace, $body);
        }
        $body = Str::replace("{event_name}", $event_name, $body);
        return $body;
    }

    /**
     * getSessionScheduleMailData()
     *
     * @return array
     */
    public function getSpeakersScheduleMailData(object $data):Array{
        $filter     = json_decode($data->filter, 1);
        $to         = [];
        $email      = [];
        // storing recipients
        if(isset($filter['recipients'])){
            foreach($filter['recipients'] as $recipients){
                $to[] = $recipients['email'];
            }
        }
        // getting all users if there is no user selected
        if(!isset($filter['users']) || count($filter['users']) <= 0){
            $filter['users'] = $this->getAllUsersByEmailType('Confirmed Speakers');
        }

        $formatedFilter = [];
        foreach($this->userFields as $fields){
            $arrayColumn = (isset($filter[$fields])) ? $filter[$fields] : [];
            $formatedFilter[$fields] = array_column($arrayColumn, 'value');
        }
        $event   = Event::find($data->event_id);
        $request = new Request();
        $request->replace([
            'paging'             => 'all',
            'sort'               => '{}',
            'filter'             => json_encode($formatedFilter),
            'role'               => 'event_admin',
            'event'              => $event,
            'confirmed_speakers' => true
        ]);
        $sendList = [];
        foreach($filter['users'] as $user){
            switch($user['value']){
                case "Submitters":
                    $request->event_role = 'event_submitter';
                    break;
                case "Admins":
                    $request->event_role = 'event_admin';
                    break;
                case "Reviewers":
                    $request->event_role = 'event_reviewer';
                    break;
                case "Co-chairs":
                    $request->event_role = 'event_co_chair';
                    break;
            }
            $result  = $this->listing($request, false)->toArray();
            $sendList     = [...$sendList, ...$result];
        }
        // making unique array
        $temp           = array_unique(array_column($sendList, 'email'));
        $sendListUnique = array_intersect_key($sendList, $temp);

        foreach($sendListUnique as $key => $res){
            $formSettings = json_decode(json_encode(config('schedule_mail_settings')), 1);
            $formSettings = json_decode(json_encode($formSettings['Confirmed Speakers']['variables']));
            $body = $this->replaceMacros($res, $data, $formSettings, $event->event_name);
            $event = Event::find($data->event_id);
            $email[] = [
                'to'          => $to,
                'bcc'         => $res['send_mail'],
                'userId'         => $res['id'],
                'subject'     => $data->subject,
                'body'        => $body,
                'attachments' => $data->attachments,
                'event'       => $event,
                'event_id'    => $data->event_id,
                'id'          => $data->id,
            ];
        }
        return $email;
    }

    // adding  selected files in zip file
    public function addingToZip(object $users, Request $request, $downloadMode = 'multiple')
    {
        // Delete older files
        $this->deleteOlderZipFiles();
        // Create a new ZipArchive instance and open or create the ZIP file
        $zip = new ZipArchive;
        $zipName = 'userFiles' . time() . '.zip';
        $zip->open($zipName, ZipArchive::CREATE | ZipArchive::OVERWRITE);

        // Get the dtcm form status
        $dtcmFormStatus = $request['dtcm_form'];
        $selectedDownloads = $request['selected_downloads'];

        if ($downloadMode === 'single') {
            //  All files in root, with [User Name]_[File Type].[extension]
            foreach ($users as $user) {
                $this->addUserFilesToZipSingleFolder($user, $zip, $dtcmFormStatus, $selectedDownloads);
            }
        } else {
            // Per-user folders, original naming
            foreach ($users as $user) {
                $this->addUserFilesToZip($user, $zip, $dtcmFormStatus, $selectedDownloads);
            }
        }

        // Close the ZIP archive
        $zip->close();

        // Set the file path
        $filepath = public_path($zipName);

        if (file_exists($filepath)) {
            // maximum size
            $maximumSizeInMB = 50;
            // Check if the file size is too large
            $fileSize = filesize($zipName);
            if ($fileSize > $maximumSizeInMB * 1024 * 1024) {
                // Handle large file size
                return $this->handleLargeFileSize($zipName, $request);
            }
        }

        // Return a response with the ZIP file for download or an error message
        return file_exists($filepath)
            ? $this->returnZipFileResponse($filepath, $zipName)
            : response(['status' => false, 'message' => "File not found"]);
    }


    /**
     * Add user files to ZIP in a single folder.
     * All files are placed in the root of the ZIP with the format: [User Name]_[File Type].[extension]
     * No subfolders are created.
     *
     * @param  object $user
     * @param  \ZipArchive $zip
     * @param  bool $dtcmFormStatus
     * @param  array $selectedDownloads
     */
    private function addUserFilesToZipSingleFolder($user, $zip, $dtcmFormStatus, $selectedDownloads)
    {
        // Create a safe user name for the file prefix
        $userName = $user->name
            ? preg_replace('/[^A-Za-z0-9_\-]/', '_', $user->name)
            : preg_replace('/[^A-Za-z0-9_\-]/', '_', $user->email);

        // Helper to add a file to the root with the user name prefix
        $addFile = function ($fileData, $typeLabel) use ($zip, $userName) {
            if (isset($fileData['file_id'])) {
                $file = \App\Models\File::whereId($fileData['file_id'])->first();
                if ($file && file_exists(storage_path('app/' . $file->filepath . '/' . $file->save_name))) {
                    $ext = pathinfo($file->save_name, PATHINFO_EXTENSION);
                    // File is added to the root with [User Name]_[File Type].[extension] format
                    $filename = "{$userName}_{$typeLabel}.{$ext}";
                    $zip->addFile(storage_path('app/' . $file->filepath . '/' . $file->save_name), $filename);
                }
            }
        };

        // Add profile image (headshot) if selected
        if ($user->avatar && $selectedDownloads['profileImage']) {
            $addFile($user->avatar, 'Headshot');
        }
        // Add company logo if selected
        if ($user->company_logo && $selectedDownloads['companyLogo']) {
            $addFile($user->company_logo, 'Company_Logo');
        }
        // Add passport images if selected and dtcmFormStatus is true
        if ($dtcmFormStatus && $user->passport_images && $selectedDownloads['passportImage']) {
            foreach ($user->passport_images as $idx => $img) {
                $addFile($img, 'Passport_Image_' . ($idx + 1));
            }
        }
        // Add Emirates ID images if selected and dtcmFormStatus is true
        if ($dtcmFormStatus && $user->emirates_id_images && $selectedDownloads['emiratesIdImage']) {
            foreach ($user->emirates_id_images as $idx => $img) {
                $addFile($img, 'Emirates_ID_Image_' . ($idx + 1));
            }
        }
    }
    public function deleteOlderZipFiles()
    {
        // public folder path
        $publicPath = public_path();

        // Get a list of all files in the public folder
        $publicFiles = scandir($publicPath);

        // delete zip files older than 5 hour
        $expirationTime = now()->subHours(5)->timestamp;

        foreach ($publicFiles as $file) {
            $filePath = $publicPath . '/' . $file;

            if (is_file($filePath) && pathinfo($filePath, PATHINFO_EXTENSION) === 'zip') {
                $fileModificationTime = filemtime($filePath);

                if ($fileModificationTime < $expirationTime) {
                    // Delete the zip file
                    unlink($filePath);
                }
            }
        }
    }

    private function addUserFilesToZip($user, $zip, $dtcmFormStatus, $selectedDownloads)
    {
        // Create a folder with the email and user ID
        $folderName = $user->name ? $user->name : $user->email;

        // Avatar to zip
        if ($user->avatar && $selectedDownloads['profileImage']) {
            $this->filesToZip($user->avatar, $zip, $folderName, $folderName);
        }

        // Company logo to zip
        if ($user->company_logo && $selectedDownloads['companyLogo']) {
            $this->filesToZip($user->company_logo, $zip, $folderName, 'company_logo');
        }

        if ($dtcmFormStatus) {
            // Passport copy to zip
            if ($user->passport_images && $selectedDownloads['passportImage']) {
                $this->filesToZip($user->passport_images, $zip, $folderName, 'passport_image');
            }

            // Emirates ID to zip
            if ($user->emirates_id_images && $selectedDownloads['emiratesIdImage']) {
                $this->filesToZip($user->emirates_id_images, $zip, $folderName, 'emirates_id_image');
            }
        }
    }
    private function handleLargeFileSize($zipName, $request)
    {
        // Generate a temporary URL for the file with an expiration time
        $expiration = now()->addHours(3);
        $temporaryUrl = URL::temporarySignedRoute(
            'downloadUserFilesZip',
            $expiration,
            ['filename' => $zipName]
        );

        // Send an email to the user with the temporary download link
        $userEmail = $request->user()->email;
        // event
        $event = $request->get('event');
        // send email to user with download link
        $this->sendUserFilesDownloadLinkMail($event, $userEmail, $temporaryUrl);

        // Return the temporary URL to the user
        return response()->json([
            'status' => true,
            'temporary_url' => urldecode($temporaryUrl),
            'message' => "File is too large for direct download.
             We've emailed you a download link, Please use that link to download the file.
            ",
        ]);
    }

    public function sendUserFilesDownloadLinkMail($event, $userEmail, $temporaryUrl)
    {
        SendUserFilesDownloadLinkEmail::dispatch($event, $userEmail, $temporaryUrl);
    }

    private function returnZipFileResponse($filepath, $zipName)
    {
        return response()->download($filepath, $zipName, ['Content-Type: application/zip', 'Content-Length: ' . filesize($filepath)])->deleteFileAfterSend(true);
    }

    public function filesToZip($data, $zip, $folderName, $prefix)
    {
        if (!is_array($data)) {
            $counter = 1;
            foreach ($data as $userFile) {
                if (isset($userFile['file_id'])) {
                    $file = \App\Models\File::whereId($userFile['file_id'])->first();
                    if ($file) {
                        // Ensure there's a "/" after "passportImages" in the file path
                        $file->filepath = rtrim($file->filepath, '/') . '/';
                        // file path
                        $filePath = storage_path("app/{$file->filepath}{$file->save_name}");
                        // file name
                        $name = $folderName . '/' . $prefix . '_' . $counter . '.' . $file->extension;
                        // add to file
                        if (file_exists($filePath)) {
                            $zip->addFile($filePath, $name);
                            $counter++;
                        }
                    }
                }
            }
        } else {
            if(isset($data['file_id'])) {
                // Handle a single file
                $file = \App\Models\File::whereId($data['file_id'])->first();
                if ($file) {
                    // file path
                    $filePath = storage_path("app/{$file->filepath}{$file->save_name}");
                    // file name
                    $name = $folderName . '/' . $prefix . '.' . $file->extension;
                    // add to file
                    if (file_exists($filePath)) {
                        $zip->addFile($filePath, $name);
                    }
                }
            }
        }
    }

    public function getSpeakerNotesRepository(): SpeakerNotesRepository
    {
        return new SpeakerNotesRepository();
    }

    /**
     * Global user search across all events
     * Reuses existing search patterns but removes event scope
     *
     * @param Request $request
     * @param Event $currentEvent
     * @return array
     */
    public function globalUserSearch(Request $request, Event $currentEvent): array
    {
        $query = User::select([
            'users.id',
            'users.first_name',
            'users.last_name',
            'users.salutation',
            'users.email',
            'users.cc_emails',
            'users.do_not_send_emails',
            'users.company',
            'users.company_address',
            'users.company_country',
            'users.company_logo',
            'users.job_title',
            'users.country',
            'users.phone',
            'users.mobile',
            'users.fax',
            'users.street',
            'users.city',
            'users.state',
            'users.post_code',
            'users.linkedin_link',
            'users.industry_code',
            'users.biography',
            'users.dietary_requirements',
            'users.nationality',
            'users.profile_type',
            'users.avatar',
            'users.vip_marked_by',
            'users.last_modified_by',
            'users.last_modified_at',
            DB::raw('concat(users.first_name," ",users.last_name) as name')
        ]);

        // Apply search filters
        $this->applyGlobalSearchFilters($query, $request);

        // Get users with pagination
        $users = $query->limit(50)->get();

        // Auth user for VIP restriction check
        $authUser = authUser();
        $authEventUser = getEventUser($currentEvent, $authUser);

        // Add events and current event status for each user
        return $users->map(function ($user) use ($currentEvent, $authEventUser) {
            // Get user's events with roles
            $userEvents = $this->getUserEventsWithRoles($user->id);

            // Check if user is in current event
            $isInCurrentEvent = $userEvents->contains('id', $currentEvent->id);

            // Check VIP contact restriction
            $contactRestricted = shouldRestrictContact($user, $authEventUser);

            return [
                'id' => $user->id,
                'name' => $user->name,
                'salutation' => $user->salutation,
                'email' => $user->email,
                'cc_emails' => $user->cc_emails,
                'do_not_send_emails' => $user->do_not_send_emails,
                'phone' => $user->phone,
                'mobile' => $user->mobile,
                'fax' => $user->fax,
                'company' => $user->company,
                'company_address' => $user->company_address,
                'company_country' => $user->company_country,
                'company_logo' => $user->company_logo,
                'job_title' => $user->job_title,
                'country' => $user->country,
                'street' => $user->street,
                'city' => $user->city,
                'state' => $user->state,
                'post_code' => $user->post_code,
                'linkedin_link' => $user->linkedin_link,
                'industry_code' => $user->industry_code,
                'biography' => $user->biography,
                'dietary_requirements' => $user->dietary_requirements,
                'nationality' => $user->nationality,
                'profile_type' => $user->profile_type,
                'avatar' => $user->avatar,
                'vip_marked_by' => $user->vip_marked_by,
                'last_modified_by' => $user->last_modified_by,
                'last_modified_at' => $user->last_modified_at,
                'contact_restricted' => $contactRestricted,
                'events' => $userEvents->map(function ($event) {
                    return [
                        'name' => $event->event_name,
                        'year' => $event->year,
                        'roles' => explode(',', $event->roles),
                        'confirmed_speaker' => (bool) $event->confirmed_speaker,
                        'type' => $event->type,
                    ];
                }),
                'is_in_current_event' => $isInCurrentEvent
            ];
        })->toArray();
    }

    /**
     * Apply search filters for global search
     * Reuses existing filter patterns
     *
     * @param Builder $query
     * @param Request $request
     */
    private function applyGlobalSearchFilters(Builder $query, Request $request): void
    {
        // Name search (first_name + last_name)
        if ($request->filled('name')) {
            $name = strtolower($request->get('name'));
            $query->where(function ($q) use ($name) {
                $q->whereRaw('lower(concat(first_name," ",last_name)) like ?', ["%{$name}%"])
                  ->orWhereRaw('lower(first_name) like ?', ["%{$name}%"])
                  ->orWhereRaw('lower(last_name) like ?', ["%{$name}%"]);
            });
        }

        // Email search
        if ($request->filled('email')) {
            $email = strtolower($request->get('email'));
            $query->whereRaw('lower(email) like ?', ["%{$email}%"]);
        }

        // Company search
        if ($request->filled('company')) {
            $company = strtolower($request->get('company'));
            $query->whereRaw('lower(company) like ?', ["%{$company}%"]);
        }

        // Job title search
        if ($request->filled('job_title')) {
            $jobTitle = strtolower($request->get('job_title'));
            $query->whereRaw('lower(job_title) like ?', ["%{$jobTitle}%"]);
        }

        // Country search
        if ($request->filled('country')) {
            $country = strtolower($request->get('country'));
            $query->whereRaw('lower(country) like ?', ["%{$country}%"]);
        }
    }

    /**
     * Get user's events with roles
     * Reuses existing relationship logic
     *
     * @param int $userId
     * @return \Illuminate\Support\Collection
     */
    private function getUserEventsWithRoles(int $userId)
    {
        return DB::table('events')
            ->join('event_user', 'events.id', '=', 'event_user.event_id')
            ->join('model_has_roles as mhs', 'mhs.model_id', '=', 'event_user.id')
            ->where('mhs.model_type', '=', EventUser::class)
            ->join('roles as r', 'r.id', '=', 'mhs.role_id')
            ->where('event_user.user_id', '=', $userId)
            ->select([
                'events.id',
                'events.event_name',
                'events.year',
                'event_user.confirmed_speaker',
                'event_user.type',
                DB::raw('GROUP_CONCAT(DISTINCT r.name) as roles')
            ])
            ->groupBy('events.id', 'events.event_name', 'events.year', 'event_user.confirmed_speaker', 'event_user.type')
            ->get();
    }

    /**
     * Get autocomplete data for specified field
     *
     * @param string $field
     * @param string $query
     * @return array
     */
    public function getAutocompleteData(string $field, string $query): array
    {
        $query = strtolower(trim($query));

        switch ($field) {
            case 'name':
                return User::select(DB::raw('concat(first_name," ",last_name) as value'))
                    ->whereRaw('lower(concat(first_name," ",last_name)) like ?', ["%{$query}%"])
                    ->distinct()
                    ->limit(10)
                    ->pluck('value')
                    ->toArray();

            case 'email':
                return User::select('email as value')
                    ->whereRaw('lower(email) like ?', ["%{$query}%"])
                    ->distinct()
                    ->limit(10)
                    ->pluck('value')
                    ->toArray();

            case 'company':
                return User::select('company as value')
                    ->whereNotNull('company')
                    ->where('company', '!=', '')
                    ->whereRaw('lower(company) like ?', ["%{$query}%"])
                    ->distinct()
                    ->limit(10)
                    ->pluck('value')
                    ->toArray();

            default:
                return [];
        }
    }
}

Directory Contents

Dirs: 0 × Files: 17
Name Size Perms Modified Actions
75.27 KB lrw-rw-r-- 2026-04-30 09:24:04
Edit Download
1.21 KB lrw-rw-r-- 2025-04-21 06:11:52
Edit Download
65 B lrw-r--r-- 2024-02-09 12:37:30
Edit Download
8.53 KB lrw-rw-r-- 2025-03-03 05:39:26
Edit Download
4.14 KB lrw-rw-r-- 2025-10-28 05:24:52
Edit Download
8.53 KB lrw-rw-r-- 2025-10-28 05:24:35
Edit Download
37.58 KB lrw-rw-r-- 2026-04-07 05:00:51
Edit Download
8.71 KB lrw-r--r-- 2024-02-09 12:37:30
Edit Download
59.48 KB lrwxrwxr-x 2026-04-30 09:24:03
Edit Download
4.78 KB lrw-r--r-- 2024-02-09 12:37:30
Edit Download
10.79 KB lrw-rw-r-- 2025-04-21 06:11:52
Edit Download
11.37 KB lrw-rw-r-- 2024-07-24 04:42:48
Edit Download
72.51 KB lrw-rw-r-- 2026-04-22 04:31:21
Edit Download
11.43 KB lrw-rw-r-- 2024-09-20 05:02:14
Edit Download
6.57 KB lrw-rw-r-- 2026-03-31 07:16:20
Edit Download
4.26 KB lrw-r--r-- 2024-02-09 12:37:30
Edit Download
128.96 KB lrw-rw-r-- 2026-05-07 09:06:13
Edit Download
If ZipArchive is unavailable, a .tar will be created (no compression).