Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/assets/javascripts/admin/adapters/embedding.js.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import RestAdapter from 'discourse/adapters/rest';

export default RestAdapter.extend({
pathFor() {
return "/admin/customize/embedding";
}
});
63 changes: 63 additions & 0 deletions app/assets/javascripts/admin/components/embeddable-host.js.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { bufferedProperty } from 'discourse/mixins/buffered-content';
import computed from 'ember-addons/ember-computed-decorators';
import { on, observes } from 'ember-addons/ember-computed-decorators';
import { popupAjaxError } from 'discourse/lib/ajax-error';

export default Ember.Component.extend(bufferedProperty('host'), {
editToggled: false,
tagName: 'tr',
categoryId: null,

editing: Ember.computed.or('host.isNew', 'editToggled'),

@on('didInsertElement')
@observes('editing')
_focusOnInput() {
Ember.run.schedule('afterRender', () => { this.$('.host-name').focus(); });
},

@computed('buffered.host', 'host.isSaving')
cantSave(host, isSaving) {
return isSaving || Ember.isEmpty(host);
},

actions: {
edit() {
this.set('categoryId', this.get('host.category.id'));
this.set('editToggled', true);
},

save() {
if (this.get('cantSave')) { return; }

const props = this.get('buffered').getProperties('host');
props.category_id = this.get('categoryId');

const host = this.get('host');
host.save(props).then(() => {
host.set('category', Discourse.Category.findById(this.get('categoryId')));
this.set('editToggled', false);
}).catch(popupAjaxError);
},

delete() {
bootbox.confirm(I18n.t('admin.embedding.confirm_delete'), (result) => {
if (result) {
this.get('host').destroyRecord().then(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The delete action chains destroyRecord().then(...) with no .catch (unlike save on line 40 which has .catch(popupAjaxError)). If the server-side destroy fails, the promise rejects silently: the row stays in the list and the user gets no feedback.

this.get('host').destroyRecord().then(() => {
  this.sendAction('deleteHost', this.get('host'));
}).catch(popupAjaxError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 delete action doesn't catch rejection

The save action chains .catch(popupAjaxError) (line 40), but delete only chains .then(...) on destroyRecord(). If the server returns an error, the promise rejects silently — the host stays in the list (correct) but the admin gets zero feedback that the delete failed.

Fix:

this.get('host').destroyRecord().then(() => {
  this.sendAction('deleteHost', this.get('host'));
}).catch(popupAjaxError);

this.sendAction('deleteHost', this.get('host'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 delete doesn't catch rejection.

The save action chains .catch(popupAjaxError) (line 40), but delete only chains .then(...) on destroyRecord(). If the server returns an error, the promise rejects silently — the row stays in the list (correct) but the admin gets zero feedback that the delete failed.

this.get('host').destroyRecord().then(() => {
  this.sendAction('deleteHost', this.get('host'));
}).catch(popupAjaxError);

});
}
});
},

cancel() {
const host = this.get('host');
if (host.get('isNew')) {
this.sendAction('deleteHost', host);
} else {
this.rollbackBuffer();
this.set('editToggled', false);
}
}
}
});
18 changes: 18 additions & 0 deletions app/assets/javascripts/admin/controllers/admin-embedding.js.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default Ember.Controller.extend({
embedding: null,

actions: {
saveChanges() {
this.get('embedding').update({});
},

addHost() {
const host = this.store.createRecord('embeddable-host');
this.get('embedding.embeddable_hosts').pushObject(host);
},

deleteHost(host) {
this.get('embedding.embeddable_hosts').removeObject(host);
}
}
});
9 changes: 9 additions & 0 deletions app/assets/javascripts/admin/routes/admin-embedding.js.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default Ember.Route.extend({
model() {
return this.store.find('embedding');
},

setupController(controller, model) {
controller.set('embedding', model);
}
});
1 change: 1 addition & 0 deletions app/assets/javascripts/admin/routes/admin-route-map.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default {
this.resource('adminUserFields', { path: '/user_fields' });
this.resource('adminEmojis', { path: '/emojis' });
this.resource('adminPermalinks', { path: '/permalinks' });
this.resource('adminEmbedding', { path: '/embedding' });
});
this.route('api');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{#if editing}}
<td>
{{input value=buffered.host placeholder="example.com" enter="save" class="host-name"}}
</td>
<td>
{{category-chooser value=categoryId}}
</td>
<td>
{{d-button icon="check" action="save" class="btn-primary" disabled=cantSave}}
{{d-button icon="times" action="cancel" class="btn-danger" disabled=host.isSaving}}
</td>
{{else}}
<td>{{host.host}}</td>
<td>{{category-badge host.category}}</td>
<td>
{{d-button icon="pencil" action="edit"}}
{{d-button icon="trash-o" action="delete" class='btn-danger'}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The delete button has no disabled binding (unlike save/cancel which bind disabled=cantSave/disabled=host.isSaving). A user can click delete twice while destroyRecord is in flight.

{{d-button icon="trash-o" action="delete" class='btn-danger' disabled=host.isSaving}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The delete button has no disabled binding, unlike save/cancel which bind disabled=cantSave/disabled=host.isSaving. A user can click delete twice while destroyRecord is in flight.

{{d-button icon="trash-o" action="delete" class='btn-danger' disabled=host.isSaving}}

</td>
{{/if}}
1 change: 1 addition & 0 deletions app/assets/javascripts/admin/templates/customize.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
{{nav-item route='adminPermalinks' label='admin.permalink.title'}}
{{nav-item route='adminEmbedding' label='admin.embedding.title'}}
{{/admin-nav}}

<div class="admin-container">
Expand Down
15 changes: 15 additions & 0 deletions app/assets/javascripts/admin/templates/embedding.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{#if embedding.embeddable_hosts}}
<table>
<tr>
<th style='width: 50%'>{{i18n "admin.embedding.host"}}</th>
<th style='width: 30%'>{{i18n "admin.embedding.category"}}</th>
<th style='width: 20%'>&nbsp;</th>
</tr>
{{#each embedding.embeddable_hosts as |host|}}
{{embeddable-host host=host deleteHost="deleteHost"}}
{{/each}}
</table>
{{/if}}

{{d-button label="admin.embedding.add_host" action="addHost" icon="plus" class="btn-primary"}}

4 changes: 2 additions & 2 deletions app/assets/javascripts/discourse/adapters/rest.js.es6
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const ADMIN_MODELS = ['plugin', 'site-customization'];
const ADMIN_MODELS = ['plugin', 'site-customization', 'embeddable-host'];

export function Result(payload, responseJson) {
this.payload = payload;
Expand All @@ -19,7 +19,7 @@ function rethrow(error) {
export default Ember.Object.extend({

basePath(store, type) {
if (ADMIN_MODELS.indexOf(type) !== -1) { return "/admin/"; }
if (ADMIN_MODELS.indexOf(type.replace('_', '-')) !== -1) { return "/admin/"; }
return "/";
},

Expand Down
18 changes: 14 additions & 4 deletions app/assets/javascripts/discourse/models/store.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,24 @@ export default Ember.Object.extend({
_hydrateEmbedded(type, obj, root) {
const self = this;
Object.keys(obj).forEach(function(k) {
const m = /(.+)\_id$/.exec(k);
const m = /(.+)\_id(s?)$/.exec(k);
if (m) {
const subType = m[1];
const hydrated = self._lookupSubType(subType, type, obj[k], root);
if (hydrated) {
obj[subType] = hydrated;

if (m[2]) {
const hydrated = obj[k].map(function(id) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 .map() called without array guard

When the plural *_ids branch is taken, obj[k].map(...) assumes obj[k] is an array. If the serializer omits the key or sends null for an empty has_many collection (possible with AMS embed: :ids), this throws TypeError: Cannot read property 'map' of null, crashing the admin embedding page hydration.

Fix:

const hydrated = (obj[k] || []).map(function(id) {
  return self._lookupSubType(subType, type, id, root);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 .map() called without an array guard.

In the plural *_ids branch, obj[k].map(...) assumes obj[k] is an array. If the serializer omits the key or sends null for an empty has_many (possible with AMS embed: :ids), this throws TypeError: Cannot read property 'map' of null, crashing hydration of the admin embedding page.

const hydrated = (obj[k] || []).map(function(id) {
  return self._lookupSubType(subType, type, id, root);
});

return self._lookupSubType(subType, type, id, root);
});
obj[self.pluralize(subType)] = hydrated || [];
delete obj[k];
} else {
const hydrated = self._lookupSubType(subType, type, obj[k], root);
if (hydrated) {
obj[subType] = hydrated;
delete obj[k];
}
}

}
});
},
Expand Down
34 changes: 34 additions & 0 deletions app/controllers/admin/embeddable_hosts_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class Admin::EmbeddableHostsController < Admin::AdminController

before_filter :ensure_logged_in, :ensure_staff

def create
save_host(EmbeddableHost.new)
end

def update
host = EmbeddableHost.where(id: params[:id]).first
save_host(host)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 No nil guards. EmbeddableHost.where(id: params[:id]).first returns nil for a missing/stale id, but save_host(nil) (line 11) and nil.destroy (line 16) raise NoMethodError → unhandled 500 instead of 404. save_host (line 23) also dereferences params[:embeddable_host][:host] without checking params[:embeddable_host].present?, producing 500 on a malformed body instead of 422.

Add a not-found guard before operating on the record:

def update
  host = EmbeddableHost.where(id: params[:id]).first
  raise Discourse::NotFound unless host
  save_host(host)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 No nil guards on update/destroy.

EmbeddableHost.where(id: params[:id]).first returns nil for a missing/stale id, so save_host(nil) (→ nil.host=, NoMethodError) and nil.destroy (line 15) raise unhandled 500s instead of 404. save_host (line 23) also dereferences params[:embeddable_host][:host] without checking params[:embeddable_host].present?, producing 500 on a malformed body instead of 422.

def update
  host = EmbeddableHost.where(id: params[:id]).first
  return render_json_error('host not found', status: 404) if host.blank?
  save_host(host)
end

def destroy
  host = EmbeddableHost.where(id: params[:id]).first
  return render_json_error('host not found', status: 404) if host.blank?
  host.destroy
  render json: success_json
end

end

def destroy
host = EmbeddableHost.where(id: params[:id]).first

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 update and destroy crash on missing record

Both actions do EmbeddableHost.where(id: params[:id]).first with no nil check. A nonexistent or stale id yields nil, so save_host(nil) (→ nil.host=, NoMethodError) and nil.destroy raise 500 instead of returning a 404.

Fix:

def update
  host = EmbeddableHost.where(id: params[:id]).first
  return render_json_error('host not found', status: 404) if host.blank?
  save_host(host)
end

def destroy
  host = EmbeddableHost.where(id: params[:id]).first
  return render_json_error('host not found', status: 404) if host.blank?
  host.destroy
  render json: success_json
end

host.destroy
render json: success_json
end

protected

def save_host(host)
host.host = params[:embeddable_host][:host]
host.category_id = params[:embeddable_host][:category_id]
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?

if host.save
render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true)
else
render_json_error(host)
end
end

end
21 changes: 21 additions & 0 deletions app/controllers/admin/embedding_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Admin::EmbeddingController < Admin::AdminController

before_filter :ensure_logged_in, :ensure_staff, :fetch_embedding

def show
render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true)
end

def update
render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 update re-renders the serialized embedding without persisting anything — it's a no-op disguised as a save. The Ember saveChanges action PUTs here and gets a 200, but nothing is written. While the template doesn't currently invoke saveChanges, this misleading success state could trap future maintainers. Either implement the update or remove the dead action/route.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 update is a no-op returning 200 OK

update ignores all request params and re-renders the same in-memory OpenStruct (rebuilt fresh from the DB on every request via fetch_embedding). It persists nothing. The Ember saveChanges action that calls embedding.update({}) is dead code — embedding.hbs has no save button.

This is misleading: a successful PUT /admin/customize/embedding that mutated nothing.

Fix: Since hosts now have their own CRUD endpoints, remove the put customize/embedding route, the update action, and the Ember saveChanges action. If global embed settings need saving, implement real persistence instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 update is a no-op returning 200 OK.

update ignores all request params and re-renders the same in-memory OpenStruct (rebuilt fresh from the DB on every request via fetch_embedding). It persists nothing. The Ember saveChanges action that PUTs here is dead code — embedding.hbs has no save button. A successful PUT /admin/customize/embedding that mutated nothing is misleading. Either implement real persistence for global embed settings, or remove the put customize/embedding route, the update action, and the Ember saveChanges action.

end

protected

def fetch_embedding
@embedding = OpenStruct.new({
id: 'default',
embeddable_hosts: EmbeddableHost.all.order(:host)
})
end
end
3 changes: 1 addition & 2 deletions app/controllers/embed_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ def count
def ensure_embeddable

if !(Rails.env.development? && current_user.try(:admin?))
raise Discourse::InvalidAccess.new('embeddable hosts not set') if SiteSetting.embeddable_hosts.blank?
raise Discourse::InvalidAccess.new('invalid referer host') unless SiteSetting.allows_embeddable_host?(request.referer)
raise Discourse::InvalidAccess.new('invalid referer host') unless EmbeddableHost.host_allowed?(request.referer)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The removed 'embeddable hosts not set' guard means all failures — unconfigured, nil referer (e.g. Referrer-Policy: no-referrer), or invalid host — now surface as the single message 'invalid referer host', conflating distinct conditions. Operators can't distinguish "add hosts" from "fix the referer". Also, the rescue URI::InvalidURIError on line 65 is dead code: record_for_host already swallows errors via rescue nil, and URI(nil) raises TypeError not URI::InvalidURIError.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Conflated error states + dead rescue.

Removing the 'embeddable hosts not set' guard means all failures — unconfigured, nil referer (e.g. Referrer-Policy: no-referrer), or invalid host — now surface as the single message 'invalid referer host', so operators can't distinguish "add hosts" from "fix the referer". Consider keeping a distinct message when no EmbeddableHost exists.

Also, the rescue URI::InvalidURIError on line 65 is dead code: record_for_host already swallows URI errors via rescue nil, and URI(nil) (a nil referer) raises TypeError, not URI::InvalidURIError — so that path is never reached here.

end

response.headers['X-Frame-Options'] = "ALLOWALL"
Expand Down
24 changes: 24 additions & 0 deletions app/models/embeddable_host.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class EmbeddableHost < ActiveRecord::Base
validates_format_of :host, :with => /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\Z/i

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Two model-level issues:

  1. The validation regex caps the TLD at [a-z]{2,5}, rejecting valid long-TLD hosts (.museum, .online, .photography). The old host_list setting had no format validator, so this is a regression for existing installs. Consider loosening to {2,24}.
  2. There is no uniqueness validation or DB index on host, allowing duplicate hosts from concurrent admin actions.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Validation regex rejects valid TLDs; no uniqueness guard.

  1. [a-z]{2,5} caps the TLD at 5 chars, rejecting valid hosts under .museum, .online, .photography, .travel, etc. The old host_list setting had no format validator, so this is a regression for existing installs. Consider {2,24}.
  2. There is no uniqueness validation or DB unique index on host, so concurrent admin actions can create duplicate allowlist entries that then both match.
validates :host, uniqueness: true, format: { with: /\A[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,24}\Z/i }
# migration:
add_index :embeddable_hosts, :host, unique: true

belongs_to :category

before_validation do
self.host.sub!(/^https?:\/\//, '')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 before_validation crashes on nil host.

self.host.sub!(...) raises NoMethodError if host is nil (e.g. a malformed request where params[:embeddable_host][:host] is nil). This bypasses the validation system and produces a 500 instead of a clean validation error that render_json_error would handle.

before_validation do
  if self.host.present?
    self.host.sub!(/^https?:\/\//, '')
    self.host.sub!(/\/.*$/, '')
  end
end

self.host.sub!(/\/.*$/, '')
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 before_validation crashes on nil host

self.host.sub!(...) raises NoMethodError if host is nil (e.g., a malformed request where params[:embeddable_host][:host] is nil). This bypasses the validation system and produces a 500 instead of a clean validation error that render_json_error would handle.

Fix:

before_validation do
  if self.host.present?
    self.host.sub!(/^https?:\/\//, '')
    self.host.sub!(/\/.*$/, '')
  end
end

def self.record_for_host(host)
uri = URI(host) rescue nil
return false unless uri.present?

host = uri.host
return false unless host.present?

where("lower(host) = ?", host).first

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Case-asymmetric matching. where("lower(host) = ?", host) lowercases the stored column but not uri.host (line 14). A referer with different casing (e.g. EvilTrout.com) won't match a stored eviltrout.com, wrongly rejecting a legitimate embed.

host = uri.host.downcase
where("lower(host) = ?", host).first

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Case-sensitivity mismatch breaks mixed-case host entries

before_validation strips scheme/path but does not downcase the host. The validation regex uses /i, so mixed-case hosts like EvilTrout.com are accepted and stored as-is. But record_for_host queries where("lower(host) = ?", host) where host comes from uri.host (Ruby's URI preserves case). So lower('EvilTrout.com') = 'eviltrout.com' ≠ 'EvilTrout.com' — the lookup returns nil and a valid host is silently rejected.

Fix: Downcase the input in the lookup:

where("lower(host) = ?", host.downcase).first

Or normalize in before_validation: self.host = self.host.downcase if self.host.present?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Case-asymmetric matching rejects legitimate mixed-case hosts.

where("lower(host) = ?", host) lowercases the stored column but not uri.host (Ruby's URI preserves case). A referer like EvilTrout.com won't match a stored eviltrout.com, so a valid embed is silently rejected. The validation regex uses /i and before_validation doesn't downcase, so mixed-case hosts are accepted and stored as-is — then never match.

Also note before_validation strips scheme and path but not port, so a host stored as example.com:8080 never matches URI(referer).host (example.com).

Fix: normalize on write and on read:

before_validation do
  if self.host.present?
    self.host = self.host.sub(/^https?:\/\//, '').sub(/\/.*$/, '').sub(/:\d+$/, '').downcase
  end
end
# ...
where("lower(host) = ?", host.downcase).first

end

def self.host_allowed?(host)
record_for_host(host).present?
end

end
14 changes: 0 additions & 14 deletions app/models/site_setting.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,6 @@ def self.anonymous_menu_items
@anonymous_menu_items ||= Set.new Discourse.anonymous_filters.map(&:to_s)
end

def self.allows_embeddable_host?(host)
return false if embeddable_hosts.blank?
uri = URI(host) rescue nil
return false unless uri.present?

host = uri.host
return false unless host.present?

!!embeddable_hosts.split("\n").detect {|h| h.sub(/^https?\:\/\//, '') == host }

hosts = embeddable_hosts.split("\n").map {|h| (URI(h).host rescue nil) || h }
!!hosts.detect {|h| h == host}
end

def self.anonymous_homepage
top_menu_items.map { |item| item.name }
.select { |item| anonymous_menu_items.include?(item) }
Expand Down
2 changes: 1 addition & 1 deletion app/models/topic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ def has_topic_embed?
end

def expandable_first_post?
SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed?
SiteSetting.embed_truncate? && has_topic_embed?
end

TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL
Expand Down
4 changes: 3 additions & 1 deletion app/models/topic_embed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ def self.import(user, url, title, contents)
# If there is no embed, create a topic, post and the embed.
if embed.blank?
Topic.transaction do
eh = EmbeddableHost.record_for_host(url)

creator = PostCreator.new(user,
title: title,
raw: absolutize_urls(url, contents),
skip_validations: true,
cook_method: Post.cook_methods[:raw_html],
category: SiteSetting.embed_category)
category: eh.try(:category_id))
post = creator.create
if post.present?
TopicEmbed.create!(topic_id: post.topic_id,
Expand Down
16 changes: 16 additions & 0 deletions app/serializers/embeddable_host_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class EmbeddableHostSerializer < ApplicationSerializer
attributes :id, :host, :category_id

def id
object.id
end

def host
object.host
end

def category_id
object.category_id
end
end

8 changes: 8 additions & 0 deletions app/serializers/embedding_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class EmbeddingSerializer < ApplicationSerializer
attributes :id
has_many :embeddable_hosts, serializer: EmbeddableHostSerializer, embed: :ids

def id
object.id
end
end
8 changes: 8 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2491,6 +2491,14 @@ en:
image: "Image"
delete_confirm: "Are you sure you want to delete the :%{name}: emoji?"

embedding:
confirm_delete: "Are you sure you want to delete that host?"
title: "Embedding"
host: "Allowed Hosts"
edit: "edit"
category: "Post to Category"
add_host: "Add Host"

permalink:
title: "Permalinks"
url: "URL"
Expand Down
2 changes: 0 additions & 2 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1164,13 +1164,11 @@ en:
autohighlight_all_code: "Force apply code highlighting to all preformatted code blocks even when they didn't explicitly specify the language."
highlighted_languages: "Included syntax highlighting rules. (Warning: including too many langauges may impact performance) see: https://highlightjs.org/static/demo/ for a demo"

embeddable_hosts: "Host(s) that can embed the comments from this Discourse forum. Hostname only, do not begin with http://"
feed_polling_enabled: "EMBEDDING ONLY: Whether to embed a RSS/ATOM feed as posts."
feed_polling_url: "EMBEDDING ONLY: URL of RSS/ATOM feed to embed."
embed_by_username: "Discourse username of the user who creates the embedded topics."
embed_username_key_from_feed: "Key to pull discourse username from feed."
embed_truncate: "Truncate the embedded posts."
embed_category: "Category of embedded topics."
embed_post_limit: "Maximum number of posts to embed."
embed_whitelist_selector: "CSS selector for elements that are allowed in embeds."
embed_blacklist_selector: "CSS selector for elements that are removed from embeds."
Expand Down
Loading