diff --git a/Gemfile b/Gemfile index 3965ec4501bc74ed118980561f109216a452ec8a..6544368ae1cbc82e64c964c03c59957972989b29 100644 --- a/Gemfile +++ b/Gemfile @@ -57,7 +57,7 @@ gem "simple_form" gem "simple_form_bs5_file_input"#, path: "../simple_form_bs5_file_input" gem "simple_form_password_with_hints"#, path: "../simple_form_password_with_hints" gem "sprockets-rails", "~> 3.4" -gem "summernote-rails", git: "https://github.com/noesya/summernote-rails.git", branch: "activestorage" +gem "summernote-rails", git: "https://github.com/noesya/summernote-rails.git", branch: "feature/cleaner" # gem "summernote-rails", path: "../summernote-rails" gem "two_factor_authentication", git: "https://github.com/noesya/two_factor_authentication.git" # gem "two_factor_authentication", path: "../two_factor_authentication" diff --git a/Gemfile.lock b/Gemfile.lock index 2d5afe7f2c7fc9695c119d4085b58e1a736cc517..9bda4c9a3d86e6bec68ba9aea630295e2db8e6d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,11 @@ GIT remote: https://github.com/noesya/summernote-rails.git - revision: 585293f150052c8f158b1fab7d8206a94d368d4a - branch: activestorage + revision: 26889491557e607cedade7590b18ae28161215ec + branch: feature/cleaner specs: - summernote-rails (0.8.20.7) + summernote-rails (0.8.20.1) nokogiri - rails (>= 6.0) + railties (>= 3.1) GIT remote: https://github.com/noesya/two_factor_authentication.git diff --git a/app/models/communication/website/imported/author.rb b/app/models/communication/website/imported/author.rb index 98469ecdf16660e072eb7885f1c3ca3ab0473fb5..46d127f66b10ded1e9d18b46bb903bd3b3f237c4 100644 --- a/app/models/communication/website/imported/author.rb +++ b/app/models/communication/website/imported/author.rb @@ -54,7 +54,7 @@ class Communication::Website::Imported::Author < ApplicationRecord def sync if author.nil? - self.author = University::Person.new university: university + self.author = University::Person.new university: university, language: university.default_language self.author.last_name = "Doe" # No title yet self.author.first_name = "John" # No title yet self.author.save diff --git a/app/models/communication/website/imported/category.rb b/app/models/communication/website/imported/category.rb index 6549a083592148bd6688d37d9d8dc303a42d5054..022282e4dca9a2dc363088659e5ad6d42b649e4a 100644 --- a/app/models/communication/website/imported/category.rb +++ b/app/models/communication/website/imported/category.rb @@ -59,8 +59,9 @@ class Communication::Website::Imported::Category < ApplicationRecord def sync if category.nil? - self.category = Communication::Website::Category.new university: university, - website: website.website # Real website, not imported website + self.category = Communication::Website::Category.new university: university, + website: website.website, # Real website, not imported website + language: website.website.default_language self.category.name = "Untitled" # No title yet self.category.save end diff --git a/app/models/communication/website/imported/page.rb b/app/models/communication/website/imported/page.rb index e064b3790967af46d6af7d30d45fab5fdba2c324..e03e729fa81d54cc7bc308a7926646e58fcfb1f4 100644 --- a/app/models/communication/website/imported/page.rb +++ b/app/models/communication/website/imported/page.rb @@ -37,7 +37,6 @@ # class Communication::Website::Imported::Page < ApplicationRecord include WithUniversity - include Communication::Website::Imported::WithRichText belongs_to :website, class_name: 'Communication::Website::Imported::Website' @@ -50,7 +49,6 @@ class Communication::Website::Imported::Page < ApplicationRecord optional: true before_validation :sync - after_commit :sync_attachments, on: [:create, :update] default_scope { order(:path) } @@ -77,6 +75,8 @@ class Communication::Website::Imported::Page < ApplicationRecord if page.nil? self.page = Communication::Website::Page.new university: university, website: website.website, # Real website, not imported website + language: website.website.default_language, + parent: website.website.special_page(Communication::Website::Page::Home), slug: path self.page.title = "Untitled" self.page.save @@ -91,37 +91,13 @@ class Communication::Website::Imported::Page < ApplicationRecord page.title = sanitized_title unless sanitized_title.blank? # If there is no title, leave it with "Untitled" page.slug = slug page.meta_description = Wordpress.clean_string excerpt.to_s - page.text = Wordpress.clean_html content.to_s page.published = true page.save - end - def sync_attachments - return unless ENV['APPLICATION_ENV'] == 'development' || updated_at > page.updated_at - if featured_medium.present? - unless featured_medium.file.attached? - featured_medium.load_remote_file! - featured_medium.save - end - page.featured_image.attach( - io: URI.open(featured_medium.file.blob.url), - filename: featured_medium.file.blob.filename, - content_type: featured_medium.file.blob.content_type - ) - else - fragment = Nokogiri::HTML.fragment(page.text.to_s) - image = fragment.css('img').first - if image.present? - begin - url = image.attr('src') - download_service = DownloadService.download(url) - page.featured_image.attach(download_service.attachable_data) - image.remove - page.update(text: fragment.to_html) - rescue - end - end - end - page.update(text: rich_text_with_attachments(page.text.to_s)) + chapter = page.blocks.where(university: website.university, template_kind: :chapter).first_or_create + chapter_data = chapter.data.deep_dup + chapter_data['text'] = Wordpress.clean_html(content.to_s) + chapter.data = chapter_data + chapter.save end end diff --git a/app/models/communication/website/imported/post.rb b/app/models/communication/website/imported/post.rb index f8b8ac4be2b0b7711c627e4134a8780a50ef565a..6cd5ecf00a6cbbf727ea6ae874bccd5e9e535eec 100644 --- a/app/models/communication/website/imported/post.rb +++ b/app/models/communication/website/imported/post.rb @@ -38,19 +38,19 @@ # class Communication::Website::Imported::Post < ApplicationRecord include WithUniversity - include Communication::Website::Imported::WithRichText + include Communication::Website::Imported::WithFeaturedImage belongs_to :website, class_name: 'Communication::Website::Imported::Website' belongs_to :post, class_name: 'Communication::Website::Post', optional: true + alias_attribute :generated_object, :post belongs_to :featured_medium, class_name: 'Communication::Website::Imported::Medium', optional: true before_validation :sync - after_commit :sync_attachments, on: [:create, :update] default_scope { order(path: :desc) } @@ -79,7 +79,8 @@ class Communication::Website::Imported::Post < ApplicationRecord def sync if post.nil? self.post = Communication::Website::Post.new university: university, - website: website.website # Real website, not imported website + website: website.website, # Real website, not imported website + language: website.website.default_language self.post.title = "Untitled" # No title yet self.post.save else @@ -94,47 +95,32 @@ class Communication::Website::Imported::Post < ApplicationRecord post.title = sanitized_title unless sanitized_title.blank? # If there is no title, leave it with "Untitled" post.slug = slug post.meta_description = Wordpress.clean_string excerpt.to_s - post.text = Wordpress.clean_html content.to_s post.created_at = created_at post.updated_at = updated_at post.published_at = published_at if published_at post.published = true + sync_author + sync_categories + post.save + + chapter = post.blocks.where(university: website.university, template_kind: :chapter).first_or_create + chapter_data = chapter.data.deep_dup + chapter_data['text'] = Wordpress.clean_html(content.to_s) + chapter.data = chapter_data + chapter.save + end + + def sync_author imported_author = website.authors.where(identifier: author).first post.author = imported_author.author if imported_author&.author.present? + end + + def sync_categories imported_categories = website.categories.where(identifier: categories) imported_categories.each do |imported_category| post.categories << imported_category.category unless post.categories.pluck(:id).include?(imported_category.category_id) end - post.save end - def sync_attachments - return unless ENV['APPLICATION_ENV'] == 'development' || updated_at > post.updated_at - if featured_medium.present? - unless featured_medium.file.attached? - featured_medium.load_remote_file! - featured_medium.save - end - post.featured_image.attach( - io: URI.open(featured_medium.file.blob.url), - filename: featured_medium.file.blob.filename, - content_type: featured_medium.file.blob.content_type - ) - else - fragment = Nokogiri::HTML.fragment(post.text.body.to_html) - image = fragment.css('img').first - if image.present? - begin - url = image.attr('src') - download_service = DownloadService.download(url) - post.featured_image.attach(download_service.attachable_data) - image.remove - post.update(text: fragment.to_html) - rescue - end - end - end - post.update(text: rich_text_with_attachments(post.text.body.to_html)) - end end diff --git a/app/models/communication/website/imported/with_featured_image.rb b/app/models/communication/website/imported/with_featured_image.rb new file mode 100644 index 0000000000000000000000000000000000000000..000d0be664295352cbb1bb7354660e25dfdc0867 --- /dev/null +++ b/app/models/communication/website/imported/with_featured_image.rb @@ -0,0 +1,48 @@ +module Communication::Website::Imported::WithFeaturedImage + extend ActiveSupport::Concern + + included do + after_commit :sync_featured_image, on: [:create, :update] + end + + def sync_featured_image + return unless ENV['APPLICATION_ENV'] == 'development' || updated_at > generated_object.updated_at + + if featured_medium.present? + sync_featured_image_from_featured_medium + else + sync_featured_image_from_content + end + end + + def sync_featured_image_from_featured_medium + unless featured_medium.file.attached? + featured_medium.load_remote_file! + featured_medium.save + end + generated_object.featured_image.attach( + io: URI.open(featured_medium.file.blob.url), + filename: featured_medium.file.blob.filename, + content_type: featured_medium.file.blob.content_type + ) + end + + def sync_featured_image_from_content + chapter = generated_object.blocks.where(university: website.university, template_kind: :chapter).first_or_create + chapter_data = chapter.data.deep_dup + fragment = Nokogiri::HTML.fragment(chapter_data['text'].to_s) + image = fragment.css('img').first + if image.present? + begin + url = image.attr('src') + download_service = DownloadService.download(url) + generated_object.featured_image.attach(download_service.attachable_data) + image.remove + chapter_data['text'] = fragment.to_html + chapter.data = chapter_data + chapter.save + rescue + end + end + end +end \ No newline at end of file diff --git a/app/models/communication/website/imported/with_rich_text.rb b/app/models/communication/website/imported/with_rich_text.rb deleted file mode 100644 index 3e3f2962ccaba928254523e0a21fb4996bf8b103..0000000000000000000000000000000000000000 --- a/app/models/communication/website/imported/with_rich_text.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Communication::Website::Imported::WithRichText - extend ActiveSupport::Concern - - # https://github.com/basecamp/trix/blob/7940a9a3b7129f8190ef37e086809260d7ccfe32/src/trix/models/attachment.coffee#L4 - TRIX_PREVIEWABLE_PATTERN = /^image(\/(gif|png|jpe?g)|$)/ - - protected - - def rich_text_with_attachments(text) - fragment = Nokogiri::HTML.fragment(text) - fragment = replace_tags_with_attachments(fragment, 'a', 'href') - fragment = replace_tags_with_attachments(fragment, 'img', 'src') - fragment.to_html - end - - def replace_tags_with_attachments(fragment, tag_name, attribute_name) - nodes = fragment.css("#{tag_name}[#{attribute_name}*=\"#{website.uploads_url}\"]") - nodes.each do |node| - begin - url = node.attr(attribute_name) - blob = load_blob_from_url(url) - blob.analyze - blob_path = Rails.application.routes.url_helpers.rails_blob_path(blob, only_path: true) - attachment_node = ActionText::Attachment.from_attachable(blob).node - attachment_node['url'] = "#{university.url}#{blob_path}" - attachment_node['width'] = blob.metadata['width'] - attachment_node['height'] = blob.metadata['height'] - attachment_node['presentation'] = TRIX_PREVIEWABLE_PATTERN.match?(blob.content_type) ? 'gallery' : nil - node.replace attachment_node.to_s - rescue - end - end - fragment - end - - def load_blob_from_url(url) - medium = website.media.for_variant_url(url).first - if medium.present? - unless medium.file.attached? - medium.load_remote_file! - medium.save - end - # Currently a copy, should we link the medium blob instead? - blob = medium.file.blob.open do |tempfile| - ActiveStorage::Blob.create_and_upload!( - io: tempfile, - filename: medium.file.blob.filename, - content_type: medium.file.blob.content_type - ) - end - else - download_service = DownloadService.download(url) - blob = ActiveStorage::Blob.create_and_upload!(download_service.attachable_data) - end - blob.update_column(:university_id, self.university_id) - blob.analyze_later - blob - end -end diff --git a/app/models/communication/website/page.rb b/app/models/communication/website/page.rb index 97a71f1a77c4addd1edaff7ff79f71f85aaa873a..1a3317f754900d41953edc7c38ebcd6b7bd61d52 100644 --- a/app/models/communication/website/page.rb +++ b/app/models/communication/website/page.rb @@ -61,7 +61,7 @@ class Communication::Website::Page < ApplicationRecord include WithPermalink include WithTranslations - has_summernote :text + has_summernote :text # TODO: Remove text attribute belongs_to :website, foreign_key: :communication_website_id diff --git a/app/models/communication/website/post.rb b/app/models/communication/website/post.rb index 5299cd44121f92a0b6a3aa2eb28d9e099f77242f..e2095f2f03f9a44b105a3600b4417bfaa345bb8b 100644 --- a/app/models/communication/website/post.rb +++ b/app/models/communication/website/post.rb @@ -49,7 +49,7 @@ class Communication::Website::Post < ApplicationRecord include WithSlug # We override slug_unavailable? method include WithTranslations - has_summernote :text + has_summernote :text # TODO: Remove text attribute has_one :imported_post, class_name: 'Communication::Website::Imported::Post', diff --git a/app/models/communication/website/with_import.rb b/app/models/communication/website/with_import.rb index 0643cd2b5d015a59ed3e814d91d1963d3503d566..bb7576c4d7dfc38d3dc2ebc19f3fc6333eae84a1 100644 --- a/app/models/communication/website/with_import.rb +++ b/app/models/communication/website/with_import.rb @@ -8,10 +8,7 @@ module Communication::Website::WithImport end def import! - imported_website = Communication::Website::Imported::Website.where( - website: self, university: university - ).first_or_create unless imported? - + create_imported_website(university: university) unless imported? imported_website.run! reload end diff --git a/app/models/concerns/with_blobs.rb b/app/models/concerns/with_blobs.rb index 27a729abf2ae36972f22040537d611906c4e7a16..67856342acef4d12e02a4c77994d9dd7f8eeceae 100644 --- a/app/models/concerns/with_blobs.rb +++ b/app/models/concerns/with_blobs.rb @@ -13,33 +13,17 @@ module WithBlobs blobs_with_ids inherited_blob_ids end - def summernote_embeds - summernote_embeds_reflection_names.map { |summernote_reflection_name| - public_send(summernote_reflection_name) - }.flatten - end - protected def explicit_blob_ids - [summernote_blob_ids] + [] end def inherited_blob_ids [] end - def summernote_blob_ids - summernote_embeds_reflection_names.map { |summernote_reflection_name| - public_send(summernote_reflection_name).pluck(:blob_id) - }.flatten - end - def blobs_with_ids(ids) university.active_storage_blobs.where(id: ids.flatten.compact) end - - def summernote_embeds_reflection_names - @summernote_embeds_reflection_names ||= _reflections.keys.select { |name| name.ends_with?('_summernote_embeds_attachments') } - end end diff --git a/app/models/concerns/with_inheritance.rb b/app/models/concerns/with_inheritance.rb index 3b3002479c448757e8f92e421554e9810eea9ce7..9df918dddedd5369dc6e92db76966d85bbe8c0ca 100644 --- a/app/models/concerns/with_inheritance.rb +++ b/app/models/concerns/with_inheritance.rb @@ -23,13 +23,13 @@ module WithInheritance def best(property) value = send(property) - html = value.nil? ? '' : value.to_html + html = value.to_s Static.blank?(html) ? parent&.send("best_#{property}") : value end def best_source(property, is_ancestor: false) value = send(property) - return (is_ancestor ? self : nil) if Static.has_content?(value&.to_html) + return (is_ancestor ? self : nil) if Static.has_content?(value.to_s) parent&.send(:best_source, property, is_ancestor: true) end end diff --git a/app/models/university/organization.rb b/app/models/university/organization.rb index 46a5f668ec2305ea372fa5f3cfd5c28fd44bb9f2..4f58c17bd550a052f7570028aee0df3f774a72be 100644 --- a/app/models/university/organization.rb +++ b/app/models/university/organization.rb @@ -45,12 +45,12 @@ class University::Organization < ApplicationRecord attr_accessor :created_from_extranet + has_summernote :text + has_many :experiences, class_name: 'University::Person::Experience', dependent: :destroy - has_summernote :text - has_one_attached_deletable :logo has_one_attached_deletable :logo_on_dark_background diff --git a/app/services/osuny/sanitizer.rb b/app/services/osuny/sanitizer.rb index ede92d17125ac5eb9b3365f935a1a1f0c57ac37f..f285c32f85e8867369cf18f183fe40ab17280660 100644 --- a/app/services/osuny/sanitizer.rb +++ b/app/services/osuny/sanitizer.rb @@ -1,6 +1,9 @@ class Osuny::Sanitizer include ActionView::Helpers::SanitizeHelper + self.sanitized_allowed_tags = Rails.application.config.action_view.sanitized_allowed_tags + self.sanitized_allowed_attributes = Rails.application.config.action_view.sanitized_allowed_attributes + # type(ActiveRecord) = ['text', 'string'] def self.sanitize(input, type = 'text') return '' if input.blank? @@ -9,12 +12,8 @@ class Osuny::Sanitizer private - # input can be String or ActionText::Content def self.sanitize_text(input) - input.is_a?(String) ? safe_list_sanitizer.sanitize(input) - : ActionText::Content.new( - safe_list_sanitizer.sanitize(input.to_html) - ) + safe_list_sanitizer.sanitize(input) end def self.sanitize_string(string) diff --git a/app/views/admin/application/property/_summernote_embeds.html.erb b/app/views/admin/application/property/_summernote_embeds.html.erb deleted file mode 100644 index a714433bb7206bedbfd181746949d5b0a1852a95..0000000000000000000000000000000000000000 --- a/app/views/admin/application/property/_summernote_embeds.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -<%= osuny_label object.class.human_attribute_name(property) %> -<% if object.summernote_embeds.any? %> - <% object.summernote_embeds.each do |embed| %> - <div class="row mb-3"> - <div class="col-md-2"> - <div class="img-thumbnail text-center"> - <% if embed.variable? %> - <%= kamifusen_tag embed, class: 'img-fluid' %> - <% else %> - <i class="<%= Icon::FILE %> p-3 fa-2x"></i> - <% end %> - </div> - </div> - <div class="col-md-10"> - <p class="mb-1"> - <%= embed.filename %> - <%= number_to_human_size embed.metadata['filesize'] %> - </p> - <%= link_to t('download'), - embed.blob.url(disposition: 'attachment', filename: embed.filename), - class: 'btn btn-xs btn-light' %> - </div> - </div> - <% end %> -<% else %> - <p>0</p> -<% end %> diff --git a/app/views/admin/university/organizations/show.html.erb b/app/views/admin/university/organizations/show.html.erb index 1359685d1c8b48ae205d22728090529cc75e5d5f..049c93a9124fadae9d59702ca2cb4cd3eb60534a 100644 --- a/app/views/admin/university/organizations/show.html.erb +++ b/app/views/admin/university/organizations/show.html.erb @@ -5,14 +5,14 @@ <%= osuny_panel University::Organization.human_attribute_name('text') do %> <%= @organization.text.to_s.html_safe %> - <% end if strip_tags(@organization.text.to_html).present? %> + <% end if strip_tags(@organization.text.to_s).present? %> <%= osuny_panel University::Organization.human_attribute_name('contact') do %> <div class="row pure__row--small"> <% [ - :address, - :zipcode, - :city, + :address, + :zipcode, + :city, :country ].each do |property| %> <% value = @organization.send property %> @@ -31,10 +31,10 @@ </div> <% end %> <% [ - :phone, - :email, - :linkedin, - :twitter, + :phone, + :email, + :linkedin, + :twitter, :mastodon ].each do |property| %> <% value = @organization.send property %> @@ -74,7 +74,7 @@ <%= osuny_label University::Organization.human_attribute_name('active') %> <p><%= t @organization.active %></> <% end %> - + <%= osuny_panel t('university.organization.logo') do %> <% if @organization.logo.attached? %> <%= osuny_label University::Organization.human_attribute_name('logo') %> diff --git a/config/application.rb b/config/application.rb index 03280c1194e0a00bbcdb9e3dee46db8cfa04474e..fa9ee9cc7659a90eecb37623b4694e88c5589804 100644 --- a/config/application.rb +++ b/config/application.rb @@ -54,19 +54,10 @@ module Osuny config.active_storage.supported_image_processing_methods = ["+"] config.action_view.sanitized_allowed_tags = [ - "a", "abbr", "acronym", "address", "b", "big", "blockquote", "br", - "cite", "code", "dd", "del", "dfn", "div", "dl", "dt", "em", - "h1", "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins", "kbd", "li", "ol", - "p", "picture", "pre", "samp", "small", "source", "span", "strong", - "sub", "sup", "tt", "u", "ul", "var", "video", "iframe", "action-text-attachment", - "table", "thead", "tbody", "tr", "td", "th" + "a", "b", "br", "em", "i", "img", "li", "ol", "p", "strong", "sub", "sup", "ul" ] config.action_view.sanitized_allowed_attributes = [ - "abbr", "allowfullscreen", "alt", "cite", "controls", "datetime", - "decoding", "frameborder", "height", "href", "loading", "mozallowfullscreen", - "name", "sizes", "src", "srcset", "target", "title", "type", - "webkitallowfullscreen", "width", "xml:lang", - "sgid", "content-type", "url", "filename", "filesize", "previewable", "referrerpolicy" + "href", "target", "title" ] config.allowed_special_chars = '#?!,_@$%^&*+:;£µ-' diff --git a/db/migrate/20230113160749_add_i18n_infos_to_communication_pages.rb b/db/migrate/20230113160749_add_i18n_infos_to_communication_pages.rb index 2315d1f17e0749496d4de5cdd99e133f00e8f7fd..ee1f2f11e95e9a6c140dce871ae64fb815e1efe0 100644 --- a/db/migrate/20230113160749_add_i18n_infos_to_communication_pages.rb +++ b/db/migrate/20230113160749_add_i18n_infos_to_communication_pages.rb @@ -2,6 +2,7 @@ class AddI18nInfosToCommunicationPages < ActiveRecord::Migration[7.0] # communication_website_pages already have language_id def up add_reference :communication_website_pages, :original, foreign_key: {to_table: :communication_website_pages}, type: :uuid + Communication::Website::Page.reset_column_information Communication::Website::Page.where(language_id: nil).each do |page| page.update_column(:language_id, page.website.default_language_id) end diff --git a/db/migrate/20230203134137_add_default_language_to_universities.rb b/db/migrate/20230203134137_add_default_language_to_universities.rb index 2f0e8f9223322cd41e4bb032622a98258ad9863e..13d38bac07f2b460f1b1c94b16e43968e353dac4 100644 --- a/db/migrate/20230203134137_add_default_language_to_universities.rb +++ b/db/migrate/20230203134137_add_default_language_to_universities.rb @@ -7,7 +7,7 @@ class AddDefaultLanguageToUniversities < ActiveRecord::Migration[7.0] language ||= Language.find_by(iso_code: 'en') language ||= Language.first - University.all.update_all(default_language_id: language.id) + University.all.update_all(default_language_id: language.id) if University.any? change_column_null :universities, :default_language_id, false end diff --git a/db/seeds.rb b/db/seeds.rb index 6415a82700766664cda817f4c0b1f36a79e99ed9..706c5282e202b4f78a21b2c5585f79f1ca444a2c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,8 +1,8 @@ -University.create name: 'Osuny', identifier: 'demo', sms_sender_name: 'Osuny' - -Language.where(name: 'French', iso_code: 'fr').first_or_create +fr = Language.where(name: 'French', iso_code: 'fr').first_or_create Language.where(name: 'English', iso_code: 'en').first_or_create +University.create name: 'Osuny', identifier: 'demo', sms_sender_name: 'Osuny', default_language: fr + Administration::Qualiopi::Criterion.destroy_all Administration::Qualiopi::Criterion.create [ {