diff --git a/app/controllers/admin/emojis_controller.rb b/app/controllers/admin/emojis_controller.rb index 37d12df4dd6..2674936f6e3 100644 --- a/app/controllers/admin/emojis_controller.rb +++ b/app/controllers/admin/emojis_controller.rb @@ -14,25 +14,50 @@ class Admin::EmojisController < Admin::AdminController .gsub(/_{2,}/, '_') .downcase - data = if Emoji.exists?(name) - failed_json.merge(errors: [I18n.t("emoji.errors.name_already_exists", name: name)]) - elsif emoji = Emoji.create_for(file, name) - emoji - else - failed_json.merge(errors: [I18n.t("emoji.errors.error_while_storing_emoji")]) - end + upload = Upload.create_for( + current_user.id, + file.tempfile, + file.original_filename, + File.size(file.tempfile.path), + image_type: 'custom_emoji' + ) + + data = + if upload.persisted? + custom_emoji = CustomEmoji.new(name: name, upload: upload) + + if custom_emoji.save + Emoji.clear_cache + { name: custom_emoji.name, url: custom_emoji.upload.url } + else + failed_json.merge(errors: custom_emoji.errors.full_messages) + end + else + failed_json.merge(errors: upload.errors.full_messages) + end MessageBus.publish("/uploads/emoji", data.as_json, user_ids: [current_user.id]) end - render json: success_json end def destroy name = params.require(:id) - Emoji[name].try(:remove) - render nothing: true + + custom_emoji = CustomEmoji.find_by(name: name) + raise Discourse::InvalidParameters unless custom_emoji + + CustomEmoji.transaction do + custom_emoji.upload.destroy! + custom_emoji.destroy! + end + + Emoji.clear_cache + + Jobs.enqueue(:rebake_custom_emoji_posts, name: name) + + render json: success_json end end diff --git a/app/jobs/onceoff/migrate_custom_emojis.rb b/app/jobs/onceoff/migrate_custom_emojis.rb new file mode 100644 index 00000000000..5fcdc867e2c --- /dev/null +++ b/app/jobs/onceoff/migrate_custom_emojis.rb @@ -0,0 +1,35 @@ +module Jobs + class MigrateCustomEmojis < Jobs::Onceoff + def execute_onceoff(args) + return if Rails.env.test? + + CustomEmoji.transaction do + Dir["#{Rails.root}/#{Emoji.base_directory}/*.{png,gif}"].each do |path| + name = File.basename(path, File.extname(path)) + + File.open(path) do |file| + upload = Upload.create_for( + Discourse.system_user.id, + file, + File.basename(path), + file.size, + image_type: 'custom_emoji' + ) + + if upload.persisted? + CustomEmoji.create!(name: name, upload: upload) + else + raise "Failed to create upload for '#{name}' custom emoji" + end + end + end + + Emoji.clear_cache + + Post.where("cooked LIKE '%#{Emoji.base_url}%'").find_each do |post| + post.rebake! + end + end + end + end +end diff --git a/app/jobs/regular/rebake_custom_emoji_posts.rb b/app/jobs/regular/rebake_custom_emoji_posts.rb new file mode 100644 index 00000000000..d1afbc87324 --- /dev/null +++ b/app/jobs/regular/rebake_custom_emoji_posts.rb @@ -0,0 +1,8 @@ +module Jobs + class RebakeCustomEmojiPosts < Jobs::Base + def execute(args) + name = args[:name] + Post.where("raw LIKE '%:#{name}:%'").find_each { |post| post.rebake! } + end + end +end diff --git a/app/jobs/regular/resize_emoji.rb b/app/jobs/regular/resize_emoji.rb deleted file mode 100644 index fa30e629e7e..00000000000 --- a/app/jobs/regular/resize_emoji.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Jobs - - class ResizeEmoji < Jobs::Base - - def execute(args) - path = args[:path] - return unless File.exists?(path) - - opts = { - allow_animation: true, - force_aspect_ratio: SiteSetting.enforce_square_emoji - } - # make sure emoji aren't too big - OptimizedImage.downsize(path, path, "100x100", opts) - end - end - -end diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb index 9a12757609c..263e7563b7e 100644 --- a/app/jobs/scheduled/clean_up_uploads.rb +++ b/app/jobs/scheduled/clean_up_uploads.rb @@ -22,11 +22,13 @@ module Jobs .joins("LEFT JOIN user_avatars ua ON (ua.gravatar_upload_id = uploads.id OR ua.custom_upload_id = uploads.id)") .joins("LEFT JOIN user_profiles up ON up.profile_background = uploads.url OR up.card_background = uploads.url") .joins("LEFT JOIN categories c ON c.uploaded_logo_id = uploads.id OR c.uploaded_background_id = uploads.id") + .joins("LEFT JOIN custom_emojis ce ON ce.upload_id = uploads.id") .where("pu.upload_id IS NULL") .where("u.uploaded_avatar_id IS NULL") .where("ua.gravatar_upload_id IS NULL AND ua.custom_upload_id IS NULL") .where("up.profile_background IS NULL AND up.card_background IS NULL") .where("c.uploaded_logo_id IS NULL AND c.uploaded_background_id IS NULL") + .where("ce.upload_id IS NULL") .where("uploads.url NOT IN (?)", ignore_urls) result.find_each do |upload| diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb new file mode 100644 index 00000000000..bdc2795846b --- /dev/null +++ b/app/models/custom_emoji.rb @@ -0,0 +1,6 @@ +class CustomEmoji < ActiveRecord::Base + belongs_to :upload + + validates :name, presence: true, uniqueness: true + validates :upload_id, presence: true +end diff --git a/app/models/emoji.rb b/app/models/emoji.rb index c58cff9dd32..7fb7e905caf 100644 --- a/app/models/emoji.rb +++ b/app/models/emoji.rb @@ -14,14 +14,6 @@ class Emoji @path = path end - def remove - return if path.blank? - if File.exists?(path) - File.delete(path) rescue nil - Emoji.clear_cache - end - end - def self.all Discourse.cache.fetch(cache_key("all_emojis")) { standard | custom } end @@ -46,14 +38,6 @@ class Emoji Emoji.custom.detect { |e| e.name == name } end - def self.create_from_path(path) - extension = File.extname(path) - Emoji.new(path).tap do |e| - e.name = File.basename(path, ".*") - e.url = "#{base_url}/#{e.name}#{extension}" - end - end - def self.create_from_db_item(emoji) name = emoji["name"] filename = "#{emoji['filename'] || name}.png" @@ -63,22 +47,6 @@ class Emoji end end - def self.create_for(file, name) - extension = File.extname(file.original_filename) - path = "#{Emoji.base_directory}/#{name}#{extension}" - full_path = "#{Rails.root}/#{path}" - - # store the emoji - FileUtils.mkdir_p(Pathname.new(path).dirname) - File.open(path, "wb") { |f| f << file.tempfile.read } - # clear the cache - Emoji.clear_cache - # launch resize job - Jobs.enqueue(:resize_emoji, path: full_path) - # return created emoji - Emoji[name] - end - def self.cache_key(name) "#{name}:#{EMOJI_VERSION}:#{Plugin::CustomEmoji.cache_key}" end @@ -124,9 +92,12 @@ class Emoji def self.load_custom result = [] - Dir.glob(File.join(Emoji.base_directory, "*.{png,gif}")) - .sort - .each { |emoji| result << Emoji.create_from_path(emoji) } + CustomEmoji.all.each do |emoji| + result << Emoji.new.tap do |e| + e.name = emoji.name + e.url = emoji.upload.url + end + end Plugin::CustomEmoji.emojis.each do |name, url| result << Emoji.new.tap do |e| diff --git a/app/models/upload.rb b/app/models/upload.rb index d6119abc13c..1200d2487eb 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -57,7 +57,12 @@ class Upload < ActiveRecord::Base end # list of image types that will be cropped - CROPPED_IMAGE_TYPES ||= %w{avatar profile_background card_background} + CROPPED_IMAGE_TYPES ||= %w{ + avatar + profile_background + card_background + custom_emoji + } WHITELISTED_SVG_ELEMENTS ||= %w{ circle @@ -92,7 +97,7 @@ class Upload < ActiveRecord::Base # options # - content_type # - origin (url) - # - image_type ("avatar", "profile_background", "card_background") + # - image_type ("avatar", "profile_background", "card_background", "custom_emoji") # - is_attachment_for_group_message (boolean) def self.create_for(user_id, file, filename, filesize, options = {}) upload = Upload.new @@ -145,6 +150,8 @@ class Upload < ActiveRecord::Base max_width = 590 * max_pixel_ratio width, height = ImageSizer.resize(w, h, max_width: max_width, max_height: max_width) OptimizedImage.downsize(file.path, file.path, "#{width}x#{height}", filename: filename, allow_animation: allow_animation) + when "custom_emoji" + OptimizedImage.downsize(file.path, file.path, "100x100", filename: filename, allow_animation: allow_animation) end end diff --git a/config/initializers/100-wrap_parameters.rb b/config/initializers/100-wrap_parameters.rb index 02e47b12b8a..999df20181e 100644 --- a/config/initializers/100-wrap_parameters.rb +++ b/config/initializers/100-wrap_parameters.rb @@ -12,4 +12,3 @@ end ActiveSupport.on_load(:active_record) do self.include_root_in_json = false end - diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 1619309dd96..b8e1d9711bb 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -391,6 +391,10 @@ en: attributes: payload_url: invalid: "URL is invalid. URL should includes http:// or https://. And no blank is allowed." + custom_emoji: + attributes: + name: + taken: is already in use by another emoji user_profile: no_info_me: "
some post with yay
" + ) + + custom_emoji.destroy! + Emoji.clear_cache + described_class.new.execute(name: 'test') + + expect(post.reload.cooked).to eq('some post with :test: yay
') + end +end