<commit_before><?php

class SV_UserTaggingImprovements_XenForo_Model_UserTagging extends XFCP_SV_UserTaggingImprovements_XenForo_Model_UserTagging
{
    public function emailAlertedUsers($contentType, $contentId, $content, array $userIds, array $taggingUser)
    {
        if (empty($userIds))
        {
            return;
        }

        $options = XenForo_Application::getOptions();
        if (!$options->sv_send_email_on_tagging)
        {
            return;
        }

        // use the alert handler to provide a content link.
        // This addon extends the relevent alert handlers to inject the required method
        $alertModel = $this->getModelFromCache('XenForo_Model_Alert');
        $alertHandler = $alertModel->getAlertHandler($contentType);
        if (empty($alertHandler) || !method_exists($alertHandler, 'getContentUrl'))
        {
            return;
        }

        $userModel = $this->_getUserModel();
        $users = $userModel->getUsersByIds($userIds, array(
            'join' => XenForo_Model_User::FETCH_USER_OPTION |
                      XenForo_Model_User::FETCH_USER_PERMISSIONS,
        ));
        foreach($users as $user)
        {
            if (empty($user['sv_email_on_tag']))
            {
                continue;
            }

            $permissions = (!empty($user['global_permission_cache'])
                            ? XenForo_Permission::unserializePermissions($user['global_permission_cache'])
                            : array());
            if (!XenForo_Permission::hasPermission($permissions, 'general', 'sv_ReceiveTagAlertEmails'))
            {
                continue;
            }

            $viewLink = $alertHandler->getContentUrl($content, true);
            if (empty($viewLink))
            {
                continue;
            }

            $this->emailAlertedUser($viewLink, $contentType, $contentId, $content, $user, $taggingUser);
        }
    }

    protected function emailAlertedUser($viewLink, $contentType, $contentId, $content, array $user, array $taggingUser)
    {
        $mail = XenForo_Mail::create('sv_user_tagged', array
        (
            'sender' => $taggingUser,
            'receiver' => $user,
            'contentType' => $contentType,
            'contentId' => $contentId,
            'viewLink' => $viewLink,
        ), $user['language_id']);

        $mail->enableAllLanguagePreCache();
        $mail->queue($user['email'], $user['username']);
    }

    public function getTaggedUsersInMessage($message, &$newMessage, $replaceStyle = 'bb')
    {
        $filteredMessage = $message;
        if ($replaceStyle == 'bb')
        {
            $this->_plainReplacements = array();
            $filteredMessage = preg_replace_callback(
                '#\[(usergroup)(=[^\]]*)?](.*)\[/\\1]#siU',
                array($this, '_plainReplaceHandler'),
                $filteredMessage
            );
        }
        else if ($replaceStyle == 'text')
        {
            $this->_plainReplacements = array();
            $filteredMessage = preg_replace_callback(
                '#(?<=^|\s|[\](,]|--|@)@\[ug_(\d+):(\'|"|&quot;|)(.*)\\2\]#iU',
                array($this, '_plainReplaceHandler'),
                $filteredMessage
            );
        }
        $matches = parent::getTaggedUsersInMessage($filteredMessage, $newMessage, $replaceStyle);
        // restore the message if there are no matches
        if (empty($matches))
        {
            $newMessage = $message;
        }

        return $matches;
    }

    protected function _getGroupMembership($user)
    {
        $groups = explode(',', $user['secondary_group_ids']);
        $groups[] = $user['user_group_id'];
        $groupKeys = array();
        foreach($groups as $group)
        {
            $groupKeys[$group] = true;
        }
        return $groupKeys;
    }

    protected function _getTagMatchUsers(array $matches)
    {
        $usersByMatch = parent::_getTagMatchUsers($matches);

        $db = $this->_getDb();
        $matchKeys = array_keys($matches);
        $whereParts = array();
        $matchParts = array();

        foreach ($matches AS $key => $match)
        {
            if (utf8_strlen($match[1][0]) > 50)
            {
                // longer than max username length
                continue;
            }

            $sql = 'usergroup.title LIKE ' . XenForo_Db::quoteLike($match[1][0], 'r', $db);
            $whereParts[] = $sql;
            $matchParts[] = 'IF(' . $sql . ', 1, 0) AS match_' . $key;
        }

        if (!$whereParts)
        {
            return $usersByMatch;
        }

        $userResults = $db->query("
            SELECT usergroup.user_group_id, usergroup.title, usergroup.sv_private,
                " . implode(', ', $matchParts) . "
            FROM xf_user_group AS usergroup
            WHERE usergroup.sv_taggable = 1 and (" . implode(' OR ', $whereParts) . ")
            ORDER BY LENGTH(usergroup.title) DESC
        ");

        $require_sort = array();

        $visitor = XenForo_Visitor::getInstance();
        $viewAllGroups = $visitor->hasPermission('general','sv_ViewPrivateGroups');
        $groupMembership = $this->_getGroupMembership($visitor->toArray());

        while ($group = $userResults->fetch())
        {
            // private groups are only view able by members and administrators.
            if (!$viewAllGroups && $group['sv_private'] && empty($groupMembership[$group['user_group_id']]))
            {
                continue;
            }

            $userInfo = array(
                'user_id' => 'ug_' . $group['user_group_id'],
                'is_group' => 1,
                'username' => $group['title'],
                'lower' => strtolower($group['title'])
            );

            foreach ($matchKeys AS $key)
            {
                if (!empty($group["match_$key"]))
                {
                    if (!isset($require_sort[$key]))
                    {
                        $require_sort[$key] = !empty($usersByMatch[$key]);
                    }
                    $usersByMatch[$key][$userInfo['user_id']] = $userInfo;
                }
            }
        }
        // sort in the groups
        foreach ($require_sort AS $key => $x)
        {
            if ($require_sort[$key])
            uasort($usersByMatch[$key], array($this, 'usergroup_sorting'));
        }

        return $usersByMatch;
    }

    public function usergroup_sorting($a, $b)
    {
        return utf8_strlen($b['lower']) - utf8_strlen($a['lower']);
    }

    protected function _replaceTagUserMatch(array $user, $replaceStyle)
    {
        if (!empty($user['is_group']) && $replaceStyle == 'bb')
        {
            $group_id = intval(str_replace('ug_', '', $user['user_id']));
            $prefix = XenForo_Application::getOptions()->userTagKeepAt ? '@' : '';
            return '[USERGROUP=' . $group_id . ']' . $prefix . $user['username'] . '[/USERGROUP]';
        }
        return parent::_replaceTagUserMatch($user, $replaceStyle);
    }

    public function getTaggableGroup($UserGroupId)
    {
        $db = $this->_getDb();
        $sql = '';
<commit_msg>Ensure consistent sorting with how the auto-complete works<commit_after><?php

class SV_UserTaggingImprovements_XenForo_Model_UserTagging extends XFCP_SV_UserTaggingImprovements_XenForo_Model_UserTagging
{
    public function emailAlertedUsers($contentType, $contentId, $content, array $userIds, array $taggingUser)
    {
        if (empty($userIds))
        {
            return;
        }

        $options = XenForo_Application::getOptions();
        if (!$options->sv_send_email_on_tagging)
        {
            return;
        }

        // use the alert handler to provide a content link.
        // This addon extends the relevent alert handlers to inject the required method
        $alertModel = $this->getModelFromCache('XenForo_Model_Alert');
        $alertHandler = $alertModel->getAlertHandler($contentType);
        if (empty($alertHandler) || !method_exists($alertHandler, 'getContentUrl'))
        {
            return;
        }

        $userModel = $this->_getUserModel();
        $users = $userModel->getUsersByIds($userIds, array(
            'join' => XenForo_Model_User::FETCH_USER_OPTION |
                      XenForo_Model_User::FETCH_USER_PERMISSIONS,
        ));
        foreach($users as $user)
        {
            if (empty($user['sv_email_on_tag']))
            {
                continue;
            }

            $permissions = (!empty($user['global_permission_cache'])
                            ? XenForo_Permission::unserializePermissions($user['global_permission_cache'])
                            : array());
            if (!XenForo_Permission::hasPermission($permissions, 'general', 'sv_ReceiveTagAlertEmails'))
            {
                continue;
            }

            $viewLink = $alertHandler->getContentUrl($content, true);
            if (empty($viewLink))
            {
                continue;
            }

            $this->emailAlertedUser($viewLink, $contentType, $contentId, $content, $user, $taggingUser);
        }
    }

    protected function emailAlertedUser($viewLink, $contentType, $contentId, $content, array $user, array $taggingUser)
    {
        $mail = XenForo_Mail::create('sv_user_tagged', array
        (
            'sender' => $taggingUser,
            'receiver' => $user,
            'contentType' => $contentType,
            'contentId' => $contentId,
            'viewLink' => $viewLink,
        ), $user['language_id']);

        $mail->enableAllLanguagePreCache();
        $mail->queue($user['email'], $user['username']);
    }

    public function getTaggedUsersInMessage($message, &$newMessage, $replaceStyle = 'bb')
    {
        $filteredMessage = $message;
        if ($replaceStyle == 'bb')
        {
            $this->_plainReplacements = array();
            $filteredMessage = preg_replace_callback(
                '#\[(usergroup)(=[^\]]*)?](.*)\[/\\1]#siU',
                array($this, '_plainReplaceHandler'),
                $filteredMessage
            );
        }
        else if ($replaceStyle == 'text')
        {
            $this->_plainReplacements = array();
            $filteredMessage = preg_replace_callback(
                '#(?<=^|\s|[\](,]|--|@)@\[ug_(\d+):(\'|"|&quot;|)(.*)\\2\]#iU',
                array($this, '_plainReplaceHandler'),
                $filteredMessage
            );
        }
        $matches = parent::getTaggedUsersInMessage($filteredMessage, $newMessage, $replaceStyle);
        // restore the message if there are no matches
        if (empty($matches))
        {
            $newMessage = $message;
        }

        return $matches;
    }

    protected function _getGroupMembership($user)
    {
        $groups = explode(',', $user['secondary_group_ids']);
        $groups[] = $user['user_group_id'];
        $groupKeys = array();
        foreach($groups as $group)
        {
            $groupKeys[$group] = true;
        }
        return $groupKeys;
    }

    protected function _getTagMatchUsers(array $matches)
    {
        $usersByMatch = parent::_getTagMatchUsers($matches);

        $db = $this->_getDb();
        $matchKeys = array_keys($matches);
        $whereParts = array();
        $matchParts = array();

        foreach ($matches AS $key => $match)
        {
            if (utf8_strlen($match[1][0]) > 50)
            {
                // longer than max username length
                continue;
            }

            $sql = 'usergroup.title LIKE ' . XenForo_Db::quoteLike($match[1][0], 'r', $db);
            $whereParts[] = $sql;
            $matchParts[] = 'IF(' . $sql . ', 1, 0) AS match_' . $key;
        }

        if (!$whereParts)
        {
            return $usersByMatch;
        }

        $userResults = $db->query("
            SELECT usergroup.user_group_id, usergroup.title, usergroup.sv_private,
                " . implode(', ', $matchParts) . "
            FROM xf_user_group AS usergroup
            WHERE usergroup.sv_taggable = 1 and (" . implode(' OR ', $whereParts) . ")
            ORDER BY LENGTH(usergroup.title) DESC
        ");

        $require_sort = array();

        $visitor = XenForo_Visitor::getInstance();
        $viewAllGroups = $visitor->hasPermission('general','sv_ViewPrivateGroups');
        $groupMembership = $this->_getGroupMembership($visitor->toArray());

        while ($group = $userResults->fetch())
        {
            // private groups are only view able by members and administrators.
            if (!$viewAllGroups && $group['sv_private'] && empty($groupMembership[$group['user_group_id']]))
            {
                continue;
            }

            $userInfo = array(
                'user_id' => 'ug_' . $group['user_group_id'],
                'is_group' => 1,
                'username' => $group['title'],
                'lower' => strtolower($group['title'])
            );

            foreach ($matchKeys AS $key)
            {
                if (!empty($group["match_$key"]))
                {
                    if (!isset($require_sort[$key]))
                    {
                        $require_sort[$key] = !empty($usersByMatch[$key]);
                    }
                    $usersByMatch[$key][$userInfo['user_id']] = $userInfo;
                }
            }
        }
        // sort in the groups
        foreach ($require_sort AS $key => $x)
        {
            if ($require_sort[$key])
            uasort($usersByMatch[$key], array($this, 'usergroup_sorting'));
        }

        return $usersByMatch;
    }

    public function usergroup_sorting($a, $b)
    {
        if (!empty($b['is_group']) && empty($a['is_group'] ))
            return 1;
        else if (empty($b['is_group']) && !empty($a['is_group']))
            return -1;
        return (utf8_strlen($b['lower']) - utf8_strlen($a['lower']));
    }

    protected function _replaceTagUserMatch(array $user, $replaceStyle)
    {
        if (!empty($user['is_group']) && $replaceStyle == 'bb')
        {
            $group_id = intval(str_replace('ug_', '', $user['user_id']));
            $prefix = XenForo_Application::getOptions()->userTagKeepAt ? '@' : '';
            return '[USERGROUP=' . $group_id . ']' . $prefix . $user['username'] . '[/USERGROUP]';
        }
        return parent::_replaceTagUserMatch($user, $replaceStyle);
    }

    public function getTaggableGroup($UserGroupId)
    {
        $db = $this->_getDb();
        $sql = '';

{'fraction_non_alphanumeric': 0.14444597006727997, 'fraction_numerical': 0.002196896883152547, 'mean_word_length': 1.9041068580542264, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2655049', 'n_tokens_mistral': 4217, 'n_tokens_neox': 4063, 'n_words': 722}