FEATURE: Can edit category/host relationships for embedding
This commit is contained in:
parent
913c3d6f63
commit
d1c69189f3
|
@ -0,0 +1,7 @@
|
||||||
|
import RestAdapter from 'discourse/adapters/rest';
|
||||||
|
|
||||||
|
export default RestAdapter.extend({
|
||||||
|
pathFor() {
|
||||||
|
return "/admin/customize/embedding";
|
||||||
|
}
|
||||||
|
});
|
|
@ -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(() => {
|
||||||
|
this.sendAction('deleteHost', this.get('host'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
const host = this.get('host');
|
||||||
|
if (host.get('isNew')) {
|
||||||
|
this.sendAction('deleteHost', host);
|
||||||
|
} else {
|
||||||
|
this.rollbackBuffer();
|
||||||
|
this.set('editToggled', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
export default Ember.Route.extend({
|
||||||
|
model() {
|
||||||
|
return this.store.find('embedding');
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
controller.set('embedding', model);
|
||||||
|
}
|
||||||
|
});
|
|
@ -27,6 +27,7 @@ export default {
|
||||||
this.resource('adminUserFields', { path: '/user_fields' });
|
this.resource('adminUserFields', { path: '/user_fields' });
|
||||||
this.resource('adminEmojis', { path: '/emojis' });
|
this.resource('adminEmojis', { path: '/emojis' });
|
||||||
this.resource('adminPermalinks', { path: '/permalinks' });
|
this.resource('adminPermalinks', { path: '/permalinks' });
|
||||||
|
this.resource('adminEmbedding', { path: '/embedding' });
|
||||||
});
|
});
|
||||||
this.route('api');
|
this.route('api');
|
||||||
|
|
||||||
|
|
|
@ -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'}}
|
||||||
|
</td>
|
||||||
|
{{/if}}
|
|
@ -5,6 +5,7 @@
|
||||||
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
|
{{nav-item route='adminUserFields' label='admin.user_fields.title'}}
|
||||||
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
|
{{nav-item route='adminEmojis' label='admin.emoji.title'}}
|
||||||
{{nav-item route='adminPermalinks' label='admin.permalink.title'}}
|
{{nav-item route='adminPermalinks' label='admin.permalink.title'}}
|
||||||
|
{{nav-item route='adminEmbedding' label='admin.embedding.title'}}
|
||||||
{{/admin-nav}}
|
{{/admin-nav}}
|
||||||
|
|
||||||
<div class="admin-container">
|
<div class="admin-container">
|
||||||
|
|
|
@ -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%'> </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"}}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ADMIN_MODELS = ['plugin', 'site-customization'];
|
const ADMIN_MODELS = ['plugin', 'site-customization', 'embeddable-host'];
|
||||||
|
|
||||||
export function Result(payload, responseJson) {
|
export function Result(payload, responseJson) {
|
||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
|
@ -19,7 +19,7 @@ function rethrow(error) {
|
||||||
export default Ember.Object.extend({
|
export default Ember.Object.extend({
|
||||||
|
|
||||||
basePath(store, type) {
|
basePath(store, type) {
|
||||||
if (ADMIN_MODELS.indexOf(type) !== -1) { return "/admin/"; }
|
if (ADMIN_MODELS.indexOf(type.replace('_', '-')) !== -1) { return "/admin/"; }
|
||||||
return "/";
|
return "/";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -189,14 +189,24 @@ export default Ember.Object.extend({
|
||||||
_hydrateEmbedded(type, obj, root) {
|
_hydrateEmbedded(type, obj, root) {
|
||||||
const self = this;
|
const self = this;
|
||||||
Object.keys(obj).forEach(function(k) {
|
Object.keys(obj).forEach(function(k) {
|
||||||
const m = /(.+)\_id$/.exec(k);
|
const m = /(.+)\_id(s?)$/.exec(k);
|
||||||
if (m) {
|
if (m) {
|
||||||
const subType = m[1];
|
const subType = m[1];
|
||||||
const hydrated = self._lookupSubType(subType, type, obj[k], root);
|
|
||||||
if (hydrated) {
|
if (m[2]) {
|
||||||
obj[subType] = hydrated;
|
const hydrated = obj[k].map(function(id) {
|
||||||
|
return self._lookupSubType(subType, type, id, root);
|
||||||
|
});
|
||||||
|
obj[self.pluralize(subType)] = hydrated || [];
|
||||||
delete obj[k];
|
delete obj[k];
|
||||||
|
} else {
|
||||||
|
const hydrated = self._lookupSubType(subType, type, obj[k], root);
|
||||||
|
if (hydrated) {
|
||||||
|
obj[subType] = hydrated;
|
||||||
|
delete obj[k];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -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)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
host = EmbeddableHost.where(id: params[:id]).first
|
||||||
|
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
|
|
@ -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)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def fetch_embedding
|
||||||
|
@embedding = OpenStruct.new({
|
||||||
|
id: 'default',
|
||||||
|
embeddable_hosts: EmbeddableHost.all.order(:host)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
|
@ -58,8 +58,7 @@ class EmbedController < ApplicationController
|
||||||
def ensure_embeddable
|
def ensure_embeddable
|
||||||
|
|
||||||
if !(Rails.env.development? && current_user.try(:admin?))
|
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 EmbeddableHost.host_allowed?(request.referer)
|
||||||
raise Discourse::InvalidAccess.new('invalid referer host') unless SiteSetting.allows_embeddable_host?(request.referer)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
response.headers['X-Frame-Options'] = "ALLOWALL"
|
response.headers['X-Frame-Options'] = "ALLOWALL"
|
||||||
|
|
|
@ -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
|
||||||
|
belongs_to :category
|
||||||
|
|
||||||
|
before_validation do
|
||||||
|
self.host.sub!(/^https?:\/\//, '')
|
||||||
|
self.host.sub!(/\/.*$/, '')
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.host_allowed?(host)
|
||||||
|
record_for_host(host).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -68,20 +68,6 @@ class SiteSetting < ActiveRecord::Base
|
||||||
@anonymous_menu_items ||= Set.new Discourse.anonymous_filters.map(&:to_s)
|
@anonymous_menu_items ||= Set.new Discourse.anonymous_filters.map(&:to_s)
|
||||||
end
|
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
|
def self.anonymous_homepage
|
||||||
top_menu_items.map { |item| item.name }
|
top_menu_items.map { |item| item.name }
|
||||||
.select { |item| anonymous_menu_items.include?(item) }
|
.select { |item| anonymous_menu_items.include?(item) }
|
||||||
|
|
|
@ -866,7 +866,7 @@ class Topic < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def expandable_first_post?
|
def expandable_first_post?
|
||||||
SiteSetting.embeddable_hosts.present? && SiteSetting.embed_truncate? && has_topic_embed?
|
SiteSetting.embed_truncate? && has_topic_embed?
|
||||||
end
|
end
|
||||||
|
|
||||||
TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL
|
TIME_TO_FIRST_RESPONSE_SQL ||= <<-SQL
|
||||||
|
|
|
@ -33,12 +33,14 @@ class TopicEmbed < ActiveRecord::Base
|
||||||
# If there is no embed, create a topic, post and the embed.
|
# If there is no embed, create a topic, post and the embed.
|
||||||
if embed.blank?
|
if embed.blank?
|
||||||
Topic.transaction do
|
Topic.transaction do
|
||||||
|
eh = EmbeddableHost.record_for_host(url)
|
||||||
|
|
||||||
creator = PostCreator.new(user,
|
creator = PostCreator.new(user,
|
||||||
title: title,
|
title: title,
|
||||||
raw: absolutize_urls(url, contents),
|
raw: absolutize_urls(url, contents),
|
||||||
skip_validations: true,
|
skip_validations: true,
|
||||||
cook_method: Post.cook_methods[:raw_html],
|
cook_method: Post.cook_methods[:raw_html],
|
||||||
category: SiteSetting.embed_category)
|
category: eh.try(:category_id))
|
||||||
post = creator.create
|
post = creator.create
|
||||||
if post.present?
|
if post.present?
|
||||||
TopicEmbed.create!(topic_id: post.topic_id,
|
TopicEmbed.create!(topic_id: post.topic_id,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
class EmbeddingSerializer < ApplicationSerializer
|
||||||
|
attributes :id
|
||||||
|
has_many :embeddable_hosts, serializer: EmbeddableHostSerializer, embed: :ids
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.id
|
||||||
|
end
|
||||||
|
end
|
|
@ -2491,6 +2491,14 @@ en:
|
||||||
image: "Image"
|
image: "Image"
|
||||||
delete_confirm: "Are you sure you want to delete the :%{name}: emoji?"
|
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:
|
permalink:
|
||||||
title: "Permalinks"
|
title: "Permalinks"
|
||||||
url: "URL"
|
url: "URL"
|
||||||
|
|
|
@ -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."
|
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"
|
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_enabled: "EMBEDDING ONLY: Whether to embed a RSS/ATOM feed as posts."
|
||||||
feed_polling_url: "EMBEDDING ONLY: URL of RSS/ATOM feed to embed."
|
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_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_username_key_from_feed: "Key to pull discourse username from feed."
|
||||||
embed_truncate: "Truncate the embedded posts."
|
embed_truncate: "Truncate the embedded posts."
|
||||||
embed_category: "Category of embedded topics."
|
|
||||||
embed_post_limit: "Maximum number of posts to embed."
|
embed_post_limit: "Maximum number of posts to embed."
|
||||||
embed_whitelist_selector: "CSS selector for elements that are allowed in embeds."
|
embed_whitelist_selector: "CSS selector for elements that are allowed in embeds."
|
||||||
embed_blacklist_selector: "CSS selector for elements that are removed from embeds."
|
embed_blacklist_selector: "CSS selector for elements that are removed from embeds."
|
||||||
|
|
|
@ -135,6 +135,8 @@ Discourse::Application.routes.draw do
|
||||||
get "customize/css_html/:id/:section" => "site_customizations#index", constraints: AdminConstraint.new
|
get "customize/css_html/:id/:section" => "site_customizations#index", constraints: AdminConstraint.new
|
||||||
get "customize/colors" => "color_schemes#index", constraints: AdminConstraint.new
|
get "customize/colors" => "color_schemes#index", constraints: AdminConstraint.new
|
||||||
get "customize/permalinks" => "permalinks#index", constraints: AdminConstraint.new
|
get "customize/permalinks" => "permalinks#index", constraints: AdminConstraint.new
|
||||||
|
get "customize/embedding" => "embedding#show", constraints: AdminConstraint.new
|
||||||
|
put "customize/embedding" => "embedding#update", constraints: AdminConstraint.new
|
||||||
get "flags" => "flags#index"
|
get "flags" => "flags#index"
|
||||||
get "flags/:filter" => "flags#index"
|
get "flags/:filter" => "flags#index"
|
||||||
post "flags/agree/:id" => "flags#agree"
|
post "flags/agree/:id" => "flags#agree"
|
||||||
|
@ -148,6 +150,7 @@ Discourse::Application.routes.draw do
|
||||||
resources :emojis, constraints: AdminConstraint.new
|
resources :emojis, constraints: AdminConstraint.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :embeddable_hosts, constraints: AdminConstraint.new
|
||||||
resources :color_schemes, constraints: AdminConstraint.new
|
resources :color_schemes, constraints: AdminConstraint.new
|
||||||
|
|
||||||
resources :permalinks, constraints: AdminConstraint.new
|
resources :permalinks, constraints: AdminConstraint.new
|
||||||
|
|
|
@ -759,16 +759,12 @@ developer:
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
embedding:
|
embedding:
|
||||||
embeddable_hosts:
|
|
||||||
default: ''
|
|
||||||
type: host_list
|
|
||||||
feed_polling_enabled: false
|
feed_polling_enabled: false
|
||||||
feed_polling_url: ''
|
feed_polling_url: ''
|
||||||
embed_by_username:
|
embed_by_username:
|
||||||
default: ''
|
default: ''
|
||||||
type: username
|
type: username
|
||||||
embed_username_key_from_feed: ''
|
embed_username_key_from_feed: ''
|
||||||
embed_category: ''
|
|
||||||
embed_post_limit: 100
|
embed_post_limit: 100
|
||||||
embed_truncate: false
|
embed_truncate: false
|
||||||
embed_whitelist_selector: ''
|
embed_whitelist_selector: ''
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
class CreateEmbeddableHosts < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :embeddable_hosts, force: true do |t|
|
||||||
|
t.string :host, null: false
|
||||||
|
t.integer :category_id, null: false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
category_id = execute("SELECT c.id FROM categories AS c
|
||||||
|
INNER JOIN site_settings AS s ON s.value = c.name
|
||||||
|
WHERE s.name = 'embed_category'")[0]['id'].to_i
|
||||||
|
|
||||||
|
|
||||||
|
if category_id == 0
|
||||||
|
category_id = execute("SELECT value FROM site_settings WHERE name = 'uncategorized_category_id'")[0]['value'].to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
embeddable_hosts = execute("SELECT value FROM site_settings WHERE name = 'embeddable_hosts'")
|
||||||
|
if embeddable_hosts && embeddable_hosts.cmd_tuples > 0
|
||||||
|
val = embeddable_hosts[0]['value']
|
||||||
|
if val.present?
|
||||||
|
records = val.split("\n")
|
||||||
|
if records.present?
|
||||||
|
records.each do |h|
|
||||||
|
execute "INSERT INTO embeddable_hosts (host, category_id, created_at, updated_at) VALUES ('#{h}', #{category_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
execute "DELETE FROM site_settings WHERE name IN ('embeddable_hosts', 'embed_category')"
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,7 +13,7 @@ class TopicRetriever
|
||||||
private
|
private
|
||||||
|
|
||||||
def invalid_host?
|
def invalid_host?
|
||||||
!SiteSetting.allows_embeddable_host?(@embed_url)
|
!EmbeddableHost.host_allowed?(@embed_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieved_recently?
|
def retrieved_recently?
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Admin::EmbeddableHostsController do
|
||||||
|
|
||||||
|
it "is a subclass of AdminController" do
|
||||||
|
expect(Admin::EmbeddableHostsController < Admin::AdminController).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Admin::EmbeddingController do
|
||||||
|
|
||||||
|
it "is a subclass of AdminController" do
|
||||||
|
expect(Admin::EmbeddingController < Admin::AdminController).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -11,7 +11,6 @@ describe EmbedController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises an error with a missing host" do
|
it "raises an error with a missing host" do
|
||||||
SiteSetting.embeddable_hosts = nil
|
|
||||||
get :comments, embed_url: embed_url
|
get :comments, embed_url: embed_url
|
||||||
expect(response).not_to be_success
|
expect(response).not_to be_success
|
||||||
end
|
end
|
||||||
|
@ -19,7 +18,7 @@ describe EmbedController do
|
||||||
context "by topic id" do
|
context "by topic id" do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
SiteSetting.embeddable_hosts = host
|
Fabricate(:embeddable_host)
|
||||||
controller.request.stubs(:referer).returns('http://eviltrout.com/some-page')
|
controller.request.stubs(:referer).returns('http://eviltrout.com/some-page')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -31,9 +30,7 @@ describe EmbedController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a host" do
|
context "with a host" do
|
||||||
before do
|
let!(:embeddable_host) { Fabricate(:embeddable_host) }
|
||||||
SiteSetting.embeddable_hosts = host
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an error with no referer" do
|
it "raises an error with no referer" do
|
||||||
get :comments, embed_url: embed_url
|
get :comments, embed_url: embed_url
|
||||||
|
@ -68,7 +65,9 @@ describe EmbedController do
|
||||||
|
|
||||||
context "with multiple hosts" do
|
context "with multiple hosts" do
|
||||||
before do
|
before do
|
||||||
SiteSetting.embeddable_hosts = "#{host}\nhttp://discourse.org\nhttps://example.com/1234"
|
Fabricate(:embeddable_host)
|
||||||
|
Fabricate(:embeddable_host, host: 'http://discourse.org')
|
||||||
|
Fabricate(:embeddable_host, host: 'https://example.com/1234')
|
||||||
end
|
end
|
||||||
|
|
||||||
context "success" do
|
context "success" do
|
||||||
|
|
|
@ -1,27 +1,4 @@
|
||||||
Fabricator(:category) do
|
Fabricator(:embeddable_host) do
|
||||||
name { sequence(:name) { |n| "Amazing Category #{n}" } }
|
host "eviltrout.com"
|
||||||
user
|
category
|
||||||
end
|
|
||||||
|
|
||||||
Fabricator(:diff_category, from: :category) do
|
|
||||||
name "Different Category"
|
|
||||||
user
|
|
||||||
end
|
|
||||||
|
|
||||||
Fabricator(:happy_category, from: :category) do
|
|
||||||
name 'Happy Category'
|
|
||||||
slug 'happy'
|
|
||||||
user
|
|
||||||
end
|
|
||||||
|
|
||||||
Fabricator(:private_category, from: :category) do
|
|
||||||
transient :group
|
|
||||||
|
|
||||||
name 'Private Category'
|
|
||||||
slug 'private'
|
|
||||||
user
|
|
||||||
after_build do |cat, transients|
|
|
||||||
cat.update!(read_restricted: true)
|
|
||||||
cat.category_groups.build(group_id: transients[:group].id, permission_type: CategoryGroup.permission_types[:full])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
Fabricator(:category) do
|
||||||
|
name { sequence(:name) { |n| "Amazing Category #{n}" } }
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
Fabricator(:diff_category, from: :category) do
|
||||||
|
name "Different Category"
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
Fabricator(:happy_category, from: :category) do
|
||||||
|
name 'Happy Category'
|
||||||
|
slug 'happy'
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
Fabricator(:private_category, from: :category) do
|
||||||
|
transient :group
|
||||||
|
|
||||||
|
name 'Private Category'
|
||||||
|
slug 'private'
|
||||||
|
user
|
||||||
|
after_build do |cat, transients|
|
||||||
|
cat.update!(read_restricted: true)
|
||||||
|
cat.category_groups.build(group_id: transients[:group].id, permission_type: CategoryGroup.permission_types[:full])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,40 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe EmbeddableHost do
|
||||||
|
|
||||||
|
it "trims http" do
|
||||||
|
eh = EmbeddableHost.new(host: 'http://example.com')
|
||||||
|
expect(eh).to be_valid
|
||||||
|
expect(eh.host).to eq('example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "trims https" do
|
||||||
|
eh = EmbeddableHost.new(host: 'https://example.com')
|
||||||
|
expect(eh).to be_valid
|
||||||
|
expect(eh.host).to eq('example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "trims paths" do
|
||||||
|
eh = EmbeddableHost.new(host: 'https://example.com/1234/45')
|
||||||
|
expect(eh).to be_valid
|
||||||
|
expect(eh.host).to eq('example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "allows_embeddable_host" do
|
||||||
|
let!(:host) { Fabricate(:embeddable_host) }
|
||||||
|
|
||||||
|
it 'works as expected' do
|
||||||
|
expect(EmbeddableHost.host_allowed?('http://eviltrout.com')).to eq(true)
|
||||||
|
expect(EmbeddableHost.host_allowed?('https://eviltrout.com')).to eq(true)
|
||||||
|
expect(EmbeddableHost.host_allowed?('https://not-eviltrout.com')).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'works with multiple hosts' do
|
||||||
|
Fabricate(:embeddable_host, host: 'discourse.org')
|
||||||
|
expect(EmbeddableHost.host_allowed?('http://eviltrout.com')).to eq(true)
|
||||||
|
expect(EmbeddableHost.host_allowed?('http://discourse.org')).to eq(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -4,36 +4,6 @@ require_dependency 'site_setting_extension'
|
||||||
|
|
||||||
describe SiteSetting do
|
describe SiteSetting do
|
||||||
|
|
||||||
describe "allows_embeddable_host" do
|
|
||||||
it 'works as expected' do
|
|
||||||
SiteSetting.embeddable_hosts = 'eviltrout.com'
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'works with a http host' do
|
|
||||||
SiteSetting.embeddable_hosts = 'http://eviltrout.com'
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'works with a https host' do
|
|
||||||
SiteSetting.embeddable_hosts = 'https://eviltrout.com'
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('https://eviltrout.com')).to eq(true)
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('https://not-eviltrout.com')).to eq(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'works with multiple hosts' do
|
|
||||||
SiteSetting.embeddable_hosts = "https://eviltrout.com\nhttps://discourse.org"
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('http://eviltrout.com')).to eq(true)
|
|
||||||
expect(SiteSetting.allows_embeddable_host?('http://discourse.org')).to eq(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'topic_title_length' do
|
describe 'topic_title_length' do
|
||||||
it 'returns a range of min/max topic title length' do
|
it 'returns a range of min/max topic title length' do
|
||||||
expect(SiteSetting.topic_title_length).to eq(
|
expect(SiteSetting.topic_title_length).to eq(
|
||||||
|
|
|
@ -12,6 +12,7 @@ describe TopicEmbed do
|
||||||
let(:title) { "How to turn a fish from good to evil in 30 seconds" }
|
let(:title) { "How to turn a fish from good to evil in 30 seconds" }
|
||||||
let(:url) { 'http://eviltrout.com/123' }
|
let(:url) { 'http://eviltrout.com/123' }
|
||||||
let(:contents) { "hello world new post <a href='/hello'>hello</a> <img src='/images/wat.jpg'>" }
|
let(:contents) { "hello world new post <a href='/hello'>hello</a> <img src='/images/wat.jpg'>" }
|
||||||
|
let!(:embeddable_host) { Fabricate(:embeddable_host) }
|
||||||
|
|
||||||
it "returns nil when the URL is malformed" do
|
it "returns nil when the URL is malformed" do
|
||||||
expect(TopicEmbed.import(user, "invalid url", title, contents)).to eq(nil)
|
expect(TopicEmbed.import(user, "invalid url", title, contents)).to eq(nil)
|
||||||
|
@ -33,6 +34,8 @@ describe TopicEmbed do
|
||||||
|
|
||||||
expect(post.topic.has_topic_embed?).to eq(true)
|
expect(post.topic.has_topic_embed?).to eq(true)
|
||||||
expect(TopicEmbed.where(topic_id: post.topic_id)).to be_present
|
expect(TopicEmbed.where(topic_id: post.topic_id)).to be_present
|
||||||
|
|
||||||
|
expect(post.topic.category).to eq(embeddable_host.category)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "Supports updating the post" do
|
it "Supports updating the post" do
|
||||||
|
|
|
@ -1238,7 +1238,7 @@ describe Topic do
|
||||||
it "doesn't return topics from muted categories" do
|
it "doesn't return topics from muted categories" do
|
||||||
user = Fabricate(:user)
|
user = Fabricate(:user)
|
||||||
category = Fabricate(:category)
|
category = Fabricate(:category)
|
||||||
topic = Fabricate(:topic, category: category)
|
Fabricate(:topic, category: category)
|
||||||
|
|
||||||
CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:muted], category.id)
|
CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:muted], category.id)
|
||||||
|
|
||||||
|
@ -1247,7 +1247,7 @@ describe Topic do
|
||||||
|
|
||||||
it "doesn't return topics from TL0 users" do
|
it "doesn't return topics from TL0 users" do
|
||||||
new_user = Fabricate(:user, trust_level: 0)
|
new_user = Fabricate(:user, trust_level: 0)
|
||||||
topic = Fabricate(:topic, user_id: new_user.id)
|
Fabricate(:topic, user_id: new_user.id)
|
||||||
|
|
||||||
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to be_blank
|
expect(Topic.for_digest(user, 1.year.ago, top_order: true)).to be_blank
|
||||||
end
|
end
|
||||||
|
@ -1397,32 +1397,34 @@ describe Topic do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "expandable_first_post?" do
|
describe "expandable_first_post?" do
|
||||||
|
|
||||||
let(:topic) { Fabricate.build(:topic) }
|
let(:topic) { Fabricate.build(:topic) }
|
||||||
|
|
||||||
before do
|
|
||||||
SiteSetting.embeddable_hosts = "http://eviltrout.com"
|
|
||||||
SiteSetting.embed_truncate = true
|
|
||||||
topic.stubs(:has_topic_embed?).returns(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is true with the correct settings and topic_embed" do
|
|
||||||
expect(topic.expandable_first_post?).to eq(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is false if embeddable_host is blank" do
|
it "is false if embeddable_host is blank" do
|
||||||
SiteSetting.embeddable_hosts = nil
|
|
||||||
expect(topic.expandable_first_post?).to eq(false)
|
expect(topic.expandable_first_post?).to eq(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is false if embed_truncate? is false" do
|
describe 'with an emeddable host' do
|
||||||
SiteSetting.embed_truncate = false
|
before do
|
||||||
expect(topic.expandable_first_post?).to eq(false)
|
Fabricate(:embeddable_host)
|
||||||
|
SiteSetting.embed_truncate = true
|
||||||
|
topic.stubs(:has_topic_embed?).returns(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is true with the correct settings and topic_embed" do
|
||||||
|
expect(topic.expandable_first_post?).to eq(true)
|
||||||
|
end
|
||||||
|
it "is false if embed_truncate? is false" do
|
||||||
|
SiteSetting.embed_truncate = false
|
||||||
|
expect(topic.expandable_first_post?).to eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is false if has_topic_embed? is false" do
|
||||||
|
topic.stubs(:has_topic_embed?).returns(false)
|
||||||
|
expect(topic.expandable_first_post?).to eq(false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is false if has_topic_embed? is false" do
|
|
||||||
topic.stubs(:has_topic_embed?).returns(false)
|
|
||||||
expect(topic.expandable_first_post?).to eq(false)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "has custom fields" do
|
it "has custom fields" do
|
||||||
|
|
|
@ -39,13 +39,17 @@ const _moreWidgets = [
|
||||||
{id: 224, name: 'Good Repellant'}
|
{id: 224, name: 'Good Repellant'}
|
||||||
];
|
];
|
||||||
|
|
||||||
const fruits = [{id: 1, name: 'apple', farmer_id: 1, category_id: 4},
|
const fruits = [{id: 1, name: 'apple', farmer_id: 1, color_ids: [1,2], category_id: 4},
|
||||||
{id: 2, name: 'banana', farmer_id: 1, category_id: 3},
|
{id: 2, name: 'banana', farmer_id: 1, color_ids: [3], category_id: 3},
|
||||||
{id: 3, name: 'grape', farmer_id: 2, category_id: 5}];
|
{id: 3, name: 'grape', farmer_id: 2, color_ids: [2], category_id: 5}];
|
||||||
|
|
||||||
const farmers = [{id: 1, name: 'Old MacDonald'},
|
const farmers = [{id: 1, name: 'Old MacDonald'},
|
||||||
{id: 2, name: 'Luke Skywalker'}];
|
{id: 2, name: 'Luke Skywalker'}];
|
||||||
|
|
||||||
|
const colors = [{id: 1, name: 'Red'},
|
||||||
|
{id: 2, name: 'Green'},
|
||||||
|
{id: 3, name: 'Yellow'}];
|
||||||
|
|
||||||
function loggedIn() {
|
function loggedIn() {
|
||||||
return !!Discourse.User.current();
|
return !!Discourse.User.current();
|
||||||
}
|
}
|
||||||
|
@ -221,12 +225,11 @@ export default function() {
|
||||||
|
|
||||||
this.get('/fruits/:id', function() {
|
this.get('/fruits/:id', function() {
|
||||||
const fruit = fruits[0];
|
const fruit = fruits[0];
|
||||||
|
return response({ __rest_serializer: "1", fruit, farmers, colors });
|
||||||
return response({ __rest_serializer: "1", fruit, farmers: [farmers[0]] });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get('/fruits', function() {
|
this.get('/fruits', function() {
|
||||||
return response({ __rest_serializer: "1", fruits, farmers });
|
return response({ __rest_serializer: "1", fruits, farmers, colors });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.get('/widgets/:widget_id', function(request) {
|
this.get('/widgets/:widget_id', function(request) {
|
||||||
|
|
|
@ -106,19 +106,31 @@ test('destroyRecord when new', function(assert) {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('find embedded', function() {
|
test('find embedded', function(assert) {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
return store.find('fruit', 1).then(function(f) {
|
return store.find('fruit', 2).then(function(f) {
|
||||||
ok(f.get('farmer'), 'it has the embedded object');
|
assert.ok(f.get('farmer'), 'it has the embedded object');
|
||||||
ok(f.get('category'), 'categories are found automatically');
|
|
||||||
|
const fruitCols = f.get('colors');
|
||||||
|
assert.equal(fruitCols.length, 2);
|
||||||
|
assert.equal(fruitCols[0].get('id'), 1);
|
||||||
|
assert.equal(fruitCols[1].get('id'), 2);
|
||||||
|
|
||||||
|
assert.ok(f.get('category'), 'categories are found automatically');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findAll embedded', function() {
|
test('findAll embedded', function(assert) {
|
||||||
const store = createStore();
|
const store = createStore();
|
||||||
return store.findAll('fruit').then(function(fruits) {
|
return store.findAll('fruit').then(function(fruits) {
|
||||||
equal(fruits.objectAt(0).get('farmer.name'), 'Old MacDonald');
|
assert.equal(fruits.objectAt(0).get('farmer.name'), 'Old MacDonald');
|
||||||
equal(fruits.objectAt(0).get('farmer'), fruits.objectAt(1).get('farmer'), 'points at the same object');
|
assert.equal(fruits.objectAt(0).get('farmer'), fruits.objectAt(1).get('farmer'), 'points at the same object');
|
||||||
equal(fruits.objectAt(2).get('farmer.name'), 'Luke Skywalker');
|
|
||||||
|
const fruitCols = fruits.objectAt(0).get('colors');
|
||||||
|
assert.equal(fruitCols.length, 2);
|
||||||
|
assert.equal(fruitCols[0].get('id'), 1);
|
||||||
|
assert.equal(fruitCols[1].get('id'), 2);
|
||||||
|
|
||||||
|
assert.equal(fruits.objectAt(2).get('farmer.name'), 'Luke Skywalker');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue