Version bump
This commit is contained in:
commit
1234acd2dd
14
Gemfile
14
Gemfile
@ -14,13 +14,13 @@ if rails_master?
|
||||
else
|
||||
# until rubygems gives us optional dependencies we are stuck with this
|
||||
# bundle update actionmailer actionpack actionview activemodel activerecord activesupport railties
|
||||
gem 'actionmailer', '5.2.2'
|
||||
gem 'actionpack', '5.2.2'
|
||||
gem 'actionview', '5.2.2'
|
||||
gem 'activemodel', '5.2.2'
|
||||
gem 'activerecord', '5.2.2'
|
||||
gem 'activesupport', '5.2.2'
|
||||
gem 'railties', '5.2.2'
|
||||
gem 'actionmailer', '5.2.2.1'
|
||||
gem 'actionpack', '5.2.2.1'
|
||||
gem 'actionview', '5.2.2.1'
|
||||
gem 'activemodel', '5.2.2.1'
|
||||
gem 'activerecord', '5.2.2.1'
|
||||
gem 'activesupport', '5.2.2.1'
|
||||
gem 'railties', '5.2.2.1'
|
||||
gem 'sprockets-rails'
|
||||
end
|
||||
|
||||
|
||||
54
Gemfile.lock
54
Gemfile.lock
@ -1,37 +1,37 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activejob (= 5.2.2)
|
||||
actionmailer (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activejob (= 5.2.2.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
actionpack (5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
rack (~> 2.0)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
actionview (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_model_serializers (0.8.4)
|
||||
activemodel (>= 3.0)
|
||||
activejob (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activejob (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activerecord (5.2.2)
|
||||
activemodel (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activemodel (5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
activerecord (5.2.2.1)
|
||||
activemodel (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
arel (>= 9.0)
|
||||
activesupport (5.2.2)
|
||||
activesupport (5.2.2.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
@ -308,9 +308,9 @@ GEM
|
||||
rails_multisite (2.0.6)
|
||||
activerecord (> 4.2, < 6)
|
||||
railties (> 4.2, < 6)
|
||||
railties (5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
railties (5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
@ -446,13 +446,13 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
actionmailer (= 5.2.2)
|
||||
actionpack (= 5.2.2)
|
||||
actionview (= 5.2.2)
|
||||
actionmailer (= 5.2.2.1)
|
||||
actionpack (= 5.2.2.1)
|
||||
actionview (= 5.2.2.1)
|
||||
active_model_serializers (~> 0.8.3)
|
||||
activemodel (= 5.2.2)
|
||||
activerecord (= 5.2.2)
|
||||
activesupport (= 5.2.2)
|
||||
activemodel (= 5.2.2.1)
|
||||
activerecord (= 5.2.2.1)
|
||||
activesupport (= 5.2.2.1)
|
||||
annotate
|
||||
aws-sdk-s3
|
||||
aws-sdk-sns
|
||||
@ -525,7 +525,7 @@ DEPENDENCIES
|
||||
rack-mini-profiler
|
||||
rack-protection
|
||||
rails_multisite
|
||||
railties (= 5.2.2)
|
||||
railties (= 5.2.2.1)
|
||||
rake
|
||||
rb-fsevent
|
||||
rb-inotify (~> 0.9)
|
||||
|
||||
@ -376,24 +376,21 @@ export default Ember.Component.extend({
|
||||
|
||||
_applyCategoryHashtagAutocomplete() {
|
||||
const siteSettings = this.siteSettings;
|
||||
const self = this;
|
||||
|
||||
this.$(".d-editor-input").autocomplete({
|
||||
template: findRawTemplate("category-tag-autocomplete"),
|
||||
key: "#",
|
||||
afterComplete() {
|
||||
self._focusTextArea();
|
||||
},
|
||||
transformComplete(obj) {
|
||||
afterComplete: () => this._focusTextArea(),
|
||||
transformComplete: obj => {
|
||||
return obj.text;
|
||||
},
|
||||
dataSource(term) {
|
||||
dataSource: term => {
|
||||
if (term.match(/\s/)) {
|
||||
return null;
|
||||
}
|
||||
return searchCategoryTag(term, siteSettings);
|
||||
},
|
||||
triggerRule(textarea, opts) {
|
||||
triggerRule: (textarea, opts) => {
|
||||
return categoryHashtagTriggerRule(textarea, opts);
|
||||
}
|
||||
});
|
||||
@ -404,17 +401,15 @@ export default Ember.Component.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
|
||||
$editorInput.autocomplete({
|
||||
template: findRawTemplate("emoji-selector-autocomplete"),
|
||||
key: ":",
|
||||
afterComplete(text) {
|
||||
self.set("value", text);
|
||||
self._focusTextArea();
|
||||
afterComplete: text => {
|
||||
this.set("value", text);
|
||||
this._focusTextArea();
|
||||
},
|
||||
|
||||
onKeyUp(text, cp) {
|
||||
onKeyUp: (text, cp) => {
|
||||
const matches = /(?:^|[^a-z])(:(?!:).?[\w-]*:?(?!:)(?:t\d?)?:?) ?$/gi.exec(
|
||||
text.substring(0, cp)
|
||||
);
|
||||
@ -424,22 +419,26 @@ export default Ember.Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
transformComplete(v) {
|
||||
transformComplete: v => {
|
||||
if (v.code) {
|
||||
return `${v.code}:`;
|
||||
} else {
|
||||
$editorInput.autocomplete({ cancel: true });
|
||||
self.set("emojiPickerIsActive", true);
|
||||
this.set(
|
||||
"isEditorFocused",
|
||||
$("textarea.d-editor-input").is(":focus")
|
||||
);
|
||||
this.set("emojiPickerIsActive", true);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
dataSource(term) {
|
||||
dataSource: term => {
|
||||
return new Ember.RSVP.Promise(resolve => {
|
||||
const full = `:${term}`;
|
||||
term = term.toLowerCase();
|
||||
|
||||
if (term.length < self.siteSettings.emoji_autocomplete_min_chars) {
|
||||
if (term.length < this.siteSettings.emoji_autocomplete_min_chars) {
|
||||
return resolve([]);
|
||||
}
|
||||
|
||||
@ -856,6 +855,7 @@ export default Ember.Component.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
this.set("isEditorFocused", $("textarea.d-editor-input").is(":focus"));
|
||||
this.set("emojiPickerIsActive", !this.get("emojiPickerIsActive"));
|
||||
},
|
||||
|
||||
|
||||
@ -2,6 +2,11 @@ import { setting } from "discourse/lib/computed";
|
||||
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
|
||||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
const categorySortCriteria = [];
|
||||
export function addCategorySortCriteria(criteria) {
|
||||
categorySortCriteria.push(criteria);
|
||||
}
|
||||
|
||||
export default buildCategoryPanel("settings", {
|
||||
emailInEnabled: setting("email_in"),
|
||||
showPositionInput: setting("fixed_category_positions"),
|
||||
@ -64,10 +69,9 @@ export default buildCategoryPanel("settings", {
|
||||
"category",
|
||||
"created"
|
||||
]
|
||||
.concat(categorySortCriteria)
|
||||
.map(s => ({ name: I18n.t("category.sort_options." + s), value: s }))
|
||||
.sort((a, b) => {
|
||||
return a.name > b.name;
|
||||
});
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
|
||||
@computed
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
isSkinTonableEmoji,
|
||||
emojiSearch
|
||||
} from "pretty-text/emoji";
|
||||
import { safariHacksDisabled } from "discourse/lib/utilities";
|
||||
const { run } = Ember;
|
||||
|
||||
const keyValueStore = new KeyValueStore("discourse_emojis_");
|
||||
@ -58,6 +59,12 @@ export default Ember.Component.extend({
|
||||
this._scrollTo();
|
||||
this._updateSelectedDiversity();
|
||||
this._checkVisibleSection(true);
|
||||
|
||||
if (
|
||||
(!this.site.isMobileDevice || this.get("isEditorFocused")) &&
|
||||
!safariHacksDisabled()
|
||||
)
|
||||
this.$filter.find("input[name='filter']").focus();
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -32,9 +32,9 @@ export default Ember.Component.extend({
|
||||
"emailOrUsername",
|
||||
"invitingToTopic",
|
||||
"isPrivateTopic",
|
||||
"topic.groupNames",
|
||||
"topic.saving",
|
||||
"topic.details.can_invite_to"
|
||||
"inviteModel.groupNames.[]",
|
||||
"inviteModel.saving",
|
||||
"inviteModel.details.can_invite_to"
|
||||
)
|
||||
disabled(
|
||||
isAdmin,
|
||||
@ -79,7 +79,7 @@ export default Ember.Component.extend({
|
||||
"emailOrUsername",
|
||||
"inviteModel.saving",
|
||||
"isPrivateTopic",
|
||||
"inviteModel.groupNames",
|
||||
"inviteModel.groupNames.[]",
|
||||
"hasCustomMessage"
|
||||
)
|
||||
disabledCopyLink(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import { bufferedRender } from "discourse-common/lib/buffered-render";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
import TopicStatusIcons from "discourse/helpers/topic-status-icons";
|
||||
|
||||
export default Ember.Component.extend(
|
||||
bufferedRender({
|
||||
@ -30,7 +31,15 @@ export default Ember.Component.extend(
|
||||
}.property("disableActions"),
|
||||
|
||||
buildBuffer(buffer) {
|
||||
const renderIcon = function(name, key, actionable) {
|
||||
const canAct = this.get("canAct");
|
||||
const topic = this.get("topic");
|
||||
|
||||
if (!topic) {
|
||||
return;
|
||||
}
|
||||
|
||||
TopicStatusIcons.render(topic, function(name, key) {
|
||||
const actionable = ["pinned", "unpinned"].includes(key) && canAct;
|
||||
const title = escapeExpression(I18n.t(`topic_statuses.${key}.help`)),
|
||||
startTag = actionable ? "a href" : "span",
|
||||
endTag = actionable ? "a" : "span",
|
||||
@ -40,32 +49,7 @@ export default Ember.Component.extend(
|
||||
buffer.push(
|
||||
`<${startTag} title='${title}' class='topic-status'>${icon}</${endTag}>`
|
||||
);
|
||||
};
|
||||
|
||||
const renderIconIf = (conditionProp, name, key, actionable) => {
|
||||
if (!this.get(conditionProp)) {
|
||||
return;
|
||||
}
|
||||
renderIcon(name, key, actionable);
|
||||
};
|
||||
|
||||
renderIconIf("topic.is_warning", "envelope", "warning");
|
||||
|
||||
if (this.get("topic.closed") && this.get("topic.archived")) {
|
||||
renderIcon("lock", "locked_and_archived");
|
||||
} else {
|
||||
renderIconIf("topic.closed", "lock", "locked");
|
||||
renderIconIf("topic.archived", "lock", "archived");
|
||||
}
|
||||
|
||||
renderIconIf("topic.pinned", "thumbtack", "pinned", this.get("canAct"));
|
||||
renderIconIf(
|
||||
"topic.unpinned",
|
||||
"thumbtack",
|
||||
"unpinned",
|
||||
this.get("canAct")
|
||||
);
|
||||
renderIconIf("topic.invisible", "far-eye-slash", "unlisted");
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@ -74,9 +74,25 @@ export default Ember.Controller.extend(BulkTopicSelection, {
|
||||
return listDraft ? "topic.open_draft" : "topic.create";
|
||||
},
|
||||
|
||||
@computed("canCreateTopic", "category", "canCreateTopicOnCategory")
|
||||
createTopicDisabled(canCreateTopic, category, canCreateTopicOnCategory) {
|
||||
return !canCreateTopic || (category && !canCreateTopicOnCategory);
|
||||
@computed(
|
||||
"canCreateTopic",
|
||||
"category",
|
||||
"canCreateTopicOnCategory",
|
||||
"tag",
|
||||
"canCreateTopicOnTag"
|
||||
)
|
||||
createTopicDisabled(
|
||||
canCreateTopic,
|
||||
category,
|
||||
canCreateTopicOnCategory,
|
||||
tag,
|
||||
canCreateTopicOnTag
|
||||
) {
|
||||
return (
|
||||
!canCreateTopic ||
|
||||
(category && !canCreateTopicOnCategory) ||
|
||||
(tag && !canCreateTopicOnTag)
|
||||
);
|
||||
},
|
||||
|
||||
queryParams: [
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
export default Ember.ArrayProxy.extend({
|
||||
render(topic, renderIcon) {
|
||||
const renderIconIf = (conditionProp, name, key) => {
|
||||
if (!topic.get(conditionProp)) {
|
||||
return;
|
||||
}
|
||||
renderIcon(name, key);
|
||||
};
|
||||
|
||||
if (topic.get("closed") && topic.get("archived")) {
|
||||
renderIcon("lock", "locked_and_archived");
|
||||
} else {
|
||||
renderIconIf("closed", "lock", "locked");
|
||||
renderIconIf("archived", "lock", "archived");
|
||||
}
|
||||
|
||||
this.forEach(args => renderIconIf(...args));
|
||||
}
|
||||
}).create({
|
||||
content: [
|
||||
["is_warning", "envelope", "warning"],
|
||||
["pinned", "thumbtack", "pinned"],
|
||||
["unpinned", "thumbtack", "unpinned"],
|
||||
["invisible", "far-eye-slash", "unlisted"]
|
||||
]
|
||||
});
|
||||
@ -41,9 +41,10 @@ import { disableNameSuppression } from "discourse/widgets/poster-name";
|
||||
import { registerCustomPostMessageCallback as registerCustomPostMessageCallback1 } from "discourse/controllers/topic";
|
||||
import Sharing from "discourse/lib/sharing";
|
||||
import { addComposerUploadHandler } from "discourse/components/composer-editor";
|
||||
import { addCategorySortCriteria } from "discourse/components/edit-category-settings";
|
||||
|
||||
// If you add any methods to the API ensure you bump up this number
|
||||
const PLUGIN_API_VERSION = "0.8.29";
|
||||
const PLUGIN_API_VERSION = "0.8.30";
|
||||
|
||||
class PluginApi {
|
||||
constructor(version, container) {
|
||||
@ -801,7 +802,6 @@ class PluginApi {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Registers a function to handle uploads for specified file types
|
||||
* The normal uploading functionality will be bypassed if function returns
|
||||
* a falsy value.
|
||||
@ -817,6 +817,18 @@ class PluginApi {
|
||||
addComposerUploadHandler(extensions, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a criteria that can be used as default topic order on category
|
||||
* pages.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* categorySortCriteria("votes");
|
||||
*/
|
||||
addCategorySortCriteria(criteria) {
|
||||
addCategorySortCriteria(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a renderer that overrides the display of category links.
|
||||
*
|
||||
|
||||
@ -111,14 +111,18 @@ export default Discourse.Route.extend({
|
||||
).then(list => {
|
||||
if (list.topic_list.tags && list.topic_list.tags.length === 1) {
|
||||
// Update name of tag (case might be different)
|
||||
tag.set("id", list.topic_list.tags[0].name);
|
||||
tag.setProperties({
|
||||
id: list.topic_list.tags[0].name,
|
||||
staff: list.topic_list.tags[0].staff
|
||||
});
|
||||
}
|
||||
controller.setProperties({
|
||||
list,
|
||||
canCreateTopic: list.get("can_create_topic"),
|
||||
loading: false,
|
||||
canCreateTopicOnCategory:
|
||||
this.get("category.permission") === PermissionType.FULL
|
||||
this.get("category.permission") === PermissionType.FULL,
|
||||
canCreateTopicOnTag: !tag.get("staff") || this.get("currentUser.staff")
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -50,4 +50,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{emoji-picker active=emojiPickerIsActive emojiSelected=(action 'emojiSelected')}}
|
||||
{{emoji-picker active=emojiPickerIsActive isEditorFocused=isEditorFocused emojiSelected=(action 'emojiSelected')}}
|
||||
|
||||
@ -85,9 +85,9 @@
|
||||
{{i18n "category.sort_order"}}
|
||||
</label>
|
||||
<div class="controls">
|
||||
{{combo-box valueAttribute="value" id="category-sort-order" content=availableSorts value=category.sort_order none="category.sort_options.default"}}
|
||||
{{combo-box valueAttribute="value" content=availableSorts value=category.sort_order none="category.sort_options.default"}}
|
||||
{{#unless isDefaultSortOrder}}
|
||||
{{combo-box castBoolean=true valueAttribute="value" id="category-sort-order" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
|
||||
{{combo-box castBoolean=true valueAttribute="value" content=sortAscendingOptions value=category.sort_ascending none="category.sort_options.default"}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
<label class="control-label" for="{{concat 'user-' elementId}}">{{{field.name}}} {{#if field.required}}<span class='required'>*</span>{{/if}}
|
||||
</label>
|
||||
{{#if field.name}}
|
||||
<label class="control-label" for="{{concat 'user-' elementId}}">
|
||||
{{{field.name}}} {{#if field.required}}<span class='required'>*</span>{{/if}}
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<div class='controls'>
|
||||
<label class="control-label checkbox-label">{{input id=(concat 'user-' elementId) checked=value type="checkbox"}} <span>{{{field.description}}}</span></label>
|
||||
<label class="control-label checkbox-label">
|
||||
{{input id=(concat 'user-' elementId) checked=value type="checkbox"}}
|
||||
<span>
|
||||
{{{field.description}}} {{#unless field.name}}{{#if field.required}}<span class='required'>*</span>{{/if}}{{/unless}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
icon="far-eye"
|
||||
label="user.unignore"}}
|
||||
{{else}}
|
||||
{{d-button class="btn-danger"
|
||||
{{d-button class="btn-default"
|
||||
action=(action "ignoreUser")
|
||||
icon="eye-slash"
|
||||
label="user.ignore"}}
|
||||
|
||||
@ -17,7 +17,7 @@ export default createWidget("home-logo", {
|
||||
},
|
||||
|
||||
logoUrl() {
|
||||
return this.siteSettings.site_home_logo_url || "";
|
||||
return this.siteSettings.site_logo_url || "";
|
||||
},
|
||||
|
||||
mobileLogoUrl() {
|
||||
|
||||
@ -431,15 +431,28 @@ createWidget("post-contents", {
|
||||
createWidget("post-notice", {
|
||||
tagName: "div.post-notice",
|
||||
|
||||
buildClasses(attrs) {
|
||||
if (attrs.postNoticeType === "first") {
|
||||
return ["new-user"];
|
||||
} else if (attrs.postNoticeType === "returning") {
|
||||
return ["returning-user"];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
const user =
|
||||
this.siteSettings.prioritize_username_in_ux || !attrs.name
|
||||
? attrs.username
|
||||
: attrs.name;
|
||||
let text, icon;
|
||||
if (attrs.postNoticeType === "first") {
|
||||
icon = "hands-helping";
|
||||
text = I18n.t("post.notice.first", { user: attrs.username });
|
||||
text = I18n.t("post.notice.first", { user });
|
||||
} else if (attrs.postNoticeType === "returning") {
|
||||
icon = "far-smile";
|
||||
text = I18n.t("post.notice.return", {
|
||||
user: attrs.username,
|
||||
user,
|
||||
time: relativeAge(attrs.postNoticeTime, {
|
||||
format: "tiny",
|
||||
addAgo: true
|
||||
|
||||
@ -16,6 +16,7 @@ createWidget("admin-menu-button", {
|
||||
action: attrs.action,
|
||||
icon: attrs.icon,
|
||||
label: attrs.fullLabel || `topic.${attrs.label}`,
|
||||
title: attrs.title,
|
||||
secondaryAction: "hideAdminMenu"
|
||||
})
|
||||
);
|
||||
@ -136,7 +137,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "toggleMultiSelect",
|
||||
icon: "tasks",
|
||||
label: "actions.multi_select"
|
||||
label: "actions.multi_select",
|
||||
title: "topic.actions.multi_select_tooltip"
|
||||
});
|
||||
|
||||
const topic = attrs.topic;
|
||||
@ -148,7 +150,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-danger",
|
||||
action: "deleteTopic",
|
||||
icon: "far-trash-alt",
|
||||
label: "actions.delete"
|
||||
label: "actions.delete",
|
||||
title: "topic.actions.delete_tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
@ -158,7 +161,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "recoverTopic",
|
||||
icon: "undo",
|
||||
label: "actions.recover"
|
||||
label: "actions.recover",
|
||||
title: "topic.actions.recover_tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
@ -168,7 +172,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "toggleClosed",
|
||||
icon: "unlock",
|
||||
label: "actions.open"
|
||||
label: "actions.open",
|
||||
title: "topic.actions.open_tooltip"
|
||||
});
|
||||
} else {
|
||||
buttons.push({
|
||||
@ -176,7 +181,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "toggleClosed",
|
||||
icon: "lock",
|
||||
label: "actions.close"
|
||||
label: "actions.close",
|
||||
title: "topic.actions.close_tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
@ -185,7 +191,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "showTopicStatusUpdate",
|
||||
icon: "far-clock",
|
||||
label: "actions.timed_update"
|
||||
label: "actions.timed_update",
|
||||
title: "topic.actions.timed_update_tooltip"
|
||||
});
|
||||
|
||||
const isPrivateMessage = topic.get("isPrivateMessage");
|
||||
@ -197,7 +204,10 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "showFeatureTopic",
|
||||
icon: "thumbtack",
|
||||
label: featured ? "actions.unpin" : "actions.pin"
|
||||
label: featured ? "actions.unpin" : "actions.pin",
|
||||
title: featured
|
||||
? "topic.actions.unpin_tooltip"
|
||||
: "topic.actions.pin_tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
@ -207,7 +217,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "showChangeTimestamp",
|
||||
icon: "calendar-alt",
|
||||
label: "change_timestamp.title"
|
||||
label: "change_timestamp.title",
|
||||
title: "topic.change_timestamp.tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
@ -216,7 +227,8 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "resetBumpDate",
|
||||
icon: "anchor",
|
||||
label: "actions.reset_bump_date"
|
||||
label: "actions.reset_bump_date",
|
||||
title: "topic.actions.reset_bump_date_tooltip"
|
||||
});
|
||||
|
||||
if (!isPrivateMessage) {
|
||||
@ -225,7 +237,10 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "toggleArchived",
|
||||
icon: "folder",
|
||||
label: topic.get("archived") ? "actions.unarchive" : "actions.archive"
|
||||
label: topic.get("archived") ? "actions.unarchive" : "actions.archive",
|
||||
title: topic.get("archived")
|
||||
? "topic.actions.unarchive_tooltip"
|
||||
: "topic.actions.archive_tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
@ -235,7 +250,10 @@ export default createWidget("topic-admin-menu", {
|
||||
buttonClass: "btn-default",
|
||||
action: "toggleVisibility",
|
||||
icon: visible ? "far-eye-slash" : "far-eye",
|
||||
label: visible ? "actions.invisible" : "actions.visible"
|
||||
label: visible ? "actions.invisible" : "actions.visible",
|
||||
title: visible
|
||||
? "topic.actions.invisible_tooltip"
|
||||
: "topic.actions.visible_tooltip"
|
||||
});
|
||||
|
||||
if (details.get("can_convert_topic")) {
|
||||
@ -246,7 +264,12 @@ export default createWidget("topic-admin-menu", {
|
||||
? "convertToPublicTopic"
|
||||
: "convertToPrivateMessage",
|
||||
icon: isPrivateMessage ? "comment" : "envelope",
|
||||
label: isPrivateMessage ? "actions.make_public" : "actions.make_private"
|
||||
label: isPrivateMessage
|
||||
? "actions.make_public"
|
||||
: "actions.make_private",
|
||||
title: isPrivateMessage
|
||||
? "topic.actions.make_public_tooltip"
|
||||
: "topic.actions.make_private_tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
@ -255,7 +278,8 @@ export default createWidget("topic-admin-menu", {
|
||||
action: "showModerationHistory",
|
||||
buttonClass: "btn-default",
|
||||
icon: "list",
|
||||
fullLabel: "admin.flags.moderation_history"
|
||||
fullLabel: "admin.flags.moderation_history",
|
||||
title: "admin.flags.moderation_history_tooltip"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -2,16 +2,7 @@ import { createWidget } from "discourse/widgets/widget";
|
||||
import { iconNode } from "discourse-common/lib/icon-library";
|
||||
import { h } from "virtual-dom";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
|
||||
function renderIcon(name, key, canAct) {
|
||||
const iconArgs = key === "unpinned" ? { class: "unpinned" } : null,
|
||||
icon = iconNode(name, iconArgs);
|
||||
|
||||
const attributes = {
|
||||
title: escapeExpression(I18n.t(`topic_statuses.${key}.help`))
|
||||
};
|
||||
return h(`${canAct ? "a" : "span"}.topic-status`, attributes, icon);
|
||||
}
|
||||
import TopicStatusIcons from "discourse/helpers/topic-status-icons";
|
||||
|
||||
export default createWidget("topic-status", {
|
||||
tagName: "div.topic-statuses",
|
||||
@ -21,25 +12,16 @@ export default createWidget("topic-status", {
|
||||
const canAct = this.currentUser && !attrs.disableActions;
|
||||
|
||||
const result = [];
|
||||
const renderIconIf = (conditionProp, name, key) => {
|
||||
if (!topic.get(conditionProp)) {
|
||||
return;
|
||||
}
|
||||
result.push(renderIcon(name, key, canAct));
|
||||
};
|
||||
|
||||
renderIconIf("is_warning", "envelope", "warning");
|
||||
TopicStatusIcons.render(topic, function(name, key) {
|
||||
const iconArgs = key === "unpinned" ? { class: "unpinned" } : null;
|
||||
const icon = iconNode(name, iconArgs);
|
||||
|
||||
if (topic.get("closed") && topic.get("archived")) {
|
||||
renderIcon("lock", "locked_and_archived");
|
||||
} else {
|
||||
renderIconIf("closed", "lock", "locked");
|
||||
renderIconIf("archived", "lock", "archived");
|
||||
}
|
||||
|
||||
renderIconIf("pinned", "thumbtack", "pinned");
|
||||
renderIconIf("unpinned", "thumbtack", "unpinned");
|
||||
renderIconIf("invisible", "far-eye-slash", "unlisted");
|
||||
const attributes = {
|
||||
title: escapeExpression(I18n.t(`topic_statuses.${key}.help`))
|
||||
};
|
||||
result.push(h(`${canAct ? "a" : "span"}.topic-status`, attributes, icon));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -54,6 +54,14 @@
|
||||
.social-link {
|
||||
margin-right: s(2);
|
||||
font-size: $font-up-4;
|
||||
.d-icon-fab-facebook-square {
|
||||
// Adheres to Facebook brand guidelines
|
||||
color: dark-light-choose($facebook, white);
|
||||
}
|
||||
.d-icon-fab-twitter-square {
|
||||
// Adheres to Twitter brand guidelines
|
||||
color: dark-light-choose($twitter, white);
|
||||
}
|
||||
}
|
||||
.link {
|
||||
font-size: $font-up-3;
|
||||
|
||||
@ -15,7 +15,7 @@ class Admin::SiteSettingsController < Admin::AdminController
|
||||
raise_access_hidden_setting(id)
|
||||
|
||||
if SiteSetting.type_supervisor.get_type(id) == :upload
|
||||
value = Upload.get_from_url(value) || ''
|
||||
value = Upload.find_by(url: value) || ''
|
||||
end
|
||||
|
||||
SiteSetting.set_and_log(id, value, current_user)
|
||||
|
||||
@ -98,6 +98,8 @@ class TagsController < ::ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
raise Discourse::NotFound if DiscourseTagging.hidden_tag_names(guardian).include?(params[:tag_id])
|
||||
|
||||
show_latest
|
||||
end
|
||||
|
||||
|
||||
@ -219,7 +219,7 @@ module ApplicationHelper
|
||||
opts[:twitter_summary_large_image] = twitter_summary_large_image_url
|
||||
end
|
||||
|
||||
opts[:image] = SiteSetting.opengraph_image_url.presence ||
|
||||
opts[:image] = SiteSetting.site_opengraph_image_url.presence ||
|
||||
twitter_summary_large_image_url.presence ||
|
||||
SiteSetting.site_large_icon_url.presence ||
|
||||
SiteSetting.site_apple_touch_icon_url.presence ||
|
||||
@ -295,7 +295,7 @@ module ApplicationHelper
|
||||
if mobile_view? && SiteSetting.site_mobile_logo_url
|
||||
SiteSetting.site_mobile_logo_url
|
||||
else
|
||||
SiteSetting.site_home_logo_url
|
||||
SiteSetting.site_logo_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -22,8 +22,7 @@ module UserNotificationsHelper
|
||||
logo_url = SiteSetting.site_digest_logo_url
|
||||
logo_url = SiteSetting.site_logo_url if logo_url.blank? || logo_url =~ /\.svg$/i
|
||||
return nil if logo_url.blank? || logo_url =~ /\.svg$/i
|
||||
|
||||
full_cdn_url(logo_url)
|
||||
logo_url
|
||||
end
|
||||
|
||||
def html_site_link(color)
|
||||
|
||||
15
app/jobs/regular/notify_tag_change.rb
Normal file
15
app/jobs/regular/notify_tag_change.rb
Normal file
@ -0,0 +1,15 @@
|
||||
require_dependency "post_alerter"
|
||||
|
||||
module Jobs
|
||||
class NotifyTagChange < Jobs::Base
|
||||
def execute(args)
|
||||
post = Post.find_by(id: args[:post_id])
|
||||
|
||||
if post&.topic
|
||||
post_alerter = PostAlerter.new
|
||||
post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]))
|
||||
post_alerter.notify_first_post_watchers(post, post_alerter.tag_watchers(post.topic))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -54,7 +54,10 @@ module Jobs
|
||||
)
|
||||
|
||||
if message
|
||||
Email::Sender.new(message, type, user).send
|
||||
Email::Sender.new(message, type, user).send(
|
||||
is_critical: self.class == Jobs::CriticalUserEmail
|
||||
)
|
||||
|
||||
if (b = user.user_stat.bounce_score) > SiteSetting.bounce_score_erode_on_send
|
||||
# erode bounce score each time we send an email
|
||||
# this means that we are punished a lot less for bounces
|
||||
|
||||
@ -7,6 +7,7 @@ module Jobs
|
||||
|
||||
# always remove invalid upload records
|
||||
Upload
|
||||
.by_users
|
||||
.where("retain_hours IS NULL OR created_at < current_timestamp - interval '1 hour' * retain_hours")
|
||||
.where("created_at < ?", grace_period.hour.ago)
|
||||
.where(url: "")
|
||||
@ -44,7 +45,8 @@ module Jobs
|
||||
end
|
||||
end.compact.uniq
|
||||
|
||||
result = Upload.where("uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours")
|
||||
result = Upload.by_users
|
||||
.where("uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours")
|
||||
.where("uploads.created_at < ?", grace_period.hour.ago)
|
||||
.joins(<<~SQL)
|
||||
LEFT JOIN site_settings ss
|
||||
|
||||
@ -20,7 +20,7 @@ module Jobs
|
||||
|
||||
if count > 0
|
||||
target_usernames = Group[:moderators].users.map do |user|
|
||||
next if user.id < 0
|
||||
next if user.bot?
|
||||
|
||||
unseen_count = user.notifications.joins(:topic)
|
||||
.where("notifications.id > ?", user.seen_notification_id)
|
||||
|
||||
@ -133,7 +133,7 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
|
||||
|
||||
def approve_account_if_needed
|
||||
if get_existing_user
|
||||
invited_user.approve(invite.invited_by_id, false)
|
||||
invited_user.approve(invite.invited_by, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -215,8 +215,7 @@ class Post < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def matches_recent_post?
|
||||
post_id = $redis.get(unique_post_key)
|
||||
post_id != (nil) && post_id.to_i != (id)
|
||||
$redis.get(unique_post_key)&.to_i != id
|
||||
end
|
||||
|
||||
def raw_hash
|
||||
|
||||
@ -1529,7 +1529,9 @@ class Report
|
||||
FROM uploads up
|
||||
JOIN users u
|
||||
ON u.id = up.user_id
|
||||
WHERE up.created_at >= '#{report.start_date}' AND up.created_at <= '#{report.end_date}'
|
||||
WHERE up.created_at >= '#{report.start_date}'
|
||||
AND up.created_at <= '#{report.end_date}'
|
||||
AND up.id > #{Upload::SEEDED_ID_THRESHOLD}
|
||||
ORDER BY up.filesize DESC
|
||||
LIMIT #{report.limit || 250}
|
||||
SQL
|
||||
@ -1548,6 +1550,54 @@ class Report
|
||||
end
|
||||
end
|
||||
|
||||
def self.report_top_ignored_users(report)
|
||||
report.modes = [:table]
|
||||
|
||||
report.labels = [
|
||||
{
|
||||
type: :user,
|
||||
properties: {
|
||||
id: :ignored_user_id,
|
||||
username: :ignored_username,
|
||||
avatar: :ignored_user_avatar_template,
|
||||
},
|
||||
title: I18n.t("reports.top_ignored_users.labels.ignored_user")
|
||||
},
|
||||
{
|
||||
type: :number,
|
||||
properties: [
|
||||
:ignores_count,
|
||||
],
|
||||
title: I18n.t("reports.top_ignored_users.labels.ignores_count")
|
||||
}
|
||||
]
|
||||
|
||||
report.data = []
|
||||
|
||||
sql = <<~SQL
|
||||
SELECT
|
||||
u.id AS user_id,
|
||||
u.username,
|
||||
u.uploaded_avatar_id,
|
||||
COUNT(*) AS ignores_count
|
||||
FROM users AS u
|
||||
INNER JOIN ignored_users AS ig ON ig.ignored_user_id = u.id
|
||||
WHERE ig.created_at >= '#{report.start_date}' AND ig.created_at <= '#{report.end_date}'
|
||||
GROUP BY u.id
|
||||
ORDER BY COUNT(*) DESC
|
||||
LIMIT #{report.limit || 250}
|
||||
SQL
|
||||
|
||||
DB.query(sql).each do |row|
|
||||
report.data << {
|
||||
ignored_user_id: row.user_id,
|
||||
ignored_username: row.username,
|
||||
ignored_user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id),
|
||||
ignores_count: row.ignores_count,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
DiscourseEvent.on(:site_setting_saved) do |site_setting|
|
||||
if ["backup_location", "s3_backup_bucket"].include?(site_setting.name.to_s)
|
||||
clear_cache(:storage_stats)
|
||||
|
||||
@ -175,69 +175,26 @@ class SiteSetting < ActiveRecord::Base
|
||||
site_logo_small_url
|
||||
site_mobile_logo_url
|
||||
site_favicon_url
|
||||
site_home_logo_url
|
||||
}.each { |client_setting| client_settings << client_setting }
|
||||
|
||||
def self.site_home_logo_url
|
||||
upload = SiteSetting.logo
|
||||
|
||||
if SiteSetting.defaults.get(:title) != SiteSetting.title && !upload
|
||||
''
|
||||
else
|
||||
full_cdn_url(upload ? upload.url : '/images/d-logo-sketch.png')
|
||||
%i{
|
||||
logo
|
||||
logo_small
|
||||
digest_logo
|
||||
mobile_logo
|
||||
large_icon
|
||||
favicon
|
||||
apple_touch_icon
|
||||
twitter_summary_large_image
|
||||
opengraph_image
|
||||
push_notifications_icon
|
||||
}.each do |setting_name|
|
||||
define_singleton_method("site_#{setting_name}_url") do
|
||||
upload = self.public_send(setting_name)
|
||||
upload ? full_cdn_url(upload.url) : ''
|
||||
end
|
||||
end
|
||||
|
||||
def self.site_logo_url
|
||||
upload = self.logo
|
||||
upload ? full_cdn_url(upload.url) : self.logo_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_logo_small_url
|
||||
upload = self.logo_small
|
||||
upload ? full_cdn_url(upload.url) : self.logo_small_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_digest_logo_url
|
||||
upload = self.digest_logo
|
||||
upload ? full_cdn_url(upload.url) : self.digest_logo_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_mobile_logo_url
|
||||
upload = self.mobile_logo
|
||||
upload ? full_cdn_url(upload.url) : self.mobile_logo_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_large_icon_url
|
||||
upload = self.large_icon
|
||||
upload ? full_cdn_url(upload.url) : self.large_icon_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_favicon_url
|
||||
upload = self.favicon
|
||||
upload ? full_cdn_url(upload.url) : self.favicon_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_apple_touch_icon_url
|
||||
upload = self.apple_touch_icon
|
||||
upload ? full_cdn_url(upload.url) : self.apple_touch_icon_url(warn: false)
|
||||
end
|
||||
|
||||
def self.opengraph_image_url
|
||||
upload = self.opengraph_image
|
||||
upload ? full_cdn_url(upload.url) : self.default_opengraph_image_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_twitter_summary_large_image_url
|
||||
self.twitter_summary_large_image&.url ||
|
||||
self.twitter_summary_large_image_url(warn: false)
|
||||
end
|
||||
|
||||
def self.site_push_notifications_icon_url
|
||||
SiteSetting.push_notifications_icon&.url ||
|
||||
SiteSetting.push_notifications_icon_url(warn: false)
|
||||
end
|
||||
|
||||
def self.shared_drafts_enabled?
|
||||
c = SiteSetting.shared_drafts_category
|
||||
c.present? && c.to_i != SiteSetting.uncategorized_category_id.to_i
|
||||
|
||||
@ -32,7 +32,8 @@ class SkippedEmailLog < ActiveRecord::Base
|
||||
sender_message_to_blank: 17,
|
||||
sender_text_part_body_blank: 18,
|
||||
sender_body_blank: 19,
|
||||
sender_post_deleted: 20
|
||||
sender_post_deleted: 20,
|
||||
sender_message_to_invalid: 21
|
||||
# you need to add the reason in server.en.yml below the "skipped_email_log" key
|
||||
# when you add a new enum value
|
||||
)
|
||||
|
||||
@ -12,6 +12,8 @@ class TagGroup < ActiveRecord::Base
|
||||
before_create :init_permissions
|
||||
before_save :apply_permissions
|
||||
|
||||
after_commit { DiscourseTagging.clear_cache! }
|
||||
|
||||
attr_accessor :permissions
|
||||
|
||||
def tag_names=(tag_names_arg)
|
||||
|
||||
@ -10,6 +10,7 @@ class Upload < ActiveRecord::Base
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
SHA1_LENGTH = 40
|
||||
SEEDED_ID_THRESHOLD = 0
|
||||
|
||||
belongs_to :user
|
||||
|
||||
@ -36,6 +37,8 @@ class Upload < ActiveRecord::Base
|
||||
UserAvatar.where(custom_upload_id: self.id).update_all(custom_upload_id: nil)
|
||||
end
|
||||
|
||||
scope :by_users, -> { where("uploads.id > ?", SEEDED_ID_THRESHOLD) }
|
||||
|
||||
def to_s
|
||||
self.url
|
||||
end
|
||||
|
||||
@ -294,6 +294,10 @@ class User < ActiveRecord::Base
|
||||
self.id > 0
|
||||
end
|
||||
|
||||
def bot?
|
||||
!self.human?
|
||||
end
|
||||
|
||||
def effective_locale
|
||||
if SiteSetting.allow_user_locale && self.locale.present?
|
||||
self.locale
|
||||
@ -401,22 +405,18 @@ class User < ActiveRecord::Base
|
||||
end
|
||||
|
||||
# Approve this user
|
||||
def approve(approved_by, send_mail = true)
|
||||
def approve(approver, send_mail = true)
|
||||
self.approved = true
|
||||
|
||||
if approved_by.is_a?(Integer)
|
||||
self.approved_by_id = approved_by
|
||||
else
|
||||
self.approved_by = approved_by
|
||||
end
|
||||
|
||||
self.approved_at = Time.zone.now
|
||||
self.approved_by = approver
|
||||
|
||||
if result = save
|
||||
send_approval_email if send_mail
|
||||
DiscourseEvent.trigger(:user_approved, self)
|
||||
end
|
||||
|
||||
StaffActionLogger.new(approver).log_user_approve(self)
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
|
||||
@ -2,7 +2,8 @@ class UserField < ActiveRecord::Base
|
||||
|
||||
include AnonCacheInvalidator
|
||||
|
||||
validates_presence_of :name, :description, :field_type
|
||||
validates_presence_of :description, :field_type
|
||||
validates_presence_of :name, unless: -> { field_type == "confirm" }
|
||||
has_many :user_field_options, dependent: :destroy
|
||||
accepts_nested_attributes_for :user_field_options
|
||||
|
||||
|
||||
@ -84,7 +84,8 @@ class UserHistory < ActiveRecord::Base
|
||||
merge_user: 65,
|
||||
entity_export: 66,
|
||||
change_password: 67,
|
||||
topic_timestamps_changed: 68
|
||||
topic_timestamps_changed: 68,
|
||||
approve_user: 69
|
||||
)
|
||||
end
|
||||
|
||||
@ -147,7 +148,8 @@ class UserHistory < ActiveRecord::Base
|
||||
:merge_user,
|
||||
:entity_export,
|
||||
:change_name,
|
||||
:topic_timestamps_changed
|
||||
:topic_timestamps_changed,
|
||||
:approve_user
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@ -8,8 +8,6 @@ class UserProfile < ActiveRecord::Base
|
||||
validates :user, presence: true
|
||||
before_save :cook
|
||||
after_save :trigger_badges
|
||||
after_commit :trigger_profile_created_event, on: :create
|
||||
after_commit :trigger_profile_updated_event, on: :update
|
||||
|
||||
validates :profile_background, upload_url: true, if: :profile_background_changed?
|
||||
validates :card_background, upload_url: true, if: :card_background_changed?
|
||||
@ -108,14 +106,6 @@ class UserProfile < ActiveRecord::Base
|
||||
tempfile.close! if tempfile && tempfile.respond_to?(:close!)
|
||||
end
|
||||
|
||||
def trigger_profile_created_event
|
||||
DiscourseEvent.trigger(:user_profile_created, self)
|
||||
end
|
||||
|
||||
def trigger_profile_updated_event
|
||||
DiscourseEvent.trigger(:user_profile_updated, self)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def trigger_badges
|
||||
|
||||
@ -370,7 +370,9 @@ class PostSerializer < BasicPostSerializer
|
||||
end
|
||||
|
||||
def include_post_notice_type?
|
||||
return false if scope.user&.id == object.user_id || !scope.user&.has_trust_level?(TrustLevel[2])
|
||||
return false if !scope.user || !scope.user.id || scope.user.id == object.user_id ||
|
||||
!object.user || object.user.anonymous? ||
|
||||
!scope.user.has_trust_level?(SiteSetting.min_post_notice_tl)
|
||||
|
||||
post_notice_type.present?
|
||||
end
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
class TagSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :topic_count
|
||||
attributes :id, :name, :topic_count, :staff
|
||||
|
||||
def staff
|
||||
DiscourseTagging.staff_tag_names.include?(name)
|
||||
end
|
||||
end
|
||||
|
||||
@ -13,7 +13,7 @@ class PostAlerter
|
||||
|
||||
def not_allowed?(user, post)
|
||||
user.blank? ||
|
||||
user.id < 0 ||
|
||||
user.bot? ||
|
||||
user.id == post.user_id
|
||||
end
|
||||
|
||||
@ -279,8 +279,7 @@ class PostAlerter
|
||||
|
||||
DiscourseEvent.trigger(:before_create_notification, user, type, post, opts)
|
||||
|
||||
return if user.blank?
|
||||
return if user.id < 0
|
||||
return if user.blank? || user.bot?
|
||||
|
||||
is_liked = type == Notification.types[:liked]
|
||||
return if is_liked && user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:never]
|
||||
|
||||
@ -67,8 +67,8 @@ class PushNotificationPusher
|
||||
protected
|
||||
|
||||
def self.get_badge
|
||||
if SiteSetting.site_push_notifications_icon_url.present?
|
||||
SiteSetting.site_push_notifications_icon_url
|
||||
if (url = SiteSetting.site_push_notifications_icon_url).present?
|
||||
url
|
||||
else
|
||||
ActionController::Base.helpers.image_url("push-notifications/discourse.png")
|
||||
end
|
||||
|
||||
@ -503,6 +503,13 @@ class StaffActionLogger
|
||||
))
|
||||
end
|
||||
|
||||
def log_user_approve(user, opts = {})
|
||||
UserHistory.create!(params(opts).merge(
|
||||
action: UserHistory.actions[:approve_user],
|
||||
target_user_id: user.id
|
||||
))
|
||||
end
|
||||
|
||||
def log_user_deactivate(user, reason, opts = {})
|
||||
raise Discourse::InvalidParameters.new(:user) unless user
|
||||
UserHistory.create!(params(opts).merge(
|
||||
|
||||
@ -1927,21 +1927,36 @@ en:
|
||||
|
||||
actions:
|
||||
recover: "Un-Delete Topic"
|
||||
recover_tooltip: "Restores the topic."
|
||||
delete: "Delete Topic"
|
||||
delete_tooltip: "Marks the topic as deleted."
|
||||
open: "Open Topic"
|
||||
open_tooltip: "Marks the topic as open and allows new replies."
|
||||
close: "Close Topic"
|
||||
close_tooltip: "Marks the topic as closed and prevents new replies."
|
||||
multi_select: "Select Posts…"
|
||||
multi_select_tooltip: "Toggles multiselect mode in which multiple posts can be selected."
|
||||
timed_update: "Set Topic Timer..."
|
||||
timed_update_tooltip: "Opens a new window in which a timer for different actions can be set."
|
||||
pin: "Pin Topic…"
|
||||
pin_tooltip: "Opens a window in which the topic can be featured in different ways."
|
||||
unpin: "Un-Pin Topic…"
|
||||
unpin_tooltip: "Unpins the topic and it will no longer be featured."
|
||||
unarchive: "Unarchive Topic"
|
||||
unarchive_tooltip: "Unfreezes the topic and allows interacting with it."
|
||||
archive: "Archive Topic"
|
||||
archive_tooltip: "Freezes the topic and prevents interacting with it."
|
||||
invisible: "Make Unlisted"
|
||||
invisible_tooltip: "Prevents the inclusion of the topic in lists and digest emails."
|
||||
visible: "Make Listed"
|
||||
visible_tooltip: "Marks the topic as visible and includes the topic in lists and digest emails."
|
||||
reset_read: "Reset Read Data"
|
||||
make_public: "Make Public Topic"
|
||||
make_public_tooltip: "Converts to public topic and makes it visible for other users."
|
||||
make_private: "Make Personal Message"
|
||||
make_private_tooltip: "Converts to personal message and makes it invisible for other users."
|
||||
reset_bump_date: "Reset Bump Date"
|
||||
reset_bump_date_tooltip: "Puts the topic back into chronological order according to when it was originally created."
|
||||
|
||||
feature:
|
||||
pin: "Pin Topic"
|
||||
@ -2099,6 +2114,7 @@ en:
|
||||
|
||||
change_timestamp:
|
||||
title: "Change Timestamp..."
|
||||
tooltip: "Opens a window in which the timestamp can be changed."
|
||||
action: "change timestamp"
|
||||
invalid_timestamp: "Timestamp cannot be in the future."
|
||||
error: "There was an error changing the timestamp of the topic."
|
||||
@ -2974,6 +2990,7 @@ en:
|
||||
old_posts: "Old Flagged Posts"
|
||||
topics: "Flagged Topics"
|
||||
moderation_history: "Moderation History"
|
||||
moderation_history_tooltip: "Shows the moderation history."
|
||||
|
||||
agree: "Agree"
|
||||
agree_title: "Confirm this flag as valid and correct"
|
||||
@ -3689,6 +3706,7 @@ en:
|
||||
entity_export: "export entity"
|
||||
change_name: "change name"
|
||||
topic_timestamps_changed: "topic timestamps changed"
|
||||
approve_user: "approved user"
|
||||
screened_emails:
|
||||
title: "Screened Emails"
|
||||
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
|
||||
|
||||
@ -1227,6 +1227,12 @@ en:
|
||||
author: Author
|
||||
filesize: File size
|
||||
description: "List all uploads by extension, filesize and author."
|
||||
top_ignored_users:
|
||||
title: "Top Ignored Users"
|
||||
labels:
|
||||
ignored_user: Ignored User
|
||||
ignores_count: Ignores count
|
||||
description: "List top ignored users."
|
||||
|
||||
dashboard:
|
||||
rails_env_warning: "Your server is running in %{env} mode."
|
||||
@ -1400,6 +1406,7 @@ en:
|
||||
post_menu_hidden_items: "The menu items to hide by default in the post menu unless an expansion ellipsis is clicked on."
|
||||
share_links: "Determine which items appear on the share dialog, and in what order."
|
||||
site_contact_username: "A valid staff username to send all automated messages from. If left blank the default System account will be used."
|
||||
site_contact_group_name: "A valid group name to be invited to all automated messages."
|
||||
send_welcome_message: "Send all new users a welcome message with a quick start guide."
|
||||
send_tl1_welcome_message: "Send new trust level 1 users a welcome message."
|
||||
suppress_reply_directly_below: "Don't show the expandable reply count on a post when there is only a single reply directly below this post."
|
||||
@ -1870,6 +1877,7 @@ en:
|
||||
|
||||
embed_truncate: "Truncate the embedded posts."
|
||||
embed_support_markdown: "Support Markdown formatting for embedded posts."
|
||||
embed_whitelist_selector: "A comma separated list of CSS elements that are allowed in embeds."
|
||||
allowed_href_schemes: "Schemes allowed in links in addition to http and https."
|
||||
embed_post_limit: "Maximum number of posts to embed."
|
||||
embed_username_required: "The username for topic creation is required."
|
||||
@ -1903,6 +1911,7 @@ en:
|
||||
max_allowed_message_recipients: "Maximum recipients allowed in a message."
|
||||
watched_words_regular_expressions: "Watched words are regular expressions."
|
||||
|
||||
min_post_notice_tl: "Minimum trust level required to see post notices."
|
||||
returning_users_days: "How many days should pass before a user is considered to be returning."
|
||||
|
||||
default_email_digest_frequency: "How often users receive summary emails by default."
|
||||
@ -1979,6 +1988,7 @@ en:
|
||||
errors:
|
||||
invalid_email: "Invalid email address."
|
||||
invalid_username: "There's no user with that username."
|
||||
invalid_group: "There's no group with that name."
|
||||
invalid_integer_min_max: "Value must be between %{min} and %{max}."
|
||||
invalid_integer_min: "Value must be %{min} or greater."
|
||||
invalid_integer_max: "Value cannot be higher than %{max}."
|
||||
@ -3455,6 +3465,7 @@ en:
|
||||
sender_text_part_body_blank: "text_part.body is blank"
|
||||
sender_body_blank: "body is blank"
|
||||
sender_post_deleted: "post has been deleted"
|
||||
sender_message_to_invalid: "recipient has invalid email address"
|
||||
|
||||
color_schemes:
|
||||
base_theme_name: "Base"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Available options:
|
||||
#
|
||||
# default - The default value of the setting.
|
||||
# default - The default value of the setting. For upload site settings, use the id of the upload seeded in db/fixtures/010_uploads.rb.
|
||||
# client - Set to true if the javascript should have access to this setting's value.
|
||||
# refresh - Set to true if clients should refresh when the setting is changed.
|
||||
# min - For a string setting, the minimum length. For an integer setting, the minimum value.
|
||||
@ -47,15 +47,18 @@ required:
|
||||
site_contact_username:
|
||||
default: ""
|
||||
type: username
|
||||
logo:
|
||||
site_contact_group_name:
|
||||
default: ""
|
||||
type: group
|
||||
logo:
|
||||
default: -1
|
||||
client: true
|
||||
type: upload
|
||||
logo_url:
|
||||
hidden: true
|
||||
default: "/images/d-logo-sketch.png"
|
||||
logo_small:
|
||||
default: ""
|
||||
default: -2
|
||||
client: true
|
||||
type: upload
|
||||
logo_small_url:
|
||||
@ -83,14 +86,14 @@ required:
|
||||
hidden: true
|
||||
default: ""
|
||||
favicon:
|
||||
default: ""
|
||||
default: -3
|
||||
client: true
|
||||
type: upload
|
||||
favicon_url:
|
||||
hidden: true
|
||||
default: "/images/default-favicon.ico"
|
||||
apple_touch_icon:
|
||||
default: ""
|
||||
default: -4
|
||||
client: true
|
||||
type: upload
|
||||
apple_touch_icon_url:
|
||||
@ -691,7 +694,13 @@ posting:
|
||||
max_users_notified_per_group_mention: 100
|
||||
newuser_max_replies_per_topic: 3
|
||||
newuser_max_mentions_per_post: 2
|
||||
title_max_word_length: 30
|
||||
title_max_word_length:
|
||||
default: 30
|
||||
locale_default:
|
||||
ja: 50
|
||||
ko: 50
|
||||
zh_CN: 50
|
||||
zh_TW: 50
|
||||
whitelisted_link_domains:
|
||||
default: ""
|
||||
type: list
|
||||
@ -790,6 +799,8 @@ posting:
|
||||
default: true
|
||||
embed_support_markdown:
|
||||
default: false
|
||||
embed_whitelist_selector:
|
||||
default: ""
|
||||
allowed_href_schemes:
|
||||
client: true
|
||||
default: ""
|
||||
@ -806,6 +817,9 @@ posting:
|
||||
default: false
|
||||
client: true
|
||||
shadowed_by_global: true
|
||||
min_post_notice_tl:
|
||||
default: 2
|
||||
enum: "TrustLevelSetting"
|
||||
returning_users_days:
|
||||
default: 60
|
||||
|
||||
@ -1473,9 +1487,6 @@ embedding:
|
||||
embed_title_scrubber:
|
||||
default: ""
|
||||
hidden: true
|
||||
embed_whitelist_selector:
|
||||
default: ""
|
||||
hidden: true
|
||||
embed_blacklist_selector:
|
||||
default: ""
|
||||
hidden: true
|
||||
|
||||
17
db/fixtures/010_uploads.rb
Normal file
17
db/fixtures/010_uploads.rb
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
-1 => "d-logo-sketch.png",
|
||||
-2 => "d-logo-sketch-small.png",
|
||||
-3 => "default-favicon.ico",
|
||||
-4 => "default-apple-touch-icon.png"
|
||||
}.each do |id, filename|
|
||||
path = Rails.root.join("public/images/#{filename}")
|
||||
|
||||
Upload.seed do |upload|
|
||||
upload.id = id
|
||||
upload.user_id = Discourse.system_user.id
|
||||
upload.original_filename = filename
|
||||
upload.url = "/images/#{filename}"
|
||||
upload.filesize = File.size(path)
|
||||
upload.extension = File.extname(path)[1..10]
|
||||
end
|
||||
end
|
||||
@ -45,7 +45,7 @@ class Auth::DefaultCurrentUserProvider
|
||||
request = @request
|
||||
|
||||
user_api_key = @env[USER_API_KEY]
|
||||
api_key = @env.blank? ? nil : request[API_KEY] || @env[HEADER_API_KEY]
|
||||
api_key = @env.blank? ? nil : @env[HEADER_API_KEY] || request[API_KEY]
|
||||
|
||||
auth_token = request.cookies[TOKEN_COOKIE] unless user_api_key || api_key
|
||||
|
||||
@ -284,7 +284,7 @@ class Auth::DefaultCurrentUserProvider
|
||||
|
||||
def lookup_api_user(api_key_value, request)
|
||||
if api_key = ApiKey.where(key: api_key_value).includes(:user).first
|
||||
api_username = request[API_USERNAME] || @env[HEADER_API_USERNAME]
|
||||
api_username = header_api_key? ? @env[HEADER_API_USERNAME] : request[API_USERNAME]
|
||||
|
||||
if api_key.allowed_ips.present? && !api_key.allowed_ips.any? { |ip| ip.include?(request.ip) }
|
||||
Rails.logger.warn("[Unauthorized API Access] username: #{api_username}, IP address: #{request.ip}")
|
||||
@ -295,9 +295,9 @@ class Auth::DefaultCurrentUserProvider
|
||||
api_key.user if !api_username || (api_key.user.username_lower == api_username.downcase)
|
||||
elsif api_username
|
||||
User.find_by(username_lower: api_username.downcase)
|
||||
elsif user_id = request["api_user_id"] || @env[HEADER_API_USER_ID]
|
||||
elsif user_id = header_api_key? ? @env[HEADER_API_USER_ID] : request["api_user_id"]
|
||||
User.find_by(id: user_id.to_i)
|
||||
elsif external_id = request["api_user_external_id"] || @env[HEADER_API_USER_EXTERNAL_ID]
|
||||
elsif external_id = header_api_key? ? @env[HEADER_API_USER_EXTERNAL_ID] : request["api_user_external_id"]
|
||||
SingleSignOnRecord.find_by(external_id: external_id.to_s).try(:user)
|
||||
end
|
||||
end
|
||||
@ -305,6 +305,10 @@ class Auth::DefaultCurrentUserProvider
|
||||
|
||||
private
|
||||
|
||||
def header_api_key?
|
||||
!!@env[HEADER_API_KEY]
|
||||
end
|
||||
|
||||
def rate_limit_admin_api_requests(api_key)
|
||||
return if Rails.env == "profile"
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ module DiscourseTagging
|
||||
|
||||
TAGS_FIELD_NAME = "tags"
|
||||
TAGS_FILTER_REGEXP = /[\/\?#\[\]@!\$&'\(\)\*\+,;=\.%\\`^\s|\{\}"<>]+/ # /?#[]@!$&'()*+,;=.%\`^|{}"<>
|
||||
TAGS_STAFF_CACHE_KEY = "staff_tag_names"
|
||||
|
||||
def self.tag_topic_by_names(topic, guardian, tag_names_arg, append: false)
|
||||
if guardian.can_tag?(topic)
|
||||
@ -194,11 +195,22 @@ module DiscourseTagging
|
||||
end
|
||||
|
||||
def self.staff_tag_names
|
||||
Tag.joins(tag_groups: :tag_group_permissions)
|
||||
.where('tag_group_permissions.group_id = ? AND tag_group_permissions.permission_type = ?',
|
||||
Group::AUTO_GROUPS[:everyone],
|
||||
TagGroupPermission.permission_types[:readonly]
|
||||
).pluck(:name)
|
||||
tag_names = Discourse.cache.read(TAGS_STAFF_CACHE_KEY, tag_names)
|
||||
|
||||
if !tag_names
|
||||
tag_names = Tag.joins(tag_groups: :tag_group_permissions)
|
||||
.where('tag_group_permissions.group_id = ? AND tag_group_permissions.permission_type = ?',
|
||||
Group::AUTO_GROUPS[:everyone],
|
||||
TagGroupPermission.permission_types[:readonly]
|
||||
).pluck(:name)
|
||||
Discourse.cache.write(TAGS_STAFF_CACHE_KEY, tag_names, expires_in: 1.hour)
|
||||
end
|
||||
|
||||
tag_names
|
||||
end
|
||||
|
||||
def self.clear_cache!
|
||||
Discourse.cache.delete(TAGS_STAFF_CACHE_KEY)
|
||||
end
|
||||
|
||||
def self.clean_tag(tag)
|
||||
|
||||
@ -21,8 +21,13 @@ module Email
|
||||
@user = user
|
||||
end
|
||||
|
||||
def send
|
||||
return if SiteSetting.disable_emails == "yes" && @email_type.to_s != "admin_login"
|
||||
def send(is_critical: false)
|
||||
if SiteSetting.disable_emails == "yes" &&
|
||||
@email_type.to_s != "admin_login" &&
|
||||
!is_critical
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
return if ActionMailer::Base::NullMail === @message
|
||||
return if ActionMailer::Base::NullMail === (@message.message rescue nil)
|
||||
@ -30,6 +35,8 @@ module Email
|
||||
return skip(SkippedEmailLog.reason_types[:sender_message_blank]) if @message.blank?
|
||||
return skip(SkippedEmailLog.reason_types[:sender_message_to_blank]) if @message.to.blank?
|
||||
|
||||
return skip(SkippedEmailLog.reason_types[:sender_message_to_invalid]) if to_address.end_with?(".invalid")
|
||||
|
||||
if @message.text_part
|
||||
if @message.text_part.body.to_s.blank?
|
||||
return skip(SkippedEmailLog.reason_types[:sender_text_part_body_blank])
|
||||
|
||||
@ -92,10 +92,6 @@ module FileStore
|
||||
def purge_tombstone(grace_period)
|
||||
end
|
||||
|
||||
def get_depth_for(id)
|
||||
[0, Math.log(id / 1_000.0, 16).ceil].max
|
||||
end
|
||||
|
||||
def get_path_for(type, id, sha, extension)
|
||||
depth = get_depth_for(id)
|
||||
tree = File.join(*sha[0, depth].chars, "")
|
||||
@ -148,6 +144,12 @@ module FileStore
|
||||
raise "Not implemented."
|
||||
end
|
||||
|
||||
def get_depth_for(id)
|
||||
depths = [0]
|
||||
depths << Math.log(id / 1_000.0, 16).ceil if id.positive?
|
||||
depths.max
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -129,7 +129,7 @@ module FileStore
|
||||
S3Inventory.new(s3_helper, :upload).backfill_etags_and_list_missing
|
||||
S3Inventory.new(s3_helper, :optimized).backfill_etags_and_list_missing unless skip_optimized
|
||||
else
|
||||
list_missing(Upload, "original/")
|
||||
list_missing(Upload.by_users, "original/")
|
||||
list_missing(OptimizedImage, "optimized/") unless skip_optimized
|
||||
end
|
||||
end
|
||||
|
||||
@ -2,7 +2,7 @@ require 'rails_helper'
|
||||
|
||||
describe <%= name %>::ActionsController do
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
end
|
||||
|
||||
it 'can list' do
|
||||
|
||||
@ -514,7 +514,7 @@ class PostCreator
|
||||
end
|
||||
|
||||
def create_post_notice
|
||||
return if @user.id < 0 || @user.staged
|
||||
return if @user.bot? || @user.staged
|
||||
|
||||
last_post_time = Post.where(user_id: @user.id)
|
||||
.order(created_at: :desc)
|
||||
|
||||
@ -83,7 +83,14 @@ class PostRevisor
|
||||
tc.check_result(false)
|
||||
next
|
||||
end
|
||||
tc.record_change('tags', prev_tags, tags) unless prev_tags.sort == tags.sort
|
||||
if prev_tags.sort != tags.sort
|
||||
tc.record_change('tags', prev_tags, tags)
|
||||
DB.after_commit do
|
||||
post = tc.topic.ordered_posts.first
|
||||
notified_user_ids = [post.user_id, post.last_editor_id].uniq
|
||||
Jobs.enqueue(:notify_tag_change, post_id: post.id, notified_user_ids: notified_user_ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ class S3Inventory
|
||||
WHERE #{model.table_name}.etag IS NULL
|
||||
AND url ILIKE '%' || #{table_name}.key")
|
||||
|
||||
uploads = (model == Upload) ? model.where("created_at < ?", inventory_date) : model
|
||||
uploads = (model == Upload) ? model.by_users.where("created_at < ?", inventory_date) : model
|
||||
missing_uploads = uploads.joins("LEFT JOIN #{table_name} ON #{table_name}.etag = #{model.table_name}.etag").where("#{table_name}.etag is NULL")
|
||||
|
||||
if (missing_count = missing_uploads.count) > 0
|
||||
|
||||
@ -394,22 +394,33 @@ class Search
|
||||
|
||||
exact = true
|
||||
|
||||
slug = match.to_s.split(":")
|
||||
next if slug.empty?
|
||||
category_slug, subcategory_slug = match.to_s.split(":")
|
||||
next unless category_slug
|
||||
|
||||
if slug[1]
|
||||
if subcategory_slug
|
||||
# sub category
|
||||
parent_category_id = Category.where(slug: slug[0].downcase, parent_category_id: nil).pluck(:id).first
|
||||
category_id = Category.where(slug: slug[1].downcase, parent_category_id: parent_category_id).pluck(:id).first
|
||||
parent_category_id = Category
|
||||
.where(
|
||||
"lower(slug) = ? AND parent_category_id IS NULL", category_slug.downcase
|
||||
)
|
||||
.pluck(:id)
|
||||
.first
|
||||
|
||||
category_id = Category
|
||||
.where("lower(slug) = ? AND parent_category_id = ?",
|
||||
subcategory_slug.downcase, parent_category_id
|
||||
)
|
||||
.pluck(:id)
|
||||
.first
|
||||
else
|
||||
# main category
|
||||
if slug[0][0] == "="
|
||||
slug[0] = slug[0][1..-1]
|
||||
if category_slug[0] == "="
|
||||
category_slug = category_slug[1..-1]
|
||||
else
|
||||
exact = false
|
||||
end
|
||||
|
||||
category_id = Category.where(slug: slug[0].downcase)
|
||||
category_id = Category.where("lower(slug) = ?", category_slug.downcase)
|
||||
.order('case when parent_category_id is null then 0 else 1 end')
|
||||
.pluck(:id)
|
||||
.first
|
||||
@ -425,7 +436,7 @@ class Search
|
||||
posts.where("topics.category_id IN (?)", category_ids)
|
||||
else
|
||||
# try a possible tag match
|
||||
tag_id = Tag.where_name(slug[0]).pluck(:id).first
|
||||
tag_id = Tag.where_name(category_slug).pluck(:id).first
|
||||
if (tag_id)
|
||||
posts.where("topics.id IN (
|
||||
SELECT DISTINCT(tt.topic_id)
|
||||
|
||||
@ -230,17 +230,25 @@ module SiteSettingExtension
|
||||
.map do |s, v|
|
||||
|
||||
value = send(s)
|
||||
type_hash = type_supervisor.type_hash(s)
|
||||
default = defaults.get(s, default_locale).to_s
|
||||
|
||||
if type_hash[:type].to_s == "upload" &&
|
||||
default.to_i < Upload::SEEDED_ID_THRESHOLD
|
||||
|
||||
default = default_uploads[default.to_i]
|
||||
end
|
||||
|
||||
opts = {
|
||||
setting: s,
|
||||
description: description(s),
|
||||
default: defaults.get(s, default_locale).to_s,
|
||||
default: default,
|
||||
value: value.to_s,
|
||||
category: categories[s],
|
||||
preview: previews[s],
|
||||
secret: secret_settings.include?(s),
|
||||
placeholder: placeholder(s)
|
||||
}.merge(type_supervisor.type_hash(s))
|
||||
}.merge!(type_hash)
|
||||
|
||||
opts
|
||||
end.unshift(locale_setting_hash)
|
||||
@ -450,7 +458,7 @@ module SiteSettingExtension
|
||||
|
||||
value = value.to_i
|
||||
|
||||
if value > 0
|
||||
if value != Upload::SEEDED_ID_THRESHOLD
|
||||
upload = Upload.find_by(id: value)
|
||||
uploads[name] = upload if upload
|
||||
end
|
||||
@ -495,6 +503,14 @@ module SiteSettingExtension
|
||||
|
||||
private
|
||||
|
||||
def default_uploads
|
||||
@default_uploads ||= {}
|
||||
|
||||
@default_uploads[provider.current_site] ||= begin
|
||||
Upload.where("id < ?", Upload::SEEDED_ID_THRESHOLD).pluck(:id, :url).to_h
|
||||
end
|
||||
end
|
||||
|
||||
def uploads
|
||||
@uploads ||= {}
|
||||
@uploads[provider.current_site] ||= {}
|
||||
|
||||
@ -32,6 +32,7 @@ class SiteSettings::TypeSupervisor
|
||||
category: 16,
|
||||
uploaded_image_list: 17,
|
||||
upload: 18,
|
||||
group: 19,
|
||||
)
|
||||
end
|
||||
|
||||
@ -177,7 +178,7 @@ class SiteSettings::TypeSupervisor
|
||||
elsif type == self.class.types[:enum]
|
||||
val = @defaults_provider[name].is_a?(Integer) ? val.to_i : val.to_s
|
||||
elsif type == self.class.types[:upload] && val.present?
|
||||
val = val.id
|
||||
val = val.is_a?(Integer) ? val : val.id
|
||||
end
|
||||
|
||||
[val, type]
|
||||
@ -239,6 +240,8 @@ class SiteSettings::TypeSupervisor
|
||||
EmailSettingValidator
|
||||
when self.class.types[:username]
|
||||
UsernameSettingValidator
|
||||
when self.class.types[:group]
|
||||
GroupSettingValidator
|
||||
when self.class.types[:integer]
|
||||
IntegerSettingValidator
|
||||
when self.class.types[:regex]
|
||||
|
||||
@ -28,6 +28,7 @@ class SystemMessage
|
||||
raw: raw,
|
||||
archetype: Archetype.private_message,
|
||||
target_usernames: @recipient.username,
|
||||
target_group_names: Group.exists?(name: SiteSetting.site_contact_group_name) ? SiteSetting.site_contact_group_name : nil,
|
||||
subtype: TopicSubtype.system_message,
|
||||
skip_validations: true)
|
||||
|
||||
|
||||
@ -118,6 +118,8 @@ task 'docker:test' do
|
||||
puts "travis_fold:start:ruby_tests" if ENV["TRAVIS"]
|
||||
unless ENV["SKIP_CORE"]
|
||||
params = []
|
||||
params << "--profile"
|
||||
params << "--fail-fast"
|
||||
if ENV["BISECT"]
|
||||
params << "--bisect"
|
||||
end
|
||||
|
||||
@ -7,7 +7,8 @@ task 'plugin:install_all_official' do
|
||||
'discourse-nginx-performance-report',
|
||||
'lazyYT',
|
||||
'poll',
|
||||
'discourse-calendar'
|
||||
'discourse-calendar',
|
||||
'discourse-chat-integration'
|
||||
])
|
||||
|
||||
map = {
|
||||
|
||||
14
lib/validators/group_setting_validator.rb
Normal file
14
lib/validators/group_setting_validator.rb
Normal file
@ -0,0 +1,14 @@
|
||||
class GroupSettingValidator
|
||||
|
||||
def initialize(opts = {})
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def valid_value?(val)
|
||||
val.blank? || Group.exists?(name: val)
|
||||
end
|
||||
|
||||
def error_message
|
||||
I18n.t('site_settings.errors.invalid_group')
|
||||
end
|
||||
end
|
||||
@ -7,7 +7,7 @@ module Discourse
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 0
|
||||
PRE = 'beta4'
|
||||
PRE = 'beta5'
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
||||
end
|
||||
|
||||
@ -3,7 +3,7 @@ require 'rails_helper'
|
||||
describe Post do
|
||||
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
end
|
||||
|
||||
describe '#local_dates' do
|
||||
|
||||
@ -22,7 +22,7 @@ RSpec.describe DiscourseNarrativeBot::AdvancedUserNarrative do
|
||||
let(:reset_trigger) { DiscourseNarrativeBot::TrackSelector.reset_trigger }
|
||||
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
SiteSetting.discourse_narrative_bot_enabled = true
|
||||
end
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ describe DiscourseNarrativeBot::NewUserNarrative do
|
||||
let(:reset_trigger) { DiscourseNarrativeBot::TrackSelector.reset_trigger }
|
||||
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
SiteSetting.discourse_narrative_bot_enabled = true
|
||||
end
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ describe DiscourseNarrativeBot::TrackSelector do
|
||||
end
|
||||
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
end
|
||||
|
||||
describe '#select' do
|
||||
|
||||
@ -27,7 +27,7 @@ describe "Discobot Certificate" do
|
||||
it 'should return the right text' do
|
||||
stub_request(:get, /letter_avatar_proxy/).to_return(status: 200)
|
||||
|
||||
stub_request(:get, "http://test.localhost//images/d-logo-sketch-small.png")
|
||||
stub_request(:get, SiteSetting.site_logo_small_url)
|
||||
.to_return(status: 200)
|
||||
|
||||
get '/discobot/certificate.svg', params: params
|
||||
|
||||
@ -13,7 +13,7 @@ describe User do
|
||||
end
|
||||
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
SiteSetting.discourse_narrative_bot_enabled = true
|
||||
end
|
||||
|
||||
|
||||
@ -92,6 +92,15 @@ describe Auth::DefaultCurrentUserProvider do
|
||||
expect(provider("/?api_key=hello&api_username=#{user.username.downcase}").current_user.id).to eq(user.id)
|
||||
end
|
||||
|
||||
it "raises for a mismatched api_key param and header username" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
params = { "HTTP_API_USERNAME" => user.username.downcase }
|
||||
expect {
|
||||
provider("/?api_key=hello", params).current_user
|
||||
}.to raise_error(Discourse::InvalidAccess)
|
||||
end
|
||||
|
||||
it "finds a user for a correct system api key with external id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
@ -99,12 +108,31 @@ describe Auth::DefaultCurrentUserProvider do
|
||||
expect(provider("/?api_key=hello&api_user_external_id=abc").current_user.id).to eq(user.id)
|
||||
end
|
||||
|
||||
it "raises for a mismatched api_key param and header external id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: '')
|
||||
params = { "HTTP_API_USER_EXTERNAL_ID" => "abc" }
|
||||
expect {
|
||||
provider("/?api_key=hello", params).current_user
|
||||
}.to raise_error(Discourse::InvalidAccess)
|
||||
end
|
||||
|
||||
it "finds a user for a correct system api key with id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
expect(provider("/?api_key=hello&api_user_id=#{user.id}").current_user.id).to eq(user.id)
|
||||
end
|
||||
|
||||
it "raises for a mismatched api_key param and header user id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
params = { "HTTP_API_USER_ID" => user.id }
|
||||
expect {
|
||||
provider("/?api_key=hello", params).current_user
|
||||
}.to raise_error(Discourse::InvalidAccess)
|
||||
end
|
||||
|
||||
context "rate limiting" do
|
||||
before do
|
||||
RateLimiter.enable
|
||||
@ -243,6 +271,15 @@ describe Auth::DefaultCurrentUserProvider do
|
||||
expect(provider("/", params).current_user.id).to eq(user.id)
|
||||
end
|
||||
|
||||
it "raises for a mismatched api_key header and param username" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
params = { "HTTP_API_KEY" => "hello" }
|
||||
expect {
|
||||
provider("/?api_username=#{user.username.downcase}", params).current_user
|
||||
}.to raise_error(Discourse::InvalidAccess)
|
||||
end
|
||||
|
||||
it "finds a user for a correct system api key with external id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
@ -251,6 +288,16 @@ describe Auth::DefaultCurrentUserProvider do
|
||||
expect(provider("/", params).current_user.id).to eq(user.id)
|
||||
end
|
||||
|
||||
it "raises for a mismatched api_key header and param external id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: '')
|
||||
params = { "HTTP_API_KEY" => "hello" }
|
||||
expect {
|
||||
provider("/?api_user_external_id=abc", params).current_user
|
||||
}.to raise_error(Discourse::InvalidAccess)
|
||||
end
|
||||
|
||||
it "finds a user for a correct system api key with id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
@ -258,6 +305,15 @@ describe Auth::DefaultCurrentUserProvider do
|
||||
expect(provider("/", params).current_user.id).to eq(user.id)
|
||||
end
|
||||
|
||||
it "raises for a mismatched api_key header and param user id" do
|
||||
user = Fabricate(:user)
|
||||
ApiKey.create!(key: "hello", created_by_id: -1)
|
||||
params = { "HTTP_API_KEY" => "hello" }
|
||||
expect {
|
||||
provider("/?api_user_id=#{user.id}", params).current_user
|
||||
}.to raise_error(Discourse::InvalidAccess)
|
||||
end
|
||||
|
||||
context "rate limiting" do
|
||||
before do
|
||||
RateLimiter.enable
|
||||
|
||||
@ -216,4 +216,29 @@ describe DiscourseTagging do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "staff_tag_names" do
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
|
||||
let(:staff_tag) { Fabricate(:tag) }
|
||||
let(:other_staff_tag) { Fabricate(:tag) }
|
||||
|
||||
let!(:staff_tag_group) {
|
||||
Fabricate(
|
||||
:tag_group,
|
||||
permissions: { "staff" => 1, "everyone" => 3 },
|
||||
tag_names: [staff_tag.name]
|
||||
)
|
||||
}
|
||||
|
||||
it "returns all staff tags" do
|
||||
expect(DiscourseTagging.staff_tag_names).to contain_exactly(staff_tag.name)
|
||||
|
||||
staff_tag_group.update(tag_names: [staff_tag.name, other_staff_tag.name])
|
||||
expect(DiscourseTagging.staff_tag_names).to contain_exactly(staff_tag.name, other_staff_tag.name)
|
||||
|
||||
staff_tag_group.update(tag_names: [other_staff_tag.name])
|
||||
expect(DiscourseTagging.staff_tag_names).to contain_exactly(other_staff_tag.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -58,6 +58,13 @@ describe Email::Sender do
|
||||
Email::Sender.new(message, :hello).send
|
||||
end
|
||||
|
||||
it "doesn't deliver when the to address uses the .invalid tld" do
|
||||
message = Mail::Message.new(body: 'hello', to: 'myemail@example.invalid')
|
||||
message.expects(:deliver_now).never
|
||||
expect { Email::Sender.new(message, :hello).send }.
|
||||
to change { SkippedEmailLog.where(reason_type: SkippedEmailLog.reason_types[:sender_message_to_invalid]).count }.by(1)
|
||||
end
|
||||
|
||||
it "doesn't deliver when the body is nil" do
|
||||
message = Mail::Message.new(to: 'eviltrout@test.domain')
|
||||
message.expects(:deliver_now).never
|
||||
|
||||
@ -18,6 +18,15 @@ RSpec.describe FileStore::BaseStore do
|
||||
.to eq('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when id is negative' do
|
||||
it 'should return the right depth' do
|
||||
upload.update!(id: -999)
|
||||
|
||||
expect(FileStore::BaseStore.new.get_path_for_upload(upload))
|
||||
.to eq('original/1X/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_path_for_optimized_image' do
|
||||
|
||||
@ -902,7 +902,7 @@ describe PostCreator do
|
||||
end
|
||||
|
||||
it 'can post to a group correctly' do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
|
||||
expect(post.topic.archetype).to eq(Archetype.private_message)
|
||||
expect(post.topic.topic_allowed_users.count).to eq(1)
|
||||
|
||||
@ -617,7 +617,7 @@ describe PostDestroyer do
|
||||
|
||||
context '@mentions' do
|
||||
it 'removes notifications when deleted' do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
user = Fabricate(:evil_trout)
|
||||
post = create_post(raw: 'Hello @eviltrout')
|
||||
expect {
|
||||
|
||||
@ -608,7 +608,7 @@ describe PostRevisor do
|
||||
let(:mentioned_user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
end
|
||||
|
||||
it "generates a notification for a mention" do
|
||||
|
||||
@ -61,6 +61,7 @@ describe "S3Inventory" do
|
||||
CSV.foreach(csv_filename, headers: false) do |row|
|
||||
Fabricate(:upload, etag: row[S3Inventory::CSV_ETAG_INDEX], created_at: 2.days.ago)
|
||||
end
|
||||
|
||||
upload = Fabricate(:upload, etag: "ETag", created_at: 1.days.ago)
|
||||
Fabricate(:upload, etag: "ETag2", created_at: Time.now)
|
||||
|
||||
@ -91,6 +92,6 @@ describe "S3Inventory" do
|
||||
expect { inventory.backfill_etags_and_list_missing }.to change { Upload.where(etag: nil).count }.by(-2)
|
||||
end
|
||||
|
||||
expect(Upload.order(:url).pluck(:url, :etag)).to eq(files)
|
||||
expect(Upload.by_users.order(:url).pluck(:url, :etag)).to eq(files)
|
||||
end
|
||||
end
|
||||
|
||||
@ -873,25 +873,28 @@ describe Search do
|
||||
|
||||
it 'supports category slug and tags' do
|
||||
# main category
|
||||
category = Fabricate(:category, name: 'category 24', slug: 'category-24')
|
||||
category = Fabricate(:category, name: 'category 24', slug: 'cateGory-24')
|
||||
topic = Fabricate(:topic, created_at: 3.months.ago, category: category)
|
||||
post = Fabricate(:post, raw: 'Sams first post', topic: topic)
|
||||
|
||||
expect(Search.execute('sams post #category-24').posts.length).to eq(1)
|
||||
expect(Search.execute('sams post #categoRy-24').posts.length).to eq(1)
|
||||
expect(Search.execute("sams post category:#{category.id}").posts.length).to eq(1)
|
||||
expect(Search.execute('sams post #category-25').posts.length).to eq(0)
|
||||
expect(Search.execute('sams post #categoRy-25').posts.length).to eq(0)
|
||||
|
||||
sub_category = Fabricate(:category, name: 'sub category', slug: 'sub-category', parent_category_id: category.id)
|
||||
second_topic = Fabricate(:topic, created_at: 3.months.ago, category: sub_category)
|
||||
Fabricate(:post, raw: 'sams second post', topic: second_topic)
|
||||
|
||||
expect(Search.execute("sams post category:category-24").posts.length).to eq(2)
|
||||
expect(Search.execute("sams post category:=category-24").posts.length).to eq(1)
|
||||
expect(Search.execute("sams post category:categoRY-24").posts.length).to eq(2)
|
||||
expect(Search.execute("sams post category:=cAtegory-24").posts.length).to eq(1)
|
||||
|
||||
expect(Search.execute("sams post #category-24").posts.length).to eq(2)
|
||||
expect(Search.execute("sams post #=category-24").posts.length).to eq(1)
|
||||
expect(Search.execute("sams post #sub-category").posts.length).to eq(1)
|
||||
|
||||
expect(Search.execute("sams post #categoRY-24:SUB-category").posts.length)
|
||||
.to eq(1)
|
||||
|
||||
# tags
|
||||
topic.tags = [Fabricate(:tag, name: 'alpha'), Fabricate(:tag, name: 'привет'), Fabricate(:tag, name: 'HeLlO')]
|
||||
expect(Search.execute('this is a test #alpha').posts.map(&:id)).to eq([post.id])
|
||||
|
||||
@ -711,6 +711,28 @@ describe SiteSettingExtension do
|
||||
|
||||
end
|
||||
|
||||
describe '.all_settings' do
|
||||
describe 'uploads settings' do
|
||||
it 'should return the right values' do
|
||||
system_upload = Fabricate(:upload, id: -999)
|
||||
settings.setting(:logo, system_upload.id, type: :upload)
|
||||
settings.refresh!
|
||||
setting = settings.all_settings.last
|
||||
|
||||
expect(setting[:value]).to eq(system_upload.url)
|
||||
expect(setting[:default]).to eq(system_upload.url)
|
||||
|
||||
upload = Fabricate(:upload)
|
||||
settings.logo = upload
|
||||
settings.refresh!
|
||||
setting = settings.all_settings.last
|
||||
|
||||
expect(setting[:value]).to eq(upload.url)
|
||||
expect(setting[:default]).to eq(system_upload.url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.client_settings_json_uncached' do
|
||||
it 'should return the right json value' do
|
||||
upload = Fabricate(:upload)
|
||||
|
||||
@ -190,6 +190,9 @@ describe SiteSettings::TypeSupervisor do
|
||||
|
||||
expect(settings.type_supervisor.to_db_value(:type_upload, upload))
|
||||
.to eq([upload.id, SiteSetting.types[:upload]])
|
||||
|
||||
expect(settings.type_supervisor.to_db_value(:type_upload, 1))
|
||||
.to eq([1, SiteSetting.types[:upload]])
|
||||
end
|
||||
|
||||
it 'returns enum value with string default' do
|
||||
|
||||
@ -6,17 +6,19 @@ describe SystemMessage do
|
||||
|
||||
context 'send' do
|
||||
|
||||
it 'should create a post correctly' do
|
||||
let(:admin) { Fabricate(:admin) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
admin = Fabricate(:admin)
|
||||
user = Fabricate(:user)
|
||||
before do
|
||||
SiteSetting.site_contact_username = admin.username
|
||||
end
|
||||
|
||||
it 'should create a post correctly' do
|
||||
system_message = SystemMessage.new(user)
|
||||
post = system_message.create(:welcome_invite)
|
||||
topic = post.topic
|
||||
|
||||
expect(post).to be_present
|
||||
expect(post).to be_valid
|
||||
expect(post.valid?).to eq(true)
|
||||
expect(topic).to be_private_message
|
||||
expect(topic).to be_valid
|
||||
expect(topic.subtype).to eq(TopicSubtype.system_message)
|
||||
@ -25,6 +27,18 @@ describe SystemMessage do
|
||||
|
||||
expect(UserArchivedMessage.where(user_id: admin.id, topic_id: topic.id).length).to eq(1)
|
||||
end
|
||||
|
||||
it 'should allow site_contact_group_name' do
|
||||
group = Fabricate(:group)
|
||||
SiteSetting.site_contact_group_name = group.name
|
||||
|
||||
post = SystemMessage.create(user, :welcome_invite)
|
||||
expect(post.topic.allowed_groups).to contain_exactly(group)
|
||||
|
||||
group.update!(name: "anewname")
|
||||
post = SystemMessage.create(user, :welcome_invite)
|
||||
expect(post.topic.allowed_groups).to contain_exactly()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
21
spec/components/validators/group_setting_validator_spec.rb
Normal file
21
spec/components/validators/group_setting_validator_spec.rb
Normal file
@ -0,0 +1,21 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe GroupSettingValidator do
|
||||
describe '#valid_value?' do
|
||||
subject(:validator) { described_class.new }
|
||||
|
||||
it "returns true for blank values" do
|
||||
expect(validator.valid_value?('')).to eq(true)
|
||||
expect(validator.valid_value?(nil)).to eq(true)
|
||||
end
|
||||
|
||||
it "returns true if value matches an existing group" do
|
||||
Fabricate(:group, name: "hello")
|
||||
expect(validator.valid_value?('hello')).to eq(true)
|
||||
end
|
||||
|
||||
it "returns false if value does not match a group" do
|
||||
expect(validator.valid_value?('notagroup')).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -274,7 +274,7 @@ describe ApplicationHelper do
|
||||
).to include("some-image.png")
|
||||
|
||||
expect(helper.crawlable_meta_data).to include(
|
||||
SiteSetting.opengraph_image_url
|
||||
SiteSetting.site_opengraph_image_url
|
||||
)
|
||||
|
||||
SiteSetting.opengraph_image = nil
|
||||
|
||||
@ -163,7 +163,7 @@ describe WatchedWord do
|
||||
end
|
||||
|
||||
it "flags on revisions" do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
post = Fabricate(:post, topic: Fabricate(:topic, user: tl2_user), user: tl2_user)
|
||||
expect {
|
||||
PostRevisor.new(post).revise!(post.user, { raw: "Want some #{flag_word.word} for cheap?" }, revised_at: post.updated_at + 10.seconds)
|
||||
|
||||
@ -50,6 +50,7 @@ describe Jobs::CleanUpUploads do
|
||||
SiteSetting.provider = SiteSettings::DbProvider.new(SiteSetting)
|
||||
SiteSetting.clean_orphan_uploads_grace_period_hours = 1
|
||||
|
||||
system_upload = fabricate_upload(id: -999)
|
||||
logo_upload = fabricate_upload
|
||||
logo_small_upload = fabricate_upload
|
||||
digest_logo_upload = fabricate_upload
|
||||
@ -84,7 +85,8 @@ describe Jobs::CleanUpUploads do
|
||||
opengraph_image_upload,
|
||||
twitter_summary_large_image_upload,
|
||||
favicon_upload,
|
||||
apple_touch_icon_upload
|
||||
apple_touch_icon_upload,
|
||||
system_upload
|
||||
].each { |record| expect(Upload.exists?(id: record.id)).to eq(true) }
|
||||
|
||||
fabricate_upload
|
||||
|
||||
@ -32,7 +32,7 @@ describe Jobs::PullHotlinkedImages do
|
||||
|
||||
describe '#execute' do
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
FastImage.expects(:size).returns([100, 100]).at_least_once
|
||||
end
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ describe Jobs::UserEmail do
|
||||
let(:staged) { Fabricate(:user, staged: true, last_seen_at: 11.minutes.ago) }
|
||||
let(:suspended) { Fabricate(:user, last_seen_at: 10.minutes.ago, suspended_at: 5.minutes.ago, suspended_till: 7.days.from_now) }
|
||||
let(:anonymous) { Fabricate(:anonymous, last_seen_at: 11.minutes.ago) }
|
||||
let(:mailer) { Mail::Message.new(to: user.email) }
|
||||
|
||||
it "raises an error when there is no user" do
|
||||
expect { Jobs::UserEmail.new.execute(type: :digest) }.to raise_error(Discourse::InvalidParameters)
|
||||
@ -26,13 +25,15 @@ describe Jobs::UserEmail do
|
||||
end
|
||||
|
||||
it "doesn't call the mailer when the user is missing" do
|
||||
UserNotifications.expects(:digest).never
|
||||
Jobs::UserEmail.new.execute(type: :digest, user_id: 1234)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "doesn't call the mailer when the user is staged" do
|
||||
UserNotifications.expects(:digest).never
|
||||
Jobs::UserEmail.new.execute(type: :digest, user_id: staged.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
context "bounce score" do
|
||||
@ -45,55 +46,48 @@ describe Jobs::UserEmail do
|
||||
|
||||
email_log = EmailLog.where(user_id: user.id).last
|
||||
expect(email_log.email_type).to eq("signup")
|
||||
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
user.email
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'to_address' do
|
||||
it 'overwrites a to_address when present' do
|
||||
UserNotifications.expects(:confirm_new_email).returns(mailer)
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id, to_address: 'jake@adventuretime.ooo')
|
||||
expect(mailer.to).to eq(['jake@adventuretime.ooo'])
|
||||
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
'jake@adventuretime.ooo'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "disable_emails setting" do
|
||||
it "sends when no" do
|
||||
SiteSetting.disable_emails = 'no'
|
||||
Email::Sender.any_instance.expects(:send).once
|
||||
Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
user.email
|
||||
)
|
||||
end
|
||||
|
||||
it "does not send an email when yes" do
|
||||
SiteSetting.disable_emails = 'yes'
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "sends when critical" do
|
||||
SiteSetting.disable_emails = 'yes'
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
Jobs::CriticalUserEmail.new.execute(type: :confirm_new_email, user_id: user.id)
|
||||
end
|
||||
end
|
||||
|
||||
context "recently seen" do
|
||||
let(:post) { Fabricate(:post, user: user) }
|
||||
|
||||
it "doesn't send an email to a user that's been recently seen" do
|
||||
user.update_column(:last_seen_at, 9.minutes.ago)
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
Jobs::UserEmail.new.execute(type: :user_replied, user_id: user.id, post_id: post.id)
|
||||
end
|
||||
|
||||
it "does send an email to a user that's been recently seen but has email_always set" do
|
||||
user.update_attributes(last_seen_at: 9.minutes.ago)
|
||||
user.user_option.update_attributes(email_always: true)
|
||||
PostTiming.create!(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id, msecs: 100)
|
||||
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
Jobs::UserEmail.new.execute(type: :user_replied, user_id: user.id, post_id: post.id)
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
user.email
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -145,49 +139,66 @@ describe Jobs::UserEmail do
|
||||
context 'args' do
|
||||
|
||||
it 'passes a token as an argument when a token is present' do
|
||||
UserNotifications.expects(:forgot_password).with(user, email_token: 'asdfasdf').returns(mailer)
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
Jobs::UserEmail.new.execute(type: :forgot_password, user_id: user.id, email_token: 'asdfasdf')
|
||||
|
||||
mail = ActionMailer::Base.deliveries.first
|
||||
|
||||
expect(mail.to).to contain_exactly(user.email)
|
||||
expect(mail.body).to include("asdfasdf")
|
||||
end
|
||||
|
||||
context "post" do
|
||||
let(:post) { Fabricate(:post, user: user) }
|
||||
|
||||
it 'passes a post as an argument when a post_id is present' do
|
||||
UserNotifications.expects(:user_private_message).with(user, post: post).returns(mailer)
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: user.id, post_id: post.id)
|
||||
end
|
||||
|
||||
it "doesn't send the email if you've seen the post" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
PostTiming.record_timing(topic_id: post.topic_id, user_id: user.id, post_number: post.post_number, msecs: 6666)
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: user.id, post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "doesn't send the email if the user deleted the post" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
post.update_column(:user_deleted, true)
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: user.id, post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "doesn't send the email if user of the post has been deleted" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
post.update_attributes!(user_id: nil)
|
||||
Jobs::UserEmail.new.execute(type: :user_replied, user_id: user.id, post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
context 'user is suspended' do
|
||||
it "doesn't send email for a pm from a regular user" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: suspended.id, post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "does send an email for a pm from a staff user" do
|
||||
pm_from_staff = Fabricate(:post, user: Fabricate(:moderator))
|
||||
pm_from_staff.topic.topic_allowed_users.create!(user_id: suspended.id)
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: suspended.id, post_id: pm_from_staff.id)
|
||||
|
||||
pm_notification = Fabricate(:notification,
|
||||
user: suspended,
|
||||
topic: pm_from_staff.topic,
|
||||
post_number: pm_from_staff.post_number,
|
||||
data: { original_post_id: pm_from_staff.id }.to_json
|
||||
)
|
||||
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_private_message,
|
||||
user_id: suspended.id,
|
||||
post_id: pm_from_staff.id,
|
||||
notification_id: pm_notification.id
|
||||
)
|
||||
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
suspended.email
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -195,15 +206,17 @@ describe Jobs::UserEmail do
|
||||
before { SiteSetting.allow_anonymous_posting = true }
|
||||
|
||||
it "doesn't send email for a pm from a regular user" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "doesn't send email for a pm from a staff user" do
|
||||
pm_from_staff = Fabricate(:post, user: Fabricate(:moderator))
|
||||
pm_from_staff.topic.topic_allowed_users.create!(user_id: anonymous.id)
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, post_id: pm_from_staff.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -244,17 +257,65 @@ describe Jobs::UserEmail do
|
||||
end
|
||||
|
||||
it "does send the email if the notification has been seen but the user is set for email_always" do
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
notification.update_column(:read, true)
|
||||
user.user_option.update_column(:email_always, true)
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id)
|
||||
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_mentioned,
|
||||
user_id: user.id,
|
||||
post_id: post.id,
|
||||
notification_id: notification.id
|
||||
)
|
||||
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
user.email
|
||||
)
|
||||
end
|
||||
|
||||
it "does send the email if the user is using daily mailing list mode" do
|
||||
Email::Sender.any_instance.expects(:send)
|
||||
user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 0)
|
||||
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id)
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_mentioned,
|
||||
user_id: user.id,
|
||||
post_id: post.id,
|
||||
notification_id: notification.id
|
||||
)
|
||||
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
user.email
|
||||
)
|
||||
end
|
||||
|
||||
context "recently seen" do
|
||||
it "doesn't send an email to a user that's been recently seen" do
|
||||
user.update!(last_seen_at: 9.minutes.ago)
|
||||
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_replied,
|
||||
user_id: user.id,
|
||||
post_id: post.id,
|
||||
notification_id: notification.id
|
||||
)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "does send an email to a user that's been recently seen but has email_always set" do
|
||||
user.update!(last_seen_at: 9.minutes.ago)
|
||||
user.user_option.update!(email_always: true)
|
||||
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_replied,
|
||||
user_id: user.id,
|
||||
post_id: post.id,
|
||||
notification_id: notification.id
|
||||
)
|
||||
|
||||
expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(
|
||||
user.email
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'max_emails_per_day_per_user limit is reached' do
|
||||
@ -361,7 +422,6 @@ describe Jobs::UserEmail do
|
||||
end
|
||||
|
||||
it "doesn't send the mail if the user is using individual mailing list mode" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1)
|
||||
# sometimes, we pass the notification_id
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
|
||||
@ -373,10 +433,11 @@ describe Jobs::UserEmail do
|
||||
post = Fabricate(:post)
|
||||
post.topic.destroy
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "doesn't send the mail if the user is using individual mailing list mode with no echo" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 2)
|
||||
# sometimes, we pass the notification_id
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
|
||||
@ -388,12 +449,15 @@ describe Jobs::UserEmail do
|
||||
post = Fabricate(:post)
|
||||
post.topic.destroy
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "doesn't send the email if the post has been user deleted" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
post.update_column(:user_deleted, true)
|
||||
Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
context 'user is suspended' do
|
||||
@ -450,8 +514,14 @@ describe Jobs::UserEmail do
|
||||
before { SiteSetting.allow_anonymous_posting = true }
|
||||
|
||||
it "doesn't send email for a pm from a regular user" do
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, notification_id: notification.id)
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_private_message,
|
||||
user_id: anonymous.id,
|
||||
post_id: post.id,
|
||||
notification_id: notification.id
|
||||
)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
|
||||
it "doesn't send email for a pm from staff" do
|
||||
@ -463,8 +533,14 @@ describe Jobs::UserEmail do
|
||||
post_number: pm_from_staff.post_number,
|
||||
data: { original_post_id: pm_from_staff.id }.to_json
|
||||
)
|
||||
Email::Sender.any_instance.expects(:send).never
|
||||
Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, notification_id: pm_notification.id)
|
||||
Jobs::UserEmail.new.execute(
|
||||
type: :user_private_message,
|
||||
user_id: anonymous.id,
|
||||
post_id: pm_from_staff.id,
|
||||
notification_id: pm_notification.id
|
||||
)
|
||||
|
||||
expect(ActionMailer::Base.deliveries).to eq([])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -31,7 +31,7 @@ RSpec.describe UploadRecovery do
|
||||
|
||||
before do
|
||||
SiteSetting.authorized_extensions = 'png|pdf'
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
end
|
||||
|
||||
after do
|
||||
|
||||
@ -68,7 +68,7 @@ describe CategoryUser do
|
||||
|
||||
context 'integration' do
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
NotificationEmailer.enable
|
||||
end
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ describe DiscourseSingleSignOn do
|
||||
SiteSetting.sso_url = @sso_url
|
||||
SiteSetting.enable_sso = true
|
||||
SiteSetting.sso_secret = @sso_secret
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
end
|
||||
|
||||
def make_sso
|
||||
|
||||
@ -18,7 +18,7 @@ describe Emoji do
|
||||
describe '.load_custom' do
|
||||
describe 'when a custom emoji has an invalid upload_id' do
|
||||
it 'should return the custom emoji without a URL' do
|
||||
CustomEmoji.create!(name: 'test', upload_id: -1)
|
||||
CustomEmoji.create!(name: 'test', upload_id: 9999)
|
||||
|
||||
emoji = Emoji.load_custom.first
|
||||
|
||||
|
||||
@ -1072,7 +1072,7 @@ describe PostAction do
|
||||
end
|
||||
|
||||
it "should create a notification in the related topic" do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
post = Fabricate(:post)
|
||||
user = Fabricate(:user)
|
||||
action = PostAction.act(user, post, PostActionType.types[:spam], message: "WAT")
|
||||
@ -1089,7 +1089,7 @@ describe PostAction do
|
||||
end
|
||||
|
||||
it "should not add a moderator post when post is flagged via private message" do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
post = Fabricate(:post)
|
||||
user = Fabricate(:user)
|
||||
action = PostAction.act(user, post, PostActionType.types[:notify_user], message: "WAT")
|
||||
|
||||
@ -41,7 +41,7 @@ describe PostMover do
|
||||
|
||||
before do
|
||||
SiteSetting.tagging_enabled = true
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
p1.replies << p3
|
||||
p2.replies << p4
|
||||
UserActionCreator.enable
|
||||
@ -570,7 +570,7 @@ describe PostMover do
|
||||
|
||||
before do
|
||||
SiteSetting.tagging_enabled = true
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
p1.replies << p3
|
||||
p2.replies << p4
|
||||
UserActionCreator.enable
|
||||
|
||||
@ -995,7 +995,7 @@ describe Post do
|
||||
end
|
||||
|
||||
before do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
end
|
||||
|
||||
describe 'when user can not mention a group' do
|
||||
|
||||
@ -2,7 +2,7 @@ require 'rails_helper'
|
||||
|
||||
describe QuotedPost do
|
||||
it 'correctly extracts quotes' do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
|
||||
topic = Fabricate(:topic)
|
||||
post1 = create_post(topic: topic, post_number: 1, raw: "foo bar")
|
||||
@ -34,7 +34,7 @@ describe QuotedPost do
|
||||
end
|
||||
|
||||
it "doesn't count quotes from the same post" do
|
||||
SiteSetting.queue_jobs = false
|
||||
run_jobs_synchronously!
|
||||
|
||||
topic = Fabricate(:topic)
|
||||
post = create_post(topic: topic, post_number: 1, raw: "foo bar")
|
||||
|
||||
@ -1094,6 +1094,35 @@ describe Report do
|
||||
include_examples "no data"
|
||||
end
|
||||
|
||||
describe "report_top_ignored_users" do
|
||||
let(:report) { Report.find("top_ignored_users") }
|
||||
let(:tarek) { Fabricate(:user, username: "tarek") }
|
||||
let(:john) { Fabricate(:user, username: "john") }
|
||||
let(:matt) { Fabricate(:user, username: "matt") }
|
||||
|
||||
context "with data" do
|
||||
before do
|
||||
Fabricate(:ignored_user, user: tarek, ignored_user: john)
|
||||
Fabricate(:ignored_user, user: tarek, ignored_user: matt)
|
||||
end
|
||||
|
||||
it "works" do
|
||||
expect(report.data.length).to eq(2)
|
||||
expect_row_to_be_equal(report.data[0], john)
|
||||
expect_row_to_be_equal(report.data[1], matt)
|
||||
end
|
||||
|
||||
def expect_row_to_be_equal(row, user)
|
||||
expect(row[:ignored_user_id]).to eq(user.id)
|
||||
expect(row[:ignored_username]).to eq(user.username)
|
||||
expect(row[:ignored_user_avatar_template]).to eq(User.avatar_template(user.username, user.uploaded_avatar_id))
|
||||
expect(row[:ignores_count]).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
include_examples "no data"
|
||||
end
|
||||
|
||||
describe "consolidated_page_views" do
|
||||
before do
|
||||
freeze_time(Time.now.at_midnight)
|
||||
|
||||
@ -150,40 +150,6 @@ describe SiteSetting do
|
||||
end
|
||||
end
|
||||
|
||||
describe '.site_home_logo_url' do
|
||||
describe 'when logo site setting is set' do
|
||||
let(:upload) { Fabricate(:upload) }
|
||||
|
||||
before do
|
||||
SiteSetting.logo = upload
|
||||
end
|
||||
|
||||
it 'should return the right URL' do
|
||||
expect(SiteSetting.site_home_logo_url)
|
||||
.to eq("#{Discourse.base_url}#{upload.url}")
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when logo site setting is not set' do
|
||||
describe 'when there is a custom title' do
|
||||
before do
|
||||
SiteSetting.title = "test"
|
||||
end
|
||||
|
||||
it 'should return a blank string' do
|
||||
expect(SiteSetting.site_home_logo_url).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when title has not been set' do
|
||||
it 'should return the default logo url' do
|
||||
expect(SiteSetting.site_home_logo_url)
|
||||
.to eq("#{Discourse.base_url}/images/d-logo-sketch.png")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'deprecated site settings' do
|
||||
before do
|
||||
SiteSetting.force_https = true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user