<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle 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 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Auth plugin "LDAP SyncPlus"
 *
 * @package    auth_ldap_syncplus
 * @copyright  2014 Alexander Bias, Ulm University <alexander.bias@uni-ulm.de>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die;

// @codingStandardsIgnoreFile
// Let codechecker ignore this file. This code mostly re-used from auth_ldap and the problems are already there and not made by us.

global $CFG;

require_once($CFG->libdir.'/authlib.php');
require_once($CFG->libdir.'/ldaplib.php');
require_once($CFG->dirroot.'/user/lib.php');
require_once($CFG->dirroot.'/auth/ldap/locallib.php');
require_once(__DIR__.'/../ldap/auth.php');
require_once(__DIR__.'/locallib.php');

/**
 * Auth plugin "LDAP SyncPlus" - Auth class
 *
 * @package    auth_ldap_syncplus
 * @copyright  2014 Alexander Bias, Ulm University <alexander.bias@uni-ulm.de>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class auth_plugin_ldap_syncplus extends auth_plugin_ldap {

    /**
     * Constructor with initialisation.
     */
    public function __construct() {
        $this->authtype = 'ldap_syncplus';
        $this->roleauth = 'auth_ldap';
        $this->errorlogtag = '[AUTH LDAP SYNCPLUS] ';
        $this->init_plugin($this->authtype);
    }

    /**
     * Old syntax of class constructor. Deprecated in PHP7.
     *
     * @deprecated since Moodle 3.1
     */
    public function auth_plugin_ldap_syncplus() {
        debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
        self::__construct();
    }


    /**
     * Syncronizes user fron external LDAP server to moodle user table
     *
     * Sync is now using username attribute.
     *
     * Syncing users removes or suspends users that dont exists anymore in external LDAP.
     * Creates new users and updates coursecreator status of users.
     *
     * @param bool $do_updates will do pull in data updates from LDAP if relevant
     */
    function sync_users($do_updates=true) {
        global $CFG, $DB;

        require_once($CFG->dirroot . '/user/profile/lib.php');

        mtrace(get_string('connectingldap', 'auth_ldap'));
        $ldapconnection = $this->ldap_connect();

        $dbman = $DB->get_manager();

        // Define table user to be created.
        $table = new xmldb_table('tmp_extuser');
        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
        $table->add_field('username', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
        $table->add_field('mnethostid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
        $table->add_index('username', XMLDB_INDEX_UNIQUE, array('mnethostid', 'username'));

        mtrace(get_string('creatingtemptable', 'auth_ldap', 'tmp_extuser'));
        $dbman->create_temp_table($table);

        // Get user's list from ldap to sql in a scalable fashion.
        // Prepare some data we'll need.
        $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')';
        $servercontrols = array();

        $contexts = explode(';', $this->config->contexts);

        if (!empty($this->config->create_context)) {
            array_push($contexts, $this->config->create_context);
        }

        $ldappagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection);
        $ldapcookie = '';
        foreach ($contexts as $context) {
            $context = trim($context);
            if (empty($context)) {
                continue;
            }

            do {
                if ($ldappagedresults) {
                    // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 3.11).
                    if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                        // Before 7.3, use this function that was deprecated in PHP 7.4.
                        ldap_control_paged_result($ldapconnection, $this->config->pagesize, true, $ldapcookie);
                    } else {
                        // PHP 7.3 and up, use server controls.
                        $servercontrols = array(array(
                            'oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => array(
                                'size' => $this->config->pagesize, 'cookie' => $ldapcookie)));
                    }
                }
                if ($this->config->search_sub) {
                    // Use ldap_search to find first user from subtree.
                    // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 3.11).
                    if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                        $ldapresult = ldap_search($ldapconnection, $context, $filter, array($this->config->user_attribute));
                    } else {
                        $ldapresult = ldap_search($ldapconnection, $context, $filter, array($this->config->user_attribute),
                            0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
                    }
                } else {
                    // Search only in this context.
                    // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 3.11).
                    if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                        $ldapresult = ldap_list($ldapconnection, $context, $filter, array($this->config->user_attribute));
                    } else {
                        $ldapresult = ldap_list($ldapconnection, $context, $filter, array($this->config->user_attribute),
                            0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
                    }
                }
                if(!$ldapresult) {
                    continue;
                }
                if ($ldappagedresults) {
                    // Get next server cookie to know if we'll need to continue searching.
                    $ldapcookie = '';
                    // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 3.11).
                    if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                        // Before 7.3, use this function that was deprecated in PHP 7.4.
                        $pagedresp = ldap_control_paged_result_response($ldapconnection, $ldapresult, $ldapcookie);
                        // Function ldap_control_paged_result_response() does not overwrite $ldapcookie if it fails, by
                        // setting this to null we avoid an infinite loop.
                        if ($pagedresp === false) {
                            $ldapcookie = null;
                        }
                    } else {
                        // Get next cookie from controls.
                        ldap_parse_result($ldapconnection, $ldapresult, $errcode, $matcheddn,
                            $errmsg, $referrals, $controls);
                        if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
                            $ldapcookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
                        }
                    }
                }
                if ($entry = @ldap_first_entry($ldapconnection, $ldapresult)) {
                    do {
                        $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute);
                        $value = core_text::convert($value[0], $this->config->ldapencoding, 'utf-8');
                        $value = trim($value);
                        $this->ldap_bulk_insert($value);
                    } while ($entry = ldap_next_entry($ldapconnection, $entry));
                }
                unset($ldapresult); // Free mem.
            } while ($ldappagedresults && $ldapcookie !== null && $ldapcookie != '');
        }

        // If LDAP paged results were used, the current connection must be completely
        // closed and a new one created, to work without paged results from here on.
        if ($ldappagedresults) {
            $this->ldap_close(true);
            $ldapconnection = $this->ldap_connect();
        }

        // Preserve our user database.
        // If the temp table is empty, it probably means that something went wrong, exit
        // so as to avoid mass deletion of users; which is hard to undo.
        $count = $DB->count_records_sql('SELECT COUNT(username) AS count, 1 FROM {tmp_extuser}');
        if ($count < 1) {
            mtrace(get_string('didntgetusersfromldap', 'auth_ldap'));
            $dbman->drop_table($table);
            $this->ldap_close();
            return false;
        } else {
            mtrace(get_string('gotcountrecordsfromldap', 'auth_ldap', $count));
        }


        // Non Grace Period Synchronisation.
        if ($this->config->removeuser != AUTH_REMOVEUSER_DELETEWITHGRACEPERIOD) {

            // User removal.
            // Find users in DB that aren't in ldap -- to be removed!
            // this is still not as scalable (but how often do we mass delete?).

            if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
                $sql = "SELECT u.*
                          FROM {user} u
                     LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                         WHERE u.auth = :auth
                               AND u.deleted = 0
                               AND e.username IS NULL";
                $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));

                if (!empty($remove_users)) {
                    mtrace(get_string('userentriestoremove', 'auth_ldap', count($remove_users)));

                    foreach ($remove_users as $user) {
                        if (delete_user($user)) {
                            mtrace("\t".get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                        } else {
                            mtrace("\t".get_string('auth_dbdeleteusererror', 'auth_db', $user->username));
                        }
                    }
                } else {
                    mtrace(get_string('nouserentriestoremove', 'auth_ldap'));
                }
                unset($remove_users); // Free mem!

            } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
                $sql = "SELECT u.*
                          FROM {user} u
                     LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                         WHERE u.auth = :auth
                               AND u.deleted = 0
                               AND u.suspended = 0
                               AND e.username IS NULL";
                $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));

                if (!empty($remove_users)) {
                    mtrace(get_string('userentriestoremove', 'auth_ldap', count($remove_users)));

                    foreach ($remove_users as $user) {
                        $updateuser = new stdClass();
                        $updateuser->id = $user->id;
                        $updateuser->suspended = 1;
                        user_update_user($updateuser, false);
                        mtrace("\t".get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                        \core\session\manager::kill_user_sessions($user->id);
                    }
                } else {
                    mtrace(get_string('nouserentriestoremove', 'auth_ldap'));
                }
                unset($remove_users); // Free mem!
            }

            // Revive suspended users.
            if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
                $sql = "SELECT u.id, u.username
                          FROM {user} u
                          JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                         WHERE (u.auth = 'nologin' OR (u.auth = ? AND u.suspended = 1)) AND u.deleted = 0";
                // Note: 'nologin' is there for backwards compatibility.
                $revive_users = $DB->get_records_sql($sql, array($this->authtype));

                if (!empty($revive_users)) {
                    mtrace(get_string('userentriestorevive', 'auth_ldap', count($revive_users)));

                    foreach ($revive_users as $user) {
                        $updateuser = new stdClass();
                        $updateuser->id = $user->id;
                        $updateuser->auth = $this->authtype;
                        $updateuser->suspended = 0;
                        user_update_user($updateuser, false);
                        mtrace("\t".get_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                    }
                } else {
                    mtrace(get_string('nouserentriestorevive', 'auth_ldap'));
                }

                unset($revive_users);
            }
        }

        // Grace Period Synchronisation.
        else if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_DELETEWITHGRACEPERIOD) {

            // Revive suspended users.
            $sql = "SELECT u.id, u.username
                      FROM {user} u
                      JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                     WHERE (u.auth = 'nologin' OR (u.auth = ? AND u.suspended = 1)) AND u.deleted = 0";
            // Note: 'nologin' is there for backwards compatibility.
            $revive_users = $DB->get_records_sql($sql, array($this->authtype));

            if (!empty($revive_users)) {
                mtrace(get_string('userentriestorevive', 'auth_ldap', count($revive_users)));

                foreach ($revive_users as $user) {
                    $updateuser = new stdClass();
                    $updateuser->id = $user->id;
                    $updateuser->auth = $this->authtype;
                    $updateuser->suspended = 0;
                    user_update_user($updateuser, false);
                    mtrace("\t".get_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                }
            } else {
                mtrace(get_string('nouserentriestorevive', 'auth_ldap'));
            }
            unset($revive_users);

            // User temporary suspending.
            $sql = "SELECT u.*
                      FROM {user} u
                 LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                     WHERE u.auth = :auth
                           AND u.deleted = 0
                           AND u.suspended = 0
                           AND e.username IS NULL";
            $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));

            if (!empty($remove_users)) {
                mtrace(get_string('userentriestosuspend', 'auth_ldap_syncplus', count($remove_users)));

                foreach ($remove_users as $user) {
                    $updateuser = new stdClass();
                    $updateuser->id = $user->id;
                    $updateuser->suspended = 1;
                    $updateuser->timemodified = time(); // Remember suspend time, abuse timemodified column for this.
                    user_update_user($updateuser, false);
                    mtrace("\t".get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                    \core\session\manager::kill_user_sessions($user->id);
                }
            } else {
                mtrace(get_string('nouserentriestosuspend', 'auth_ldap_syncplus'));
            }
            unset($remove_users); // Free mem!

            // User complete removal.
            $sql = "SELECT u.*
                      FROM {user} u
                 LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                     WHERE u.auth = :auth
                           AND u.deleted = 0
                           AND e.username IS NULL";
            $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));

            if (!empty($remove_users)) {
                mtrace(get_string('userentriestoremove', 'auth_ldap', count($remove_users)));

                foreach ($remove_users as $user) {
                    // Do only if user was suspended before grace period.
                    $graceperiod = max(intval($this->config->removeuser_graceperiod), 0);
                            // Fix problems if grace period setting was negative or no number.
                    if (time() - $user->timemodified >= $graceperiod * 24 * 3600) {
                        if (delete_user($user)) {
                            mtrace("\t".get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                        } else {
                            mtrace("\t".get_string('auth_dbdeleteusererror', 'auth_db', $user->username));
                        }
                        // Otherwise inform about ongoing grace period.
                    } else {
                        mtrace("\t".get_string('waitinginremovalqueue', 'auth_ldap_syncplus', array('days'=>$graceperiod, 'name'=>$user->username, 'id'=>$user->id)));
                    }
                }
            } else {
                mtrace(get_string('nouserentriestoremove', 'auth_ldap'));
            }
            unset($remove_users); // Free mem!
        }

        // User Updates - time-consuming (optional).
        if ($do_updates) {
            // Narrow down what fields we need to update.
            $updatekeys = $this->get_profile_keys();
        } else {
            mtrace(get_string('noupdatestobedone', 'auth_ldap'));
        }
        if ($do_updates and !empty($updatekeys)) { // run updates only if relevant.
            $users = $DB->get_records_sql('SELECT u.username, u.id
                                             FROM {user} u
                                            WHERE u.deleted = 0 AND u.auth = ? AND u.mnethostid = ?',
                                          array($this->authtype, $CFG->mnet_localhost_id));
            if (!empty($users)) {
                mtrace(get_string('userentriestoupdate', 'auth_ldap', count($users)));

                $transaction = $DB->start_delegated_transaction();
                $xcount = 0;
                $maxxcount = 100;

                foreach ($users as $user) {
                    $userinfo = $this->get_userinfo($user->username);
                    if (!$this->update_user_record($user->username, $updatekeys, true,
                            $this->is_user_suspended((object) $userinfo))) {
                        $skipped = ' - '.get_string('skipped');
                    }
                    else {
                        $skipped = '';
                    }
                    mtrace("\t".get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)).$skipped);
                    $xcount++;

                    // Update system roles, if needed.
                    $this->sync_roles($user);
                }
                $transaction->allow_commit();
                unset($users); // free mem.
            }
        } else { // end do updates.
            mtrace(get_string('noupdatestobedone', 'auth_ldap'));
        }

        // User Additions.
        // Find users missing in DB that are in LDAP
        // and gives me a nifty object I don't want.
        // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin.
        if (!empty($this->config->sync_script_createuser_enabled) and $this->config->sync_script_createuser_enabled == 1) {
            $sql = 'SELECT e.id, e.username
                      FROM {tmp_extuser} e
                      LEFT JOIN {user} u ON (e.username = u.username AND e.mnethostid = u.mnethostid)
                     WHERE u.id IS NULL';
            $add_users = $DB->get_records_sql($sql);

            if (!empty($add_users)) {
                mtrace(get_string('userentriestoadd', 'auth_ldap', count($add_users)));

                $transaction = $DB->start_delegated_transaction();
                foreach ($add_users as $user) {
                    $user = $this->get_userinfo_asobj($user->username);

                    // Prep a few params.
                    $user->modified   = time();
                    $user->confirmed  = 1;
                    $user->auth       = $this->authtype;
                    $user->mnethostid = $CFG->mnet_localhost_id;
                    // get_userinfo_asobj() might have replaced $user->username with the value
                    // from the LDAP server (which can be mixed-case). Make sure it's lowercase.
                    $user->username = trim(core_text::strtolower($user->username));
                    // It isn't possible to just rely on the configured suspension attribute since
                    // things like active directory use bit masks, other things using LDAP might
                    // do different stuff as well.
                    //
                    // The cast to int is a workaround for MDL-53959.
                    $user->suspended = (int)$this->is_user_suspended($user);

                    if (empty($user->calendartype)) {
                        $user->calendartype = $CFG->calendartype;
                    }

                    $id = user_create_user($user, false);
                    mtrace("\t".get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)));
                    $euser = $DB->get_record('user', array('id' => $id));

                    if (!empty($this->config->forcechangepassword)) {
                        set_user_preference('auth_forcepasswordchange', 1, $id);
                    }

                    // Save custom profile fields.
                    $this->update_user_record($user->username, $this->get_profile_keys(true), false);

                    // Add roles if needed.
                    $this->sync_roles($euser);
                }
                $transaction->allow_commit();
                unset($add_users); // free mem
            } else {
                mtrace(get_string('nouserstobeadded', 'auth_ldap'));
            }
        } else {
            mtrace(get_string('nouserstobeadded', 'auth_ldap'));
        }

        $dbman->drop_table($table);
        $this->ldap_close();

        return true;
    }


    /**
     * Support login via email ($CFG->authloginviaemail) for first-time LDAP logins
     * @return void
     */
    public function loginpage_hook() {
        global $CFG, $frm, $DB;

        // If $CFG->authloginviaemail is not set, users don't want to login by mail, call parent hook and return.
        if ($CFG->authloginviaemail != 1) {
            parent::loginpage_hook(); // Call parent function to retain its functionality.
            return;
        }

        // Get submitted form data.
        $frm = data_submitted();

        // If there is no username submitted, there's nothing to do, call parent hook and return.
        if (empty($frm->username)) {
            parent::loginpage_hook(); // Call parent function to retain its functionality.
            return;
        }

        // Clean username parameter to make sure that its an email address.
        $email = clean_param($frm->username, PARAM_EMAIL);

        // If we don't have an email adress, there's nothing to do, call parent hook and return.
        if ($email == '' || strpos($email, '@') == false) {
            parent::loginpage_hook(); // Call parent function to retain its functionality.
            return;
        }

        // If there is an existing useraccount with this email adress as email address (then a Moodle account already exists and
        // the standard mechanism of $CFG->authloginviaemail will kick in automatically) or if there is an existing useraccount
        // with this email adress as username (which is not forbidden, so this useraccount has to be used), call parent hook and
        // return.
        if ($DB->count_records_select('user', '(username = :p1 OR email = :p2) AND deleted = 0',
                                        array('p1' => $email, 'p2' => $email)) > 0) {
            parent::loginpage_hook(); // Call parent function to retain its functionality.
            return;
        }

        // Get auth plugin.
        $authplugin = get_auth_plugin('ldap_syncplus');

        // If there is no email field mapping configured, we don't know where we can find the email adress in LDAP,
        // call parent hook and return.
        if (empty($authplugin->config->field_map_email)) {
            parent::loginpage_hook(); // Call parent function to retain its functionality.
            return;
        }

        // Prepare LDAP search.
        $contexts = explode(';', $authplugin->config->contexts);
        $filter = '(&('.$authplugin->config->field_map_email.'='.ldap_filter_addslashes($email).')'.
                $authplugin->config->objectclass.')';

        // Connect to LDAP.
        $ldapconnection = $authplugin->ldap_connect();

        // Array for saving the user's ids which are found in the configured LDAP contexts.
        $uidsfound = array();

        // Look for users matching the given email adress in LDAP.
        foreach ($contexts as $context) {
            // Verify that the given context is valid.
            $context = trim($context);
            if (empty($context)) {
                continue;
            }

            // Search LDAP.
            if ($authplugin->config->search_sub) {
                // Use ldap_search to find first user from subtree.
                $ldapresult = ldap_search($ldapconnection, $context, $filter, array($authplugin->config->user_attribute));
            } else {
                // Search only in this context.
                $ldapresult = ldap_list($ldapconnection, $context, $filter, array($authplugin->config->user_attribute));
            }

            // If there is no LDAP result or if the user was not found in this context, continue with next context.
            if (!$ldapresult || ldap_count_entries($ldapconnection, $ldapresult) == 0) {
                continue;
            }

            // If there is not exactly one matching user, we can't continue, call parent hook and return.
            if (ldap_count_entries($ldapconnection, $ldapresult) != 1) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }

            // Get this one matching user entry.
            if (!$ldapentry = ldap_first_entry($ldapconnection, $ldapresult)) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }

            // Get the uid attribute's value(s) from this user entry.
            $values = ldap_get_values($ldapconnection, $ldapentry, $authplugin->config->user_attribute);

            // If there is not exactly one copy of the uid attribute in the LDAP user entry, we don't know which one to use,
            // call parent hook and return.
            if ($values['count'] != 1) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }

            // Remember this one user's uid attribute.
            $uidsfound[] = $values[0];

            unset($ldapresult); // Free mem!
        }

        // After we have checked all contexts, verify that we have found only one user in total.
        // If not, we can't continue, call parent hook and return.
        if (count($uidsfound) != 1) {
            parent::loginpage_hook(); // Call parent function to retain its functionality.
            return;

            // Success!
            // Replace the form data's username with the user attribute from LDAP, it will be held in the global $frm variable.
        } else {
            $frm->username = $uidsfound[0];
            parent::loginpage_hook(); // Call parent function to retain its functionality.
            return;
        }
    }
}