biovision-base 0.34.190331.1 → 0.36.190526.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/biovision/base/biovision.js +52 -7
- data/app/assets/javascripts/biovision/base/components/carousel.js +127 -17
- data/app/assets/javascripts/biovision/base/components/video-stretcher.js +51 -0
- data/app/assets/stylesheets/biovision/base/biovision.scss +18 -2
- data/app/controllers/admin/settings_controller.rb +7 -12
- data/app/controllers/admin_controller.rb +4 -0
- data/app/controllers/concerns/authentication.rb +3 -0
- data/app/controllers/editable_pages_controller.rb +2 -0
- data/app/controllers/my/profiles_controller.rb +1 -1
- data/app/helpers/biovision_users_helper.rb +1 -1
- data/app/jobs/editable_page_body_parser_job.rb +16 -0
- data/app/models/biovision_component.rb +18 -11
- data/app/models/editable_page.rb +21 -16
- data/app/models/foreign_site.rb +9 -3
- data/app/services/biovision/components/base_component.rb +17 -40
- data/app/services/canonizer.rb +21 -14
- data/app/services/oembed_receiver.rb +92 -0
- data/app/services/user_bouncer.rb +3 -4
- data/app/uploaders/simple_file_uploader.rb +15 -0
- data/app/views/admin/editable_pages/entity/_in_list.html.erb +1 -1
- data/app/views/admin/editable_pages/show.html.erb +2 -2
- data/app/views/admin/index/_biovision_base.html.erb +2 -2
- data/app/views/admin/settings/component/_new_parameter.html.erb +5 -13
- data/app/views/admin/settings/component/_parameters.html.erb +6 -6
- data/app/views/admin/settings/show.html.erb +1 -1
- data/app/views/admin/users/entity/_in_list.html.erb +3 -0
- data/app/views/admin/users/show.html.erb +1 -1
- data/app/views/editable_pages/_editable_page.html.erb +1 -1
- data/app/views/editable_pages/_form.html.erb +2 -2
- data/app/views/editable_pages/entity/_content.html.erb +1 -1
- data/app/views/editable_pages/entity/_metadata.html.erb +12 -10
- data/app/views/index/index/_default_dashboard.html.erb +3 -1
- data/app/views/my/index/index/_dashboard.html.erb +16 -0
- data/app/views/shared/_breadcrumbs.html.erb +4 -2
- data/app/views/shared/_track.html.erb +8 -8
- data/app/views/shared/admin/_breadcrumbs.html.erb +4 -2
- data/app/views/shared/editable_pages/_body.html.erb +2 -2
- data/app/views/shared/entity/_metadata.html.erb +13 -0
- data/app/views/shared/forms/_toggle_wysiwyg.html.erb +24 -0
- data/app/views/shared/forms/_wysiwyg.html.erb +1 -1
- data/app/views/simple_blocks/_form.html.erb +12 -0
- data/app/views/users/_form.html.erb +22 -22
- data/config/locales/components-ru.yml +2 -3
- data/config/routes.rb +41 -90
- data/db/migrate/20181217000000_create_biovision_components.rb +16 -39
- data/db/migrate/20181217000110_create_editable_pages.rb +11 -40
- data/db/migrate/20190326120000_create_simple_blocks.rb +7 -1
- data/db/migrate/20190423101010_add_parameters_to_biovision_components.rb +29 -0
- data/db/migrate/20190429111111_add_parsed_body_to_editable_pages.rb +18 -0
- data/lib/biovision/base/base_methods.rb +10 -0
- data/lib/biovision/base/privilege_methods.rb +5 -0
- data/lib/biovision/base/version.rb +1 -1
- metadata +9 -3
- data/app/views/layouts/profile.html.erb +0 -30
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Common administrative controller
|
1
4
|
class AdminController < ApplicationController
|
2
5
|
before_action :restrict_access
|
3
6
|
|
@@ -5,6 +8,7 @@ class AdminController < ApplicationController
|
|
5
8
|
|
6
9
|
def restrict_access
|
7
10
|
return if UserPrivilege.user_has_any_privilege?(current_user)
|
11
|
+
|
8
12
|
handle_http_401("User #{current_user&.id} has no privileges")
|
9
13
|
end
|
10
14
|
end
|
@@ -10,6 +10,9 @@ module Authentication
|
|
10
10
|
|
11
11
|
# @param [User] user
|
12
12
|
def create_token_for_user(user)
|
13
|
+
forced_user = User.find_by(id: user.native_id)
|
14
|
+
user = forced_user unless forced_user.nil?
|
15
|
+
|
13
16
|
token = user.tokens.create!(tracking_for_entity)
|
14
17
|
|
15
18
|
cookies['token'] = {
|
@@ -20,6 +20,7 @@ class EditablePagesController < AdminController
|
|
20
20
|
def create
|
21
21
|
@entity = EditablePage.new(entity_parameters)
|
22
22
|
if @entity.save
|
23
|
+
EditablePageBodyParserJob.perform_later(@entity.id)
|
23
24
|
form_processed_ok(admin_editable_page_path(id: @entity.id))
|
24
25
|
else
|
25
26
|
form_processed_with_error(:new)
|
@@ -33,6 +34,7 @@ class EditablePagesController < AdminController
|
|
33
34
|
# patch /editable_pages/:id
|
34
35
|
def update
|
35
36
|
if @entity.update(entity_parameters)
|
37
|
+
EditablePageBodyParserJob.perform_later(@entity.id)
|
36
38
|
form_processed_ok(admin_editable_page_path(id: @entity.id))
|
37
39
|
else
|
38
40
|
form_processed_with_error(:edit)
|
@@ -8,7 +8,7 @@ class My::ProfilesController < ApplicationController
|
|
8
8
|
before_action :restrict_anonymous_access, except: %i[check new create]
|
9
9
|
before_action :set_handler
|
10
10
|
|
11
|
-
layout 'profile', only: %i[show edit]
|
11
|
+
# layout 'profile', only: %i[show edit]
|
12
12
|
|
13
13
|
# post /my/profile/check
|
14
14
|
def check
|
@@ -24,7 +24,7 @@ module BiovisionUsersHelper
|
|
24
24
|
return I18n.t(:anonymous) if entity.nil? || entity.deleted?
|
25
25
|
|
26
26
|
link_options = { class: 'profile' }.merge(options)
|
27
|
-
link_to(text, user_profile_path(slug: entity.
|
27
|
+
link_to(text, user_profile_path(slug: entity.slug), link_options)
|
28
28
|
end
|
29
29
|
|
30
30
|
# @param [User] entity
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Parse editable page body
|
4
|
+
class EditablePageBodyParserJob < ApplicationJob
|
5
|
+
queue_as :default
|
6
|
+
|
7
|
+
# @param [Integer] id
|
8
|
+
def perform(id)
|
9
|
+
entity = EditablePage.find_by(id: id)
|
10
|
+
|
11
|
+
return if entity.nil?
|
12
|
+
|
13
|
+
entity.parsed_body = OembedReceiver.convert(entity.body)
|
14
|
+
entity.save
|
15
|
+
end
|
16
|
+
end
|
@@ -10,7 +10,10 @@
|
|
10
10
|
class BiovisionComponent < ApplicationRecord
|
11
11
|
include RequiredUniqueSlug
|
12
12
|
|
13
|
-
|
13
|
+
SLUG_LIMIT = 250
|
14
|
+
SLUG_PATTERN = /\A[a-z][-a-z0-9_]+[a-z0-9]\z/i.freeze
|
15
|
+
SLUG_PATTERN_HTML = '^[a-zA-Z][-a-zA-Z0-9_]+[a-zA-Z0-9]$'
|
16
|
+
VALUE_LIMIT = 65_535
|
14
17
|
|
15
18
|
# Find component by slug
|
16
19
|
#
|
@@ -20,24 +23,28 @@ class BiovisionComponent < ApplicationRecord
|
|
20
23
|
end
|
21
24
|
|
22
25
|
# @param [String] slug
|
26
|
+
# @param [String] default_value
|
27
|
+
def get(slug, default_value = '')
|
28
|
+
parameters.fetch(slug.to_s) { default_value }
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [String] slug
|
32
|
+
# @deprecated use #get
|
23
33
|
def receive(slug)
|
24
|
-
|
34
|
+
parameters[slug.to_s]
|
25
35
|
end
|
26
36
|
|
27
37
|
# @param [String] slug
|
28
|
-
# @param [String]
|
29
|
-
|
30
|
-
|
38
|
+
# @param [String] default_value
|
39
|
+
# @deprecated use #get
|
40
|
+
def receive!(slug, default_value = '')
|
41
|
+
get(slug, default_value)
|
31
42
|
end
|
32
43
|
|
33
44
|
# @param [String] slug
|
34
45
|
# @param [String] value
|
35
46
|
def []=(slug, value)
|
36
|
-
|
37
|
-
|
38
|
-
biovision_parameters.create!(slug: slug, value: value.to_s)
|
39
|
-
else
|
40
|
-
parameter.update!(value: value.to_s)
|
41
|
-
end
|
47
|
+
parameters[slug.to_s] = value.to_s
|
48
|
+
save!
|
42
49
|
end
|
43
50
|
end
|
data/app/models/editable_page.rb
CHANGED
@@ -3,19 +3,20 @@
|
|
3
3
|
# Editable page for site
|
4
4
|
#
|
5
5
|
# Attributes:
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
6
|
+
# body [String]
|
7
|
+
# image [EditablePageImageUploader], optional
|
8
|
+
# image_alt_text [String], optional
|
9
|
+
# language_id [Language]
|
10
|
+
# meta_description [String], optional
|
11
|
+
# meta_keywords [String], optional
|
12
|
+
# meta_title [String], optional
|
13
|
+
# name [String]
|
14
|
+
# nav_group [String], optional
|
15
|
+
# parsed_body [Text]
|
16
|
+
# priority [Integer]
|
17
|
+
# slug [String]
|
18
|
+
# url [String], optional
|
19
|
+
# visible [Boolean]
|
19
20
|
class EditablePage < ApplicationRecord
|
20
21
|
include RequiredUniqueName
|
21
22
|
include FlatPriority
|
@@ -23,21 +24,21 @@ class EditablePage < ApplicationRecord
|
|
23
24
|
include Checkable
|
24
25
|
include Toggleable
|
25
26
|
|
26
|
-
BODY_LIMIT =
|
27
|
+
BODY_LIMIT = 16_777_215
|
27
28
|
META_LIMIT = 255
|
28
29
|
NAME_LIMIT = 100
|
29
30
|
SLUG_LIMIT = 100
|
30
31
|
|
31
32
|
toggleable :visible
|
32
33
|
|
33
|
-
mount_uploader :image,
|
34
|
+
mount_uploader :image, SimpleImageUploader
|
34
35
|
|
35
36
|
belongs_to :language, optional: true
|
36
37
|
|
37
38
|
before_validation { self.slug = slug.strip unless slug.nil? }
|
38
39
|
|
39
40
|
validates_presence_of :slug
|
40
|
-
validates_uniqueness_of :slug, scope:
|
41
|
+
validates_uniqueness_of :slug, scope: :language_id
|
41
42
|
validates_length_of :body, maximum: BODY_LIMIT
|
42
43
|
validates_length_of :image_alt_text, maximum: META_LIMIT
|
43
44
|
validates_length_of :name, maximum: NAME_LIMIT
|
@@ -89,6 +90,10 @@ class EditablePage < ApplicationRecord
|
|
89
90
|
name
|
90
91
|
end
|
91
92
|
|
93
|
+
def text
|
94
|
+
parsed_body.blank? ? body : parsed_body
|
95
|
+
end
|
96
|
+
|
92
97
|
# @deprecated use #meta_keywords
|
93
98
|
def keywords
|
94
99
|
meta_keywords
|
data/app/models/foreign_site.rb
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Model for foreign site for external authentication
|
4
|
+
#
|
5
|
+
# Attributes:
|
6
|
+
# foreign_users_count [Integer]
|
7
|
+
# name [String]
|
8
|
+
# slug [String]
|
1
9
|
class ForeignSite < ApplicationRecord
|
2
10
|
include RequiredUniqueName
|
3
11
|
include RequiredUniqueSlug
|
@@ -10,9 +18,7 @@ class ForeignSite < ApplicationRecord
|
|
10
18
|
validates_length_of :name, maximum: NAME_LIMIT
|
11
19
|
validates_length_of :slug, maximum: SLUG_LIMIT
|
12
20
|
|
13
|
-
|
14
|
-
ordered_by_name
|
15
|
-
end
|
21
|
+
scope :list_for_administration, -> { ordered_by_name }
|
16
22
|
|
17
23
|
# @param [Hash] data
|
18
24
|
# @param [Hash] tracking
|
@@ -26,51 +26,28 @@ module Biovision
|
|
26
26
|
handler_class.new(entity)
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
@component.settings.merge!(normalize_settings(data))
|
32
|
-
@component.save!
|
29
|
+
def self.default_privilege_name
|
30
|
+
self.class.to_s.demodulize.underscore.gsub('component', 'manager')
|
33
31
|
end
|
34
32
|
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
# @param [User] user
|
34
|
+
# @param [Hash] options
|
35
|
+
def self.allow?(user, options = {})
|
36
|
+
return false if user.nil?
|
38
37
|
|
39
|
-
|
40
|
-
@component.biovision_parameters.list_for_administration
|
41
|
-
end
|
38
|
+
privilege = options[:privilege] || default_privilege_name
|
42
39
|
|
43
|
-
|
44
|
-
#
|
45
|
-
# @param [String] slug
|
46
|
-
def parameter(slug)
|
47
|
-
@component.biovision_parameters.find_by(slug: slug)
|
40
|
+
UserPrivilege.user_has_privilege?(user, privilege)
|
48
41
|
end
|
49
42
|
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# @param [String] name
|
55
|
-
# @param [String] description
|
56
|
-
def set_parameter(slug, value, name = '', description = '')
|
57
|
-
@component[slug] = value
|
58
|
-
|
59
|
-
item = parameter(slug)
|
60
|
-
item.update(name: name, description: description) if item.deletable?
|
61
|
-
|
62
|
-
item
|
43
|
+
# @param [Hash] data
|
44
|
+
def settings=(data)
|
45
|
+
@component.settings.merge!(normalize_settings(data))
|
46
|
+
@component.save!
|
63
47
|
end
|
64
48
|
|
65
|
-
|
66
|
-
|
67
|
-
# @param [String] slug
|
68
|
-
def delete_parameter(slug)
|
69
|
-
item = parameter(slug)
|
70
|
-
|
71
|
-
return unless item&.deletable?
|
72
|
-
|
73
|
-
item.destroy
|
49
|
+
def settings
|
50
|
+
@component.settings
|
74
51
|
end
|
75
52
|
|
76
53
|
# Receive parameter value with default
|
@@ -82,7 +59,7 @@ module Biovision
|
|
82
59
|
# @param [String] default
|
83
60
|
# @return [String]
|
84
61
|
def receive(key, default = '')
|
85
|
-
@component.
|
62
|
+
@component.get(key, default)
|
86
63
|
end
|
87
64
|
|
88
65
|
# Receive parameter value or nil
|
@@ -90,9 +67,9 @@ module Biovision
|
|
90
67
|
# Returns value of component's parameter of nil when it's not found
|
91
68
|
#
|
92
69
|
# @param [String] key
|
93
|
-
# @return [String
|
70
|
+
# @return [String]
|
94
71
|
def [](key)
|
95
|
-
@component.
|
72
|
+
@component.get(key)
|
96
73
|
end
|
97
74
|
|
98
75
|
# Set parameter
|
data/app/services/canonizer.rb
CHANGED
@@ -1,31 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Helper for canonizing and transliterating strings
|
1
4
|
class Canonizer
|
5
|
+
# Keys are not latin letters
|
2
6
|
TRANSLITERATION_MAP = {
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
}
|
7
|
+
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e',
|
8
|
+
'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k',
|
9
|
+
'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o', 'п' => 'p', 'р' => 'r',
|
10
|
+
'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'kh', 'ц' => 'c',
|
11
|
+
'ч' => 'ch', 'ш' => 'sh', 'щ' => 'shh', 'ъ' => '', 'ы' => 'y', 'ь' => '',
|
12
|
+
'э' => 'e', 'ю' => 'yu', 'я' => 'ya',
|
13
|
+
'å' => 'ao', 'ä' => 'ae', 'ö' => 'oe', 'é' => 'e'
|
14
|
+
}.freeze
|
11
15
|
|
12
16
|
# @param [String] text
|
13
17
|
def self.transliterate(text)
|
14
|
-
|
15
|
-
|
16
|
-
|
18
|
+
pattern = Regexp.new "[#{TRANSLITERATION_MAP.keys.join}]"
|
19
|
+
result = text.to_s.downcase.gsub(pattern, TRANSLITERATION_MAP)
|
20
|
+
|
21
|
+
a = /[^-a-z0-9_]/ # non-allowed characters will be replaced with dash
|
22
|
+
b = /\A[-_]*([-a-z0-9_]*[a-z0-9]+)[-_]*\z/ # chop leading and trailing dash
|
23
|
+
result.gsub(a, '-').gsub(b, '\1').gsub(/--+/, '-').gsub(/-+\z/, '')
|
17
24
|
end
|
18
25
|
|
19
26
|
# @param [String] input
|
20
27
|
def self.canonize(input)
|
21
|
-
lowered
|
28
|
+
lowered = input.to_s.downcase.strip
|
22
29
|
canonized = lowered.gsub(/[^a-zа-я0-9ё]/, '')
|
23
30
|
canonized.empty? ? lowered : canonized
|
24
31
|
end
|
25
32
|
|
26
33
|
# @param [String] input
|
27
34
|
def self.urlize(input)
|
28
|
-
lowered = input.downcase.squish
|
29
|
-
lowered.gsub(/[^a-zа-я0-9ё]/, '-').gsub(
|
35
|
+
lowered = input.to_s.downcase.squish
|
36
|
+
lowered.gsub(/[^a-zа-я0-9ё]/, '-').gsub(/-+\z/, '')
|
30
37
|
end
|
31
38
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Receiver for OEmbed-wrapped content
|
4
|
+
class OembedReceiver
|
5
|
+
PATTERN = %r{<oembed url="([^"]+)"></oembed>}.freeze
|
6
|
+
|
7
|
+
attr_accessor :url
|
8
|
+
|
9
|
+
# @param [String] url
|
10
|
+
def initialize(url = '')
|
11
|
+
@url = url
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [String] text
|
15
|
+
def self.convert(text)
|
16
|
+
receiver = new
|
17
|
+
text.gsub(PATTERN) do |fragment|
|
18
|
+
receiver.url = fragment.match(PATTERN)[1]
|
19
|
+
receiver.code
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def code
|
24
|
+
@host = URI.parse(@url).host
|
25
|
+
receive url_for_host
|
26
|
+
end
|
27
|
+
|
28
|
+
def fallback
|
29
|
+
attributes = %(rel="external nofollow noopener noreferrer" target="_blank")
|
30
|
+
%(<a href="#{@url}" #{attributes}>#{@host}</a>)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @param [String] embed_url
|
36
|
+
def receive(embed_url)
|
37
|
+
response = RestClient.get(embed_url)
|
38
|
+
parse(response.body)
|
39
|
+
rescue RestClient::Exception => e
|
40
|
+
Rails.logger.warn("Cannot receive data for #{embed_url}: #{e}")
|
41
|
+
fallback
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [String] response
|
45
|
+
def parse(response)
|
46
|
+
json = JSON.parse(response)
|
47
|
+
json['html'] || fallback
|
48
|
+
rescue JSON::ParserError => e
|
49
|
+
Rails.logger.warn("Cannot parse response #{response}: #{e}")
|
50
|
+
fallback
|
51
|
+
end
|
52
|
+
|
53
|
+
def url_for_host
|
54
|
+
case @host
|
55
|
+
when 'www.youtube.com', 'youtube.com', 'youtu.be'
|
56
|
+
url_for_youtube
|
57
|
+
when 'twitter.com', 'www.twitter.com'
|
58
|
+
url_for_twitter
|
59
|
+
when 'www.facebook.com', 'facebook.com'
|
60
|
+
url_for_facebook
|
61
|
+
when 'www.instagram.com', 'instagr.am', 'instagram.com'
|
62
|
+
url_for_instagram
|
63
|
+
else
|
64
|
+
default_url
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def url_for_youtube
|
69
|
+
"https://www.youtube.com/oembed?url=#{CGI.escape(@url)}&format=json"
|
70
|
+
end
|
71
|
+
|
72
|
+
def url_for_twitter
|
73
|
+
"https://publish.twitter.com/oembed?url=#{CGI.escape(@url)}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def url_for_facebook
|
77
|
+
url = CGI.escape(@url)
|
78
|
+
if @url.match?('/videos/')
|
79
|
+
"https://www.facebook.com/plugins/video/oembed.json/?url=#{url}"
|
80
|
+
else
|
81
|
+
"https://www.facebook.com/plugins/post/oembed.json/?url=#{url}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def url_for_instagram
|
86
|
+
"https://api.instagram.com/oembed?url=#{CGI.escape(@url)}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def default_url
|
90
|
+
"https://#{@host}/oembed?url=#{CGI.escape(@url)}&format=json"
|
91
|
+
end
|
92
|
+
end
|