class PageContext < Radius::Context class TagError < StandardError; end attr_reader :page def initialize(page) super() @page = page globals.page = @page # ... # # Causes the tags referring to a page's attributes to refer to the current page. # define_tag 'page' do |tag| tag.locals.page = tag.globals.page tag.expand end # # # # etc... # ((@page.attributes.symbolize_keys.keys + [:url]) - [:created_by, :updated_by]).each do |method| define_tag(method.to_s) do |tag| tag.locals.page.send(method) end end # # ... # # Gives access to a page's children. # define_tag 'children' do |tag| tag.locals.children = tag.locals.page.children tag.expand end # # # # Renders the total number of children. # define_tag 'children:count' do |tag| tag.locals.children.count end # # ... # # Returns the first child. Inside this tag all page attribute tags are mapped to # the first child. # define_tag 'children:first' do |tag| children = tag.locals.children if first = children.first tag.locals.page = first tag.expand end end # # ... # # Returns the last child. Inside this tag all page attribute tags are mapped to # the last child. # define_tag 'children:last' do |tag| children = tag.locals.children if last = children.last tag.locals.page = last tag.expand end end # # # ... # # # Cycles through each of the children. Inside this tag all page attribute tags # are mapped to the current child page. # define_tag "children:each" do |tag| attr = tag.attr.symbolize_keys options = {} [:limit, :offset].each do |symbol| if number = attr[symbol] if number =~ /^\d{1,4}$/ options[symbol] = number.to_i else raise TagError.new("`#{symbol}' attribute of `each' tag must be a positive number between 1 and 4 digits") end end end by = (attr[:by] || 'published_at').strip order = (attr[:order] || 'asc').strip order_string = '' if @page.attributes.keys.include?(by) order_string << by else raise TagError.new("`by' attribute of `each' tag must be set to a valid field name") end if order =~ /^(asc|desc)$/i order_string << " #{$1.upcase}" else raise TagError.new(%{`order' attribute of `each' tag must be set to either "asc" or "desc"}) end options[:order] = order_string status = (attr[:status] || 'published').downcase unless status == 'all' stat = Status[status] unless stat.nil? options[:conditions] = ["(virtual = ?) and (status_id = ?)", false, stat.id] else raise TagError.new(%{`status' attribute of `each' tag must be set to a valid status}) end else options[:conditions] = ["virtual = ?", false] end result = [] children = tag.locals.children tag.locals.previous_headers = {} children.find(:all, options).each do |item| tag.locals.child = item tag.locals.page = item result << tag.expand end result end # # ... # # Page attribute tags inside of this tag refer to the current child. Not needed in # most cases. # define_tag 'children:each:child' do |tag| tag.locals.page = tag.locals.child tag.expand end # #
... # # Renders the tag contents only if the contents do not match the previous header. This # is extremely useful for rendering date headers for a list of child pages. # # If you would like to use several header blocks you may use the 'name' attribute to # name the header. When a header is named it will not restart until another header of # the same name is different. # # Using the 'restart' attribute you can cause other named headers to restart when the # present header changes. Simply specify the names of the other headers in a semicolon # separated list. # define_tag 'children:each:header' do |tag| previous_headers = tag.locals.previous_headers name = tag.attr['name'] || :unnamed restart = (tag.attr['restart'] || '').split(';') header = tag.expand unless header == previous_headers[name] previous_headers[name] = header unless restart.empty? restart.each do |n| previous_headers[n] = nil end end header end end # # # # Renders the main content of a page. Use the 'part' attribute to select a specific # page part. By default the 'part' attribute is set to body. Use the 'inherit' # attribute to specify that if a page does not have a content part by that name that # the tag should render the parent's content part. By default 'inherit' is set to # 'false'. Use the 'contextual' attribute to force a part inherited from a parent # part to be evaluated in the context of the child page. By default 'contextual' # is set to false. # define_tag 'content' do |tag| page = tag.locals.page part_name = tag_part_name(tag) boolean_attr = proc do |attribute_name, default| attribute = (tag.attr[attribute_name] || default).to_s raise TagError.new(%{`#{attribute_name}' attribute of `content' tag must be set to either "true" or "false"}) unless attribute =~ /true|false/i (attribute.downcase == 'true') ? true : false end inherit = boolean_attr['inherit', false] part_page = page if inherit while (part_page.part(part_name).nil? and (not part_page.parent.nil?)) do part_page = part_page.parent end end contextual = boolean_attr['contextual', true] if inherit and contextual part = part_page.part(part_name) page.behavior.render_snippet(part) unless part.nil? else part_page.behavior.render_page_part(part_name) end end # # ... # # Renders the containing elements only if the part exists on a page. By default the # 'part' attribute is set 'body'. # define_tag 'if_content' do |tag| page = tag.locals.page part_name = tag_part_name(tag) unless page.part(part_name).nil? tag.expand end end # # ... # # The opposite of the 'if_content' tag. # define_tag 'unless_content' do |tag| page = tag.locals.page part_name = tag_part_name(tag) if page.part(part_name).nil? tag.expand end end # # ... # # Renders the containing elements only if the page's url matches the regular expression # given in the 'matches' attribute. If the 'ìgnore_case' attribute is set to false, the # match is case sensitive. By default, 'ignore_case' is set to true. # define_tag 'if_url' do |tag| raise TagError.new("`if_url' tag must contain a `matches' attribute.") unless tag.attr.has_key?('matches') regexp = build_regexp_for(tag, 'matches') unless tag.locals.page.url.match(regexp).nil? tag.expand end end # # ... # # The opposite of the 'if_url' tag. # define_tag 'unless_url' do |tag| raise TagError.new("`unless_url' tag must contain a `matches' attribute.") unless tag.attr.has_key?('matches') regexp = build_regexp_for(tag, 'matches') if tag.locals.page.url.match(regexp).nil? tag.expand end end # # # # Renders the name of the Author of the current page. # define_tag 'author' do |tag| page = tag.locals.page if author = page.created_by author.name end end # # # # Renders the date that a page was published (or in the event that it has # not ben modified yet, the date that it was created). The format attribute # uses the same formating codes used by the Ruby +strftime+ function. By # default it's set to '%A, %B %d, %Y'. # define_tag 'date' do |tag| page = tag.locals.page format = tag_time_format(tag) if date = page.published_at || page.created_at date.strftime(format) end end # # # ... # # Renders a link to the page. When used as a single tag it uses the page's title # for the link name. When used as a double tag the part inbetween both tags will # be used as the link text. The link tag passes all attributes over to the HTML # 'a' tag. This is very useful for passing attributes like the 'class' attribute # or 'id' attribute. If the 'anchor' attribute is passed to the tag it will # append a pound sign ('#') followed by the value of the attribute to the 'href' # attribute of the HTML 'a' tag--effectively making an HTML anchor. # define_tag 'link' do |tag| options = tag.attr.dup anchor = options['anchor'] ? "##{options.delete('anchor')}" : '' attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip attributes = " #{attributes}" unless attributes.empty? text = tag.double? ? tag.expand : tag.render('title') %{#{text}} end # # # # Renders a trail of breadcrumbs to the current page. The separator attribute # specifies the HTML fragment that is inserted between each of the breadcrumbs. By # default it is set to ' > '. # define_tag 'breadcrumbs' do |tag| page = tag.locals.page breadcrumbs = [page.breadcrumb] page.ancestors.each do |ancestor| breadcrumbs.unshift %{#{ancestor.breadcrumb}} end separator = tag.attr['separator'] || ' > ' breadcrumbs.join(separator) end # # # # Renders the snippet specified in the 'name' attribute within the context of a page. # define_tag 'snippet' do |tag| if name = tag.attr['name'] if snippet = Snippet.find_by_name(name.strip) page = tag.locals.page page.behavior.render_snippet(snippet) else raise TagError.new('snippet not found') end else raise TagError.new("`snippet' tag must contain `name' attribute") end end # # ... # # Inside this tag all page related tags refer to the tag found at the 'url' attribute. # define_tag 'find' do |tag| if url = tag.attr['url'] if found = Page.find_by_url(tag.attr['url']) tag.locals.page = found tag.expand end else raise TagError.new("`find' tag must contain `url' attribute") end end # # # ... # ... # ... # # # Randomly renders one of the options specified by the 'option' tags. # define_tag 'random' do |tag| tag.locals.random = [] tag.expand options = tag.locals.random option = options[rand(options.size)] option.call if option end define_tag 'random:option' do |tag| items = tag.locals.random items << tag.block end # # ... # # Nothing inside a set of comment tags is rendered. # define_tag 'comment' do |tag| end # # ... # # Escapes angle brackets, etc... # define_tag "escape_html" do |tag| CGI.escapeHTML(tag.expand) end # # # # Outputs the date using the format mandated by RFC 1123. (Ideal for RSS feeds.) # define_tag "rfc1123_date" do |tag| page = tag.locals.page if date = page.published_at || page.created_at CGI.rfc1123_date(date.to_time) end end # # # # # # | # # # Renders a list of links specified in the 'urls' attribute according to three # states: # # * 'normal' specifies the normal state for the link # * 'here' specifies the state of the link when the url matches the current # page's url # * 'selected' specifies the state of the link when the current page matches # is a child of the specified url # # The 'between' tag specifies what sould be inserted inbetween each of the links. # define_tag 'navigation' do |tag| hash = tag.locals.navigation = {} tag.expand raise TagError.new("`navigation' tag must include a `normal' tag") unless hash.has_key? :normal result = [] pairs = tag.attr['urls'].to_s.split(';').collect do |pair| parts = pair.split(':') value = parts.pop key = parts.join(':') [key.strip, value.strip] end pairs.each do |title, url| compare_url = remove_trailing_slash(url) page_url = remove_trailing_slash(@page.url) hash[:title] = title hash[:url] = url case page_url when compare_url result << (hash[:here] || hash[:selected] || hash[:normal]).call when Regexp.compile( '^' + Regexp.quote(url)) result << (hash[:selected] || hash[:normal]).call else result << hash[:normal].call end end between = hash.has_key?(:between) ? hash[:between].call : ' ' result.join(between) end [:normal, :here, :selected, :between].each do |symbol| define_tag "navigation:#{symbol}" do |tag| hash = tag.locals.navigation hash[symbol] = tag.block end end [:title, :url].each do |symbol| define_tag "navigation:#{symbol}" do |tag| hash = tag.locals.navigation hash[symbol] end end end def render_tag(name, attributes = {}, &block) super rescue Exception => e render_error_message(e.message) end def tag_missing(name, attributes = {}, &block) super rescue Radius::UndefinedTagError => e raise TagError.new(e.message) end private def render_error_message(message) "
#{message}
" end def remove_trailing_slash(string) (string =~ %r{^(.*?)/$}) ? $1 : string end def tag_part_name(tag) tag.attr['part'] || 'body' end def tag_time_format(tag) (tag.attr['format'] || '%A, %B %d, %Y') end def postgres? ActiveRecord::Base.connection.adapter_name =~ /postgres/i end def build_regexp_for(tag,attribute_name) ignore_case = tag.attr.has_key?('ignore_case') && tag.attr['ignore_case']=='false' ? nil : true begin regexp = Regexp.new(tag.attr['matches'],ignore_case) rescue RegexpError => e raise TagError.new("Malformed regular expression in `#{attribute_name}' argument of `#{tag.name}' tag: #{e.message}") end return regexp end end