2013-07-06 19:10:53 +02:00
# Post processing that we can do after a post has already been cooked.
2013-07-08 01:39:08 +02:00
# For example, inserting the onebox content, or image sizes/thumbnails.
2013-02-05 14:16:51 -05:00
2013-11-20 13:10:08 +01:00
require_dependency 'url_helper'
2015-12-04 01:31:18 +05:30
require_dependency 'pretty_text'
2018-03-13 13:07:51 -04:00
require_dependency 'quote_comparer'
2013-02-05 14:16:51 -05:00
class CookedPostProcessor
2013-06-21 18:29:40 +02:00
include ActionView :: Helpers :: NumberHelper
2013-02-19 17:57:14 +11:00
2018-11-27 16:00:31 +08:00
INLINE_ONEBOX_LOADING_CSS_CLASS = "inline-onebox-loading"
INLINE_ONEBOX_CSS_CLASS = "inline-onebox"
2018-12-14 17:44:38 -05:00
LOADING_SIZE = 10
LOADING_COLORS = 32
2018-11-27 16:00:31 +08:00
2017-11-13 11:19:06 +11:00
attr_reader :cooking_options , :doc
2016-08-12 15:28:54 -04:00
2017-07-28 10:20:09 +09:00
def initialize ( post , opts = {})
2013-02-05 14:16:51 -05:00
@dirty = false
@opts = opts
@post = post
2013-12-06 11:16:13 +01:00
@previous_cooked = ( @post . cooked || "" ) . dup
2015-09-29 18:51:26 +02:00
# NOTE: we re-cook the post here in order to prevent timing issues with edits
# cf. https://meta.discourse.org/t/edit-of-rebaked-post-doesnt-show-in-html-only-in-raw/33815/6
2015-12-04 01:31:18 +05:30
@cooking_options = post . cooking_options || opts [ :cooking_options ] || {}
2018-11-26 14:23:56 +08:00
@cooking_options [ :topic_id ] = post . topic_id
2015-12-04 01:31:18 +05:30
@cooking_options = @cooking_options . symbolize_keys
2016-04-12 14:09:59 -04:00
2018-11-26 12:57:07 +08:00
@doc = Nokogiri :: HTML :: fragment ( post . cook ( post . raw , @cooking_options ))
2018-11-26 14:45:00 +08:00
@has_oneboxes = post . post_analyzer . found_oneboxes?
2013-02-19 17:57:14 +11:00
@size_cache = {}
2018-12-14 17:44:38 -05:00
@disable_loading_image = !! opts [ :disable_loading_image ]
2013-02-05 14:16:51 -05:00
end
2013-11-22 01:52:26 +01:00
def post_process ( bypass_bump = false )
2015-08-14 13:05:13 +10:00
DistributedMutex . synchronize ( "post_process_ #{ @post . id } " ) do
2017-10-17 14:17:00 +11:00
DiscourseEvent . trigger ( :before_post_process_cooked , @doc , @post )
2018-12-07 14:07:11 +02:00
removed_direct_reply_full_quotes
2015-08-14 13:05:13 +10:00
post_process_oneboxes
2017-11-16 15:45:07 +01:00
post_process_images
2018-03-13 13:07:51 -04:00
post_process_quotes
2015-08-14 13:05:13 +10:00
optimize_urls
2017-10-23 18:15:51 +02:00
update_post_image
2017-10-23 19:09:38 +02:00
enforce_nofollow
2015-08-14 13:05:13 +10:00
pull_hotlinked_images ( bypass_bump )
2016-04-07 12:27:26 -04:00
grant_badges
2018-09-06 14:08:03 +08:00
@post . link_post_uploads ( fragments : @doc )
2017-06-23 14:35:10 -04:00
DiscourseEvent . trigger ( :post_process_cooked , @doc , @post )
nil
2016-04-05 15:12:02 -04:00
end
end
2016-04-06 12:02:18 -04:00
def has_emoji?
( @doc . css ( "img.emoji" ) - @doc . css ( ".quote img" )) . size > 0
end
2016-04-07 12:27:26 -04:00
def grant_badges
2016-04-05 16:13:10 -04:00
return unless Guardian . new . can_see? ( @post )
2016-04-13 16:38:24 -04:00
BadgeGranter . grant ( Badge . find ( Badge :: FirstEmoji ), @post . user , post_id : @post . id ) if has_emoji?
BadgeGranter . grant ( Badge . find ( Badge :: FirstOnebox ), @post . user , post_id : @post . id ) if @has_oneboxes
2016-08-10 19:24:01 +02:00
BadgeGranter . grant ( Badge . find ( Badge :: FirstReplyByEmail ), @post . user , post_id : @post . id ) if @post . is_reply_by_email?
2013-02-05 14:16:51 -05:00
end
2013-02-25 19:42:20 +03:00
def post_process_images
2017-11-16 15:45:07 +01:00
extract_images . each do | img |
2018-10-11 03:27:21 +05:30
unless add_image_placeholder! ( img )
2017-11-16 15:45:07 +01:00
limit_size! ( img )
convert_to_link! ( img )
end
2013-04-13 16:31:20 +02:00
end
2013-06-15 12:29:20 +02:00
end
2013-04-13 16:31:20 +02:00
2018-03-13 13:07:51 -04:00
def post_process_quotes
@doc . css ( "aside.quote" ) . each do | q |
post_number = q [ 'data-post' ]
topic_id = q [ 'data-topic' ]
if topic_id && post_number
comparer = QuoteComparer . new (
topic_id . to_i ,
post_number . to_i ,
q . css ( 'blockquote' ) . text
)
if comparer . modified?
q [ 'class' ] = (( q [ 'class' ] || '' ) + " quote-modified" ) . strip
end
end
end
end
2018-12-07 14:07:11 +02:00
def removed_direct_reply_full_quotes
2018-12-12 16:42:53 +02:00
return if ! SiteSetting . remove_full_quote || @post . post_number == 1
2018-12-07 14:07:11 +02:00
num_quotes = @doc . css ( "aside.quote" ) . size
return if num_quotes != 1
prev = Post . where ( 'post_number < ? AND topic_id = ? AND post_type = ? AND not hidden' , @post . post_number , @post . topic_id , Post . types [ :regular ] ) . order ( 'post_number desc' ) . limit ( 1 ) . pluck ( :raw ) . first
return if ! prev
2018-12-18 16:46:20 +02:00
new_raw = @post . raw . gsub ( /\A\s*\[quote[^\]]*\]\s* #{ Regexp . quote ( prev . strip ) } \s*\[\/quote\]/ , '' )
2018-12-07 14:07:11 +02:00
return if @post . raw == new_raw
PostRevisor . new ( @post ) . revise! (
Discourse . system_user ,
{
raw : new_raw . strip ,
edit_reason : I18n . t ( :removed_direct_reply_full_quotes )
},
2018-12-12 16:42:53 +02:00
skip_validations : true ,
bypass_bump : true
2018-12-07 14:07:11 +02:00
)
end
2018-10-11 03:27:21 +05:30
def add_image_placeholder! ( img )
src = img [ "src" ]. sub ( /^https?:/i , "" )
if large_images . include? ( src )
return add_large_image_placeholder! ( img )
elsif broken_images . include? ( src )
return add_broken_image_placeholder! ( img )
end
false
end
2017-11-15 16:00:47 +05:30
def add_large_image_placeholder! ( img )
url = img [ "src" ]
is_hyperlinked = is_a_hyperlink? ( img )
placeholder = create_node ( "div" , "large-image-placeholder" )
img . add_next_sibling ( placeholder )
placeholder . add_child ( img )
a = create_link_node ( nil , url , true )
img . add_next_sibling ( a )
span = create_span_node ( "url" , url )
a . add_child ( span )
2018-11-26 16:49:57 -05:00
span . add_previous_sibling ( create_icon_node ( "far-image" ))
2017-11-15 16:00:47 +05:30
span . add_next_sibling ( create_span_node ( "help" , I18n . t ( "upload.placeholders.too_large" , max_size_kb : SiteSetting . max_image_size_kb )))
# Only if the image is already linked
if is_hyperlinked
parent = placeholder . parent
parent . add_next_sibling ( placeholder )
2017-11-15 17:36:48 +05:30
if parent . name == 'a' && parent [ "href" ]. present?
if url == parent [ "href" ]
parent . remove
else
parent [ "class" ] = "link"
a . add_previous_sibling ( parent )
lspan = create_span_node ( "url" , parent [ "href" ] )
parent . add_child ( lspan )
lspan . add_previous_sibling ( create_icon_node ( "link" ))
end
2017-11-15 16:00:47 +05:30
end
end
img . remove
2018-10-11 03:27:21 +05:30
true
2017-11-16 15:45:07 +01:00
end
def add_broken_image_placeholder! ( img )
img . name = "span"
2018-11-26 16:49:57 -05:00
img . set_attribute ( "class" , "broken-image" )
2017-11-16 15:45:07 +01:00
img . set_attribute ( "title" , I18n . t ( "post.image_placeholder.broken" ))
2018-11-26 16:49:57 -05:00
img << "<svg class= \" fa d-icon d-icon-unlink svg-icon \" aria-hidden= \" true \" ><use xlink:href= \" #unlink \" ></use></svg>"
2017-11-16 15:45:07 +01:00
img . remove_attribute ( "src" )
img . remove_attribute ( "width" )
img . remove_attribute ( "height" )
2018-10-11 03:27:21 +05:30
true
2017-11-15 16:00:47 +05:30
end
def large_images
2018-09-06 09:58:01 +08:00
@large_images ||=
begin
JSON . parse ( @post . custom_fields [ Post :: LARGE_IMAGES ]. presence || "[]" )
rescue JSON :: ParserError
[]
end
2017-11-16 15:45:07 +01:00
end
def broken_images
2018-09-06 09:58:01 +08:00
@broken_images ||=
begin
JSON . parse ( @post . custom_fields [ Post :: BROKEN_IMAGES ]. presence || "[]" )
rescue JSON :: ParserError
[]
end
2017-11-16 15:45:07 +01:00
end
def downloaded_images
2018-09-06 09:58:01 +08:00
@downloaded_images ||= @post . downloaded_images
2017-11-15 16:00:47 +05:30
end
2013-07-08 01:39:08 +02:00
def extract_images
2017-11-16 15:45:07 +01:00
# all images with a src attribute
2014-07-18 17:54:18 +02:00
@doc . css ( "img[src]" ) -
2017-11-16 15:45:07 +01:00
# minus data images
2014-07-18 17:54:18 +02:00
@doc . css ( "img[src^='data']" ) -
2017-11-16 15:45:07 +01:00
# minus emojis
2015-08-05 12:57:31 +02:00
@doc . css ( "img.emoji" ) -
2018-10-11 03:27:21 +05:30
# minus oneboxed images
oneboxed_images -
2017-11-16 15:45:07 +01:00
# minus images inside quotes
2014-07-18 17:54:18 +02:00
@doc . css ( ".quote img" )
2013-07-08 01:39:08 +02:00
end
2016-10-31 15:11:33 +05:30
def extract_images_for_post
2017-11-16 15:45:07 +01:00
# all images with a src attribute
2015-10-15 11:00:47 +02:00
@doc . css ( "img[src]" ) -
2017-11-16 15:45:07 +01:00
# minus emojis
2015-10-15 11:00:47 +02:00
@doc . css ( "img.emoji" ) -
2017-11-16 15:45:07 +01:00
# minus images inside quotes
2015-10-15 11:00:47 +02:00
@doc . css ( ".quote img" )
end
2014-07-21 15:59:34 +02:00
def oneboxed_images
2018-08-01 02:51:02 +05:30
@doc . css ( ".onebox-body img, .onebox img, img.onebox" )
2017-06-02 15:09:06 +05:30
end
2013-11-05 19:04:47 +01:00
def limit_size! ( img )
2013-11-25 18:36:13 +01:00
# retrieve the size from
# 1) the width/height attributes
# 2) the dimension from the preview (image_sizes)
# 3) the dimension of the original image (HTTP request)
w , h = get_size_from_attributes ( img ) ||
get_size_from_image_sizes ( img [ "src" ] , @opts [ :image_sizes ] ) ||
get_size ( img [ "src" ] )
2016-06-14 14:31:51 -04:00
2013-11-05 19:04:47 +01:00
# limit the size of the thumbnail
img [ "width" ] , img [ "height" ] = ImageSizer . resize ( w , h )
2013-07-08 01:39:08 +02:00
end
2013-11-25 18:36:13 +01:00
def get_size_from_attributes ( img )
w , h = img [ "width" ]. to_i , img [ "height" ]. to_i
2015-08-29 14:56:25 -07:00
return [ w , h ] unless w <= 0 || h <= 0
# if only width or height are specified attempt to scale image
if w > 0 || h > 0
w = w . to_f
h = h . to_f
2016-03-08 11:29:18 +08:00
return unless original_image_size = get_size ( img [ "src" ] )
original_width , original_height = original_image_size . map ( & :to_f )
2015-08-29 14:56:25 -07:00
if w > 0
2017-07-28 10:20:09 +09:00
ratio = w / original_width
[ w . floor , ( original_height * ratio ) . floor ]
2015-08-29 14:56:25 -07:00
else
2017-07-28 10:20:09 +09:00
ratio = h / original_height
[ ( original_width * ratio ) . floor , h . floor ]
2015-08-29 14:56:25 -07:00
end
end
2013-11-25 18:36:13 +01:00
end
2013-11-05 19:04:47 +01:00
def get_size_from_image_sizes ( src , image_sizes )
return unless image_sizes . present?
image_sizes . each do | image_size |
url , size = image_size [ 0 ] , image_size [ 1 ]
2015-03-16 18:57:15 +01:00
if url && url . include? ( src ) &&
size && size [ "width" ]. to_i > 0 && size [ "height" ]. to_i > 0
return [ size [ "width" ] , size [ "height" ]]
end
2013-11-05 19:04:47 +01:00
end
2013-06-15 12:29:20 +02:00
end
2013-02-21 12:07:36 +11:00
2018-10-03 13:44:53 +10:00
def add_to_size_cache ( url , w , h )
@size_cache [ url ] = [ w , h ]
end
2013-11-05 19:04:47 +01:00
def get_size ( url )
2015-08-07 19:31:15 +02:00
return @size_cache [ url ] if @size_cache . has_key? ( url )
2013-11-05 19:04:47 +01:00
absolute_url = url
absolute_url = Discourse . base_url_no_prefix + absolute_url if absolute_url =~ /^\/[^\/]/
2017-05-08 15:35:31 -04:00
return unless absolute_url
2013-11-05 19:04:47 +01:00
# FastImage fails when there's no scheme
2013-12-16 11:44:59 +01:00
absolute_url = SiteSetting . scheme + ":" + absolute_url if absolute_url . start_with? ( "//" )
2013-11-05 19:04:47 +01:00
return unless is_valid_image_url? ( absolute_url )
2015-08-07 19:31:15 +02:00
2013-11-05 19:04:47 +01:00
# we can *always* crawl our own images
2016-03-08 11:38:26 +08:00
return unless SiteSetting . crawl_images? || Discourse . store . has_been_uploaded? ( url )
2015-08-07 19:31:15 +02:00
2017-10-18 23:54:36 +02:00
@size_cache [ url ] = FastImage . size ( absolute_url )
2018-08-14 12:23:32 +02:00
rescue Zlib :: BufError , URI :: Error , OpenSSL :: SSL :: SSLError
2018-01-11 22:17:06 +05:30
# FastImage.size raises BufError for some gifs, leave it.
2013-06-17 22:46:48 +02:00
end
2013-11-05 19:04:47 +01:00
def is_valid_image_url? ( url )
uri = URI . parse ( url )
%w(http https) . include? uri . scheme
2018-08-14 12:23:32 +02:00
rescue URI :: Error
2013-02-19 17:57:14 +11:00
end
2013-11-05 19:04:47 +01:00
def convert_to_link! ( img )
2013-02-19 17:57:14 +11:00
src = img [ "src" ]
2018-06-18 17:10:23 +08:00
return if src . blank? || is_a_hyperlink? ( img ) || is_svg? ( img )
2013-02-19 17:57:14 +11:00
2013-07-06 19:10:53 +02:00
width , height = img [ "width" ]. to_i , img [ "height" ]. to_i
2017-10-23 17:43:53 +02:00
# TODO: store original dimentions in db
2017-10-18 23:54:36 +02:00
original_width , original_height = ( get_size ( src ) || [ 0 , 0 ] ) . map ( & :to_i )
2013-02-19 17:57:14 +11:00
2015-08-07 19:31:15 +02:00
# can't reach the image...
2017-10-18 23:54:36 +02:00
if original_width == 0 || original_height == 0
2015-08-12 16:10:42 +02:00
Rails . logger . info "Can't reach ' #{ src } ' to get its dimension."
2015-08-07 19:31:15 +02:00
return
end
2017-10-18 23:54:36 +02:00
return if original_width <= width && original_height <= height
return if original_width <= SiteSetting . max_image_width && original_height <= SiteSetting . max_image_height
2013-07-08 01:39:08 +02:00
2018-06-05 17:13:00 +02:00
crop = SiteSetting . min_ratio_to_crop > 0
crop &&= original_width . to_f / original_height . to_f < SiteSetting . min_ratio_to_crop
if crop
2016-05-23 16:18:30 +02:00
width , height = ImageSizer . crop ( original_width , original_height )
img [ "width" ] = width
img [ "height" ] = height
end
2017-10-18 23:54:36 +02:00
if upload = Upload . get_from_url ( src )
2018-12-14 16:50:28 -05:00
upload . create_thumbnail! ( width , height , crop : crop )
2018-10-03 13:44:53 +10:00
each_responsive_ratio do | ratio |
resized_w = ( width * ratio ) . to_i
resized_h = ( height * ratio ) . to_i
if upload . width && resized_w <= upload . width
2018-12-14 16:50:28 -05:00
upload . create_thumbnail! ( resized_w , resized_h , crop : crop )
2018-10-03 13:44:53 +10:00
end
end
2018-12-14 17:44:38 -05:00
unless @disable_loading_image
upload . create_thumbnail! ( LOADING_SIZE , LOADING_SIZE , format : 'png' , colors : LOADING_COLORS )
end
2013-07-08 01:39:08 +02:00
end
2013-02-19 17:57:14 +11:00
2018-10-25 16:08:10 +02:00
add_lightbox! ( img , original_width , original_height , upload , cropped : crop )
2013-07-08 01:39:08 +02:00
end
2018-12-14 17:44:38 -05:00
def loading_image ( upload )
upload . thumbnail ( LOADING_SIZE , LOADING_SIZE )
end
2013-11-05 19:04:47 +01:00
def is_a_hyperlink? ( img )
2013-02-19 17:57:14 +11:00
parent = img . parent
while parent
2013-11-20 13:10:08 +01:00
return true if parent . name == "a"
2017-10-23 17:43:53 +02:00
parent = parent . parent if parent . respond_to? ( :parent )
2013-02-19 17:57:14 +11:00
end
2013-11-20 13:10:08 +01:00
false
2013-07-08 01:39:08 +02:00
end
2013-02-19 17:57:14 +11:00
2018-10-03 13:44:53 +10:00
def each_responsive_ratio
SiteSetting
. responsive_post_image_sizes
. split ( '|' )
. map ( & :to_f )
. sort
. each { | r | yield r if r > 1 }
end
2018-10-25 16:08:10 +02:00
def add_lightbox! ( img , original_width , original_height , upload , cropped : false )
2013-06-26 02:44:20 +02:00
# first, create a div to hold our lightbox
2017-11-15 16:00:47 +05:30
lightbox = create_node ( "div" , "lightbox-wrapper" )
2013-07-08 01:39:08 +02:00
img . add_next_sibling ( lightbox )
lightbox . add_child ( img )
2013-06-26 02:44:20 +02:00
# then, the link to our larger image
2017-11-15 16:00:47 +05:30
a = create_link_node ( "lightbox" , img [ "src" ] )
2013-02-19 17:57:14 +11:00
img . add_next_sibling ( a )
2014-10-15 19:20:04 +02:00
if upload && Discourse . store . internal?
a [ "data-download-href" ] = Discourse . store . download_url ( upload )
end
2013-02-19 17:57:14 +11:00
a . add_child ( img )
2013-07-08 01:39:08 +02:00
# replace the image by its thumbnail
2013-11-05 19:04:47 +01:00
w , h = img [ "width" ]. to_i , img [ "height" ]. to_i
2018-08-28 12:48:43 +10:00
if upload
thumbnail = upload . thumbnail ( w , h )
2018-10-03 13:44:53 +10:00
if thumbnail && thumbnail . filesize . to_i < upload . filesize
2018-10-25 16:08:10 +02:00
img [ "src" ] = thumbnail . url
2018-10-03 13:44:53 +10:00
srcset = + ""
each_responsive_ratio do | ratio |
resized_w = ( w * ratio ) . to_i
resized_h = ( h * ratio ) . to_i
2018-10-25 16:08:10 +02:00
if ! cropped && upload . width && resized_w > upload . width
2018-10-03 13:44:53 +10:00
cooked_url = UrlHelper . cook_url ( upload . url )
2018-10-25 16:08:10 +02:00
srcset << ", #{ cooked_url } #{ ratio . to_s . sub ( /\.0$/ , "" ) } x"
elsif t = upload . thumbnail ( resized_w , resized_h )
cooked_url = UrlHelper . cook_url ( t . url )
srcset << ", #{ cooked_url } #{ ratio . to_s . sub ( /\.0$/ , "" ) } x"
2018-10-03 13:44:53 +10:00
end
2018-10-25 16:08:10 +02:00
img [ "srcset" ] = " #{ UrlHelper . cook_url ( img [ "src" ] ) }#{ srcset } " if srcset . present?
end
2018-10-03 13:44:53 +10:00
else
img [ "src" ] = upload . url
end
2018-12-14 17:44:38 -05:00
if small_upload = loading_image ( upload )
img [ "data-small-upload" ] = small_upload . url
end
2018-08-28 12:48:43 +10:00
end
2013-07-08 01:39:08 +02:00
2013-06-26 02:44:20 +02:00
# then, some overlay informations
2017-11-15 16:00:47 +05:30
meta = create_node ( "div" , "meta" )
2013-07-08 01:39:08 +02:00
img . add_next_sibling ( meta )
2013-06-21 18:29:40 +02:00
2013-11-05 19:04:47 +01:00
filename = get_filename ( upload , img [ "src" ] )
2018-12-24 20:33:17 -08:00
informations = " #{ original_width } × #{ original_height } "
2013-07-24 17:24:28 +10:00
informations << " #{ number_to_human_size ( upload . filesize ) } " if upload
2013-06-21 18:29:40 +02:00
2016-08-11 11:27:12 +08:00
a [ "title" ] = CGI . escapeHTML ( img [ "title" ] || filename )
2013-11-29 20:03:39 +01:00
2016-08-11 11:27:12 +08:00
meta . add_child create_span_node ( "filename" , a [ "title" ] )
2013-06-26 02:44:20 +02:00
meta . add_child create_span_node ( "informations" , informations )
meta . add_child create_span_node ( "expand" )
2013-06-21 18:29:40 +02:00
end
2013-02-19 17:57:14 +11:00
2013-06-26 21:53:31 +02:00
def get_filename ( upload , src )
return File . basename ( src ) unless upload
2013-07-04 00:39:23 +02:00
return upload . original_filename unless upload . original_filename =~ /^blob(\.png)?$/i
2013-11-05 19:04:47 +01:00
return I18n . t ( "upload.pasted_image_filename" )
2013-06-26 21:53:31 +02:00
end
2017-11-15 16:00:47 +05:30
def create_node ( tag_name , klass )
node = Nokogiri :: XML :: Node . new ( tag_name , @doc )
node [ "class" ] = klass if klass . present?
node
end
2017-07-28 10:20:09 +09:00
def create_span_node ( klass , content = nil )
2017-11-15 16:00:47 +05:30
span = create_node ( "span" , klass )
2013-06-21 18:29:40 +02:00
span . content = content if content
span
2013-02-05 14:16:51 -05:00
end
2017-11-15 16:00:47 +05:30
def create_icon_node ( klass )
2018-11-26 16:49:57 -05:00
icon = create_node ( "svg" , "fa d-icon d-icon- #{ klass } svg-icon" )
icon . set_attribute ( "aria-hidden" , "true" )
icon << "<use xlink:href= \" # #{ klass } \" ></use>"
2017-11-15 16:00:47 +05:30
end
def create_link_node ( klass , url , external = false )
a = create_node ( "a" , klass )
a [ "href" ] = url
if external
a [ "target" ] = "_blank"
a [ "rel" ] = "nofollow noopener"
end
a
end
2016-10-31 15:11:33 +05:30
def update_post_image
img = extract_images_for_post . first
2017-06-09 13:16:50 +02:00
return if img . blank?
2016-10-31 15:11:33 +05:30
if img [ "src" ]. present?
@post . update_column ( :image_url , img [ "src" ][ 0 ... 255 ] ) # post
@post . topic . update_column ( :image_url , img [ "src" ][ 0 ... 255 ] ) if @post . is_first_post? # topic
2013-07-08 01:39:08 +02:00
end
end
2013-11-05 19:04:47 +01:00
def post_process_oneboxes
2018-11-27 16:00:31 +08:00
limit = SiteSetting . max_oneboxes_per_post
oneboxes = {}
inlineOneboxes = {}
Oneboxer . apply ( @doc , extra_paths : [ ". #{ INLINE_ONEBOX_LOADING_CSS_CLASS } " ] ) do | url , element |
is_onebox = element [ "class" ] == Oneboxer :: ONEBOX_CSS_CLASS
map = is_onebox ? oneboxes : inlineOneboxes
skip_onebox = limit <= 0 && ! map [ url ]
if skip_onebox
2018-11-29 14:33:01 +08:00
if is_onebox
element . remove_class ( 'onebox' )
else
remove_inline_onebox_loading_class ( element )
end
2018-11-27 16:00:31 +08:00
next
end
limit -= 1
map [ url ] = true
if is_onebox
@has_oneboxes = true
Oneboxer . onebox ( url ,
invalidate_oneboxes : !! @opts [ :invalidate_oneboxes ] ,
user_id : @post &. user_id ,
category_id : @post &. topic &. category_id
)
else
process_inline_onebox ( element )
false
end
2016-11-03 22:48:32 +01:00
end
2017-10-23 19:09:38 +02:00
2017-06-02 15:09:06 +05:30
oneboxed_images . each do | img |
2017-11-27 13:41:28 +05:30
next if img [ "src" ]. blank?
2017-11-16 15:45:07 +01:00
src = img [ "src" ]. sub ( /^https?:/i , "" )
2018-10-07 17:40:15 +05:30
parent = img . parent
img_classes = ( img [ "class" ] || "" ) . split ( " " )
link_classes = (( parent &. name == "a" && parent [ "class" ] ) || "" ) . split ( " " )
2017-11-16 15:45:07 +01:00
2018-10-11 03:27:21 +05:30
if img_classes . include? ( "onebox" ) || link_classes . include? ( "onebox" )
next if add_image_placeholder! ( img )
elsif large_images . include? ( src ) || broken_images . include? ( src )
img . remove
2017-11-15 16:00:47 +05:30
next
end
2017-11-16 15:45:07 +01:00
upload_id = downloaded_images [ src ]
2018-12-15 03:19:45 +05:30
upload = Upload . find_by_id ( upload_id ) if upload_id
2017-10-23 18:15:51 +02:00
img [ "src" ] = upload . url if upload . present?
2017-10-23 19:09:38 +02:00
2017-11-15 16:00:47 +05:30
# make sure we grab dimensions for oneboxed images
# and wrap in a div
2017-10-31 13:50:44 +11:00
limit_size! ( img )
2017-11-08 11:50:01 +11:00
next if img [ "class" ]&. include? ( 'onebox-avatar' )
2018-10-07 17:40:15 +05:30
parent_class = parent && parent [ "class" ]
2018-03-22 01:30:05 +05:30
width = img [ "width" ]. to_i
height = img [ "height" ]. to_i
2017-11-13 11:19:06 +11:00
2018-03-22 01:30:05 +05:30
if parent_class &. include? ( "onebox-body" ) && width > 0 && height > 0
2017-11-13 11:19:06 +11:00
# special instruction for width == height, assume we are dealing with an avatar
if ( img [ "width" ]. to_i == img [ "height" ]. to_i )
found = false
parent = img
while parent = parent . parent
2017-11-13 16:06:18 +11:00
if parent [ "class" ] && parent [ "class" ]. include? ( "whitelistedgeneric" )
2017-11-13 11:19:06 +11:00
found = true
break
end
end
if found
img [ "class" ] = img [ "class" ]. to_s + " onebox-avatar"
next
end
end
2017-11-28 12:32:35 +11:00
if width < 64 && height < 64
img [ "class" ] = img [ "class" ]. to_s + " onebox-full-image"
else
img . delete ( 'width' )
img . delete ( 'height' )
new_parent = img . add_next_sibling ( "<div class='aspect-image' style='--aspect-ratio: #{ width } / #{ height } ;'/>" )
new_parent . first . add_child ( img )
end
2018-03-23 17:35:17 +05:30
elsif ( parent_class &. include? ( "instagram-images" ) || parent_class &. include? ( "tweet-images" )) && width > 0 && height > 0
2018-03-22 01:30:05 +05:30
img . remove_attribute ( "width" )
img . remove_attribute ( "height" )
img . parent [ "class" ] = "aspect-image-full-size"
img . parent [ "style" ] = "--aspect-ratio: #{ width } / #{ height } ;"
2017-10-31 13:50:44 +11:00
end
end
2018-03-26 15:54:39 +05:30
if @cooking_options [ :omit_nofollow ] || ! SiteSetting . add_rel_nofollow_to_user_content
@doc . css ( ".onebox-body a, .onebox a" ) . each { | a | a . remove_attribute ( "rel" ) }
end
2013-02-05 14:16:51 -05:00
end
2013-11-05 19:04:47 +01:00
def optimize_urls
2014-10-15 19:20:04 +02:00
%w{href data-download-href} . each do | selector |
@doc . css ( "a[ #{ selector } ]" ) . each do | a |
2018-08-14 12:23:32 +02:00
a [ selector ] = UrlHelper . cook_url ( a [ selector ]. to_s )
2014-10-15 19:20:04 +02:00
end
2013-11-05 19:04:47 +01:00
end
2014-07-18 17:54:18 +02:00
@doc . css ( "img[src]" ) . each do | img |
2018-08-14 12:23:32 +02:00
img [ "src" ] = UrlHelper . cook_url ( img [ "src" ]. to_s )
2013-11-05 19:04:47 +01:00
end
2013-02-05 14:16:51 -05:00
end
2017-10-23 19:09:38 +02:00
2017-10-23 18:15:51 +02:00
def enforce_nofollow
if ! @cooking_options [ :omit_nofollow ] && SiteSetting . add_rel_nofollow_to_user_content
PrettyText . add_rel_nofollow_to_user_content ( @doc )
end
end
2013-02-05 14:16:51 -05:00
2013-11-22 01:52:26 +01:00
def pull_hotlinked_images ( bypass_bump = false )
2013-11-15 15:22:18 +01:00
# is the job enabled?
return unless SiteSetting . download_remote_images_to_local?
2013-11-15 16:46:41 +01:00
# have we enough disk space?
return if disable_if_low_on_disk_space
2016-07-06 19:21:48 +05:30
# don't download remote images for posts that are more than n days old
return unless @post . created_at > ( Date . today - SiteSetting . download_remote_images_max_days_old )
2013-11-05 19:04:47 +01:00
# we only want to run the job whenever it's changed by a user
2017-11-16 15:45:07 +01:00
return if @post . last_editor_id && @post . last_editor_id <= 0
2013-11-05 19:04:47 +01:00
# make sure no other job is scheduled
Jobs . cancel_scheduled_job ( :pull_hotlinked_images , post_id : @post . id )
# schedule the job
2015-11-24 14:28:42 -05:00
delay = SiteSetting . editing_grace_period + 1
2013-11-22 01:52:26 +01:00
Jobs . enqueue_in ( delay . seconds . to_i , :pull_hotlinked_images , post_id : @post . id , bypass_bump : bypass_bump )
2013-07-10 22:55:37 +02:00
end
2013-11-15 16:46:41 +01:00
def disable_if_low_on_disk_space
2014-10-15 19:20:04 +02:00
return false if available_disk_space >= SiteSetting . download_remote_images_threshold
SiteSetting . download_remote_images_to_local = false
# log the site setting change
reason = I18n . t ( "disable_remote_images_download_reason" )
staff_action_logger = StaffActionLogger . new ( Discourse . system_user )
2017-07-28 10:20:09 +09:00
staff_action_logger . log_site_setting_change ( "download_remote_images_to_local" , true , false , details : reason )
2015-08-14 17:46:15 -04:00
2014-10-15 19:20:04 +02:00
# also send a private message to the site contact user
2015-08-14 17:46:15 -04:00
notify_about_low_disk_space
2014-10-15 19:20:04 +02:00
true
2013-11-15 16:46:41 +01:00
end
2015-08-14 17:46:15 -04:00
def notify_about_low_disk_space
SystemMessage . create_from_system_user ( Discourse . site_contact_user , :download_remote_images_disabled )
end
2013-11-15 16:46:41 +01:00
def available_disk_space
2015-01-27 08:25:32 +11:00
100 - `df -P #{ Rails . root } /public/uploads | tail -1 | tr -s ' ' | cut -d ' ' -f 5` . to_i
2013-11-15 16:46:41 +01:00
end
2013-06-15 12:29:20 +02:00
def dirty?
2013-12-06 11:16:13 +01:00
@previous_cooked != html
2013-06-15 12:29:20 +02:00
end
def html
@doc . try ( :to_html )
2013-02-05 14:16:51 -05:00
end
2018-06-18 17:10:23 +08:00
private
2018-11-27 16:00:31 +08:00
def process_inline_onebox ( element )
inline_onebox = InlineOneboxer . lookup (
element . attributes [ "href" ]. value ,
invalidate : !! @opts [ :invalidate_oneboxes ]
)
2018-11-26 09:21:38 +08:00
2018-11-27 16:00:31 +08:00
if title = inline_onebox &. dig ( :title )
element . children = title
element . add_class ( INLINE_ONEBOX_CSS_CLASS )
2018-11-26 09:21:38 +08:00
end
2018-11-27 16:00:31 +08:00
remove_inline_onebox_loading_class ( element )
end
def remove_inline_onebox_loading_class ( element )
element . remove_class ( INLINE_ONEBOX_LOADING_CSS_CLASS )
2018-11-26 09:21:38 +08:00
end
2018-06-18 17:10:23 +08:00
def is_svg? ( img )
2018-06-20 10:47:14 +08:00
path =
begin
URI ( img [ "src" ] ) . path
2018-08-14 12:23:32 +02:00
rescue URI :: Error
2018-06-20 10:47:14 +08:00
nil
end
File . extname ( path ) == '.svg' if path
2018-06-18 17:10:23 +08:00
end
2013-02-05 14:16:51 -05:00
end