Skip to content
Snippets Groups Projects
User.class.php 61.2 KiB
Newer Older
<?php
/**
 * User.class.php
 * model class for combined auth_user_md5/user_info record
 * this class represents one user, the attributes from tables
 * auth_user_md5 and user_info were merged.
 *
 * @code
 * $a_user = User::find($id);
 * $another_users_email = User::findByUsername($username)->email;
 * $a_user->email = $another_users_email;
 * $a_user->store();
 * @endcode
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * @author      André Noack <noack@data-quest.de>
 * @copyright   2011 Stud.IP Core-Group
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
 * @category    Stud.IP
 *
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
 * @property string $id alias column for user_id
 * @property string $user_id database column
 * @property string $username database column
 * @property string $password database column
 * @property string $perms database column
 * @property string $vorname database column
 * @property string $nachname database column
 * @property string $email database column
 * @property string $matriculation_number database column
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
 * @property string $validation_key database column
 * @property string|null $auth_plugin database column
 * @property int $locked database column
 * @property string|null $lock_comment database column
 * @property string|null $locked_by database column
 * @property string $visible database column
 * @property SimpleORMapCollection|CourseMember[] $course_memberships has_many CourseMember
 * @property SimpleORMapCollection|InstituteMember[] $institute_memberships has_many InstituteMember
 * @property SimpleORMapCollection|AdmissionApplication[] $admission_applications has_many AdmissionApplication
 * @property SimpleORMapCollection|ArchivedCourseMember[] $archived_course_memberships has_many ArchivedCourseMember
 * @property SimpleORMapCollection|DatafieldEntryModel[] $datafields has_many DatafieldEntryModel
 * @property SimpleORMapCollection|UserStudyCourse[] $studycourses has_many UserStudyCourse
 * @property SimpleORMapCollection|Statusgruppen[] $contactgroups has_many Statusgruppen
 * @property SimpleORMapCollection|ResourcePermission[] $resource_permissions has_many ResourcePermission
 * @property SimpleORMapCollection|ResourceTemporaryPermission[] $resource_temporary_permissions has_many ResourceTemporaryPermission
 * @property SimpleORMapCollection|ConsultationBlock[] $consultation_blocks has_many ConsultationBlock
 * @property SimpleORMapCollection|ConsultationBooking[] $consultation_bookings has_many ConsultationBooking
 * @property SimpleORMapCollection|ConsultationResponsibility[] $consultation_responsibilities has_many ConsultationResponsibility
 * @property SimpleORMapCollection|Kategorie[] $profile_categories has_many Kategorie
 * @property SimpleORMapCollection|MvvContact[] $mvv_assignments has_many MvvContact
 * @property SimpleORMapCollection|CourseMemberNotification[] $course_notifications has_many CourseMemberNotification
 * @property UserInfo $info has_one UserInfo
 * @property UserOnline $online has_one UserOnline
 * @property Courseware\Unit $courseware_units has_one Courseware\Unit
 * @property SimpleORMapCollection|User[] $contacts has_and_belongs_to_many User
 * @property SimpleORMapCollection|UserDomain[] $domains has_and_belongs_to_many UserDomain
 * @property-read mixed $config additional field
 * @property mixed $hobby additional field
 * @property mixed $lebenslauf additional field
 * @property mixed $publi additional field
 * @property mixed $schwerp additional field
 * @property mixed $home additional field
 * @property mixed $privatnr additional field
 * @property mixed $privatcell additional field
 * @property mixed $privadr additional field
 * @property mixed $score additional field
 * @property mixed $geschlecht additional field
 * @property mixed $mkdate additional field
 * @property mixed $chdate additional field
 * @property mixed $title_front additional field
 * @property mixed $title_rear additional field
 * @property mixed $preferred_language additional field
 * @property mixed $smsforward_copy additional field
 * @property mixed $smsforward_rec additional field
 * @property mixed $email_forward additional field
 * @property mixed $motto additional field
 * @property mixed $lock_rule additional field
 * @property mixed $oercampus_description additional field
class User extends AuthUserMd5 implements Range, PrivacyObject, Studip\Calendar\Owner
{
    /**
     *
     */
    protected static function configure($config = [])
    {
        $config['has_many']['course_memberships'] = [
            'class_name' => CourseMember::class,
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_many']['institute_memberships'] = [
            'class_name' => InstituteMember::class,
            'order_by'   => 'ORDER BY priority ASC',
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_many']['admission_applications'] = [
            'class_name' => AdmissionApplication::class,
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_many']['archived_course_memberships'] = [
            'class_name' => ArchivedCourseMember::class,
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_many']['datafields'] = [
            'class_name'  => DatafieldEntryModel::class,
            'foreign_key' => function ($user) {
                return [$user];
            },
            'assoc_foreign_key' => function ($model, $params) {
                $model->setValue('range_id', $params[0]->id);
            },
            'assoc_func' => 'findByModel',
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_many']['studycourses'] = [
            'class_name' => UserStudyCourse::class,
            'assoc_func' => 'findByUser',
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_and_belongs_to_many']['contacts'] = [
            'thru_table'     => 'contact',
            'thru_key'       => 'owner_id',
            'thru_assoc_key' => 'user_id',
            'order_by'       => 'ORDER BY Nachname, Vorname',
            'on_delete'      => 'delete',
            'on_store'       => 'store',
        ];
        $config['has_many']['contactgroups'] = [
            'class_name'        => Statusgruppen::class,
            'assoc_foreign_key' => 'range_id',
            'on_delete'         => 'delete',
            'on_store'          => 'store',
        ];
        $config['has_one']['info'] = [
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_one']['online'] = [
            'on_delete'  => 'delete',
            'on_store'   => 'store',
        ];
        $config['has_many']['resource_permissions'] = [
            'class_name' => ResourcePermission::class,
            'on_delete'  => 'delete',
            'on_store'   => 'store'
        ];
        $config['has_many']['resource_temporary_permissions'] = [
            'class_name' => ResourceTemporaryPermission::class,
            'on_delete'  => 'delete',
            'on_store'   => 'store'
        ];
        $config['has_many']['consultation_blocks'] = [
            'class_name'        => ConsultationBlock::class,
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            'assoc_foreign_key' => 'range_id',
            'on_delete'         => 'delete',
        ];
        $config['has_many']['consultation_bookings'] = [
            'class_name' => ConsultationBooking::class,
            'on_delete'  => 'delete',
        ];
        $config['has_many']['consultation_responsibilities'] = [
            'class_name'        => ConsultationResponsibility::class,
            'assoc_func'        => 'findByUserId',
            'on_delete'         => 'delete',
        ];
        $config['has_many']['profile_categories'] = [
            'class_name'        => Kategorie::class,
            'assoc_foreign_key' => 'range_id',
            'on_delete'         => 'delete',
        ];


        $config['has_many']['mvv_assignments'] = [
            'class_name'        => MvvContact::class,
            'assoc_foreign_key' => 'contact_id',
        ];

        $config['has_and_belongs_to_many']['domains'] = [
            'thru_table'        => 'user_userdomains',
            'on_delete'         => 'delete',
            'on_store'          => 'store',
            'order_by'          => 'ORDER BY name',
        ];

        $config['has_many']['course_notifications'] = [
            'class_name'        => CourseMemberNotification::class,
            'on_delete'         => 'delete',
        ];

Peter Thienel's avatar
Peter Thienel committed
        $config['has_many']['extern_pages_configs'] = [
            'class_name' => ExternPageConfig::class,
            'assoc_foreign_key' => 'author_id'
        ];

        $config['additional_fields']['config']['get'] = function ($user) {
            return UserConfig::get($user->id);
        };

        $config['registered_callbacks']['after_delete'][] = 'cbRemoveFeedback';
        $config['registered_callbacks']['after_delete'][] = 'cbRemoveForumVisits';
        $config['registered_callbacks']['before_store'][] = 'cbClearCaches';
        $config['registered_callbacks']['before_store'][] = 'cbStudipLog';

        $info = new UserInfo();
        $info_meta = $info->getTableMetadata();
        foreach ($info_meta ['fields'] as $field => $meta) {
            if ($field !== $info_meta['pk'][0]) {
                $config['additional_fields'][$field] = [
                    'get'            => '_getAdditionalValueFromRelation',
                    'set'            => '_setAdditionalValueFromRelation',
                    'relation'       => 'info',
                    'relation_field' => $field,
                ];
            }
        }

        parent::configure($config);
    }

    /**
     * @param $type string type of callback
     */
    protected function cbStudipLog($type)
    {
        if ($type == 'before_store' && !$this->isNew()) {
            if ($this->isFieldDirty('locked') && $this->isFieldDirty('lock_comment')) {
                if ((int)$this->locked === 1) {
                    StudipLog::log('USER_LOCK',
                        $this->user_id,
                        null,
                        sprintf(
                            'Kommentar: %s',
                            $this->lock_comment
                        )
                    );
                } else {
                    StudipLog::log('USER_UNLOCK',
                        $this->user_id
                    );
                }
    /**
     * Returns the currently authenticated user.
     *
     */
    public static function findCurrent()
    {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
        if (isset($GLOBALS['user']) && is_object($GLOBALS['user'])) {
            return $GLOBALS['user']->getAuthenticatedUser();
        }
    }

    /**
     * build new object with given data
     *
     * @param $data array assoc array of record
     * @return User
     */
    public static function build($data, $is_new = true)
    {
        // Note: This should be used instead of `new static()` since PHPStan
        $class = get_called_class();
        $user = new $class();

        $user->info = new UserInfo();
        $user->setData($data);
        $user->setNew($is_new);
        foreach (array_keys($user->db_fields()) as $field) {
            $user->content_db[$field] = $user->content[$field];
        }
        $user->info = UserInfo::build($data, $is_new);
        return $user;
    }

    /**
     * Returns user object including user_info
     *
     * @param string $id
     */
    public static function findFull($id)
    {
        $sql = "SELECT *
                FROM auth_user_md5
                LEFT JOIN user_info USING (user_id)
                WHERE user_id = ?";
        $data = DBManager::get()->fetchOne($sql, [$id]);
            return static::buildExisting($data);
    }

    /**
     * Returns user objects including user_info
     *
     * @param array $ids
     * @param string $order_by
     * @return User[] User
     */
    public static function findFullMany($ids, $order_by = '')
    {
        $sql = "SELECT *
                FROM auth_user_md5
                LEFT JOIN user_info USING (user_id)
                WHERE user_id IN (?) " . $order_by;
        $data = DBManager::get()->fetchAll($sql, [$ids], [static::class, 'buildExisting']);
        return $data;
    }

    /**
     * return user object for given username
     *
     * @param string $username a username
     * @return User
     */
    public static function findByUsername($username)
    {
        return parent::findOneByUsername($username);
    }

    /**
     * returns an array of User-objects that have the given value in the
     * given datafield.
     * @param string $datafield_id
     * @param array of User
     */
    public static function findByDatafield($datafield_id, $value)
    {
            array_column(
                DatafieldEntryModel::findBySQL(
                    'datafield_id = :datafield_id AND content = :value',
                    compact('datafield_id', 'value')
    }

    /**
     * Wraps a search parameter in %..% if the parameter itself does not
     * contain % or _.
     *
     * @param String $needle Search parameter
     * @return String containing the wrapped needle if neccessary
     */
    private static function searchParam($needle)
    {
        if (preg_match('/[%_]/S', $needle)) {
            return $needle;
        }

        return '%' . $needle . '%';
    }

    /**
     * Temporary migrate to User.class.php
     *
     * @param $attributes
     * @return array
     */
    public static function search($attributes)
    {
        $params = [];
        $joins  = [];
        $where  = [];

        $query = "SELECT au.*, ui.*
                  FROM `auth_user_md5` au
                  LEFT JOIN `user_online` uo ON (au.`user_id` = uo.`user_id`)
                  LEFT JOIN `user_info` ui ON (au.`user_id` = ui.`user_id`)";

        if (!empty($attributes['username'])) {
            $where[] =  "au.`username` like :username";
            $params[':username'] = self::searchParam($attributes['username']);
        }

        if (!empty($attributes['vorname'])) {
            $where[] = "au.`Vorname` LIKE :vorname";
            $params[':vorname'] = self::searchParam($attributes['vorname']);
        }

        if (!empty($attributes['nachname'])) {
            $where[] = "au.`Nachname` LIKE :nachname";
            $params[':nachname'] = self::searchParam($attributes['nachname']);
        }

        if (!empty($attributes['email'])) {
            $where[] = "au.`Email` LIKE :email";
            $params[':email'] = self::searchParam($attributes['email']);
        }

        //permissions
        if (!empty($attributes['perm']) && $attributes['perm'] !== 'alle') {
            $where[] = "au.`perms` = :perms";
            $params[':perms'] = $attributes['perm'];
        }

        //locked user
Moritz Strohm's avatar
Moritz Strohm committed
        if (!empty($attributes['locked'])) {
            $where[] = "au.`locked` = 1";
        }

        // show only users who are not lecturers
        if (!empty($attributes['show_only_not_lectures'])) {
            $where[] = "au.`user_id` NOT IN (SELECT `user_id` FROM `seminar_user` WHERE `status` = 'dozent') ";
        }

        if (!empty($attributes['auth_plugins'])) {
            $where[] = "IFNULL(`auth_plugin`, 'preliminary') = :auth_plugins ";
            $params[':auth_plugins'] = $attributes['auth_plugins'];
        }

        //inactivity
        if (!is_null($attributes['inaktiv']) && $attributes['inaktiv'][0] != 'nie') {
            $comp = in_array(trim($attributes['inaktiv'][0]), ['=', '>', '<=']) ? $attributes['inaktiv'][0] : '=';
            $days = (int)$attributes['inaktiv'][1];
            $where[] = "uo.`last_lifesign` {$comp} UNIX_TIMESTAMP(TIMESTAMPADD(DAY, -{$days}, NOW())) ";
        } elseif (!is_null($attributes['inaktiv'])) {
            $where[] = "uo.`last_lifesign` IS NULL";
        }

        //datafields
        if (
            !empty($attributes['datafields'])
            && is_array($attributes['datafields'])
            && count($attributes['datafields']) > 0
        ) {
            $joins[] = "LEFT JOIN `datafields_entries` de ON (de.`range_id` = au.`user_id`)";
            foreach ($attributes['datafields'] as $id => $entry) {
                $where[] = "de.`datafield_id` = :df_id_". $id;
                $where[] = "de.`content` LIKE :df_content_". $id;
                $params[':df_id_' . $id] = $id;
                $params[':df_content_' . $id] = $entry;
            }
        }

        // roles
        if (!empty($attributes['roles'])) {
            $joins[] = "LEFT JOIN `roles_user` ON roles_user.`userid` = au.`user_id`";
            $where[] = "roles_user.`roleid` IN (:roles)";
            $params[':roles'] = $attributes['roles'];
        }

        // userdomains
        if (!empty($attributes['userdomains'])) {
            $joins[] = "LEFT JOIN `user_userdomains` uud ON (au.`user_id` = uud.`user_id`)";
            $joins[] = "LEFT JOIN `userdomains` uds USING (`userdomain_id`)";
            if ($attributes['userdomains'] === 'null-domain') {
                $where[] = "`userdomain_id` IS NULL ";
            } else {
                $where[] = "userdomain_id = :userdomains";
                $params[':userdomains'] = $attributes['userdomains'];
            }
        }

        // degree or studycourse
        if (!empty($attributes['degree']) || !empty($attributes['studycourse']) || !empty($attributes['fachsem'])) {
            $joins[] = "LEFT JOIN `user_studiengang` us ON (us.`user_id` = au.`user_id`)";
            if (!empty($attributes['degree'])) {
                $where[] = "us.`abschluss_id` IN (:degree)";
                $params[':degree'] = $attributes['degree'];
            }

            if (!empty($attributes['studycourse'])) {
                $where[] = "us.`fach_id` IN (:studycourse)";
                $params[':studycourse'] = $attributes['studycourse'];
            }

            if(!empty($attributes['fachsem'])) {
                $where[] = 'us.`semester` = :fachsem';
                $params[':fachsem'] = $attributes['fachsem'];
            }
        }

        if (!empty($attributes['institute'])) {
            $joins[] = "LEFT JOIN `user_inst` uis ON uis.`user_id` = au.`user_id`";
            $where[] = "uis.`Institut_id` = :institute";
            $params[':institute'] = $attributes['institute'];
        }

        $query .= implode(' ', $joins);
        $query .= " WHERE 1 AND ";
        $query .= implode(' AND ', $where);
        $query .= " GROUP BY au.`user_id` ";

        if (!empty($attributes['sortby'])) {
            //sortieren
            switch ($attributes['sortby']) {
                case "perms":
                    $query .= "ORDER BY au.`perms` {$attributes['order']}, au.`username`";
                    break;
                case "Vorname":
                    $query .= "ORDER BY au.`Vorname` {$attributes['order']}, au.`Nachname`";
                    break;
                case "Nachname":
                    $query .= "ORDER BY au.`Nachname` {$attributes['order']}, au.`Vorname`";
                    break;
                case "Email":
                    $query .= "ORDER BY au.`Email` {$attributes['order']}, au.`username`";
                    break;
                case 'matriculation_number':
                    $query .= "ORDER BY au.`matriculation_number` {$attributes['order']}, au.`username`";
                    break;
                case "changed":
                    $query .= "ORDER BY uo.`last_lifesign` {$attributes['order']}, au.`username`";
                    break;
                case "mkdate":
                    $query .= "ORDER BY ui.`mkdate` {$attributes['order']}, au.`username`";
                    break;
                case "auth_plugin":
                    $query .= "ORDER BY `auth_plugin` {$attributes['order']}, au.`username`";
                    break;
                default:
                    $query .= " ORDER BY au.`username` {$attributes['order']}";
            }
        return DBManager::get()->fetchAll($query, $params, [static::class, 'buildExisting']);
    }


    /**
     * @see SimpleORMap::store()
     */
    public function store()
    {
        if ($this->isDirty() && !$this->info->isFieldDirty('chdate')) {
            $this->info->setValue('chdate', time());
        }
        return parent::store();
    }

    /**
     * @see SimpleORMap::triggerChdate()
     */
    public function triggerChdate()
    {
       return $this->info->triggerChdate();
    }

    /**
     * returns the name in specified format
     * (formats defined in $GLOBALS['_fullname_sql'])
     *
     * @param string one of full,full_rev,no_title,no_title_rev,no_title_short,no_title_motto,full_rev_username
     * @return string guess what - the fullname
     */
    public function getFullName($format = 'default')
    {
        static $concat,$left,$if,$quote;

        if ($format === 'default') {
            $format = 'full';
        }

        $sql = $GLOBALS['_fullname_sql'][$format] ?? null;
        if (!$sql || $format == 'no_title') {
            return $this->vorname . ' ' . $this->nachname;
        }
        if ($format == 'no_title_rev') {
            return $this->nachname . ', ' . $this->vorname;
        }
        if ($concat === null) {
            $concat = function() {return join('', func_get_args());};
            $left = function($str, $c = 0) {return mb_substr($str,0,$c);};
            $if = function($ok,$yes,$no) {return $ok ? $yes : $no;};
            $quote = function($str) {return "'" . addcslashes($str, "\\'\0") . "'";};
        }

        $data = array_map($quote, $this->toArray('vorname nachname username title_front title_rear motto perms'));
        $replace_func['CONCAT'] = '$concat';
        $replace_func['LEFT'] = '$left';
        $replace_func['UCASE'] = 'mb_strtoupper';
        $replace_func['IF'] = '$if';
        $eval = strtr($sql, $replace_func);
        $eval = strtr(mb_strtolower($eval), $data);
        return eval('return ' . $eval . ';');
    }

    public function toArrayRecursive($only_these_fields = null)
    {
        $ret = parent::toArrayRecursive($only_these_fields);
        unset($ret['info']);
        return  $ret;
    }

    /**
     * Returns whether the user was assigned a certain role.
     *
     * @param string $role         The role to check
     * @param string $institute_id An optional institute_id
     * @return bool True if the user was assigned this role, false otherwise
     */
    public function hasRole($role, $institute_id = '')
    {
        return RolePersistence::isAssignedRole($this->user_id, $role, $institute_id);
    }

    /**
     * Returns the roles that were assigned to the user.
     *
     * @param boolean $with_implicit
     * @return array
     */
    public function getRoles($with_implicit = false)
    {
        return RolePersistence::getAssignedRoles($this->user_id, $with_implicit);
    }

    /**
     * Returns whether the given user is stored in contacts.
     *
     * @param User $another_user
     * @return bool
     */
    public function isFriendOf($another_user)
    {
        return (bool) DBManager::get()->fetchColumn("SELECT 1 FROM contact WHERE owner_id=? AND user_id=?", [$this->user_id, $another_user->user_id]);
    }

    /**
     * checks if at least one field was modified since last restore
     *
     * @return boolean
     */
    public function isDirty()
    {
        return parent::isDirty() || $this->info->isDirty();
    }

    /**
     * checks if given field was modified since last restore
     *
     * @param string $field
     * @return boolean
     */
    public function isFieldDirty($field)
    {
        $field = mb_strtolower($field);
        return (array_key_exists($field, $this->content_db) ? parent::isFieldDirty($field) : $this->info->isFieldDirty($field));
    }

    /**
     * reverts value of given field to last restored value
     *
     * @param string $field
     * @return mixed the restored value
     */
    public function revertValue($field)
    {
        $field = mb_strtolower($field);
        return (array_key_exists($field, $this->content_db) ? parent::revertValue($field) : $this->info->revertValue($field));
    }

    /**
     * returns unmodified value of given field
     *
     * @param string $field
     * @throws InvalidArgumentException
     * @return mixed
     */
    public function getPristineValue($field)
    {
        $field = mb_strtolower($field);
        return (array_key_exists($field, $this->content_db) ? parent::getPristineValue($field) : $this->info->getPristineValue($field));
    }

    /**
     * Returns data of table row as assoc array with raw contents like
     * they are in the database.
     * Pass array of fieldnames or ws separated string to limit
     * fields.
     *
     * @param mixed $only_these_fields
     * @return array
     */
    public function toRawArray($only_these_fields = null)
    {
        return array_merge($this->info->toRawArray($only_these_fields), parent::toRawArray($only_these_fields));
    }

    /**
     * @param string $relation
     */
    public function initRelation($relation)
    {
        parent::initRelation($relation);
        if ($relation == 'info' && is_null($this->relations['info'])) {
            $options = $this->getRelationOptions($relation);
            $result = new $options['class_name'];
            $foreign_key_value = call_user_func($options['assoc_func_params_func'], $this);
            call_user_func($options['assoc_foreign_key_setter'], $result, $foreign_key_value);
            $this->relations[$relation] = $result;
        }
    }

    /**
     * This function returns the perms allowed for an institute for the current user
     *
     * @return array list of perms
     */
    public function getInstitutePerms()
    {
        if($this->perms === 'admin') {
            return ['admin'];
        }
        $allowed_status = [];
        $possible_status = ['autor', 'tutor', 'dozent'];

        $pos = array_search($this->perms, $possible_status);

        if ($pos !== false) {
            $allowed_status = array_slice($possible_status, 0, $pos + 1);
        }
        return $allowed_status;
    }

    /**
     * Get the decorated StudIP-Kings information
     * @return String
     */
    public function getStudipKingIcon()
    {
        $is_king = StudipKing::is_king($this->user_id, TRUE);

        $result = '';
        foreach ($is_king as $type => $text) {
            $type = str_replace('_', '-', $type);
            $result .= Assets::img('crowns/crown-' . $type . '.png', ['alt' => $text, 'title' => $text]);
        }

        return $result ?: null;
    }

    /**
     * Builds an array containing all available elements that are part of a
     * user's homepage together with their visibility. It isn't sufficient to
     * just load the visibility settings from database, because if the user
     * has added some data (e.g. CV) but not yet assigned a special visibility
     * to that field, it wouldn't show up.
     *
     * @return array An array containing all available homepage elements
     * together with their visibility settings in the form
     * $name => $visibility.
     */
    public function getHomepageElements()
    {
        $homepage_visibility = get_local_visibility_by_id($this->id, 'homepage');
        if (is_array(json_decode($homepage_visibility, true))) {
            $homepage_visibility = json_decode($homepage_visibility, true);
        } else {
            $homepage_visibility = [];
        }

        // News
        $news = StudipNews::GetNewsByRange($this->id, true);

        // Non-private dates.
        if (Config::get()->CALENDAR_ENABLE) {
Jan-Hendrik Willms's avatar
Jan-Hendrik Willms committed
            $dates = CalendarDateAssignment::countBySql('range_id = ?', [$this->id]);
        } else {
            $dates = [];
        }

        // Votes
        if (Config::get()->VOTE_ENABLE) {
            $activeVotes  = Questionnaire::countBySQL("user_id = ? AND visible = '1'", [$this->id]);
            $stoppedVotes = Questionnaire::countBySQL("user_id = ? AND visible = '0'", [$this->id]);
        } else {
            $activeVotes = [];
            $stoppedVotes = [];
        }
        // Evaluations
        $evalDB = new EvaluationDB();
        $activeEvals = $evalDB->getEvaluationIDs($this->id, EVAL_STATE_ACTIVE);
        // Free datafields
        $data_fields = DataFieldEntry::getDataFieldEntries($this->id, 'user');

        // Now join all available elements with visibility settings.
        $homepage_elements = [];

Moritz Strohm's avatar
Moritz Strohm committed
        if (Avatar::getAvatar($this->id)->is_customized() && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['picture'])) {
            $homepage_elements['picture'] = [
                'name'        => _('Eigenes Bild'),
                'visibility'  => $homepage_visibility['picture'] ?? get_default_homepage_visibility($this->id),
                'extern'      => true,
                'identifier'  => 'commondata'
            ];
        }

Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->motto && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['motto'])) {
            $homepage_elements['motto'] = [
                'name'       => _('Motto'),
                'visibility' => $homepage_visibility['motto'] ?? get_default_homepage_visibility($this->id),
                'identifier' => 'privatedata'
            ];
        }
        if (Config::get()->ENABLE_SKYPE_INFO) {
Moritz Strohm's avatar
Moritz Strohm committed
            if ($GLOBALS['user']->cfg->getValue('SKYPE_NAME') && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['skype_name'])) {
                $homepage_elements['skype_name'] = [
                    'name'       => _('Skype Name'),
                    'visibility' => $homepage_visibility['skype_name'] ?? get_default_homepage_visibility($this->id),
                    'identifier' => 'privatedata'
                ];
            }
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->privatnr && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['Private Daten_phone'])) {
            $homepage_elements['private_phone'] = [
                'name'       => _('Private Telefonnummer'),
                'visibility' => $homepage_visibility['private_phone'] ?? get_default_homepage_visibility($this->id),
                'identifier' => 'privatedata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->privatcell && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['private_cell'])) {
            $homepage_elements['private_cell'] = [
                'name'       => _('Private Handynummer'),
                'visibility' => $homepage_visibility['private_cell'] ?? get_default_homepage_visibility($this->id),
                'identifier' => 'privatedata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->privadr && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['privadr'])) {
            $homepage_elements['privadr'] = [
                'name'         => _('Private Adresse'),
                'visibility'   => $homepage_visibility['privadr'] ?? get_default_homepage_visibility($this->id),
                'identifier'   => 'privatedata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->home && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['homepage'])) {
            $homepage_elements['homepage'] = [
                'name'        => _('Homepage-Adresse'),
                'visibility'  => $homepage_visibility['homepage'] ?? get_default_homepage_visibility($this->id),
                'extern'      => true,
                'identifier'  => 'privatedata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($news && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['news'])) {
            $homepage_elements['news'] = [
                'name'       => _('Ankündigungen'),
                'visibility' => $homepage_visibility['news'] ?? get_default_homepage_visibility($this->id),
                'extern'     => true,
                'identifier' => 'commondata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if (Config::get()->CALENDAR_ENABLE && $dates && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['dates'])) {
            $homepage_elements['termine'] = [
                'name'       => _('Termine'),
                'visibility' => $homepage_visibility['termine'] ?? get_default_homepage_visibility($this->id),
                'extern'     => true,
                'identifier' => 'commondata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if (Config::get()->VOTE_ENABLE && ($activeVotes || $stoppedVotes || $activeEvals) && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['votes'])) {
            $homepage_elements['votes'] = [
                'name'       => _('Fragebögen'),
                'visibility' => $homepage_visibility['votes'] ?? get_default_homepage_visibility($this->id),
                'identifier' => 'commondata'
            ];
        }

        $query = "SELECT 1
                  FROM user_inst
                  LEFT JOIN Institute USING (Institut_id)
                  WHERE user_id = ? AND inst_perms = 'user'";
        $statement = DBManager::get()->prepare($query);
        $statement->execute([$this->id]);
Moritz Strohm's avatar
Moritz Strohm committed
        if ($statement->fetchColumn() && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['studying'])) {
            $homepage_elements['studying'] = [
                'name'       => _('Wo ich studiere'),
                'visibility' => $homepage_visibility['studying'] ?? get_default_homepage_visibility($this->id),
                'identifier' => 'studdata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->lebenslauf && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['lebenslauf'])) {
            $homepage_elements['lebenslauf'] = [
                'name'       => _('Lebenslauf'),
                'visibility' => $homepage_visibility['lebenslauf'] ?? get_default_homepage_visibility($this->id),
                'extern'     => true,
                'identifier' => 'privatedata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->hobby && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['hobby'])) {
            $homepage_elements['hobby'] = [
                'name'       => _('Hobbys'),
                'visibility' => $homepage_visibility['hobby'] ?? get_default_homepage_visibility($this->id),
                'identifier' => 'privatedata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->publi && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['publi'])) {
            $homepage_elements['publi'] = [
                'name'       => _('Publikationen'),
                'visibility' => $homepage_visibility['publi'] ?? get_default_homepage_visibility($this->id),
                'extern'     => true,
                'identifier' => 'privatedata'
            ];
        }
Moritz Strohm's avatar
Moritz Strohm committed
        if ($this->info->schwerp && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms]['schwerp'])) {
            $homepage_elements['schwerp'] = [
                'name'       => _('Schwerpunkte'),
                'visibility' => $homepage_visibility['schwerp'] ?? get_default_homepage_visibility($this->id),
                'extern'     => true,
                'identifier' => 'privatedata'
            ];
        }

        if ($data_fields) {
            foreach ($data_fields as $key => $field) {
Moritz Strohm's avatar
Moritz Strohm committed
                if ($field->getValue() && $field->isEditable($this->perms) && empty($GLOBALS['NOT_HIDEABLE_FIELDS'][$this->perms][$key])) {
                    $homepage_elements[$key] = [
                        'name'       => $field->getName(),
                        'visibility' => $homepage_visibility[$key] ?? get_default_homepage_visibility($this->id),
                        'extern'     => true,
                        'identifier' => 'additionaldata'
                    ];
                }
            }
        }

        foreach ($this->profile_categories as $category) {
            $homepage_elements['kat_' . $category->id] = [
                'name'       => $category->name,
                'visibility' => $homepage_visibility['kat_' . $category->id] ?? get_default_homepage_visibility($this->id),
                'extern'     => true,
                'identifier' => 'owncategory'
            ];
        }

        return $homepage_elements;
    }

    /**
     * Changes a user's email adress.
     *
     * @param string $email New email
     * @param bool   $force Force update (even if nothing actually changed)
     * @return bool
     */
    public function changeEmail($email, $force = false)
    {
        // Email did not actually change and update is not forced
        if ($this->email === $email && !$force) {
            return true;
        }

        // Is changing of email globally allowed?
        if (!Config::get()->ALLOW_CHANGE_EMAIL) {
            return false;
        }

        // Is changing of email allowed by auth plugin?
        if (StudipAuthAbstract::CheckField('auth_user_md5.Email', $this->auth_plugin) || LockRules::check($this->user_id, 'email')) {
            return false;
        }

        $validator          = new email_validation_class; ## Klasse zum Ueberpruefen der Eingaben
        $validator->timeout = 10;
        $REMOTE_ADDR        = $_SERVER['REMOTE_ADDR'];
        $Zeit               = date('H:i:s, d.m.Y');

        // accept only registered domains if set
        $email_restriction = trim(Config::get()->EMAIL_DOMAIN_RESTRICTION);
        if (!$validator->ValidateEmailAddress($email, $email_restriction)) {
            if ($email_restriction) {
                $email_restriction_msg_part = '';
                $email_restriction_parts    = explode(',', $email_restriction);
                for ($email_restriction_count = 0; $email_restriction_count < count($email_restriction_parts); $email_restriction_count++) {
                    if ($email_restriction_count == count($email_restriction_parts) - 1) {
                        $email_restriction_msg_part .= '@' . trim($email_restriction_parts[$email_restriction_count]) . '<br>';
                    } else if (($email_restriction_count + 1) % 3) {
                        $email_restriction_msg_part .= '@' . trim($email_restriction_parts[$email_restriction_count]) . ', ';
                    } else {
                        $email_restriction_msg_part .= '@' . trim($email_restriction_parts[$email_restriction_count]) . ',<br>';
                    }
                }
                PageLayout::postError(sprintf(_('Die E-Mail-Adresse fehlt, ist falsch geschrieben oder gehört nicht zu folgenden Domains:%s'),
                    '<br>' . htmlReady($email_restriction_msg_part)));
            } else {
                PageLayout::postError(_('Die E-Mail-Adresse fehlt oder ist falsch geschrieben!'));