<?php

/**
* LimeSurvey
* Copyright (C) 2011-2026 The LimeSurvey Project Team
* All rights reserved.
* License: GNU/GPL License v2 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*
*/

use LimeSurvey\Models\Services\UserManager;

/**
 * Class User
 *
 * @property integer $uid User ID - primary key
 * @property string $users_name Users username
 * @property string $password User's password hash
 * @property string $full_name User's full name
 * @property integer $parent_id
 * @property string $lang User's preferred language: (auto: automatic | languagecodes eg 'en')
 * @property string $email User's e-mail address
 * @property string $htmleditormode User's preferred HTML editor mode:(default|inline|popup|none)
 * @property string $templateeditormode User's preferred template editor mode:(default|full|none)
 * @property string $questionselectormode User's preferred Question type selector:(default|full|none)
 * @property string $one_time_pw User's one-time-password hash
 * @property integer $dateformat Date format type 1-12
 * @property string $created Time created Time user was created as 'YYYY-MM-DD hh:mm:ss'
 * @property string $modified Time modified Time created Time user was modified as 'YYYY-MM-DD hh:mm:ss'
 * @property string $validation_key  used for email link to reset or create a password for a survey participant
 *                                   Link is send when user is created or password has been reset
 * @property string $validation_key_expiration datetime when the validation key expires
 * @property string $last_forgot_email_password datetime when user send email for forgot pw the last time (prevent bot)
 *
 * @property Permission[] $permissions
 * @property User $parentUser Parent user
 * @property string $parentUserName  Parent user's name
 * @property string $last_login
 * @property Permissiontemplates[] $roles
 * @property UserGroup[] $groups
 * @property int $user_status User's account status (1: activated | 0: deactivated)
 */

class User extends LSActiveRecord
{
    /** @var int maximum time the validation_key is valid*/
    public const MAX_EXPIRATION_TIME_IN_HOURS = 48;

    /** @var int maximum days the validation key is valid */
    private const MAX_EXPIRATION_TIME_IN_DAYS = 2;

    /** @var int  maximum length for the validation_key*/
    private const MAX_VALIDATION_KEY_LENGTH = 38;

    /**
     * @var string $lang Default value for user language
     */
    public $lang = 'auto';

    public $searched_value;

    /** @var null|string To be in search */
    public $search_parentUserName;

    /**
     * @inheritdoc
     * @return User
     */
    public static function model($className = __CLASS__)
    {
        /** @var self $model */
        $model = parent::model($className);
        return $model;
    }

    /** @inheritdoc */
    public function tableName()
    {
        return '{{users}}';
    }

    /** @inheritdoc */
    public function primaryKey()
    {
        return 'uid';
    }
    /** @inheritdoc */
    public function relations()
    {
        return array(
            'permissions' => array(self::HAS_MANY, 'Permission', 'uid'),
            'parentUser' => array(self::HAS_ONE, 'User', array('uid' => 'parent_id')),
            'settings' => array(self::HAS_MANY, 'SettingsUser', 'uid'),
            'groups' => array(self::MANY_MANY, 'UserGroup', '{{user_in_groups}}(uid,ugid)'),
            'roles' => array(self::MANY_MANY, 'Permissiontemplates', '{{user_in_permissionrole}}(uid,ptid)')
        );
    }

    /** @inheritdoc */
    public function rules()
    {
        return array(
            array('users_name, password', 'required'),
            array('email', 'required', 'on' => 'insert'),
            array('users_name', 'unique'),
            array('users_name', 'length','max' => 64),
            array('full_name', 'length','max' => 50),
            array('email', 'email', 'allowEmpty' => true),
            array('email', 'unique', 'allowEmpty' => true, 'message' => gT("Email address '{value}' is already used by another user.", 'unescaped')),
            array('full_name', 'LSYii_Validators'), // XSS if non super-admin
            array('parent_id', 'default', 'value' => 0),
            array('parent_id', 'numerical', 'integerOnly' => true),
            array('lang', 'default', 'value' => Yii::app()->getConfig('defaultlang')),
            array('lang', 'LSYii_Validators', 'isLanguage' => true),
            array('htmleditormode', 'default', 'value' => 'default'),
            array('htmleditormode', 'in', 'range' => array('default', 'inline', 'popup', 'none'), 'allowEmpty' => true),
            array('questionselectormode', 'default', 'value' => 'default'),
            array('questionselectormode', 'in', 'range' => array('default', 'full', 'none'), 'allowEmpty' => true),
            array('templateeditormode', 'default', 'value' => 'default'),
            array('templateeditormode', 'in', 'range' => array('default', 'full', 'none'), 'allowEmpty' => true),
            array('dateformat', 'numerical', 'integerOnly' => true, 'allowEmpty' => true),
            array('expires', 'date','format' => ['yyyy-M-d H:m:s.???','yyyy-M-d H:m:s','yyyy-M-d H:m'],'allowEmpty' => true),
            array('users_name', 'unsafe' , 'on' => ['update']),

            // created as datetime default current date in create scenario ?
            // modifier as datetime default current date ?
            array('validation_key', 'length','max' => self::MAX_VALIDATION_KEY_LENGTH),
            //todo: write a rule for date (can also be null)
            //array('lastForgotPwEmail', 'numerical', 'integerOnly' => true, 'allowEmpty' => true),
        );
    }

    /** @inheritdoc */
    protected function beforeSave()
    {
        // Normalize empty email to NULL so the database unique index
        // allows multiple users without an email address.
        if ($this->email === '') {
            $this->email = null;
        }
        return parent::beforeSave();
    }

    /** @inheritdoc */
    public function scopes()
    {
        if (App()->getConfig("DBVersion") < 495) {
            /* No expires column before 495 */
            return array(
                'active' => [],
                'notexpired' => [],
            );
        }
        $notExpiredScope = array(
            'condition' => "expires > :now OR expires IS NULL",
            'params' => array(
                'now' => gmdate("Y-m-d H:i:s"),
            )
        );
        if (App()->getConfig("DBVersion") < 619) {
            /* No user_status column before 619 */
            return array(
                'active' => [],
                'notexpired' => $notExpiredScope
            );
        }
        $userStatusType = \Yii::app()->db->schema->getTable('{{users}}')->columns['user_status']->dbType;
        $activeScope = array(
            'condition' => 'user_status = :active',
            'params' => array(
                'active' => $userStatusType == 'boolean' ? 'TRUE' :  '1',
            )
        );
        return array(
            'active' => $activeScope,
            'notexpired' => $notExpiredScope
        );
    }

    public function attributeLabels()
    {
        return [
            'uid' => gT('User ID'),
            'users_name' => gT('Username'),
            'password' => gT('Password'),
            'full_name' => gT('Full name'),
            'parent_id' => gT('Parent user'),
            'lang' => gT('Language'),
            'email' => gT('Email'),
            'htmleditormode' => gT('Editor mode'),
            'templateeditormode' => gT('Theme editor mode'),
            'questionselectormode' => gT('Question selector mode'),
            'one_time_pw' => gT('One-time password'),
            'dateformat' => gT('Date format'),
            'created' => gT('Created'),
            'modified' => gT('Modified'),
            'last_login' => gT('Last recorded login'),
            'expires' => gT("Expiry date/time:"),
            'user_status' => gT("Status"),
        ];
    }

    /**
     * @inheritDoc
     * Delete user in related model after deletion
     * return void
     **/
    protected function afterDelete()
    {
        parent::afterDelete();
        /* Delete all permission */
        Permission::model()->deleteAll(
            "uid = :uid",
            [":uid" => $this->uid]
        );
        /* Delete potential roles */
        UserInPermissionrole::model()->deleteAll(
            "uid = :uid",
            [":uid" => $this->uid]
        );
        /* User settings */
        SettingsUser::model()->deleteAll(
            "uid = :uid",
            [":uid" => $this->uid]
        );
        /* User in group */
        UserInGroup::model()->deleteAll(
            "uid = :uid",
            [":uid" => $this->uid]
        );
    }

    /**
     * @return string
     */
    public function getSurveysCreated()
    {
        $noofsurveys = Survey::model()->countByAttributes(array("owner_id" => $this->uid));
        return $noofsurveys;
    }

    /**
     * Return the needed PHP date format for current user
     * @return string
     */
    public function getDateFormat()
    {
        $dateFormat = getDateFormatData(Yii::app()->session['dateformat']);
        return $dateFormat['phpdate'];
    }

    /**
     * Return a formatted date attribute in current user date format.
     * @param Object $data see https://www.yiiframework.com/doc/api/1.1/CDataColumn#value-detail
     * @param string $attribute date attribute name
     * @return string formatted date
     */
    private function getFormattedDate($data, $attribute)
    {
        if ($data->$attribute) {
            return strval(convertDateTimeFormat($data->$attribute, 'Y-m-d', $this->getDateFormat()));
        }
        return "";
    }

    /**
     * @deprecated 6.17.0
     */
    public function getFormattedDateCreated()
    {
        return $this->getFormattedDate($this, 'created');
    }

    /**
     * get a boolean and return an HTML for grid
     * @param Object $data see https://www.yiiframework.com/doc/api/1.1/CDataColumn#value-detail
     * @param string $attribute to use
     * @return string the html for grid
     **/
    private function getFormattedBoolean($data, $attribute)
    {
        if ($data->$attribute) {
            return '<span class="text-success ri-check-fill"></span><span class="sr-only">' . gT("Yes") . '</span>';
        }
        return '<span class="sr-only">' . gT("No") . '</span>';
    }

    /**
     * Creates new user
     *
     * @access public
     * @param string $new_user
     * @param string $new_pass
     * @param string $new_full_name
     * @param int $parent_user
     * @param string $new_email
     * @param string|null $expires
     * @param boolean $status
     * @return integer|User User ID on success, User model with errors on validation failure
     */
    public static function insertUser($new_user, $new_pass, $new_full_name, $parent_user, $new_email, $expires = null, $status = true)
    {
        $oUser = new self();
        $oUser->users_name = $new_user;
        $oUser->setPassword($new_pass);
        $oUser->full_name = $new_full_name;
        $oUser->parent_id = $parent_user;
        $oUser->lang = 'auto';
        $oUser->email = $new_email;
        $oUser->created = gmdate('Y-m-d H:i:s');
        $oUser->modified = gmdate('Y-m-d H:i:s');
        $oUser->expires = $expires;
        $oUser->user_status = $status;
        if ($oUser->save()) {
            return $oUser->uid;
        } else {
            return $oUser;
        }
    }

    /**
     * Finds user by username
     * @param string $sUserName
     * @return User
     */
    public static function findByUsername($sUserName)
    {
        /** @var User $oUser */
        $oUser = User::model()->findByAttributes(array(
            'users_name' => $sUserName
        ));
        return $oUser;
    }

    /**
     * Updates user password hash
     *
     * @param int $iUserID The User ID
     * @param string $sPassword The clear text password
     * @return int number of rows updated
     */
    public static function updatePassword($iUserID, $sPassword)
    {
        return User::model()->updateByPk($iUserID, array('password' => password_hash($sPassword, PASSWORD_DEFAULT)));
    }

    /**
     * Set user password with hash
     *
     * @param string $sPassword The clear text password
     * @return \User
     */
    public function setPassword($sPassword, $save = false)
    {
        $this->password = password_hash($sPassword, PASSWORD_DEFAULT);
        if ($save) {
            $this->save();
        }
        return $this; // Return current object
    }

    /**
     * Check if password is OK for current \User
     *
     * @param string $sPassword The clear password
     * @return boolean
     */
    public function checkPassword($sPassword)
    {
        // password can not be empty
        if (empty($this->password)) {
            return false;
        }
        // Password is OK
        if (password_verify($sPassword, $this->password)) {
            return true;
        }
        // It can be an old password
        if ($this->password == hash('sha256', $sPassword)) {
            $this->setPassword($sPassword, true);
            return true;
        }
        return false;
    }


    /**
     * Checks the strength of a given password against configured validation rules.
     *
     * This function evaluates the password strength based on length, presence of lowercase
     * and uppercase letters, numbers, and special characters. It also allows for plugin-based
     * additional password requirement checks.
     *
     * @param string $password The password to check for strength
     *
     * @return string An error message if the password doesn't meet the requirements, or an empty string if it's valid
     */
    public function checkPasswordStrength(string $password)
    {
        $settings = Yii::app()->getConfig("passwordValidationRules");
        $length = strlen($password);
        $lowercase = preg_match_all('@[a-z]@', $password);
        $uppercase = preg_match_all('@[A-Z]@', $password);
        $number    = preg_match_all('@[0-9]@', $password);
        $specialChars = preg_match_all('@[^\w]@', $password);

        $resultDefaultRules = "";
        if ((int) $settings['min'] > 0) {
            if ($length < $settings['min']) {
                $resultDefaultRules = sprintf(ngT('Password must be at least %d character long|Password must be at least %d characters long', $settings['min']), $settings['min']);
            }
        }
        if ((int) $settings['max'] > 0) {
            if ($length > $settings['max']) {
                $resultDefaultRules = sprintf(ngT('Password must be at most %d character long|Password must be at most %d characters long', $settings['max']), $settings['max']);
            }
        }
        if ((int) $settings['lower'] > 0) {
            if ($lowercase < $settings['lower']) {
                $resultDefaultRules = sprintf(ngT('Password must include at least %d lowercase letter|Password must include at least %d lowercase letters', $settings['lower']), $settings['lower']);
            }
        }
        if ((int) $settings['upper'] > 0) {
            if ($uppercase < $settings['upper']) {
                $resultDefaultRules = sprintf(ngT('Password must include at least %d uppercase letter|Password must include at least %d uppercase letters', $settings['upper']), $settings['upper']);
            }
        }
        if ((int) $settings['numeric'] > 0) {
            if ($number < $settings['numeric']) {
                $resultDefaultRules = sprintf(ngT('Password must include at least %d number|Password must include at least %d numbers', $settings['numeric']), $settings['numeric']);
            }
        }
        if ((int) $settings['symbol'] > 0) {
            if ($specialChars < $settings['symbol']) {
                $resultDefaultRules = sprintf(ngT('Password must include at least %d special character|Password must include at least %d special characters', $settings['symbol']), $settings['symbol']);
            }
        }
        $passwordOk = ($resultDefaultRules === '');
        $oPasswordTestEvent = new PluginEvent('checkPasswordRequirement');
        $oPasswordTestEvent->set('password', $password);
        $oPasswordTestEvent->set('passwordOk', $passwordOk);
        $oPasswordTestEvent->set('passwordError', $resultDefaultRules);
        Yii::app()->getPluginManager()->dispatchEvent($oPasswordTestEvent);
        return ($oPasswordTestEvent->get('passwordOk') ? '' : $oPasswordTestEvent->get('passwordError'));
    }

    /**
     * Checks if
     *  -- password strength
     *  -- oldpassword is correct
     *  -- oldpassword and newpassword are identical
     *  -- newpassword and repeatpassword are identical
     *  -- newpassword is not empty
     *
     * @param string $newPassword
     * @param string $oldPassword

     * @param string $repeatPassword
     * @return string empty string means everything is ok, otherwise error message is returned
     */
    public function validateNewPassword(string $newPassword, string $oldPassword, string $repeatPassword)
    {
        $errorMsg = '';

        if (!empty($newPassword)) {
            $errorMsg = $this->checkPasswordStrength($newPassword);
        }

        if ($errorMsg === '') {
            if (!$this->checkPassword($oldPassword)) {
                // Always check password
                $errorMsg = gT("Your new password was not saved because the old password was wrong.");
            } elseif (trim($oldPassword) === trim($newPassword)) {
                //First test if old and new password are identical => no need to save it (or ?)
                $errorMsg = gT("Your new password was not saved because it matches the old password.");
            } elseif (trim($newPassword) !== trim($repeatPassword)) {
                //Then test the new password and the repeat password for identity
                $errorMsg = gT("Your new password was not saved because the passwords did not match.");
                //Now check if the old password matches the old password saved
            } elseif (empty(trim($newPassword))) {
                $errorMsg = gT("The new password can not be empty.");
            }
        }

        return $errorMsg;
    }

    /**
     * @todo document me
     */
    public function getPasswordHelpText()
    {
        $settings =  Yii::app()->getConfig("passwordValidationRules");
        $txt = gT('A password must meet the following requirements: ');
        if ((int) $settings['min'] > 0) {
            $txt .= sprintf(ngT('At least %d character long.|At least %d characters long.', $settings['min']), $settings['min']) . ' ';
        }
        if ((int) $settings['max'] > 0) {
            $txt .= sprintf(ngT('At most %d character long.|At most %d characters long.', $settings['max']), $settings['max']) . ' ';
        }
        if ((int) $settings['min'] > 0 && (int) $settings['max'] > 0) {
            if ($settings['min'] == $settings['max']) {
                $txt .= sprintf(ngT('Exactly %d character long.|Exactly %d characters long.', $settings['min']), $settings['min']) . ' ';
            } elseif ($settings['min'] < $settings['max']) {
                $txt .= sprintf(gT('Between %d and %d characters long.'), $settings['min'], $settings['max']) . ' ';
            }
        }
        if ((int) $settings['lower'] > 0) {
            $txt .= sprintf(ngT('At least %d lower case letter.|At least %d lower case letters.', $settings['lower']), $settings['lower']) . ' ';
        }
        if ((int) $settings['upper'] > 0) {
            $txt .= sprintf(ngT('At least %d upper case letter.|At least %d upper case letters.', $settings['upper']), $settings['upper']) . ' ';
        }
        if ((int) $settings['numeric'] > 0) {
            $txt .= sprintf(ngT('At least %d number.|At least %d numbers.', $settings['numeric']), $settings['numeric']) . ' ';
        }
        if ((int) $settings['symbol'] > 0) {
            $txt .= sprintf(ngT('At least %d special character.|At least %d special characters.', $settings['symbol']), $settings['symbol']) . ' ';
        }
        return($txt);
    }

    /**
     * Returns User ID common in Survey_Permissions and User_in_groups
     * @param $surveyid
     * @param $postusergroupid
     * @return CDbDataReader
     */
    public function getCommonUID($surveyid, $postusergroupid)
    {
        $query2 = "SELECT b.uid FROM (SELECT uid FROM {{permissions}} WHERE entity_id = :surveyid AND entity = 'survey') AS c RIGHT JOIN {{user_in_groups}} AS b ON b.uid = c.uid WHERE c.uid IS NULL AND b.ugid = :postugid";
        return Yii::app()->db->createCommand($query2)->bindParam(":surveyid", $surveyid, PDO::PARAM_INT)->bindParam(":postugid", $postusergroupid, PDO::PARAM_INT)->query(); //Checked
    }

    /**
     * @todo document me
     */
    public function getGroupList()
    {
        $collector = array_map(function ($oUserInGroup) {
            return $oUserInGroup->name;
        }, $this->groups);
        return join(', ', $collector);
    }

    /**
     * Return all super admins in the system
     * @return User[]
     */
    public function getSuperAdmins()
    {
        // TODO should be static
        $criteria = new CDbCriteria();
        /* have read superadmin permissions */
        $criteria->with = array('permissions');
        $criteria->compare('permissions.permission', 'superadmin');
        $criteria->compare('permissions.read_p', '1');
        /* OR are inside forcedsuperadmin config */
        $criteria->addInCondition('t.uid', App()->getConfig('forcedsuperadmin'), 'OR');
        /** @var User[] $users */
        $users = $this->findAll($criteria);
        return $users;
    }

    /**
     * Returns buttons for gridview.
     * @deprecated 6.17.0 use directly getButtons
     * @return string
     */
    public function getManagementButtons()
    {
        return $this->getButtons();
    }

    /**
     * Gets the buttons for the GridView
     * @return string
     */
    public function getButtons()
    {
        $permission_superadmin_read = Permission::model()->hasGlobalPermission('superadmin', 'read');
        $permission_users_read = Permission::model()->hasGlobalPermission('users', 'read');
        $permission_users_update = Permission::model()->hasGlobalPermission('users', 'update');
        $permission_users_delete = Permission::model()->hasGlobalPermission('users', 'delete');
        $userManager = new UserManager(App()->user, $this);
        // User is owned or created by you
        $ownedOrCreated = $this->parent_id == App()->session['loginID'];

        $detailUrl = App()->getController()->createUrl('userManagement/viewUser', ['userid' => $this->uid]);
        $setPermissionsUrl = App()->getController()->createUrl('userManagement/userPermissions', ['userid' => $this->uid]);
        $setRoleUrl = App()->getController()->createUrl('userManagement/addRole', ['userid' => $this->uid]);
        $editUrl = App()->getController()->createUrl('userManagement/addEditUser', ['userid' => $this->uid]);
        $setTemplatePermissionsUrl = App()->getController()->createUrl('userManagement/userTemplatePermissions', ['userid' => $this->uid]);
        $changeOwnershipUrl = App()->getController()->createUrl('userManagement/takeOwnership');
        $deleteUrl = App()->getController()->createUrl('userManagement/deleteConfirm', ['userid' => $this->uid, 'user' => $this->full_name]);

        $dropdownItems = [];
        $dropdownItems[] = [
            'title'            => gT('User details'),
            'iconClass'        => "ri-search-line",
            'linkClass'        => "UserManagement--action--openmodal UserManagement--action--userdetail",
            'linkAttributes'   => [
                'data-href' => $detailUrl,
            ],
            'enabledCondition' =>
                $permission_superadmin_read || $permission_users_read
                || ($permission_superadmin_read
                    && (Permission::isForcedSuperAdmin($this->uid)
                        || $this->uid == App()->user->getId()
                    )
                )
                || (!$permission_superadmin_read
                    && ($this->uid == App()->user->getId() // You can see yourself
                        || ($permission_users_update
                            && $ownedOrCreated
                        )
                    )
                )
        ];

        $permission = ( $permission_superadmin_read && !(Permission::isForcedSuperAdmin($this->uid) || $this->uid == App()->user->getId()))
            || (!$permission_superadmin_read && ($this->uid != App()->session['loginID'] //Can't change your own permissions
                    && ( $permission_users_update && $ownedOrCreated)
                    && !Permission::isForcedSuperAdmin($this->uid)
                )
            );

        if ($this->user_status) {
            $activateUrl = App()->getController()->createUrl('userManagement/activationConfirm', ['userid' => $this->uid, 'action' => 'deactivate']);
            $dropdownItems[] = [
                'title'            => gT('Deactivate'),
                'iconClass'        => "ri-user-unfollow-fill text-danger",
                'linkClass'        => $permission ? "UserManagement--action--openmodal UserManagement--action--status" : '',
                'linkAttributes'   => [
                    'data-href' => $permission ? $activateUrl : '#',
                ],
                'enabledCondition' => $permission
            ];
        } else {
            $activateUrl = App()->getController()->createUrl('userManagement/activationConfirm', ['userid' => $this->uid, 'action' => 'activate']);
            $dropdownItems[] = [
                'title'            => gT('Activate'),
                'iconClass'        => "ri-user-follow-fill",
                'linkClass'        => $permission ? "UserManagement--action--openmodal UserManagement--action--status" : '',
                'linkAttributes'   => [
                    'data-href' => $permission ? $activateUrl : '#',
                ],
                'enabledCondition' => $permission
            ];
        }
        $dropdownItems[] = [
            'title'            => gT('Edit permissions'),
            'iconClass'        => "ri-lock-fill",
            'linkClass'        => "UserManagement--action--openmodal UserManagement--action--permissions",
            'linkAttributes'   => [
                'data-href'      => $setPermissionsUrl,
                'data-modalsize' => 'modal-xl',
            ],
            'enabledCondition' =>
                ($permission_superadmin_read
                    && !(Permission::isForcedSuperAdmin($this->uid)
                        || $this->uid == App()->user->getId()
                    )
                )
                || (!$permission_superadmin_read
                    && ($this->uid != App()->session['loginID'] //Can't change your own permissions
                        && (
                            $permission_users_update
                            && $ownedOrCreated
                        )
                        && !Permission::isForcedSuperAdmin($this->uid)
                    )
                )
        ];
        $dropdownItems[] = [
            'title'            => gT('User role'),
            'iconClass'        => "ri-group-fill",
            'linkClass'        => "UserManagement--action--openmodal UserManagement--action--addrole",
            'linkAttributes'   => [
                'data-href' => $setRoleUrl,
            ],
            'enabledCondition' => $userManager->canAssignRole() && $this->uid != App()->user->getId()
        ];
        $dropdownItems[] = [
            'title'            => gT('Edit user'),
            'iconClass'        => "ri-pencil-fill",
            'linkClass'        => "UserManagement--action--openmodal UserManagement--action--edituser",
            'linkAttributes'   => [
                'data-href' => $editUrl,
            ],
            'enabledCondition' => $this->canEdit()
                                && $this->uid != App()->user->getId() // To update self : must use personal settings
        ];
        $dropdownItems[] = [
            'title'            => gT('Theme permissions'),
            'iconClass'        => "ri-brush-fill",
            'linkClass'        => "UserManagement--action--openmodal UserManagement--action--templatepermissions",
            'linkAttributes'   => [
                'data-href' => $setTemplatePermissionsUrl,
            ],
            'enabledCondition' =>
                ($permission_superadmin_read
                    && !(Permission::isForcedSuperAdmin($this->uid)
                        || $this->uid == App()->user->getId()
                    )
                )
        ];
        $dropdownItems[] = [
            'title'            => gT('Take ownership'),
            'iconClass'        => "ri-user-received-fill",
            'linkId'        => "UserManagement--takeown-$this->uid",
            'linkAttributes'   => [
                'data-bs-toggle' => 'modal',
                'data-bs-target' => '#confirmation-modal',
                'data-url'       => $changeOwnershipUrl,
                'data-userid'    => $this->uid,
                'data-user'      => CHtml::encode($this->full_name),
                'data-action'    => 'deluser',
                'data-onclick'   => "LS.UserManagement.triggerRunAction(\"#UserManagement--takeown-$this->uid\")",
                'data-message'   => gT('Do you want to take ownership of this user?'),
            ],
            'enabledCondition' =>
                ($permission_superadmin_read
                    && !(Permission::isForcedSuperAdmin($this->uid)
                        || $this->uid == App()->user->getId()
                    )
                    && $this->parent_id != App()->session['loginID']
                )
                || (!$permission_superadmin_read
                    && (Permission::isForcedSuperAdmin(App()->session['loginID'])
                        && $this->parent_id != App()->session['loginID']
                    )
                )
        ];
        $dropdownItems[] = [
            'title'            => gT('Delete user'),
            'iconClass'        => "ri-delete-bin-fill text-danger",
            'linkClass'        => "UserManagement--action--openmodal UserManagement--action--delete",
            'linkId'           => "UserManagement--delete-$this->uid",
            'linkAttributes'   => [
                'data-href' => $deleteUrl,
            ],
            'enabledCondition' =>
                ($permission_superadmin_read
                    && !(Permission::isForcedSuperAdmin($this->uid)
                        || $this->uid == App()->user->getId()
                    )
                )
                || (!$permission_superadmin_read
                    && ($this->uid != App()->session['loginID'] // One cant delete onesself
                        && (
                            $permission_users_delete // Global permission to delete users
                            && $this->parent_id == App()->session['loginID'] // User is owned by current admin
                        )
                        && !Permission::isForcedSuperAdmin($this->uid) // Can't delete forced superadmins, ever
                    )
                )
        ];

        return App()->getController()->widget('ext.admin.grid.GridActionsWidget.GridActionsWidget', ['dropdownItems' => $dropdownItems], true);
    }

    public function getParentUserName()
    {
        if ($this->parentUser) {
            return $this->parentUser->users_name;
        }
        // root user, no parent
        return null;
    }

    public function getRoleList()
    {
        $list = array_map(
            function ($oRoleMapping) {
                return $oRoleMapping->name;
            },
            $this->roles
        );
        return join(', ', $list);
    }

    /**
     * Returns the last login formatted for displaying.
     * @return string
     */
    public function getLastloginFormatted()
    {
        $lastLogin = $this->last_login;
        if ($lastLogin == null) {
            return '---';
        } else {
            $lastLogin = getDateOfUTC($lastLogin);
        }

        $date = new DateTime($lastLogin);
        return $date->format($this->getDateFormat()) . ' ' . $date->format('H:i');
    }

    /**
     * Used in management grid before 6.17.0
     * @deprecated 6.17.0
     * @return string
     */
    public function getManagementCheckbox()
    {
        return "<input type='checkbox' class='usermanagement--selector-userCheckbox' name='selectedUser[]' value='" . $this->uid . "'>";
    }
    /**
     * Get column definition for grid
     * @return array
     */
    public function getManagementColums()
    {
        $cols = [
            [
                'id' => 'uid',
                'class' => 'CCheckBoxColumn',
                'selectableRows' => 2, // allow multiple selection
                'filterHtmlOptions' => ['class' => 'ls-sticky-column'],
                'headerHtmlOptions' => ['class' => 'ls-sticky-column'],
                'htmlOptions' => ['class' => 'ls-sticky-column text-end'],
                'checkBoxHtmlOptions' => ['class' => 'usermanagement--selector-userCheckbox'], // Class used in test
                'disabled' => function ($data) {
                    return $data->uid == \App()->getCurrentUserId();
                }
            ],
            [
                "name"   => 'uid',
                "header" => gT("User ID"),
                'htmlOptions' => ['class' => 'text-end uid'],// uid class used in test
            ],
            [
                "name"   => 'users_name',
                "header" => gT("Username")
            ],
            [
                "name"   => 'email',
                "header" => gT("Email")
            ],
            [
                "name"   => 'full_name',
                "header" => gT("Full name")
            ],
            [
                "name"   => "created",
                "header" => gT("Created on"),
                "value"  => function ($data) {
                    return $this->getFormattedDate($data, "created");
                },
                "filter" => $this->getDateFilter("created"),
            ],
            [
                "name"   => "search_parentUserName",
                "value"  => '$data->parentUserName',
                "header" => gT("Created by"),
            ],
            /**
             * CLSGridView include extra columns before the 2 last columns,
             * Survey have Action column here, User din't have Action column
             * Add one hidden Action column for CLSGridView
             */
            [
                'header' => '',
                'value' => '',
                'headerHtmlOptions' => ['class' => 'hidden d-none'],
                'htmlOptions'       => ['class' => 'hidden d-none']
            ],
        ];
        return $cols;
    }

    /**
     * Get additional (optional) column definition for grid
     * @return [][]
     */
    public function getAdditionalColumns()
    {
        $permission_read_users      = Permission::model()->hasGlobalPermission('users', 'read');
        $permission_read_usergroups = Permission::model()->hasGlobalPermission('usergroups', 'read');
        $permission_read_surveys    = Permission::model()->hasGlobalPermission('surveys', 'read');
        $cols = [
            "expires" => [
                "name"   => 'expires',
                "header" => gT('Expires'),
                "value"  => function ($data) {
                    return $this->getFormattedDate($data, "expires");
                },
                "filter" => $this->getDateFilter("expires"),
            ],
            "user_status" => [
                "name"   => 'user_status',
                "header" => gT('Status'),
                "value"  => function ($data) {
                    return $this->getFormattedBoolean($data, "isActive");
                },
                "type" => 'raw',
                "htmlOptions" => ['class' => 'text-center'],
                "filter" => ['Y' => gT('Yes'), 'N' => gT('No')], // Y/N, default is set to 1
            ],
        ];

        // Number of Surveys
        // This info is already guessable by people able to list all Surveys
        if ($permission_read_surveys) {
            $cols['surveysCreated'] = array(
                "name" => 'surveysCreated',
                "header" => gT("Owned surveys"),
                'filter' => false
            );
        }
        // Usergroups Names
        // This info is safe to be shown to who can read all Users and Groups.
        // TODO: When there will be a more robust Group permissions system,
        //       this column could be enabled by default, since each Group would
        //       be checked individually.
        if ($permission_read_users && $permission_read_usergroups) {
            $cols['groupList'] = array(
                "name" => 'groupList',
                "header" => gT("User groups"),
                'filter' => false
            );
        }

        // Role Names
        // Knowing this info makes sense if you can read all Users
        if ($permission_read_users) {
            $cols['roleList'] = array(
                "name" => 'roleList',
                "header" => gT("Applied role"),
                'filter' => false
            );
        }

        /** If you can set superadmin : allow see of superadmin permissions */
        if (Permission::model()->hasGlobalPermission('superadmin', 'update')) {
            $cols['isSuperAdmin'] = array(
                "name" => 'isSuperAdmin',
                "header" => gT("Superadmin"),
                "value"  => function ($data) {
                    return $this->getFormattedBoolean($data, "isSuperAdmin");
                },
                "htmlOptions" => ['class' => 'text-center'],
                "type" => 'raw',
                "filter" => false
            );
        }
        /**
         * If you are superadmin : allow see of login via DB permissions.
         * Useful for systems that use other authentication methods
         **/
        if (Permission::model()->hasGlobalPermission('superadmin', 'read')) {
            $cols['haveDbAuthentication'] = array(
                "name" => 'haveDbAuthentication',
                "header" => gT("DB auth"), // need short header, use short word for "Database authentication"
                "value"  => function ($data) {
                    return $this->getFormattedBoolean($data, "haveDbAuthentication");
                },
                "htmlOptions" => ['class' => 'text-center'],
                "type" => 'raw',
                "filter" => false
            );
        }
        return $cols;
    }

    /**
     * @deprecated ?
     * @return array
     */
    public function getColums()
    {
        $cols = array(
            array(
                "name" => 'buttons',
                "type" => 'raw',
                "header" => gT("Action")
            ),
            array(
                "name" => 'uid',
                "header" => gT("User ID")
            ),
            array(
                "name" => 'users_name',
                "header" => gT("Username")
            ),
            array(
                "name" => 'email',
                "header" => gT("Email")
            ),
            array(
                "name" => 'full_name',
                "header" => gT("Full name")
            )
        );
        if (Permission::model()->hasGlobalPermission('superadmin', 'read')) {
            $cols[] = array(
                "name" => 'surveysCreated',
                "header" => gT("No of surveys")
            );
        }

        $cols[] = array(
            "name" => "parentUserName",
            "header" => gT("Created by"),
        );

        $cols[] = array(
            "name" => "created",
            "header" => gT("Created on"),
            "value" => '$data->formattedDateCreated',
            "filter" => $this->getDateFilter("created"),
        );
        return $cols;
    }

    /**
     * get specific filter for date
     * @param string $column
     * @return string the HTML filter for date
     */
    public function getDateFilter($column)
    {
        $dateFilter = "<div class='input-group'>";
        $dateFilter .= "<span class='input-group-text'>&gt;=</span>";
        $dateFilter .= CHtml::dateField(
            get_class($this) . "[" . $column . "]",
            $this->getAttribute($column),
            [
                'class' => "form-control"
            ]
        );
        $dateFilter .= "</div>";
        return $dateFilter;
    }

    /** @inheritdoc */
    public function search()
    {
        // @todo Please modify the following code to remove attributes that should not be searched.
        $pageSize = App()->user->getState('pageSize', Yii::app()->params['defaultPageSize']);
        $criteria = new CDbCriteria();

        $criteria->compare('t.uid', $this->uid);
        $criteria->compare('t.full_name', $this->full_name, true);
        $criteria->compare('t.users_name', $this->users_name, true);
        $criteria->compare('t.email', $this->email, true);
        if ($this->user_status === "Y") {
            $criteria->addCondition('t.user_status <> 0 OR t.user_status IS NULL');
        }
        if ($this->user_status === "N") {
            $criteria->addCondition('t.user_status = 0');
        }
        //filter for date comparison
        foreach (['created','expires'] as $dateAttribute) {
            if ($this->getAttribute($dateAttribute)) {
                $datetime = DateTime::createFromFormat("Y-m-d", $this->getAttribute($dateAttribute)); // Fix date
                if ($datetime) {
                    $dateCompare = $this->getAttribute($dateAttribute) . ' 00:00:00';
                    $criteria->compare('t.' . $dateAttribute, ">=" . $dateCompare, true);
                } else {
                    $this->setAttribute($dateAttribute, null);
                }
            }
        }
        /* $this->search_parentUserName is not set like default Yii grid, set it manually */
        $searchValues = App()->getRequest()->getParam("User");
        if (!empty($searchValues['search_parentUserName'])) {
            $getParentName = $this->search_parentUserName = strval($searchValues['search_parentUserName']);
            $criteria->join = "LEFT JOIN {{users}} u ON t.parent_id = u.uid";
            $criteria->compare('u.users_name', $getParentName, true);
        }


        return new CActiveDataProvider($this, array(
            'criteria' => $criteria,
            'pagination' => array(
                'pageSize' => $pageSize
            )
        ));
    }

    /**
     * Creates a validation key and saves it in table user for this user.
     *
     * @return bool true if validation_key could be saved in db, false otherwise
     */
    public function setValidationKey()
    {
        $this->validation_key = randomChars(self::MAX_VALIDATION_KEY_LENGTH);

        return $this->save();
    }

    /**
     * Creates the validation key expiration date and save it in db
     *
     * @return bool true if datetime could be saved, false otherwise
     * @throws Exception
     */
    public function setValidationExpiration()
    {
        $datePlusMaxExpiration = new DateTime('now', new DateTimeZone('UTC'));
        $datePlusString = 'P' . self::MAX_EXPIRATION_TIME_IN_DAYS . 'D';
        $dateInterval = new DateInterval($datePlusString);
        $datePlusMaxExpiration->add($dateInterval);

        $this->validation_key_expiration = $datePlusMaxExpiration->format('Y-m-d H:i:s');

        return $this->save();
    }

    /**
     * Returns true if the user has expired.
     *
     * @return boolean
     */
    public function isExpired()
    {
        $expired = false;
        if (!empty($this->expires)) {
            // Compare expiration time (stored in UTC) with current UTC time
            $now = gmdate("Y-m-d H:i:s");
            $expirationTime = (string) $this->expires;

            // Time comparison (treat equality as expired, aligning with notexpired scope: expires > now)
            $expired = new DateTime($expirationTime) <= new DateTime($now);
        }
        return $expired;
    }

    /**
     * Check if user is active, used for grid
     * @return boolean
     */
    public function getIsActive()
    {
        return $this->isActive();
    }

    /**
     * Check if user is active
     * @return boolean
     */
    public function isActive()
    {
        /* Default is active, user_status must be set (to be tested during DB update); deactivated set user_status to 0 */
        return !isset($this->user_status) || $this->user_status !== 0;
    }

    /**
     * Check if user can login
     * @return boolean
     */
    public function canLogin()
    {
        return $this->isActive() && !$this->isExpired();
    }

    /**
     * Check if user is superadmin for grid
     * @return boolean
     */
    public function getIsSuperAdmin()
    {
        return Permission::model()->hasGlobalPermission('superadmin', 'read', $this->uid);
    }

    /**
     * Check if user have database authentication allowed
     * @return boolean
     */
    public function getHaveDbAuthentication()
    {
        return Permission::model()->hasGlobalPermission('auth_db', 'read', $this->uid);
    }

    /**
     * Get the description to be used in list
     * @return string
     */
    public function getDisplayName()
    {
        if (empty($this->full_name)) {
            return $this->users_name;
        }
        return sprintf(gT("%s (%s)"), $this->users_name, $this->full_name);
    }

    /**
     * @param $userGroupId
     * @return CActiveDataProvider
     */
    public function searchUserGroupMembers($userGroupId)
    {
        $pageSize = Yii::app()->user->getState('pageSize', Yii::app()->params['defaultPageSize']);
        $criteria = new CDbCriteria();
        $criteria->join = 'INNER JOIN {{user_in_groups}} uig on t.uid = uig.uid';
        $criteria->condition .= 'uig.ugid=:ugid';
        $criteria->params = array(':ugid' => $userGroupId);
        $criteria->compare('t.users_name', $this->users_name, true);
        $criteria->compare('t.email', $this->email, true);


        return new CActiveDataProvider($this, array(
            'criteria' => $criteria,
            'pagination' => array(
                'pageSize' => $pageSize
            )
        ));
    }

    /**
     * Returns button for gridview.
     * @return string
     */
    public function getGroupMemberListButtons()
    {
        $userGroupId = Yii::app()->request->getQuery('ugid', 0);
        $userGroup = UserGroup::model()->findByPk($userGroupId);

        $currentUserId = $this->uid;
        $canDelete = Permission::model()->hasGlobalPermission('usergroups', 'update')
            && $userGroup && $userGroup->owner_id == Yii::app()->session['loginID'];
        $isDeletable = $userGroup
            && ($canDelete || Permission::model()->hasGlobalPermission('superadmin'))
            && $currentUserId != '1';

        $dropdownItems[] = [
            'title'            => gT('Delete'),
            'iconClass'        => 'ri-delete-bin-fill text-danger',
            'enabledCondition' => $isDeletable,
            'linkAttributes'   => [
                'data-bs-toggle' => "modal",
                'data-btnclass'  => 'btn-danger',
                'data-btntext'   => gT('Delete'),
                'data-post-url'  => App()->createUrl("userGroup/deleteUserFromGroup"),
                'data-post-datas' => json_encode(['ugid' => $userGroupId, 'uid' => $currentUserId]),
                'data-message'   => sprintf(
                    gT("Are you sure you want to delete user '%s' from user group '%s'?"),
                    CHtml::encode($this->users_name),
                    CHtml::encode($userGroup->name)
                ),
                'data-bs-target' => "#confirmation-modal"
            ]
        ];
        return App()->getController()->widget(
            'ext.admin.grid.GridActionsWidget.GridActionsWidget',
            ['dropdownItems' => $dropdownItems],
            true
        );
    }

    /**
     * Return true if user with id $managerId can edit this user
     * @param int|null $managerId default to current user
     *
     * @return bool
     */
    public function canEdit($managerId = null)
    {
        if (is_null($managerId)) {
            $managerId = Permission::model()->getUserId();
        }
        /* user can update himself */
        if ($managerId == $this->uid) {
            return true;
        }
        /* forcedsuperamdin (user #1) can always update all */
        if (Permission::isForcedSuperAdmin($managerId)) {
            return true;
        }
        /* forcedsuperamdin can not be update (except by another forcedsuperamdin done before) */
        if (Permission::isForcedSuperAdmin($this->uid)) {
            return false;
        }
        /* If target user is superamdin : managingUser must be allowed to create superadmin and be parent */
        if (Permission::model()->hasGlobalPermission('superadmin', 'read', $this->uid)) {
            return Permission::model()->hasGlobalPermission('superadmin', 'create', $managerId)
                && $this->parent_id == $managerId;
        }
        /* superamin can update all other user */
        if (Permission::model()->hasGlobalPermission('superadmin', 'read', $managerId)) {
            return true;
        }
        /* Finally : simple user can update only childs users */
        return Permission::model()->hasGlobalPermission('users', 'update', $managerId)
                && $this->parent_id == $managerId;
    }

    /**
     * Set user activation status
     *
     * @param string $status
     * @return bool
     */
    public function setActivationStatus($status = 'activate')
    {
        if ($status == 'activate') {
            $this->user_status = 1;
        } else {
            $this->user_status = 0;
        }
        return $this->save();
    }
}
