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
require_dependency 'oneboxer'
class CookedPostProcessor
2013-06-21 18:29:40 +02:00
include ActionView :: Helpers :: NumberHelper
2013-02-19 17:57:14 +11:00
2013-02-05 14:16:51 -05:00
def initialize ( post , opts = { } )
@dirty = false
@opts = opts
@post = post
2013-04-10 17:52:38 +10:00
@doc = Nokogiri :: HTML :: fragment ( post . cooked )
2013-02-19 17:57:14 +11:00
@size_cache = { }
2013-06-15 12:29:20 +02:00
@has_been_uploaded_cache = { }
2013-02-05 14:16:51 -05:00
end
2013-06-15 12:29:20 +02:00
def post_process
2013-07-10 22:55:37 +02:00
post_process_attachments
2013-06-15 12:29:20 +02:00
post_process_images
post_process_oneboxes
2013-02-05 14:16:51 -05:00
end
2013-07-10 22:55:37 +02:00
def post_process_attachments
attachments . each do | attachment |
href = attachment [ 'href' ]
attachment [ 'href' ] = relative_to_absolute ( href )
2013-07-14 12:28:24 +02:00
# update reverse index
2013-07-10 22:55:37 +02:00
if upload = Upload . get_from_url ( href )
associate_to_post ( upload )
end
end
end
2013-02-25 19:42:20 +03:00
def post_process_images
2013-07-08 01:39:08 +02:00
images = extract_images
2013-07-06 19:10:53 +02:00
return if images . blank?
2013-02-05 14:16:51 -05:00
2013-02-19 17:57:14 +11:00
images . each do | img |
2013-07-08 01:39:08 +02:00
if img [ 'src' ] . present?
# keep track of the original src
src = img [ 'src' ]
# make sure the src is absolute (when working with locally uploaded files)
img [ 'src' ] = relative_to_absolute ( src )
# make sure the img has proper width and height attributes
2013-06-15 12:29:20 +02:00
update_dimensions! ( img )
2013-06-17 01:00:25 +02:00
# retrieve the associated upload, if any
2013-07-10 22:55:37 +02:00
if upload = Upload . get_from_url ( src )
2013-06-17 22:46:48 +02:00
# update reverse index
2013-07-08 01:39:08 +02:00
associate_to_post ( upload )
2013-06-17 01:00:25 +02:00
end
2013-07-08 01:39:08 +02:00
# lightbox treatment
convert_to_link! ( img , upload )
2013-06-15 12:29:20 +02:00
# mark the post as dirty whenever the src has changed
@dirty |= src != img [ 'src' ]
2013-02-05 14:16:51 -05:00
end
2013-04-13 16:31:20 +02:00
end
2013-02-19 17:57:14 +11:00
2013-04-13 16:31:20 +02:00
# Extract the first image from the first post and use it as the 'topic image'
2013-07-08 01:39:08 +02:00
extract_topic_image ( images )
2013-02-19 17:57:14 +11:00
end
2013-06-15 12:29:20 +02:00
def post_process_oneboxes
args = { post_id : @post . id }
args [ :invalidate_oneboxes ] = true if @opts [ :invalidate_oneboxes ]
# bake onebox content into the post
result = Oneboxer . apply ( @doc ) do | url , element |
Oneboxer . onebox ( url , args )
end
# mark the post as dirty whenever a onebox as been baked
@dirty |= result . changed?
end
2013-04-13 16:31:20 +02:00
2013-07-08 01:39:08 +02:00
def extract_images
# do not extract images inside a onebox or a quote
@doc . css ( " img " ) - @doc . css ( " .onebox-result img " ) - @doc . css ( " .quote img " )
end
def relative_to_absolute ( src )
if src =~ / \ A \/ [^ \/ ] /
Discourse . base_url_no_prefix + src
else
src
end
end
2013-06-15 12:29:20 +02:00
def update_dimensions! ( img )
return if img [ 'width' ] . present? && img [ 'height' ] . present?
w , h = get_size_from_image_sizes ( img [ 'src' ] , @opts [ :image_sizes ] ) || image_dimensions ( img [ 'src' ] )
if w && h
2013-07-08 01:39:08 +02:00
img [ 'width' ] = w
img [ 'height' ] = h
2013-06-15 12:29:20 +02:00
@dirty = true
end
end
2013-02-21 12:07:36 +11:00
2013-06-17 22:46:48 +02:00
def associate_to_post ( upload )
return if PostUpload . where ( post_id : @post . id , upload_id : upload . id ) . count > 0
PostUpload . create ( { post_id : @post . id , upload_id : upload . id } )
rescue ActiveRecord :: RecordNotUnique
# do not care if it's already associated
end
2013-07-08 01:39:08 +02:00
def optimize_image! ( img )
# TODO
2013-06-16 10:21:01 +02:00
# 1) optimize using image_optim
2013-07-08 01:39:08 +02:00
# 2) .png vs. .jpg (> 1.5x)
2013-02-19 17:57:14 +11:00
end
2013-06-21 18:29:40 +02:00
def convert_to_link! ( img , upload = nil )
2013-02-19 17:57:14 +11:00
src = img [ " src " ]
2013-07-06 19:10:53 +02:00
return unless src . present?
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
2013-04-13 16:31:20 +02:00
original_width , original_height = get_size ( src )
2013-02-19 17:57:14 +11:00
2013-07-08 01:39:08 +02:00
return if original_width . to_i < = width && original_height . to_i < = height
return if original_width . to_i < = SiteSetting . max_image_width
return if is_a_hyperlink ( img )
if upload
# create a thumbnail
upload . create_thumbnail!
# optimize image
# TODO: optimize_image!(img)
end
2013-02-19 17:57:14 +11:00
2013-07-08 01:39:08 +02:00
add_lightbox! ( img , original_width , original_height , upload )
@dirty = true
end
def is_a_hyperlink ( img )
2013-02-19 17:57:14 +11:00
parent = img . parent
while parent
return if parent . name == " a "
break unless parent . respond_to? :parent
parent = parent . parent
end
2013-07-08 01:39:08 +02:00
end
2013-02-19 17:57:14 +11:00
2013-07-08 01:39:08 +02:00
def add_lightbox! ( img , original_width , original_height , upload = nil )
2013-06-26 02:44:20 +02:00
# first, create a div to hold our lightbox
2013-07-08 01:39:08 +02:00
lightbox = Nokogiri :: XML :: Node . new ( " div " , @doc )
img . add_next_sibling ( lightbox )
lightbox . add_child ( img )
2013-06-26 02:44:20 +02:00
# then, the link to our larger image
2013-07-08 01:39:08 +02:00
a = Nokogiri :: XML :: Node . new ( " a " , @doc )
2013-02-19 17:57:14 +11:00
img . add_next_sibling ( a )
2013-07-08 01:39:08 +02:00
a [ " href " ] = img [ 'src' ]
2013-02-19 17:57:14 +11:00
a [ " class " ] = " lightbox "
a . add_child ( img )
2013-07-08 01:39:08 +02:00
# replace the image by its thumbnail
img [ 'src' ] = upload . thumbnail_url if upload && upload . has_thumbnail?
2013-06-26 02:44:20 +02:00
# then, some overlay informations
2013-07-08 01:39:08 +02:00
meta = Nokogiri :: XML :: Node . new ( " div " , @doc )
2013-06-26 02:44:20 +02:00
meta [ " class " ] = " meta "
2013-07-08 01:39:08 +02:00
img . add_next_sibling ( meta )
2013-06-21 18:29:40 +02:00
2013-07-08 01:39:08 +02:00
filename = get_filename ( upload , img [ 'src' ] )
2013-06-21 18:29:40 +02:00
informations = " #{ original_width } x #{ 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
2013-06-26 02:44:20 +02:00
meta . add_child create_span_node ( " filename " , filename )
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-06-26 21:53:31 +02:00
return I18n . t ( 'upload.pasted_image_filename' )
end
2013-06-21 18:29:40 +02:00
def create_span_node ( klass , content = nil )
2013-07-08 01:39:08 +02:00
span = Nokogiri :: XML :: Node . new ( " span " , @doc )
2013-06-21 18:29:40 +02:00
span . content = content if content
span [ 'class' ] = klass
span
2013-02-05 14:16:51 -05:00
end
2013-07-08 01:39:08 +02:00
def extract_topic_image ( images )
if @post . post_number == 1
img = images . first
@post . topic . update_column :image_url , img [ 'src' ] if img [ 'src' ] . present?
end
end
2013-02-19 17:57:14 +11:00
def get_size_from_image_sizes ( src , image_sizes )
if image_sizes . present?
if dim = image_sizes [ src ]
ImageSizer . resize ( dim [ 'width' ] , dim [ 'height' ] )
end
end
end
2013-02-05 14:16:51 -05:00
2013-06-15 12:29:20 +02:00
# Retrieve the image dimensions for a url
def image_dimensions ( url )
w , h = get_size ( url )
ImageSizer . resize ( w , h ) if w && h
2013-02-05 14:16:51 -05:00
end
2013-06-15 12:29:20 +02:00
def get_size ( url )
2013-06-22 13:38:42 +02:00
# make sure s3 urls have a scheme (otherwise, FastImage will fail)
2013-07-08 01:39:08 +02:00
url = " http: " + url if Upload . is_on_s3? ( url )
return unless is_valid_image_uri? ( url )
2013-06-22 13:38:42 +02:00
# we can *always* crawl our own images
2013-06-16 10:21:01 +02:00
return unless SiteSetting . crawl_images? || Upload . has_been_uploaded? ( url )
2013-06-15 12:29:20 +02:00
@size_cache [ url ] || = FastImage . size ( url )
rescue Zlib :: BufError # FastImage.size raises BufError for some gifs
2013-02-05 14:16:51 -05:00
end
2013-06-22 13:38:42 +02:00
def is_valid_image_uri? ( url )
2013-06-15 12:29:20 +02:00
uri = URI . parse ( url )
2013-06-22 13:38:42 +02:00
%w( http https ) . include? uri . scheme
2013-06-24 21:38:00 +02:00
rescue URI :: InvalidURIError
2013-04-10 17:52:38 +10:00
end
2013-07-10 22:55:37 +02:00
def attachments
if SiteSetting . enable_s3_uploads?
2013-07-22 02:39:17 +02:00
@doc . css ( " a.attachment[href^= \" #{ S3Store . base_url } \" ] " )
2013-07-10 22:55:37 +02:00
else
# local uploads are identified using a relative uri
2013-07-22 02:39:17 +02:00
@doc . css ( " a.attachment[href^= \" #{ LocalStore . directory } \" ] " ) +
# when cdn is enabled, we have the whole url
@doc . css ( " a.attachment[href^= \" #{ LocalStore . base_url } \" ] " )
2013-07-10 22:55:37 +02:00
end
end
2013-06-15 12:29:20 +02:00
def dirty?
@dirty
end
def html
@doc . try ( :to_html )
2013-02-05 14:16:51 -05:00
end
end