HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: /var/www/html/appointmentbook.me/wp-content/plugins/booknetic/app/Backend/Staff/StaffObject.php
<?php

namespace BookneticApp\Backend\Staff;

use BookneticApp\Models\Holiday;
use BookneticApp\Models\Location;
use BookneticApp\Models\ServiceStaff;
use BookneticApp\Models\SpecialDay;
use BookneticApp\Models\Staff;
use BookneticApp\Models\Timesheet;
use BookneticApp\Providers\Core\Capabilities;
use BookneticApp\Providers\Core\CapabilitiesException;
use BookneticApp\Providers\Core\Permission;
use BookneticApp\Providers\DB\DB;
use BookneticApp\Providers\Helpers\Date;
use BookneticApp\Providers\Helpers\Helper;
use BookneticApp\Providers\Helpers\Math;
use Exception;
use WP_User;

class StaffObject
{
    private int $id;
    private int $wpUser;
    private string $name;
    private string $profession;
    private string $phone;
    private string $email;
    private int $allowToLogin;
    private string $useExistingWpUser;
    private string $wpUserPassword;
    private int $updateWpUser;
    private string $note;
    private array $locations = [];
    private array $services = [];
    private array $weeklySchedule = [];
    private array $oldInfo = [];
    private int $oldWpUser;
    private string $profileImage = '';

    private bool $isEdit;
    private array $data;
    private array $serviceStaff = [];

    public function __construct()
    {
        $this->id = Helper::_post( 'id', '0', 'integer' );
        $this->wpUser = Helper::_post( 'wp_user', '0', 'integer' );
        $this->name = Helper::_post( 'name', '', 'string' );
        $this->profession = Helper::_post( 'profession', '', 'string' );
        $this->phone = Helper::_post( 'phone', '', 'string' );
        $this->email = Helper::_post( 'email', '', 'email' );
        $this->allowToLogin = Helper::_post( 'allow_staff_to_login', '0', 'int', [ '0', '1' ] );
        $this->useExistingWpUser = Helper::_post( 'wp_user_use_existing', 'yes', 'string', [ 'yes', 'no' ] );
        $this->wpUserPassword = Helper::_post( 'wp_user_password', '', 'string' );
        $this->updateWpUser = Helper::_post( 'update_wp_user', '0', 'int', [ '0', '1' ] );
        $this->note = Helper::_post( 'note', '', 'string' );

        $this->data = [
            'name' => $this->name,
            'profession' => $this->profession,
            'phone_number' => $this->phone,
            'about' => $this->note,
            'email' => $this->email
        ];

        $this->isEdit = $this->id > 0;
    }

    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @throws CapabilitiesException
     */
    public function hasCapability(): void
    {
        if ( $this->isEdit ) {
            Capabilities::must( 'staff_edit' );
        } else {
            Capabilities::must( 'staff_add' );
        }
    }

    /**
     * @throws Exception
     */
    public function validate(): void
    {
        if ( ! empty( $this->name ) && ! empty( $this->email ) ) {
            return;
        }

        throw new Exception( bkntc__( 'Please fill in all required fields correctly!' ) );
    }

    public function isEdit(): bool
    {
        return $this->isEdit;
    }

    /**
     * @throws Exception
     */
    public function checkIfUserCanCreateNewStaff()
    {
        if ( $this->isEdit ) {
            return;
        }

        if ( Permission::isAdministrator() ) {
            return;
        }

        if ( ! ( Permission::isAdministrator() || Capabilities::userCan( 'staff_add' ) ) && ! in_array( $this->id, Permission::myStaffId() ) ) {
            throw new Exception( bkntc__( 'You do not have sufficient permissions to perform this action' ) );
        }

    }

    /**
     * @throws Exception
     */
    public function fetchOldStaffInfo(): void
    {
        if ( ! $this->isEdit ) {
            return;
        }

        $this->oldInfo = Staff::get( $this->id )->toArray();

        if ( ! $this->oldInfo ) {
            throw new Exception( bkntc__( 'Staff not found or permission denied!' ) );
        }

        $this->oldWpUser = $this->oldInfo[ 'user_id' ] ?? 0;
    }

    /**
     * @throws Exception
     */
    public function fetchLocations(): void
    {
        $locations = Helper::_post( 'locations', '', 'string' );

        if ( ! Capabilities::tenantCan( 'locations' ) ) {
            $locations = $this->isEdit ? $this->oldInfo[ 'locations' ] : Location::limit( 1 )->fetch()->id;
        }

        $locations = explode( ',', $locations );

        foreach ( $locations as $location ) {
            if ( is_numeric( $location ) && $location > 0 ) {
                $this->locations[] = (int) $location;
            }
        }

        if ( empty( $this->locations ) ) {
            throw new Exception( bkntc__( 'Please select a location!' ) );
        }

        $this->data[ 'locations' ] = implode( ',', $this->locations );
    }

    public function fetchServices(): void
    {
        $services = Helper::_post( 'services', '', 'string' );
        $services = explode( ',', $services );

        foreach ( $services as $service ) {
            if ( is_numeric( $service ) && $service > 0 ) {
                $this->services[] = (int) $service;
            }
        }
    }

    /**
     * @throws Exception
     */
    public function checkAllowedStaffLimit(): void
    {
        if ( $this->isEdit ) {
            return;
        }

        $allowedLimit = Capabilities::getLimit( 'staff_allowed_max_number' );

        if ( $allowedLimit > -1 && Staff::count() >= $allowedLimit ) {
            throw new Exception( bkntc__( 'You can\'t add more than %d Staff. Please upgrade your plan to add more Staff.', [ $allowedLimit ] ) );
        }
    }

    /**
     * @throws Exception
     */
    public function parseWeeklySchedule(): void
    {
        $schedule = Helper::_post( 'weekly_schedule', '', 'string' );

        // check a weekly schedule array
        if ( empty( $schedule ) ) {
            throw new Exception( bkntc__( 'Please fill the weekly schedule correctly!' ) );
        }

        $schedule = json_decode( $schedule, true );

        if ( empty( $schedule ) || ! is_array( $schedule ) || count( $schedule ) !== 7 ) {
            return;
        }

        foreach ( $schedule as $dayInfo ) {
            if ( $this->validateDayInfo( $dayInfo ) ) {
                throw new Exception( bkntc__( 'Please fill the weekly schedule correctly!' ) );
            }

            $isDayOff = $dayInfo[ 'day_off' ];
            $timeEnd = $dayInfo[ 'end' ] == "24:00" ? "24:00" : Date::timeSQL( $dayInfo[ 'end' ] );

            $breaks = $isDayOff ? [] : $dayInfo[ 'breaks' ];

            $newBreaks = [];

            foreach ( $breaks as $break ) {
                if ( is_array( $break )
                    && isset( $break[ 0 ] ) && is_string( $break[ 0 ] )
                    && isset( $break[ 1 ] ) && is_string( $break[ 1 ] )
                    && Date::epoch( $break[ 1 ] ) > Date::epoch( $break[ 0 ] )
                ) {
                    $newBreaks[] = [ Date::timeSQL( $break[ 0 ] ), Date::timeSQL( $break[ 1 ] ) ];
                }
            }

            $this->weeklySchedule[] = [
                'day_off' => $isDayOff,
                'start' => $isDayOff ? '' : Date::timeSQL( $dayInfo[ 'start' ] ),
                'end' => $isDayOff ? '' : $timeEnd,
                'breaks' => $newBreaks,
            ];
        }
    }

    private function validateDayInfo( $info ): bool
    {
        return ! (
            isset( $info[ 'start' ] ) && is_string( $info[ 'start' ] )
            && isset( $info[ 'end' ] ) && is_string( $info[ 'end' ] )
            && isset( $info[ 'day_off' ] ) && is_numeric( $info[ 'day_off' ] )
            && isset( $info[ 'breaks' ] ) && is_array( $info[ 'breaks' ] )
        );
    }

    /**
     * @throws Exception
     */
    public function handleStaffLogin(): void
    {
        if ( ! Permission::isAdministrator() && ! Capabilities::userCan( 'staff_allow_to_login' ) ) {
            return;
        }

        if ( $this->allowToLogin != 1 ) {
            $this->loginDisallowed();
            return;
        }

        if ( $this->useExistingWpUser == 'yes' ) {
            $this->wpUserSpecified();
        } else if ( $this->useExistingWpUser == 'no' ) {
            $this->wpUserNotSpecified();
        }

        $this->data[ 'user_id' ] = $this->wpUser;
    }

    private function loginDisallowed(): void
    {
        $this->removeRoleFromOldUser();

        $this->wpUser = 0;
        $this->data[ 'user_id' ] = 0;
    }

    /**
     * @throws Exception
     */
    private function wpUserSpecified(): void
    {
        if ( ! ( $this->wpUser > 0 ) ) {
            throw new Exception( bkntc__( 'Please select WordPress user!' ) );
        }

        if ( isset( $this->oldWpUser ) && $this->wpUser !== $this->oldWpUser ) {
            $this->removeRoleFromOldUser();
        }

        $this->addRoleToNewUser();


        if ( ! $this->isEdit || ! $this->updateWpUser ) {
            return;
        }

        $this->wpUpdateUser( [
            'user_email' => $this->email,
            'display_name' => $this->name,
            'first_name' => $this->name,
        ] );

        DB::DB()->update( DB::DB()->users, [
            'user_login' => $this->email
        ], [
            'ID' => $this->wpUser
        ] );
    }

    /**
     * @throws Exception
     */
    private function wpUserNotSpecified(): void
    {
        $emailExists = email_exists( $this->email );
        $userNameExists = username_exists( $this->email );
        $wpUserExists = $emailExists !== false || $userNameExists !== false;

        if ( ! ( $this->isEdit && $this->oldWpUser > 0 ) && empty( $this->wpUserPassword ) ) {
            throw new Exception( bkntc__( 'Please type the password of the WordPress user!' ) );
        } else if ( ( ! $this->isEdit || $this->email != $this->oldInfo[ 'email' ] ) && $wpUserExists ) {
            throw new Exception( bkntc__( 'The WordPress user with the same email address already exists!' ) );
        }

        if ( $wpUserExists ) {
            $wpUser = empty( $emailExists ) ? $userNameExists : $emailExists;
            $userToBeUpdated = get_userdata( $wpUser );
            $isUserLoginEmail = filter_var( $userToBeUpdated->user_login, FILTER_VALIDATE_EMAIL );

            $userUpdateInfo = [
                'ID' => $wpUser,
                'user_email' => $this->email,
                'display_name' => $this->name,
                'role' => 'booknetic_staff',
                'first_name' => $this->name
            ];

            if ( $isUserLoginEmail )
                $userUpdateInfo[ 'user_login' ] = $this->email;

            if ( ! empty( $this->wpUserPassword ) ) {
                $userUpdateInfo[ 'user_pass' ] = $this->wpUserPassword;
            }

            $wpUser = wp_update_user( $userUpdateInfo );
        } else {
            $wpUser = wp_insert_user( [
                'user_login' => $this->email,
                'user_email' => $this->email,
                'display_name' => $this->name,
                'first_name' => $this->name,
                'last_name' => '',
                'role' => 'booknetic_staff',
                'user_pass' => $this->wpUserPassword
            ] );
        }

        if ( is_wp_error( $wpUser ) ) {
            throw new Exception( $wpUser->get_error_message() );
        }

        $this->wpUser = $wpUser;
    }

    /**
     * @throws Exception
     */
    private function wpUpdateUser( $data ): void
    {
        $data[ 'ID' ] = $this->wpUser;

        $wpError = wp_update_user( $data );

        if ( is_wp_error( $wpError ) ) {
            throw new Exception( $wpError->get_error_message() );
        }
    }

    private function removeRoleFromOldUser(): void
    {
        if ( ! $this->isEdit || $this->oldWpUser <= 0 ) {
            return;
        }

        $userData = get_userdata( $this->oldWpUser );

        if ( ! $userData || ! in_array( 'booknetic_staff', $userData->roles ) ) {
            return;
        }

        $user = new WP_User( $this->oldWpUser );
        $user->remove_role( 'booknetic_staff' );
    }

    private function addRoleToNewUser(): void
    {
        $user = new WP_User( $this->wpUser );
        $user->add_role( 'booknetic_staff' );
    }

    /**
     * @throws Exception
     */
    public function handleProfileImage(): void
    {
        if ( ! isset( $_FILES[ 'image' ] ) || ! is_string( $_FILES[ 'image' ][ 'tmp_name' ] ) ) {
            return;
        }

        $pathInfo = pathinfo( $_FILES[ "image" ][ "name" ] );
        $extension = strtolower( $pathInfo[ 'extension' ] );

        if ( ! in_array( $extension, [ 'jpg', 'jpeg', 'png' ] ) ) {
            throw new Exception( bkntc__( 'Only JPG and PNG images allowed!' ) );
        }

        $this->profileImage = md5( base64_encode( rand( 1, 9999999 ) . microtime( true ) ) ) . '.' . $extension;
        $fileName = Helper::uploadedFile( $this->profileImage, 'Staff' );

        move_uploaded_file( $_FILES[ 'image' ][ 'tmp_name' ], $fileName );

        $this->data[ 'profile_image' ] = $this->profileImage;
    }

    public function applySqlDataFilters(): void
    {
        if ( $this->isEdit && $this->oldInfo[ 'user_id' ] > 0 && ! Permission::isAdministrator() ) {
            $this->data[ 'email' ] = $this->oldInfo[ 'email' ];
        }

        $this->data = apply_filters( 'staff_sql_data', $this->data );
    }

    /**
     * Saves the current object.
     */
    public function save(): void
    {
        if ( $this->isEdit ) {
            $this->update();
            return;
        }

        $this->insert();
        do_action( 'bkntc_staff_created', $this->getId() );
    }

    /**
     * Update the staff information and delete related timesheets and service staff records.
     */
    private function update(): void
    {
        if ( empty( $this->profileImage ) ) {
            unset( $this->data[ 'profile_image' ] );
        } else if ( ! empty( $this->oldInfo[ 'profile_image' ] ) ) {
            $this->removePreviousImage();
        }

        Staff::whereId( $this->id )->update( $this->data );
        Timesheet::where( 'staff_id', $this->id )->delete();

        $this->serviceStaff = ServiceStaff::where( 'staff_id', $this->id )->fetchAll();

        ServiceStaff::where( 'staff_id', $this->id )->delete();
    }

    /**
     * Inserts the staff data into the database.
     */
    private function insert(): void
    {
        $this->data[ 'is_active' ] = 1;

        Staff::insert( $this->data );

        $this->id = DB::lastInsertedId();
    }

    private function removePreviousImage(): void
    {
        $filePath = Helper::uploadedFile( $this->oldInfo[ 'profile_image' ], 'Staff' );

        if ( is_file( $filePath ) && is_writable( $filePath ) ) {
            unlink( $filePath );
        }
    }

    public function saveServiceStaff(): void
    {
        foreach ( $this->services as $serviceId ) {
            $serviceStaffData = [
                'staff_id' => $this->id,
                'service_id' => $serviceId,
                'price' => Math::floor( -1 ),
                'deposit' => Math::floor( -1 ),
                'deposit_type' => 'percent'
            ];

            if ( ! empty( $this->serviceStaff ) ) {
                foreach ( $this->serviceStaff as $row ) {
                    if ( $row->service_id == $serviceId && $row->staff_id == $this->id ) {
                        unset( $row[ 'id' ] );
                        $serviceStaffData = $row->toArray();
                    }
                }
            }

            ServiceStaff::insert( $serviceStaffData );
        }
    }

    public function saveWeeklySchedule(): void
    {
        if ( empty( $this->weeklySchedule ) ) {
            return;
        }

        Timesheet::insert( [
            'timesheet' => json_encode( $this->weeklySchedule ),
            'staff_id' => $this->id
        ] );
    }

    public function saveSpecialDays(): void
    {
        $specialDays = Helper::_post( 'special_days', '', 'string' );

        $specialDays = json_decode( $specialDays, true );
        $specialDays = is_array( $specialDays ) ? $specialDays : [];

        $specialDayIds = [];

        foreach ( $specialDays as $day ) {
            if (
                ! (
                    isset( $day[ 'date' ] ) && is_string( $day[ 'date' ] )
                    && isset( $day[ 'start' ] ) && is_string( $day[ 'start' ] )
                    && isset( $day[ 'end' ] ) && is_string( $day[ 'end' ] )
                    && isset( $day[ 'breaks' ] ) && is_array( $day[ 'breaks' ] )
                )
            ) {
                continue;
            }

            $spId = isset( $day[ 'id' ] ) ? (int) $day[ 'id' ] : 0;
            $date = Date::dateSQL( Date::reformatDateFromCustomFormat( $day[ 'date' ] ) );

            $newBreaks = [];

            foreach ( $day[ 'breaks' ] as $break ) {
                if ( is_array( $break )
                    && isset( $break[ 0 ] ) && is_string( $break[ 0 ] )
                    && isset( $break[ 1 ] ) && is_string( $break[ 1 ] )
                    && Date::epoch( $break[ 1 ] ) > Date::epoch( $break[ 0 ] )
                ) {
                    $newBreaks[] = [ Date::timeSQL( $break[ 0 ] ), $break[ 1 ] == '24:00' ? '24:00' : Date::timeSQL( $break[ 1 ] ) ];
                }
            }

            $timesheet = json_encode( [
                'day_off' => 0,
                'start' => Date::timeSQL( $day[ 'start' ] ),
                'end' => ( $day[ 'end' ] == "24:00" ? "24:00" : Date::timeSQL( $day[ 'end' ] ) ),
                'breaks' => $newBreaks,
            ] );

            if ( $spId > 0 ) {
                SpecialDay::where( 'id', $spId )
                    ->where( 'staff_id', $this->id )
                    ->update( [
                        'timesheet' => $timesheet,
                        'date' => $date
                    ] );

                $specialDayIds[] = $spId;
            } else {
                SpecialDay::insert( [
                    'timesheet' => $timesheet,
                    'date' => $date,
                    'staff_id' => $this->id
                ] );

                $specialDayIds[] = DB::lastInsertedId();
            }
        }

        if ( ! $this->isEdit ) {
            return;
        }

        $oldDays = SpecialDay::where( 'staff_id', $this->id );

        if ( ! empty( $specialDayIds ) ) {
            $oldDays = $oldDays->where( 'id', 'not in', $specialDayIds );
        }

        $oldDays->delete();
    }

    public function saveHolidays(): void
    {
        $holidays = Helper::_post( 'holidays', '', 'string' );
        $holidays = json_decode( $holidays, true );
        $holidays = is_array( $holidays ) ? $holidays : [];

        $holidayIds = [];

        foreach ( $holidays as $holiday ) {
            if ( ! is_numeric( $holiday[ 'id' ] ) ) {
                continue;
            }

            if ( empty( $holiday[ 'date' ] ) || ! is_string( $holiday[ 'date' ] ) ) {
                continue;
            }

            $holidayId = (int) $holiday[ 'id' ];
            $holidayDate = Date::dateSQL( $holiday[ 'date' ] );

            if ( $holidayId === 0 ) {
                Holiday::insert( [
                    'date' => $holidayDate,
                    'staff_id' => $this->id
                ] );

                $holidayIds[] = DB::lastInsertedId();
            } else {
                $holidayIds[] = $holidayId;
            }
        }

        if ( ! $this->isEdit ) {
            return;
        }

        $oldDays = Holiday::where( 'staff_id', $this->id );

        if ( ! empty( $holidayIds ) ) {
            $oldDays = $oldDays->where( 'id', 'not in', $holidayIds );
        }

        $oldDays->delete();
    }

    public function saveTranslations(): void
    {
        Staff::handleTranslation( $this->id );
    }
}