public function sync_users()

in classes/feature/usersync/main.php [962:1182]


    public function sync_users(array $aadusers = array()) {
        global $DB, $CFG;

        $aadsync = $this->get_sync_options();
        $switchauthminupnsplit0 = get_config('local_o365', 'switchauthminupnsplit0');
        if (empty($switchauthminupnsplit0)) {
            $switchauthminupnsplit0 = 10;
        }

        $usernames = [];
        $upns = [];

        $guestsync = array_key_exists('guestsync', $aadsync);

        foreach ($aadusers as $i => $user) {
            if (!isset($user['userPrincipalName'])) {
                // User doesn't have userPrincipalName, should be deleted users.
                unset($aadusers[$i]);
                continue;
            }

            if (!$guestsync) {
                if (strpos($user['userPrincipalName'], '#EXT#') !== false) {
                    // The user is a guest user, and the guest sync option is disabled. Skip processing the user.
                    unset($aadusers[$i]);
                    continue;
                }
            }

            $upnlower = \core_text::strtolower($user['userPrincipalName']);
            $aadusers[$i]['upnlower'] = $upnlower;

            $usernames[] = $upnlower;
            $upns[] = $upnlower;

            $upnsplit = explode('@', $upnlower);
            if (!empty($upnsplit[0])) {
                $aadusers[$i]['upnsplit0'] = $upnsplit[0];
                $usernames[] = $upnsplit[0];
            }

            // Convert upn for guest users.
            if (stripos($upnlower, '#ext#') !== false) {
                $upnlower = \core_text::strtolower($user['mail']);

                $usernames[] = $upnlower;
                $upns[] = $upnlower;

                $upnsplit = explode('@', $upnlower);
                if (!empty($upnsplit[0])) {
                    $usernames[] = $upnsplit[0];
                }
            }
        }

        if (!$aadusers) {
            return true;
        }

        $select = 'SELECT LOWER(u.username) AS username,';
        if (isset($aadsync['emailsync'])) {
            $select .= ' LOWER(u.email) AS email,';
        }

        $sql = "$select
                       u.id as muserid,
                       u.auth,
                       u.suspended,
                       tok.id as tokid,
                       conn.id as existingconnectionid,
                       assign.assigned assigned,
                       assign.photoid photoid,
                       assign.photoupdated photoupdated,
                       obj.id AS objectid
                  FROM {user} u
             LEFT JOIN {auth_oidc_token} tok ON tok.userid = u.id
             LEFT JOIN {local_o365_connections} conn ON conn.muserid = u.id
             LEFT JOIN {local_o365_appassign} assign ON assign.muserid = u.id
             LEFT JOIN {local_o365_objects} obj ON obj.type = ? AND obj.moodleid = u.id
                 WHERE u.mnethostid = ? AND u.deleted = ?
              ORDER BY CONCAT(u.username, '~')"; // Sort john.smith@email.com before john.smith.
        $params = array_merge(['user'], [$CFG->mnet_localhost_id, '0']);
        $existingusers = $DB->get_records_sql($sql, $params);

        foreach ($existingusers as $id => $existinguser) {
            if (isset($aadsync['emailsync'])) {
                if (!in_array($existinguser->email, $usernames)) {
                    unset($existingusers[$id]);
                }
            } else {
                if (!in_array($existinguser->username, $usernames)) {
                    unset($existingusers[$id]);
                }
            }
        }

        // Fetch linked AAD user accounts.
        [$upnsql, $upnparams] = $DB->get_in_or_equal($upns);
        [$usernamesql, $usernameparams] = $DB->get_in_or_equal($usernames, SQL_PARAMS_QM, 'param', false);
        $sql = 'SELECT tok.oidcusername,
                       u.username as username,
                       u.id as muserid,
                       u.auth,
                       tok.id as tokid,
                       conn.id as existingconnectionid,
                       assign.assigned assigned,
                       assign.photoid photoid,
                       assign.photoupdated photoupdated,
                       obj.id AS objectid
                  FROM {user} u
             LEFT JOIN {auth_oidc_token} tok ON tok.userid = u.id
             LEFT JOIN {local_o365_connections} conn ON conn.muserid = u.id
             LEFT JOIN {local_o365_appassign} assign ON assign.muserid = u.id
             LEFT JOIN {local_o365_objects} obj ON obj.type = ? AND obj.moodleid = u.id
                 WHERE tok.oidcusername '.$upnsql.' AND u.username '.$usernamesql.' AND u.mnethostid = ? AND u.deleted = ? ';
        $params = array_merge(['user'], $upnparams, $usernameparams, [$CFG->mnet_localhost_id, '0']);
        $linkedexistingusers = $DB->get_records_sql($sql, $params);

        $existingusers = $existingusers + $linkedexistingusers;

        $processedusers = [];

        foreach ($aadusers as $aaduser) {
            $this->mtrace(' ');

            if (unified::is_configured()) {
                $userobjectid = $aaduser['id'];
            } else {
                $userobjectid = $aaduser['objectId'];
            }

            if (empty($aaduser['upnlower'])) {
                $this->mtrace('Azure AD user missing UPN (' . $userobjectid . '); skipping...');
                continue;
            }

            $this->mtrace('Syncing user '.$aaduser['upnlower']);

            // Process guest users.
            $aaduser['convertedupn'] = $aaduser['upnlower'];
            if (stripos($aaduser['userPrincipalName'], '#EXT#') !== false) {
                $aaduser['convertedupn'] = strtolower($aaduser['mail']);
            }

            if (in_array($aaduser['convertedupn'], $processedusers)) {
                $this->mtrace('User already processed; skipping...');
                continue;
            } else {
                $processedusers[] = $aaduser['convertedupn'];
            }

            if (!isset($existingusers[$aaduser['upnlower']]) && !isset($existingusers[$aaduser['upnsplit0']]) &&
                !isset($existingusers[$aaduser['convertedupn']])) {
                $this->sync_new_user($aadsync, $aaduser, isset($aadsync['guestsync']));
            } else {
                $existinguser = null;
                if (isset($existingusers[$aaduser['upnlower']])) {
                    $existinguser = $existingusers[$aaduser['upnlower']];
                    $exactmatch = true;
                } else if (isset($existingusers[$aaduser['upnsplit0']])) {
                    $existinguser = $existingusers[$aaduser['upnsplit0']];
                    $exactmatch = strlen($aaduser['upnsplit0']) >= $switchauthminupnsplit0;
                } else if (isset($existingusers[$aaduser['convertedupn']])) {
                    $existinguser = $existingusers[$aaduser['convertedupn']];
                    $exactmatch = true;
                }

                // Process guest users.
                if (stripos($aaduser['upnlower'], '_ext_') !== false) {
                    $this->mtrace('The user is a guest user.');
                    if (!isset($aadsync['guestsync'])) {
                        $this->mtrace('The option to sync guest users is turned off.');
                        $this->mtrace('User is already synced, but not updated.');

                        continue;
                    }
                }

                $this->sync_existing_user($aadsync, $aaduser, $existinguser, $exactmatch);

                if ($existinguser->auth === 'oidc' || empty($existinguser->tokid)) {
                    // Create userobject if it does not exist.
                    if (empty($existinguser->objectid)) {
                        $this->mtrace('Adding o365 object record for user.');
                        $now = time();
                        $userobjectdata = (object)[
                            'type' => 'user',
                            'subtype' => '',
                            'objectid' => $userobjectid,
                            'o365name' => $aaduser['userPrincipalName'],
                            'moodleid' => $existinguser->muserid,
                            'tenant' => '',
                            'timecreated' => $now,
                            'timemodified' => $now,
                        ];
                        $userobjectdata->id = $DB->insert_record('local_o365_objects', $userobjectdata);
                    }
                    // User already connected.
                    $this->mtrace('User is now synced.');
                }

                // Update existing user on moodle from AD.
                if ($existinguser->auth === 'oidc') {
                    if (isset($aadsync['update'])) {
                        $this->mtrace('Updating Moodle user data from Azure AD user data.');
                        $fullexistinguser = get_complete_user_data('username', $existinguser->username);
                        if ($fullexistinguser) {
                            $existingusercopy = \core_user::get_user_by_username($existinguser->username);
                            $fullexistinguser->description = $existingusercopy->description;
                            $this->update_user_from_aaddata($aaduser, $fullexistinguser);
                            $this->mtrace('User is now updated.');
                        } else {
                            $this->mtrace('Update failed for user with username "' . $existinguser->username . '".');
                        }
                    }
                }
            }
        }

        return true;
    }