require 'tempfile'
module Zena
module Use
module Rendering
class Redirect < Exception
attr_reader :url
def initialize(url)
@url = url
end
end # Redirect class
module ViewMethods
# Append javascript to the end of the page.
def render_js(in_html = true)
return '' unless js_data
js = js_data.join("\n")
if in_html
javascript_tag(js)
else
js
end
end
def set_headers(params)
zafu_headers.merge!(params)
end
def help(key)
%Q{
}
end
end
module ControllerMethods
def self.included(base)
base.send(:helper_attr, :js_data, :zafu_headers)
base.send(:layout, false)
end
def js_data
@js_data ||= []
end
def zafu_headers
@zafu_headers ||= {}
end
# TODO: test
# Our own handling of exceptions
def rescue_action_in_public(exception)
case exception
when ActiveRecord::RecordNotFound, ActionController::UnknownAction
render_404(exception)
else
render_500(exception)
end
end
def rescue_action(exception)
case exception
when ActiveRecord::RecordNotFound, ActionController::UnknownAction
render_404(exception)
else
super
end
end
# TODO: test
def render_404(exception)
if Thread.current[:visitor]
# page not found
@node = current_site.root_node
zafu_node('@node', Project)
respond_to do |format|
format.html do
not_found = "#{SITES_ROOT}/#{current_site.host}/public/#{prefix}/404.html"
if File.exists?(not_found)
render :text => File.read(not_found), :status => '404 Not Found'
else
render_and_cache :mode => '+notFound', :format => 'html', :cache_url => "/#{prefix}/404.html", :status => '404 Not Found'
end
end
format.all { render :nothing => true, :status => "404 Not Found" }
end
else
# site not found
respond_to do |format|
format.html { render :text => File.read("#{Zena::ROOT}/app/views/sites/404.html"), :status => '404 Not Found' }
format.all { render :nothing => true, :status => "404 Not Found" }
end
end
rescue ActiveRecord::RecordNotFound => err
# this is bad
render_500(err)
end
# TODO: test
def render_500(exception)
# TODO: send an email with the exception ?
# msg =<<-END_MSG
# Something bad happened to your zena installation:
# --------------------------
# #{exception.message}
# --------------------------
# #{exception.backtrace.join("\n")}
# END_MSG
respond_to do |format|
format.html { render :text => File.read("#{Zena::ROOT}/app/views/nodes/500.html"), :status => '500 Error' }
format.all { render :nothing => true, :status => "500 Error" }
end
end
def render_and_cache(options={})
opts = {:skin => @node[:skin], :cache => true}.merge(options)
opts[:mode ] ||= params[:mode]
opts[:format] ||= params[:format].blank? ? 'html' : params[:format]
#
# cleanup before rendering
params.delete(:mode)
if opts[:format] != 'html'
method = "render_to_#{opts[:format]}"
if params.keys.include?('debug')
template_path = template_url(opts)
result = {
:data => render_to_string(:file => template_path, :layout=>false),
:disposition => params['disposition'] || 'inline',
:type => 'text/html',
}
opts[:cache] = false
elsif respond_to?(method)
# Call custom rendering engine 'render_to_pdf' for example.
result = send(method, opts)
else
template_path = template_url(opts)
# FIXME: Use Mime types to resolve content type...
content_type = (Zena::EXT_TO_TYPE[opts[:format]] || ['application/octet-stream'])[0]
result = {
:data => render_to_string(:file => template_path, :layout=>false),
:disposition => 'inline',
:type => content_type,
:filename => @node.title
}
end
if result[:type] == 'text/html'
# error reporting from rendering engine
opts[:cache] = false
render :text => result[:data]
else
if zafu_headers
if disposition = zafu_headers.delete('Content-Disposition')
result.delete(:filename) if disposition =~ /filename\s*=/
result[:disposition] = disposition
end
if type = zafu_headers.delete('Content-Type')
result[:type] = type
end
if filename = zafu_headers.delete('filename')
result[:filename] = filename
end
headers.merge!(zafu_headers)
end
if data = result.delete(:data)
send_data(data , result)
elsif file = result.delete(:file)
send_file(file , result)
else
# Should never happen
raise Exception.new("Render '#{params[:format]}' should return either :file or :data (none found).")
end
end
cache_page(:content_data => result[:data], :content_path => result[:file]) if opts[:cache]
else
# html
render :file => template_url(opts), :layout=>false, :status => opts[:status]
headers.merge!(zafu_headers)
cache_page(:url => opts[:cache_url]) if opts[:cache]
end
# This does not work, Rendering::Redirect is wrapped in TemplateError
# rescue Zena::Use::Rendering::Redirect
# This does not work either: infinity loop, CPU hog on errors.
# rescue ActionView::TemplateError => err
# orig = err.original_exception
# if orig.kind_of?(Zena::Use::Rendering::Redirect)
# redirect_to orig.url
# else
# raise err
# end
end
# Cache page content into a static file in the current sites directory : SITES_ROOT/test.host/public
def cache_page(opts={})
if cachestamp_format?(params['format'])
headers['Expires'] = (Time.now + 365*24*3600).strftime("%a, %d %b %Y %H:%M:%S GMT")
headers['Cache-Control'] = (!current_site.authentication? && @node.public?) ? 'public' : 'private'
end
return unless perform_caching && caching_allowed(:authenticated => opts.delete(:authenticated))
url = page_cache_file(opts.delete(:url))
opts = {:expire_after => nil,
:path => (current_site.public_path + url),
:content_data => response.body,
:node_id => @node[:id]
}.merge(opts)
secure!(CachedPage) { CachedPage.create(opts) }
end
# Return true if we can cache the current page
def caching_allowed(opts = {})
return false if current_site.authentication? || query_params != {}
opts[:authenticated] || visitor.is_anon?
end
# Cache file path that reflects the called url
def page_cache_file(url = nil)
path = url || url_for(:only_path => true, :skip_relative_url_root => true, :cachestamp => nil)
path = ((path.empty? || path == "/") ? "/index" : URI.unescape(path))
ext = params[:format].blank? ? 'html' : params[:format]
path << ".#{ext}" unless path =~ /\.#{ext}(\?\d+|)$/
#
# QUERY_STRING in cached page ?
#
# Do not cache filename with query or apache will not see it !
# if cachestamp_format?(params['format'])
# path << "?" << make_cachestamp(@node, params['mode'])
# end
path
end
# Find the proper layout to render 'admin' actions. The layout is searched into the visitor's contact's skin first
# and then into default. This action is also responsible for setting a default @title_for_layout.
def admin_layout
@title_for_layout ||= "#{params[:controller]}/#{params[:action]}"
template_url(:mode => '+adminLayout')
end
# TODO: test
def popup_layout
js_data << "var is_editor = true;"
template_url(:mode=>'+popupLayout')
end
# Return the window title to use.
def title_for_layout
return '' unless @node
@node.title + (@node.kind_of?(Document) ? ".#{@node.ext}" : '')
end
# Use the current visitor as master node.
def visitor_node
@node = visitor.node
zafu_node('@node', Node)
end
private
# This is called before rendering for special formats (pdf) in order to rewrite
# urls to localhost (the rendering engine is external to Zena and will need to
# make calls to get assets).
def baseurl
if Zena::ASSET_PORT
if Zena::ASSET_PORT == request.port
raise Exception.new("Custom rendering not allowed on this process (port == asset_port).")
else
"http://localhost:#{Zena::ASSET_PORT}"
end
else
raise Exception.new("Using custom rendering without an asset host ('asset_port' setting in bricks.yml).")
end
end
def get_render_auth_params
{
:http_user => visitor.id,
:http_password => visitor.persistence_token,
:baseurl => baseurl
}
end
end # ControllerMethods
module ZafuMethods
def r_headers
headers = []
@params.each do |k, v|
headers << "#{k.to_s.inspect} => #{RubyLess.translate_string(self, v)}"
end
if headers.empty?
out ""
else
out "<% set_headers(#{headers.join(', ')}) %>"
end
end
def r_style
@markup.tag = 'style'
expand_with
end
def r_not_found
out "<% raise ActiveRecord::RecordNotFound %>"
end
# Does not work properly. FIXME.
# def r_redirect
# out parser_error('Missing "url" parameter.') unless url = @params[:url]
# code = ::RubyLess.translate_string(self, url)
# out "<% raise Zena::Use::Rendering::Redirect.new(#{code}) %>"
# end
end # ZafuMethods
end # Rendering
end # Use
end # Zena