2013-07-07 01:10:53 +08:00
# Post processing that we can do after a post has already been cooked.
2013-07-08 07:39:08 +08:00
# For example, inserting the onebox content, or image sizes/thumbnails.
2013-02-06 03:16:51 +08:00
2013-11-20 20:10:08 +08:00
require_dependency 'url_helper'
2015-12-04 04:01:18 +08:00
require_dependency 'pretty_text'
2018-03-14 01:07:51 +08:00
require_dependency 'quote_comparer'
2013-02-06 03:16:51 +08:00
class CookedPostProcessor
2013-06-22 00:29:40 +08:00
include ActionView :: Helpers :: NumberHelper
2013-02-19 14:57:14 +08: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-15 06:44:38 +08:00
LOADING_SIZE = 10
LOADING_COLORS = 32
2018-11-27 16:00:31 +08:00
2017-11-13 08:19:06 +08:00
attr_reader :cooking_options , :doc
2016-08-13 03:28:54 +08:00
2017-07-28 09:20:09 +08:00
def initialize ( post , opts = { } )
2013-02-06 03:16:51 +08:00
@dirty = false
@opts = opts
@post = post
2013-12-06 18:16:13 +08:00
@previous_cooked = ( @post . cooked || " " ) . dup
2015-09-30 00:51:26 +08: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 04:01:18 +08:00
@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 04:01:18 +08:00
@cooking_options = @cooking_options . symbolize_keys
2016-04-13 02:09:59 +08: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 14:57:14 +08:00
@size_cache = { }
2018-12-15 06:44:38 +08:00
@disable_loading_image = ! ! opts [ :disable_loading_image ]
2013-02-06 03:16:51 +08:00
end
2019-01-17 10:24:32 +08:00
def post_process ( bypass_bump : false , new_post : false )
2015-08-14 11:05:13 +08:00
DistributedMutex . synchronize ( " post_process_ #{ @post . id } " ) do
2017-10-17 11:17:00 +08:00
DiscourseEvent . trigger ( :before_post_process_cooked , @doc , @post )
2019-01-17 10:24:32 +08:00
removed_direct_reply_full_quotes if new_post
2015-08-14 11:05:13 +08:00
post_process_oneboxes
2017-11-16 22:45:07 +08:00
post_process_images
2018-03-14 01:07:51 +08:00
post_process_quotes
2015-08-14 11:05:13 +08:00
optimize_urls
2017-10-24 00:15:51 +08:00
update_post_image
2017-10-24 01:09:38 +08:00
enforce_nofollow
2015-08-14 11:05:13 +08:00
pull_hotlinked_images ( bypass_bump )
2016-04-08 00:27:26 +08:00
grant_badges
2018-09-06 14:08:03 +08:00
@post . link_post_uploads ( fragments : @doc )
2017-06-24 02:35:10 +08:00
DiscourseEvent . trigger ( :post_process_cooked , @doc , @post )
nil
2016-04-06 03:12:02 +08:00
end
end
2016-04-07 00:02:18 +08:00
def has_emoji?
( @doc . css ( " img.emoji " ) - @doc . css ( " .quote img " ) ) . size > 0
end
2016-04-08 00:27:26 +08:00
def grant_badges
2016-04-06 04:13:10 +08:00
return unless Guardian . new . can_see? ( @post )
2016-04-14 04:38:24 +08: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-11 01:24:01 +08:00
BadgeGranter . grant ( Badge . find ( Badge :: FirstReplyByEmail ) , @post . user , post_id : @post . id ) if @post . is_reply_by_email?
2013-02-06 03:16:51 +08:00
end
2013-02-26 00:42:20 +08:00
def post_process_images
2017-11-16 22:45:07 +08:00
extract_images . each do | img |
2018-10-11 05:57:21 +08:00
unless add_image_placeholder! ( img )
2017-11-16 22:45:07 +08:00
limit_size! ( img )
convert_to_link! ( img )
end
2013-04-13 22:31:20 +08:00
end
2013-06-15 18:29:20 +08:00
end
2013-04-13 22:31:20 +08:00
2018-03-14 01:07:51 +08: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 20:07:11 +08:00
def removed_direct_reply_full_quotes
2018-12-12 22:42:53 +08:00
return if ! SiteSetting . remove_full_quote || @post . post_number == 1
2018-12-07 20:07:11 +08: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 22:46:20 +08:00
new_raw = @post . raw . gsub ( / \ A \ s* \ [quote[^ \ ]]* \ ] \ s* #{ Regexp . quote ( prev . strip ) } \ s* \ [ \/ quote \ ] / , '' )
2018-12-07 20:07:11 +08: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 22:42:53 +08:00
skip_validations : true ,
bypass_bump : true
2018-12-07 20:07:11 +08:00
)
end
2018-10-11 05:57:21 +08:00
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 18:30:47 +08:00
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-27 05:49:57 +08:00
span . add_previous_sibling ( create_icon_node ( " far-image " ) )
2017-11-15 18:30:47 +08:00
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 20:06:48 +08:00
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 18:30:47 +08:00
end
end
img . remove
2018-10-11 05:57:21 +08:00
true
2017-11-16 22:45:07 +08:00
end
def add_broken_image_placeholder! ( img )
img . name = " span "
2018-11-27 05:49:57 +08:00
img . set_attribute ( " class " , " broken-image " )
2017-11-16 22:45:07 +08:00
img . set_attribute ( " title " , I18n . t ( " post.image_placeholder.broken " ) )
2018-11-27 05:49:57 +08:00
img << " <svg class= \" fa d-icon d-icon-unlink svg-icon \" aria-hidden= \" true \" ><use xlink:href= \" # unlink \" ></use></svg> "
2017-11-16 22:45:07 +08:00
img . remove_attribute ( " src " )
img . remove_attribute ( " width " )
img . remove_attribute ( " height " )
2018-10-11 05:57:21 +08:00
true
2017-11-15 18:30:47 +08:00
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 22:45:07 +08: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 22:45:07 +08:00
end
def downloaded_images
2018-09-06 09:58:01 +08:00
@downloaded_images || = @post . downloaded_images
2017-11-15 18:30:47 +08:00
end
2013-07-08 07:39:08 +08:00
def extract_images
2017-11-16 22:45:07 +08:00
# all images with a src attribute
2014-07-18 23:54:18 +08:00
@doc . css ( " img[src] " ) -
2017-11-16 22:45:07 +08:00
# minus data images
2014-07-18 23:54:18 +08:00
@doc . css ( " img[src^='data'] " ) -
2017-11-16 22:45:07 +08:00
# minus emojis
2015-08-05 18:57:31 +08:00
@doc . css ( " img.emoji " ) -
2018-10-11 05:57:21 +08:00
# minus oneboxed images
oneboxed_images -
2017-11-16 22:45:07 +08:00
# minus images inside quotes
2014-07-18 23:54:18 +08:00
@doc . css ( " .quote img " )
2013-07-08 07:39:08 +08:00
end
2016-10-31 17:41:33 +08:00
def extract_images_for_post
2017-11-16 22:45:07 +08:00
# all images with a src attribute
2015-10-15 17:00:47 +08:00
@doc . css ( " img[src] " ) -
2017-11-16 22:45:07 +08:00
# minus emojis
2015-10-15 17:00:47 +08:00
@doc . css ( " img.emoji " ) -
2017-11-16 22:45:07 +08:00
# minus images inside quotes
2015-10-15 17:00:47 +08:00
@doc . css ( " .quote img " )
end
2014-07-21 21:59:34 +08:00
def oneboxed_images
2018-08-01 05:21:02 +08:00
@doc . css ( " .onebox-body img, .onebox img, img.onebox " )
2017-06-02 17:39:06 +08:00
end
2013-11-06 02:04:47 +08:00
def limit_size! ( img )
2013-11-26 01:36:13 +08: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-15 02:31:51 +08:00
2013-11-06 02:04:47 +08:00
# limit the size of the thumbnail
img [ " width " ] , img [ " height " ] = ImageSizer . resize ( w , h )
2013-07-08 07:39:08 +08:00
end
2013-11-26 01:36:13 +08:00
def get_size_from_attributes ( img )
w , h = img [ " width " ] . to_i , img [ " height " ] . to_i
2015-08-30 05:56:25 +08: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-30 05:56:25 +08:00
if w > 0
2017-07-28 09:20:09 +08:00
ratio = w / original_width
[ w . floor , ( original_height * ratio ) . floor ]
2015-08-30 05:56:25 +08:00
else
2017-07-28 09:20:09 +08:00
ratio = h / original_height
[ ( original_width * ratio ) . floor , h . floor ]
2015-08-30 05:56:25 +08:00
end
end
2013-11-26 01:36:13 +08:00
end
2013-11-06 02:04:47 +08: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-17 01:57:15 +08: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-06 02:04:47 +08:00
end
2013-06-15 18:29:20 +08:00
end
2013-02-21 09:07:36 +08:00
2018-10-03 11:44:53 +08:00
def add_to_size_cache ( url , w , h )
@size_cache [ url ] = [ w , h ]
end
2013-11-06 02:04:47 +08:00
def get_size ( url )
2015-08-08 01:31:15 +08:00
return @size_cache [ url ] if @size_cache . has_key? ( url )
2013-11-06 02:04:47 +08:00
absolute_url = url
absolute_url = Discourse . base_url_no_prefix + absolute_url if absolute_url =~ / ^ \/ [^ \/ ] /
2017-05-09 03:35:31 +08:00
return unless absolute_url
2013-11-06 02:04:47 +08:00
# FastImage fails when there's no scheme
2013-12-16 18:44:59 +08:00
absolute_url = SiteSetting . scheme + " : " + absolute_url if absolute_url . start_with? ( " // " )
2013-11-06 02:04:47 +08:00
return unless is_valid_image_url? ( absolute_url )
2015-08-08 01:31:15 +08:00
2013-11-06 02:04:47 +08: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-08 01:31:15 +08:00
2017-10-19 05:54:36 +08:00
@size_cache [ url ] = FastImage . size ( absolute_url )
2018-08-14 18:23:32 +08:00
rescue Zlib :: BufError , URI :: Error , OpenSSL :: SSL :: SSLError
2018-01-12 00:47:06 +08:00
# FastImage.size raises BufError for some gifs, leave it.
2013-06-18 04:46:48 +08:00
end
2013-11-06 02:04:47 +08:00
def is_valid_image_url? ( url )
uri = URI . parse ( url )
%w( http https ) . include? uri . scheme
2018-08-14 18:23:32 +08:00
rescue URI :: Error
2013-02-19 14:57:14 +08:00
end
2013-11-06 02:04:47 +08:00
def convert_to_link! ( img )
2013-02-19 14:57:14 +08: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 14:57:14 +08:00
2013-07-07 01:10:53 +08:00
width , height = img [ " width " ] . to_i , img [ " height " ] . to_i
2017-10-23 23:43:53 +08:00
# TODO: store original dimentions in db
2017-10-19 05:54:36 +08:00
original_width , original_height = ( get_size ( src ) || [ 0 , 0 ] ) . map ( & :to_i )
2013-02-19 14:57:14 +08:00
2015-08-08 01:31:15 +08:00
# can't reach the image...
2017-10-19 05:54:36 +08:00
if original_width == 0 || original_height == 0
2015-08-12 22:10:42 +08:00
Rails . logger . info " Can't reach ' #{ src } ' to get its dimension. "
2015-08-08 01:31:15 +08:00
return
end
2017-10-19 05:54:36 +08: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 07:39:08 +08:00
2018-06-05 23:13:00 +08: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 22:18:30 +08:00
width , height = ImageSizer . crop ( original_width , original_height )
img [ " width " ] = width
img [ " height " ] = height
end
2017-10-19 05:54:36 +08:00
if upload = Upload . get_from_url ( src )
2018-12-15 05:50:28 +08:00
upload . create_thumbnail! ( width , height , crop : crop )
2018-10-03 11:44:53 +08: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-15 05:50:28 +08:00
upload . create_thumbnail! ( resized_w , resized_h , crop : crop )
2018-10-03 11:44:53 +08:00
end
end
2018-12-15 06:44:38 +08:00
unless @disable_loading_image
upload . create_thumbnail! ( LOADING_SIZE , LOADING_SIZE , format : 'png' , colors : LOADING_COLORS )
end
2013-07-08 07:39:08 +08:00
end
2013-02-19 14:57:14 +08:00
2018-10-25 22:08:10 +08:00
add_lightbox! ( img , original_width , original_height , upload , cropped : crop )
2013-07-08 07:39:08 +08:00
end
2018-12-15 06:44:38 +08:00
def loading_image ( upload )
upload . thumbnail ( LOADING_SIZE , LOADING_SIZE )
end
2013-11-06 02:04:47 +08:00
def is_a_hyperlink? ( img )
2013-02-19 14:57:14 +08:00
parent = img . parent
while parent
2013-11-20 20:10:08 +08:00
return true if parent . name == " a "
2017-10-23 23:43:53 +08:00
parent = parent . parent if parent . respond_to? ( :parent )
2013-02-19 14:57:14 +08:00
end
2013-11-20 20:10:08 +08:00
false
2013-07-08 07:39:08 +08:00
end
2013-02-19 14:57:14 +08:00
2018-10-03 11:44:53 +08: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 22:08:10 +08:00
def add_lightbox! ( img , original_width , original_height , upload , cropped : false )
2013-06-26 08:44:20 +08:00
# first, create a div to hold our lightbox
2017-11-15 18:30:47 +08:00
lightbox = create_node ( " div " , " lightbox-wrapper " )
2013-07-08 07:39:08 +08:00
img . add_next_sibling ( lightbox )
lightbox . add_child ( img )
2013-06-26 08:44:20 +08:00
# then, the link to our larger image
2017-11-15 18:30:47 +08:00
a = create_link_node ( " lightbox " , img [ " src " ] )
2013-02-19 14:57:14 +08:00
img . add_next_sibling ( a )
2014-10-16 01:20:04 +08:00
if upload && Discourse . store . internal?
a [ " data-download-href " ] = Discourse . store . download_url ( upload )
end
2013-02-19 14:57:14 +08:00
a . add_child ( img )
2013-07-08 07:39:08 +08:00
# replace the image by its thumbnail
2013-11-06 02:04:47 +08:00
w , h = img [ " width " ] . to_i , img [ " height " ] . to_i
2018-08-28 10:48:43 +08:00
if upload
thumbnail = upload . thumbnail ( w , h )
2018-10-03 11:44:53 +08:00
if thumbnail && thumbnail . filesize . to_i < upload . filesize
2019-02-21 02:24:38 +08:00
img [ " src " ] = thumbnail . url
2018-10-03 11:44:53 +08:00
srcset = + " "
each_responsive_ratio do | ratio |
resized_w = ( w * ratio ) . to_i
resized_h = ( h * ratio ) . to_i
2018-10-25 22:08:10 +08:00
if ! cropped && upload . width && resized_w > upload . width
2018-10-03 11:44:53 +08:00
cooked_url = UrlHelper . cook_url ( upload . url )
2018-10-25 22:08:10 +08: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 11:44:53 +08:00
end
2018-10-25 22:08:10 +08:00
img [ " srcset " ] = " #{ UrlHelper . cook_url ( img [ " src " ] ) } #{ srcset } " if srcset . present?
end
2018-10-03 11:44:53 +08:00
else
2019-02-21 02:24:38 +08:00
img [ " src " ] = upload . url
2018-10-03 11:44:53 +08:00
end
2018-12-15 06:44:38 +08:00
if small_upload = loading_image ( upload )
2019-02-21 02:24:38 +08:00
img [ " data-small-upload " ] = small_upload . url
2018-12-15 06:44:38 +08:00
end
2018-08-28 10:48:43 +08:00
end
2013-07-08 07:39:08 +08:00
2013-06-26 08:44:20 +08:00
# then, some overlay informations
2017-11-15 18:30:47 +08:00
meta = create_node ( " div " , " meta " )
2013-07-08 07:39:08 +08:00
img . add_next_sibling ( meta )
2013-06-22 00:29:40 +08:00
2013-11-06 02:04:47 +08:00
filename = get_filename ( upload , img [ " src " ] )
2018-12-25 12:33:17 +08:00
informations = " #{ original_width } × #{ original_height } "
2013-07-24 15:24:28 +08:00
informations << " #{ number_to_human_size ( upload . filesize ) } " if upload
2013-06-22 00:29:40 +08:00
2016-08-11 11:27:12 +08:00
a [ " title " ] = CGI . escapeHTML ( img [ " title " ] || filename )
2013-11-30 03:03:39 +08:00
2016-08-11 11:27:12 +08:00
meta . add_child create_span_node ( " filename " , a [ " title " ] )
2013-06-26 08:44:20 +08:00
meta . add_child create_span_node ( " informations " , informations )
meta . add_child create_span_node ( " expand " )
2013-06-22 00:29:40 +08:00
end
2013-02-19 14:57:14 +08:00
2013-06-27 03:53:31 +08:00
def get_filename ( upload , src )
return File . basename ( src ) unless upload
2013-07-04 06:39:23 +08:00
return upload . original_filename unless upload . original_filename =~ / ^blob( \ .png)?$ /i
2013-11-06 02:04:47 +08:00
return I18n . t ( " upload.pasted_image_filename " )
2013-06-27 03:53:31 +08:00
end
2017-11-15 18:30:47 +08:00
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 09:20:09 +08:00
def create_span_node ( klass , content = nil )
2017-11-15 18:30:47 +08:00
span = create_node ( " span " , klass )
2013-06-22 00:29:40 +08:00
span . content = content if content
span
2013-02-06 03:16:51 +08:00
end
2017-11-15 18:30:47 +08:00
def create_icon_node ( klass )
2018-11-27 05:49:57 +08: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 18:30:47 +08:00
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 17:41:33 +08:00
def update_post_image
img = extract_images_for_post . first
2017-06-09 19:16:50 +08:00
return if img . blank?
2016-10-31 17:41:33 +08:00
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 07:39:08 +08:00
end
end
2013-11-06 02:04:47 +08: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-04 05:48:32 +08:00
end
2017-10-24 01:09:38 +08:00
2017-06-02 17:39:06 +08:00
oneboxed_images . each do | img |
2017-11-27 16:11:28 +08:00
next if img [ " src " ] . blank?
2017-11-16 22:45:07 +08:00
src = img [ " src " ] . sub ( / ^https?: /i , " " )
2018-10-07 20:10:15 +08:00
parent = img . parent
img_classes = ( img [ " class " ] || " " ) . split ( " " )
link_classes = ( ( parent & . name == " a " && parent [ " class " ] ) || " " ) . split ( " " )
2017-11-16 22:45:07 +08:00
2018-10-11 05:57:21 +08:00
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 18:30:47 +08:00
next
end
2017-11-16 22:45:07 +08:00
upload_id = downloaded_images [ src ]
2018-12-15 05:49:45 +08:00
upload = Upload . find_by_id ( upload_id ) if upload_id
2017-10-24 00:15:51 +08:00
img [ " src " ] = upload . url if upload . present?
2017-10-24 01:09:38 +08:00
2017-11-15 18:30:47 +08:00
# make sure we grab dimensions for oneboxed images
# and wrap in a div
2017-10-31 10:50:44 +08:00
limit_size! ( img )
2017-11-08 08:50:01 +08:00
next if img [ " class " ] & . include? ( 'onebox-avatar' )
2018-10-07 20:10:15 +08:00
parent_class = parent && parent [ " class " ]
2018-03-22 04:00:05 +08:00
width = img [ " width " ] . to_i
height = img [ " height " ] . to_i
2017-11-13 08:19:06 +08:00
2018-03-22 04:00:05 +08:00
if parent_class & . include? ( " onebox-body " ) && width > 0 && height > 0
2017-11-13 08:19:06 +08: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 13:06:18 +08:00
if parent [ " class " ] && parent [ " class " ] . include? ( " whitelistedgeneric " )
2017-11-13 08:19:06 +08:00
found = true
break
end
end
if found
img [ " class " ] = img [ " class " ] . to_s + " onebox-avatar "
next
end
end
2017-11-28 09:32:35 +08: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 20:05:17 +08:00
elsif ( parent_class & . include? ( " instagram-images " ) || parent_class & . include? ( " tweet-images " ) ) && width > 0 && height > 0
2018-03-22 04:00:05 +08:00
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 10:50:44 +08:00
end
end
2018-03-26 18:24:39 +08:00
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-06 03:16:51 +08:00
end
2013-11-06 02:04:47 +08:00
def optimize_urls
2014-10-16 01:20:04 +08:00
%w{ href data-download-href } . each do | selector |
@doc . css ( " a[ #{ selector } ] " ) . each do | a |
2018-08-14 18:23:32 +08:00
a [ selector ] = UrlHelper . cook_url ( a [ selector ] . to_s )
2014-10-16 01:20:04 +08:00
end
2013-11-06 02:04:47 +08:00
end
2019-02-21 02:24:38 +08:00
%w{ src data-small-upload } . each do | selector |
@doc . css ( " img[ #{ selector } ] " ) . each do | img |
img [ selector ] = UrlHelper . cook_url ( img [ selector ] . to_s )
end
2013-11-06 02:04:47 +08:00
end
2013-02-06 03:16:51 +08:00
end
2017-10-24 01:09:38 +08:00
2017-10-24 00:15:51 +08: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-06 03:16:51 +08:00
2013-11-22 08:52:26 +08:00
def pull_hotlinked_images ( bypass_bump = false )
2013-11-15 22:22:18 +08:00
# is the job enabled?
return unless SiteSetting . download_remote_images_to_local?
2013-11-15 23:46:41 +08:00
# have we enough disk space?
return if disable_if_low_on_disk_space
2016-07-06 21:51:48 +08:00
# 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-06 02:04:47 +08:00
# we only want to run the job whenever it's changed by a user
2017-11-16 22:45:07 +08:00
return if @post . last_editor_id && @post . last_editor_id < = 0
2013-11-06 02:04:47 +08:00
# make sure no other job is scheduled
Jobs . cancel_scheduled_job ( :pull_hotlinked_images , post_id : @post . id )
# schedule the job
2015-11-25 03:28:42 +08:00
delay = SiteSetting . editing_grace_period + 1
2013-11-22 08:52:26 +08:00
Jobs . enqueue_in ( delay . seconds . to_i , :pull_hotlinked_images , post_id : @post . id , bypass_bump : bypass_bump )
2013-07-11 04:55:37 +08:00
end
2013-11-15 23:46:41 +08:00
def disable_if_low_on_disk_space
2014-10-16 01:20:04 +08: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 09:20:09 +08:00
staff_action_logger . log_site_setting_change ( " download_remote_images_to_local " , true , false , details : reason )
2015-08-15 05:46:15 +08:00
2014-10-16 01:20:04 +08:00
# also send a private message to the site contact user
2015-08-15 05:46:15 +08:00
notify_about_low_disk_space
2014-10-16 01:20:04 +08:00
true
2013-11-15 23:46:41 +08:00
end
2015-08-15 05:46:15 +08:00
def notify_about_low_disk_space
SystemMessage . create_from_system_user ( Discourse . site_contact_user , :download_remote_images_disabled )
end
2013-11-15 23:46:41 +08:00
def available_disk_space
2015-01-27 05:25:32 +08:00
100 - ` df -P #{ Rails . root } /public/uploads | tail -1 | tr -s ' ' | cut -d ' ' -f 5 ` . to_i
2013-11-15 23:46:41 +08:00
end
2013-06-15 18:29:20 +08:00
def dirty?
2013-12-06 18:16:13 +08:00
@previous_cooked != html
2013-06-15 18:29:20 +08:00
end
def html
@doc . try ( :to_html )
2013-02-06 03:16:51 +08: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 )
2019-01-10 09:02:05 +08:00
element . children = CGI . escapeHTML ( title )
2018-11-27 16:00:31 +08:00
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 18:23:32 +08: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-06 03:16:51 +08:00
end