jomz / radiant-tags-extension

Extends Radiant CMS with tagging capabilities. Tagging as in "2.0" and tagclouds.

This URL has Read+Write access

radiant-tags-extension / app / models / radius_tags.rb
100644 250 lines (217 sloc) 8.49 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
module RadiusTags
  include Radiant::Taggable
  include ActionView::Helpers::TextHelper
  
  class TagError < StandardError; end
  
  desc %{
Expands if a <pre><r:tagged with="" /></pre> call would return items. Takes the same options as the 'tagged' tag.
The <pre><r:unless_tagged with="" /></pre> is also available.
}
  tag "if_tagged" do |tag|
    tag.locals.tagged_results = find_with_tag_options(tag)
    tag.expand unless tag.locals.tagged_results.empty?
  end
  tag "unless_tagged" do |tag|
    tag.expand if find_with_tag_options(tag).empty?
  end
  
  desc %{
Find all pages with certain tags, within in an optional scope. Additionally, you may set with_any to true to select pages that have any of the listed tags (opposed to all listed tags which is the provided default).
*Usage:*
<pre><code><r:tagged with="shoes diesel" [scope="/fashion/cult-update"] [with_any="true"] [offset="number"] [limit="number"] [by="attribute"] [order="asc|desc"]>...</r:tagged></code></pre>
}
  tag "tagged" do |tag|
    unless tag.locals.tagged_results.nil? # We're inside an r:if_tagged, so results are already available;
      results = tag.locals.tagged_results
    else
      results = find_with_tag_options(tag)
    end
    output = []
    results.each do |page|
      tag.locals.page = page
      output << tag.expand
    end
    output
  end
  
  desc %{
Render a Tag cloud
The results_page attribute will default to #{Radiant::Config['tags.results_page_url']}
*Usage:*
<pre><code><r:tag_cloud_list [limit="number"] [results_page="/some/url"] [scope="/some/url"]/></code></pre>
}
  tag "tag_cloud" do |tag|
    tag_cloud = MetaTag.cloud(:limit => tag.attr['limit'] || 5).sort
    tag_cloud = filter_tags_to_url_scope(tag_cloud, tag.attr['scope']) unless tag.attr['scope'].nil?
    
    results_page = tag.attr['results_page'] || Radiant::Config['tags.results_page_url']
    output = "<ol class=\"tag_cloud\">"
    if tag_cloud.length > 0
     build_tag_cloud(tag_cloud, %w(size1 size2 size3 size4 size5 size6 size7 size8 size9)) do |tag, cloud_class, amount|
     output += "<li class=\"#{cloud_class}\"><span>#{pluralize(amount, 'page is', 'pages are')} tagged with </span><a href=\"#{results_page}/#{tag}\" class=\"tag\">#{tag}</a></li>"
     end
    else
     return "<p>No tags found.</p>"
    end
    output += "</ol>"
  end
 
  desc %{
Render a Tag list, more for 'categories'-ish usage, i.e.: Cats (2) Logs (1) ...
The results_page attribute will default to #{Radiant::Config['tags.results_page_url']}
*Usage:*
<pre><code><r:tag_cloud_list [results_page="/some/url"] [scope="/some/url"]/></code></pre>
}
  tag "tag_cloud_list" do |tag|
    tag_cloud = MetaTag.cloud({:limit => 100}).sort
    tag_cloud = filter_tags_to_url_scope(tag_cloud, tag.attr['scope']) unless tag.attr['scope'].nil?
    
    results_page = tag.attr['results_page'] || Radiant::Config['tags.results_page_url']
    output = "<ul class=\"tag_list\">"
    if tag_cloud.length > 0
        build_tag_cloud(tag_cloud, %w(size1 size2 size3 size4 size5 size6 size7 size8 size9)) do |tag, cloud_class, amount|
          output += "<li class=\"#{cloud_class}\"><a href=\"#{results_page}?tag=#{tag}\" class=\"tag\">#{tag} (#{amount})</a></li>"
        end
    else
        return "<p>No tags found.</p>"
    end
    output += "</ul>"
  end
 
  desc "List the current page's tags"
  tag "tag_list" do |tag|
    output = []
    tag.locals.page.tag_list.split(MetaTag::DELIMITER).each {|t| output << "<a href=\"#{tag_item_url(t)}\" class=\"tag\">#{t}</a>"}
    output.join ", "
  end
  
  desc "List the current page's tagsi as technorati tags. this should be included in the body of a post or in your rss feed"
  tag "tag_list_technorati" do |tag|
    output = []
    tag.locals.page.tag_list.split(MetaTag::DELIMITER).each {|t| output << "<a href=\"http://technorati.com/tag/#{ t.split(" ").join("+")}\" rel=\"tag\">#{t}</a>"}
    output.join ", "
  end
 
  desc "Set the scope for all tags in the database"
  tag "all_tags" do |tag|
    tag.expand
  end
  
  desc %{
Iterates through each tag and allows you to specify the order: by popularity or by name.
The default is by name. You may also limit the search; the default is 5 results.
Usage: <pre><code><r:all_tags:each order="popularity" limit="5">...</r:all_tags:each></code></pre>
}
  tag "all_tags:each" do |tag|
    order = tag.attr['order'] || 'name'
    limit = tag.attr['limit'] || '5'
    result = []
    case order
    when 'name'
      all_tags = MetaTag.find(:all, :limit => limit)
    else
      all_tags = MetaTag.cloud(:limit => limit)
    end
    all_tags.each do |t|
      tag.locals.meta_tag = t
      result << tag.expand
    end
    result
  end
  
  desc "Renders the tag's name"
  tag "all_tags:each:name" do |tag|
    tag.locals.meta_tag.name
  end
  
  desc "Set the scope for the tag's pages"
  tag "all_tags:each:pages" do |tag|
    tag.expand
  end
  
  desc "Iterates through each page"
  tag "all_tags:each:pages:each" do |tag|
    result = []
    tag.locals.meta_tag.taggables.each do |taggable|
      if taggable.is_a?(Page)
        tag.locals.page = taggable
        result << tag.expand
      end
    end
    result
  end
  
  private
  
  def build_tag_cloud(tag_cloud, style_list)
    max, min = 0, 0
    tag_cloud.each do |tag|
      max = tag.popularity.to_i if tag.popularity.to_i > max
      min = tag.popularity.to_i if tag.popularity.to_i < min
    end
    
    divisor = ((max - min) / style_list.size) + 1
 
    tag_cloud.each do |tag|
      yield tag.name, style_list[(tag.popularity.to_i - min) / divisor], tag.popularity.to_i
    end
  end
 
  def tag_item_url(name)
    "#{Radiant::Config['tags.results_page_url']}/#{name}"
  end
  
  def find_with_tag_options(tag)
    options = tagged_with_options(tag)
    with_any = tag.attr['with_any'] || false
    scope_attr = tag.attr['scope'] || '/'
    results = []
    raise TagError, "`tagged' tag must contain a `with' attribute." unless (tag.attr['with'] || tag.locals.page.class_name = TagSearchPage)
    ttag = tag.attr['with'] || @request.parameters[:tag]
    
    scope = scope_attr == 'current_page' ? Page.find_by_url(@request.request_uri) : Page.find_by_url(scope_attr)
    return "The scope attribute must be a valid url to an existing page." if scope.class_name.eql?('FileNotFoundPage')
    
    if with_any
      Page.tagged_with_any(ttag, options).each do |page|
          next unless (page.ancestors.include?(scope) or page == scope)
          results << page
      end
    else
      Page.tagged_with(ttag, options).each do |page|
          next unless (page.ancestors.include?(scope) or page == scope)
          results << page
      end
    end
    results
  end
  
  def tagged_with_options(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 self.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
    options
  end
 
  def filter_tags_to_url_scope(tags, scope)
    new_tags = []
    tags.each do |t|
      catch :record_found do # using fancy ballsports stuff to avoid unnecessary db calls (by calling each page, ànd by calling page.url)
        t.pages.each do |p|
          (new_tags << t; throw :record_found) if p.url.include?(scope)
        end
      end
    end
    new_tags
  end
end