<commit_before>import { url } from 'discourse/lib/computed';
import RestModel from 'discourse/models/rest';
import UserStream from 'discourse/models/user-stream';
import UserPostsStream from 'discourse/models/user-posts-stream';
import Singleton from 'discourse/mixins/singleton';
import { longDate } from 'discourse/lib/formatter';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import Badge from 'discourse/models/badge';
import UserBadge from 'discourse/models/user-badge';
import UserActionStat from 'discourse/models/user-action-stat';
import UserAction from 'discourse/models/user-action';
import Group from 'discourse/models/group';
import Topic from 'discourse/models/topic';

const User = RestModel.extend({

  hasPMs: Em.computed.gt("private_messages_stats.all", 0),
  hasStartedPMs: Em.computed.gt("private_messages_stats.mine", 0),
  hasUnreadPMs: Em.computed.gt("private_messages_stats.unread", 0),
  hasPosted: Em.computed.gt("post_count", 0),
  hasNotPosted: Em.computed.not("hasPosted"),
  canBeDeleted: Em.computed.and("can_be_deleted", "hasNotPosted"),

  redirected_to_top: {
    reason: null,
  },

  @computed()
  stream() {
    return UserStream.create({ user: this });
  },

  @computed()
  postsStream() {
    return UserPostsStream.create({ user: this });
  },

  staff: Em.computed.or('admin', 'moderator'),

  destroySession() {
    return Discourse.ajax(`/session/${this.get('username')}`, { type: 'DELETE'});
  },

  @computed("username_lower")
  searchContext(username) {
    return {
      type: 'user',
      id: username,
      user: this
    };
  },

  @computed("username", "name")
  displayName(username, name) {
    if (Discourse.SiteSettings.enable_names && !Ember.isEmpty(name)) {
      return name;
    }
    return username;
  },

  @computed('profile_background')
  profileBackground(bgUrl) {
    if (Em.isEmpty(bgUrl) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
    return ('background-image: url(' + Discourse.getURLWithCDN(bgUrl) + ')').htmlSafe();
  },

  @computed()
  path() {
    // no need to observe, requires a hard refresh to update
    return Discourse.getURL(`/users/${this.get('username_lower')}`);
  },

  adminPath: url('username_lower', "/admin/users/%@"),

  mutedTopicsPath: url('/latest?state=muted'),

  @computed("username")
  username_lower(username) {
    return username.toLowerCase();
  },

  @computed("trust_level")
  trustLevel(trustLevel) {
    return Discourse.Site.currentProp('trustLevels').findProperty('id', parseInt(trustLevel, 10));
  },

  isBasic: Em.computed.equal('trust_level', 0),
  isLeader: Em.computed.equal('trust_level', 3),
  isElder: Em.computed.equal('trust_level', 4),
  canManageTopic: Em.computed.or('staff', 'isElder'),

  isSuspended: Em.computed.equal('suspended', true),

  @computed("suspended_till")
  suspended(suspendedTill) {
    return suspendedTill && moment(suspendedTill).isAfter();
  },

  @computed("suspended_till")
  suspendedTillDate(suspendedTill) {
    return longDate(suspendedTill);
  },

  changeUsername(new_username) {
    return Discourse.ajax(`/users/${this.get('username_lower')}/preferences/username`, {
      type: 'PUT',
      data: { new_username }
    });
  },

  changeEmail(email) {
    return Discourse.ajax(`/users/${this.get('username_lower')}/preferences/email`, {
      type: 'PUT',
      data: { email }
    });
  },

  copy() {
    return Discourse.User.create(this.getProperties(Ember.keys(this)));
  },

  save() {
    const data = this.getProperties(
            'auto_track_topics_after_msecs',
            'bio_raw',
            'website',
            'location',
            'name',
            'locale',
            'email_digests',
            'email_direct',
            'email_always',
            'email_private_messages',
            'dynamic_favicon',
            'digest_after_days',
            'new_topic_duration_minutes',
            'external_links_in_new_tab',
            'mailing_list_mode',
            'enable_quoting',
            'disable_jump_reply',
            'custom_fields',
            'user_fields',
            'muted_usernames',
            'profile_background',
            'card_background',
            'automatically_unpin_topics'
          );

    ['muted','watched','tracked'].forEach(s => {
      let cats = this.get(s + 'Categories').map(c => c.get('id'));
      // HACK: denote lack of categories
      if (cats.length === 0) { cats = [-1]; }
      data[s + '_category_ids'] = cats;
    });

    if (!Discourse.SiteSettings.edit_history_visible_to_public) {
      data['edit_history_public'] = this.get('edit_history_public');
    }

    // TODO: We can remove this when migrated fully to rest model.
    this.set('isSaving', true);
    return Discourse.ajax(`/users/${this.get('username_lower')}`, {
      data: data,
      type: 'PUT'
    }).then(result => {
      this.set('bio_excerpt', result.user.bio_excerpt);
      const userProps = this.getProperties('enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon');
      Discourse.User.current().setProperties(userProps);
    }).finally(() => {
      this.set('isSaving', false);
    });
  },

  changePassword() {
    return Discourse.ajax("/session/forgot_password", {
      dataType: 'json',
      data: { login: this.get('username') },
      type: 'POST'
    });
  },

  loadUserAction(id) {
    const stream = this.get('stream');
    return Discourse.ajax(`/user_actions/${id}.json`, { cache: 'false' }).then(result => {
      if (result && result.user_action) {
        const ua = result.user_action;

        if ((this.get('stream.filter') || ua.action_type) !== ua.action_type) return;
        if (!this.get('stream.filter') && !this.inAllStream(ua)) return;

        const action = UserAction.collapseStream([UserAction.create(ua)]);
        stream.set('itemsLoaded', stream.get('itemsLoaded') + 1);
        stream.get('content').insertAt(0, action[0]);
      }
    });
  },

  inAllStream(ua) {
    return ua.action_type === UserAction.TYPES.posts ||
           ua.action_type === UserAction.TYPES.topics;
  },

  @computed("groups.@each")
  displayGroups() {
    const groups = this.get('groups');
    const filtered = groups.filter(group => {
      return !group.automatic || group.name === "moderators";
    });<commit_msg>SECURITY: topic titles can show up in user page unescaped when streamed in<commit_after>import { url } from 'discourse/lib/computed';
import RestModel from 'discourse/models/rest';
import UserStream from 'discourse/models/user-stream';
import UserPostsStream from 'discourse/models/user-posts-stream';
import Singleton from 'discourse/mixins/singleton';
import { longDate } from 'discourse/lib/formatter';
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
import Badge from 'discourse/models/badge';
import UserBadge from 'discourse/models/user-badge';
import UserActionStat from 'discourse/models/user-action-stat';
import UserAction from 'discourse/models/user-action';
import Group from 'discourse/models/group';
import Topic from 'discourse/models/topic';

const User = RestModel.extend({

  hasPMs: Em.computed.gt("private_messages_stats.all", 0),
  hasStartedPMs: Em.computed.gt("private_messages_stats.mine", 0),
  hasUnreadPMs: Em.computed.gt("private_messages_stats.unread", 0),
  hasPosted: Em.computed.gt("post_count", 0),
  hasNotPosted: Em.computed.not("hasPosted"),
  canBeDeleted: Em.computed.and("can_be_deleted", "hasNotPosted"),

  redirected_to_top: {
    reason: null,
  },

  @computed()
  stream() {
    return UserStream.create({ user: this });
  },

  @computed()
  postsStream() {
    return UserPostsStream.create({ user: this });
  },

  staff: Em.computed.or('admin', 'moderator'),

  destroySession() {
    return Discourse.ajax(`/session/${this.get('username')}`, { type: 'DELETE'});
  },

  @computed("username_lower")
  searchContext(username) {
    return {
      type: 'user',
      id: username,
      user: this
    };
  },

  @computed("username", "name")
  displayName(username, name) {
    if (Discourse.SiteSettings.enable_names && !Ember.isEmpty(name)) {
      return name;
    }
    return username;
  },

  @computed('profile_background')
  profileBackground(bgUrl) {
    if (Em.isEmpty(bgUrl) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
    return ('background-image: url(' + Discourse.getURLWithCDN(bgUrl) + ')').htmlSafe();
  },

  @computed()
  path() {
    // no need to observe, requires a hard refresh to update
    return Discourse.getURL(`/users/${this.get('username_lower')}`);
  },

  adminPath: url('username_lower', "/admin/users/%@"),

  mutedTopicsPath: url('/latest?state=muted'),

  @computed("username")
  username_lower(username) {
    return username.toLowerCase();
  },

  @computed("trust_level")
  trustLevel(trustLevel) {
    return Discourse.Site.currentProp('trustLevels').findProperty('id', parseInt(trustLevel, 10));
  },

  isBasic: Em.computed.equal('trust_level', 0),
  isLeader: Em.computed.equal('trust_level', 3),
  isElder: Em.computed.equal('trust_level', 4),
  canManageTopic: Em.computed.or('staff', 'isElder'),

  isSuspended: Em.computed.equal('suspended', true),

  @computed("suspended_till")
  suspended(suspendedTill) {
    return suspendedTill && moment(suspendedTill).isAfter();
  },

  @computed("suspended_till")
  suspendedTillDate(suspendedTill) {
    return longDate(suspendedTill);
  },

  changeUsername(new_username) {
    return Discourse.ajax(`/users/${this.get('username_lower')}/preferences/username`, {
      type: 'PUT',
      data: { new_username }
    });
  },

  changeEmail(email) {
    return Discourse.ajax(`/users/${this.get('username_lower')}/preferences/email`, {
      type: 'PUT',
      data: { email }
    });
  },

  copy() {
    return Discourse.User.create(this.getProperties(Ember.keys(this)));
  },

  save() {
    const data = this.getProperties(
            'auto_track_topics_after_msecs',
            'bio_raw',
            'website',
            'location',
            'name',
            'locale',
            'email_digests',
            'email_direct',
            'email_always',
            'email_private_messages',
            'dynamic_favicon',
            'digest_after_days',
            'new_topic_duration_minutes',
            'external_links_in_new_tab',
            'mailing_list_mode',
            'enable_quoting',
            'disable_jump_reply',
            'custom_fields',
            'user_fields',
            'muted_usernames',
            'profile_background',
            'card_background',
            'automatically_unpin_topics'
          );

    ['muted','watched','tracked'].forEach(s => {
      let cats = this.get(s + 'Categories').map(c => c.get('id'));
      // HACK: denote lack of categories
      if (cats.length === 0) { cats = [-1]; }
      data[s + '_category_ids'] = cats;
    });

    if (!Discourse.SiteSettings.edit_history_visible_to_public) {
      data['edit_history_public'] = this.get('edit_history_public');
    }

    // TODO: We can remove this when migrated fully to rest model.
    this.set('isSaving', true);
    return Discourse.ajax(`/users/${this.get('username_lower')}`, {
      data: data,
      type: 'PUT'
    }).then(result => {
      this.set('bio_excerpt', result.user.bio_excerpt);
      const userProps = this.getProperties('enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon');
      Discourse.User.current().setProperties(userProps);
    }).finally(() => {
      this.set('isSaving', false);
    });
  },

  changePassword() {
    return Discourse.ajax("/session/forgot_password", {
      dataType: 'json',
      data: { login: this.get('username') },
      type: 'POST'
    });
  },

  loadUserAction(id) {
    const stream = this.get('stream');
    return Discourse.ajax(`/user_actions/${id}.json`, { cache: 'false' }).then(result => {
      if (result && result.user_action) {
        const ua = result.user_action;

        if ((this.get('stream.filter') || ua.action_type) !== ua.action_type) return;
        if (!this.get('stream.filter') && !this.inAllStream(ua)) return;

        ua.title = Discourse.Emoji.unescape(Handlebars.Utils.escapeExpression(ua.title));
        const action = UserAction.collapseStream([UserAction.create(ua)]);
        stream.set('itemsLoaded', stream.get('itemsLoaded') + 1);
        stream.get('content').insertAt(0, action[0]);
      }
    });
  },

  inAllStream(ua) {
    return ua.action_type === UserAction.TYPES.posts ||
           ua.action_type === UserAction.TYPES.topics;
  },

  @computed("groups.@each")
  displayGroups() {
    const groups = this.get('groups');
    const filtered = groups.filter(group => {
      return !group.automatic || group.name === "moderators";
    });
{'fraction_non_alphanumeric': 0.1581898901783628, 'fraction_numerical': 0.002180855206791806, 'mean_word_length': 3.352542372881356, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, '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': '670658', 'n_tokens_mistral': 4208, 'n_tokens_neox': 4091, 'n_words': 694}