Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 373
- Log:
Initial import of Radiant 0.9.1, which is now packaged as a gem. This is an
import of the tagged 0.9.1 source checked out from GitHub, which isn't quite
the same as the gem distribution - but it doesn't seem to be available in an
archived form and the installed gem already has modifications, so this is
the closest I can get.
- Author:
- rool
- Date:
- Mon Mar 21 13:40:05 +0000 2011
- Size:
- 39654 Bytes
1 | module StandardTags |
2 | |
3 | include Radiant::Taggable |
4 | include LocalTime |
5 | include WillPaginate::ViewHelpers |
6 | |
7 | class TagError < StandardError; end |
8 | |
9 | desc %{ |
10 | Causes the tags referring to a page's attributes to refer to the current page. |
11 | |
12 | *Usage:* |
13 | |
14 | <pre><code><r:page>...</r:page></code></pre> |
15 | } |
16 | tag 'page' do |tag| |
17 | tag.locals.page = tag.globals.page |
18 | tag.expand |
19 | end |
20 | |
21 | [:breadcrumb, :slug, :title].each do |method| |
22 | desc %{ |
23 | Renders the @#{method}@ attribute of the current page. |
24 | } |
25 | tag method.to_s do |tag| |
26 | tag.locals.page.send(method) |
27 | end |
28 | end |
29 | |
30 | desc %{ |
31 | Renders the @url@ attribute of the current page. |
32 | } |
33 | tag 'url' do |tag| |
34 | relative_url_for(tag.locals.page.url, tag.globals.page.request) |
35 | end |
36 | |
37 | desc %{ |
38 | Gives access to a page's children. |
39 | |
40 | *Usage:* |
41 | |
42 | <pre><code><r:children>...</r:children></code></pre> |
43 | } |
44 | tag 'children' do |tag| |
45 | tag.locals.children = tag.locals.page.children |
46 | tag.expand |
47 | end |
48 | |
49 | desc %{ |
50 | Renders the total number of children. |
51 | } |
52 | tag 'children:count' do |tag| |
53 | options = children_find_options(tag) |
54 | options.delete(:order) # Order is irrelevant |
55 | tag.locals.children.count(options) |
56 | end |
57 | |
58 | desc %{ |
59 | Returns the first child. Inside this tag all page attribute tags are mapped to |
60 | the first child. Takes the same ordering options as @<r:children:each>@. |
61 | |
62 | *Usage:* |
63 | |
64 | <pre><code><r:children:first>...</r:children:first></code></pre> |
65 | } |
66 | tag 'children:first' do |tag| |
67 | options = children_find_options(tag) |
68 | children = tag.locals.children.find(:all, options) |
69 | if first = children.first |
70 | tag.locals.page = first |
71 | tag.expand |
72 | end |
73 | end |
74 | |
75 | desc %{ |
76 | Returns the last child. Inside this tag all page attribute tags are mapped to |
77 | the last child. Takes the same ordering options as @<r:children:each>@. |
78 | |
79 | *Usage:* |
80 | |
81 | <pre><code><r:children:last>...</r:children:last></code></pre> |
82 | } |
83 | tag 'children:last' do |tag| |
84 | options = children_find_options(tag) |
85 | children = tag.locals.children.find(:all, options) |
86 | if last = children.last |
87 | tag.locals.page = last |
88 | tag.expand |
89 | end |
90 | end |
91 | |
92 | desc %{ |
93 | Cycles through each of the children. Inside this tag all page attribute tags |
94 | are mapped to the current child page. |
95 | |
96 | Supply @paginated="true"@ to paginate the displayed list. will_paginate view helper |
97 | options can also be specified, including @per_page@, @previous_label@, @next_label@, |
98 | @class@, @separator@, @inner_window@ and @outer_window@. |
99 | |
100 | *Usage:* |
101 | |
102 | <pre><code><r:children:each [offset="number"] [limit="number"] |
103 | [by="published_at|updated_at|created_at|slug|title|keywords|description"] |
104 | [order="asc|desc"] |
105 | [status="draft|reviewed|published|hidden|all"] |
106 | [paginated="true"] |
107 | [per_page="number"] |
108 | > |
109 | ... |
110 | </r:children:each> |
111 | </code></pre> |
112 | } |
113 | tag 'children:each' do |tag| |
114 | options = children_find_options(tag) |
115 | paging = pagination_find_options(tag) |
116 | result = [] |
117 | tag.locals.previous_headers = {} |
118 | displayed_children = paging ? tag.locals.children.paginate(options.merge(paging)) : tag.locals.children.all(options) |
119 | displayed_children.each_with_index do |item, i| |
120 | tag.locals.child = item |
121 | tag.locals.page = item |
122 | tag.locals.first_child = i == 0 |
123 | tag.locals.last_child = i == displayed_children.length - 1 |
124 | result << tag.expand |
125 | end |
126 | if paging && displayed_children.total_pages > 1 |
127 | tag.locals.paginated_list = displayed_children |
128 | result << tag.render('pagination', tag.attr.dup) |
129 | end |
130 | result |
131 | end |
132 | |
133 | desc %{ |
134 | The pagination tag is not usually called directly. Supply paginated="true" when you display a list and it will |
135 | be automatically display only the current page of results, with pagination controls at the bottom. |
136 | |
137 | *Usage:* |
138 | |
139 | <pre><code><r:children:each paginated="true" per_page="50" container="false" previous_label="foo" next_label="bar"> |
140 | <r:child>...</r:child> |
141 | </r:children:each> |
142 | </code></pre> |
143 | } |
144 | tag 'pagination' do |tag| |
145 | if tag.locals.paginated_list |
146 | will_paginate(tag.locals.paginated_list, will_paginate_options(tag)) |
147 | end |
148 | end |
149 | |
150 | desc %{ |
151 | Page attribute tags inside of this tag refer to the current child. This is occasionally |
152 | useful if you are inside of another tag (like <r:find>) and need to refer back to the |
153 | current child. |
154 | |
155 | *Usage:* |
156 | |
157 | <pre><code><r:children:each> |
158 | <r:child>...</r:child> |
159 | </r:children:each> |
160 | </code></pre> |
161 | } |
162 | tag 'children:each:child' do |tag| |
163 | tag.locals.page = tag.locals.child |
164 | tag.expand |
165 | end |
166 | |
167 | desc %{ |
168 | Renders the tag contents only if the current page is the first child in the context of |
169 | a children:each tag |
170 | |
171 | *Usage:* |
172 | |
173 | <pre><code><r:children:each> |
174 | <r:if_first > |
175 | ... |
176 | </r:if_first> |
177 | </r:children:each> |
178 | </code></pre> |
179 | |
180 | } |
181 | tag 'children:each:if_first' do |tag| |
182 | tag.expand if tag.locals.first_child |
183 | end |
184 | |
185 | |
186 | desc %{ |
187 | Renders the tag contents unless the current page is the first child in the context of |
188 | a children:each tag |
189 | |
190 | *Usage:* |
191 | |
192 | <pre><code><r:children:each> |
193 | <r:unless_first > |
194 | ... |
195 | </r:unless_first> |
196 | </r:children:each> |
197 | </code></pre> |
198 | |
199 | } |
200 | tag 'children:each:unless_first' do |tag| |
201 | tag.expand unless tag.locals.first_child |
202 | end |
203 | |
204 | desc %{ |
205 | Renders the tag contents only if the current page is the last child in the context of |
206 | a children:each tag |
207 | |
208 | *Usage:* |
209 | |
210 | <pre><code><r:children:each> |
211 | <r:if_last > |
212 | ... |
213 | </r:if_last> |
214 | </r:children:each> |
215 | </code></pre> |
216 | |
217 | } |
218 | tag 'children:each:if_last' do |tag| |
219 | tag.expand if tag.locals.last_child |
220 | end |
221 | |
222 | |
223 | desc %{ |
224 | Renders the tag contents unless the current page is the last child in the context of |
225 | a children:each tag |
226 | |
227 | *Usage:* |
228 | |
229 | <pre><code><r:children:each> |
230 | <r:unless_last > |
231 | ... |
232 | </r:unless_last> |
233 | </r:children:each> |
234 | </code></pre> |
235 | |
236 | } |
237 | tag 'children:each:unless_last' do |tag| |
238 | tag.expand unless tag.locals.last_child |
239 | end |
240 | |
241 | desc %{ |
242 | Renders the tag contents only if the contents do not match the previous header. This |
243 | is extremely useful for rendering date headers for a list of child pages. |
244 | |
245 | If you would like to use several header blocks you may use the @name@ attribute to |
246 | name the header. When a header is named it will not restart until another header of |
247 | the same name is different. |
248 | |
249 | Using the @restart@ attribute you can cause other named headers to restart when the |
250 | present header changes. Simply specify the names of the other headers in a semicolon |
251 | separated list. |
252 | |
253 | *Usage:* |
254 | |
255 | <pre><code><r:children:each> |
256 | <r:header [name="header_name"] [restart="name1[;name2;...]"]> |
257 | ... |
258 | </r:header> |
259 | </r:children:each> |
260 | </code></pre> |
261 | } |
262 | tag 'children:each:header' do |tag| |
263 | previous_headers = tag.locals.previous_headers |
264 | name = tag.attr['name'] || :unnamed |
265 | restart = (tag.attr['restart'] || '').split(';') |
266 | header = tag.expand |
267 | unless header == previous_headers[name] |
268 | previous_headers[name] = header |
269 | unless restart.empty? |
270 | restart.each do |n| |
271 | previous_headers[n] = nil |
272 | end |
273 | end |
274 | header |
275 | end |
276 | end |
277 | |
278 | desc %{ |
279 | Page attribute tags inside this tag refer to the parent of the current page. |
280 | |
281 | *Usage:* |
282 | |
283 | <pre><code><r:parent>...</r:parent></code></pre> |
284 | } |
285 | tag "parent" do |tag| |
286 | parent = tag.locals.page.parent |
287 | tag.locals.page = parent |
288 | tag.expand if parent |
289 | end |
290 | |
291 | desc %{ |
292 | Renders the contained elements only if the current contextual page has a parent, i.e. |
293 | is not the root page. |
294 | |
295 | *Usage:* |
296 | |
297 | <pre><code><r:if_parent>...</r:if_parent></code></pre> |
298 | } |
299 | tag "if_parent" do |tag| |
300 | parent = tag.locals.page.parent |
301 | tag.expand if parent |
302 | end |
303 | |
304 | desc %{ |
305 | Renders the contained elements only if the current contextual page has no parent, i.e. |
306 | is the root page. |
307 | |
308 | *Usage:* |
309 | |
310 | <pre><code><r:unless_parent>...</r:unless_parent></code></pre> |
311 | } |
312 | tag "unless_parent" do |tag| |
313 | parent = tag.locals.page.parent |
314 | tag.expand unless parent |
315 | end |
316 | |
317 | desc %{ |
318 | Renders the contained elements only if the current contextual page has one or |
319 | more child pages. The @status@ attribute limits the status of found child pages |
320 | to the given status, the default is @"published"@. @status="all"@ includes all |
321 | non-virtual pages regardless of status. |
322 | |
323 | *Usage:* |
324 | |
325 | <pre><code><r:if_children [status="published"]>...</r:if_children></code></pre> |
326 | } |
327 | tag "if_children" do |tag| |
328 | children = tag.locals.page.children.count(:conditions => children_find_options(tag)[:conditions]) |
329 | tag.expand if children > 0 |
330 | end |
331 | |
332 | desc %{ |
333 | Renders the contained elements only if the current contextual page has no children. |
334 | The @status@ attribute limits the status of found child pages to the given status, |
335 | the default is @"published"@. @status="all"@ includes all non-virtual pages |
336 | regardless of status. |
337 | |
338 | *Usage:* |
339 | |
340 | <pre><code><r:unless_children [status="published"]>...</r:unless_children></code></pre> |
341 | } |
342 | tag "unless_children" do |tag| |
343 | children = tag.locals.page.children.count(:conditions => children_find_options(tag)[:conditions]) |
344 | tag.expand unless children > 0 |
345 | end |
346 | |
347 | desc %{ |
348 | Aggregates the children of multiple URLs using the @urls@ attribute. |
349 | Useful for combining many different sections/categories into a single |
350 | feed or listing. |
351 | |
352 | *Usage*: |
353 | |
354 | <pre><code><r:aggregate urls="/section1; /section2; /section3"> ... </r:aggregate></code></pre> |
355 | } |
356 | tag "aggregate" do |tag| |
357 | raise "`urls' attribute required" unless tag.attr["urls"] |
358 | urls = tag.attr["urls"].split(";").map(&:strip).reject(&:blank?).map { |u| clean_url u } |
359 | parent_ids = urls.map {|u| Page.find_by_url(u) }.map(&:id) |
360 | tag.locals.parent_ids = parent_ids |
361 | tag.expand |
362 | end |
363 | |
364 | desc %{ |
365 | Sets the scope to the individual aggregated page allowing you to |
366 | iterate through each of the listed urls. |
367 | |
368 | *Usage*: |
369 | |
370 | <pre><code><r:aggregate:each urls="/section1; /section2; /section3"> ... </r:aggregate:each></code></pre> |
371 | } |
372 | tag "aggregate:each" do |tag| |
373 | aggregates = [] |
374 | tag.locals.aggregated_pages = tag.locals.parent_ids.map {|p| Page.find(p)} |
375 | tag.locals.aggregated_pages.each do |aggregate_page| |
376 | tag.locals.page = aggregate_page |
377 | aggregates << tag.expand |
378 | end |
379 | aggregates |
380 | end |
381 | |
382 | tag "aggregate:each:children" do |tag| |
383 | tag.locals.children = tag.locals.page.children |
384 | tag.expand |
385 | end |
386 | |
387 | tag "aggregate:each:children:each" do |tag| |
388 | options = children_find_options(tag) |
389 | result = [] |
390 | children = tag.locals.children |
391 | tag.locals.previous_headers = {} |
392 | children.find(:all, options).each do |item| |
393 | tag.locals.child = item |
394 | tag.locals.page = item |
395 | result << tag.expand |
396 | end |
397 | result |
398 | end |
399 | |
400 | tag "aggregate:children" do |tag| |
401 | tag.expand |
402 | end |
403 | |
404 | desc %{ |
405 | Renders the total count of children of the aggregated pages. Accepts the |
406 | same options as @<r:children:each />@. |
407 | |
408 | *Usage*: |
409 | |
410 | <pre><code><r:aggregate urls="/section1; /section2; /section3"> |
411 | <r:children:count /> |
412 | </r:aggregate></code></pre> |
413 | } |
414 | tag "aggregate:children:count" do |tag| |
415 | options = aggregate_children(tag) |
416 | if ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql' |
417 | options[:group] = Page.columns.map {|c| c.name}.join(', ') |
418 | Page.find(:all, options).size |
419 | else |
420 | Page.count(options) |
421 | end |
422 | end |
423 | desc %{ |
424 | Renders the contained block for each child of the aggregated pages. Accepts the |
425 | same options as the plain @<r:children:each />@. |
426 | |
427 | *Usage*: |
428 | |
429 | <pre><code><r:aggregate urls="/section1; /section2; /section3"> |
430 | <r:children:each> |
431 | ... |
432 | </r:children:each> |
433 | </r:aggregate></code></pre> |
434 | } |
435 | tag "aggregate:children:each" do |tag| |
436 | options = aggregate_children(tag) |
437 | children = Page.find(:all, options) |
438 | tag.locals.previous_headers = {} |
439 | returning String.new do |output| |
440 | children.each do |child| |
441 | tag.locals.page = child |
442 | tag.locals.child = child |
443 | output << tag.expand |
444 | end |
445 | end |
446 | end |
447 | |
448 | desc %{ |
449 | Renders the first child of the aggregated pages. Accepts the |
450 | same options as @<r:children:each />@. |
451 | |
452 | *Usage*: |
453 | |
454 | <pre><code><r:aggregate urls="/section1; /section2; /section3"> |
455 | <r:children:first> |
456 | ... |
457 | </r:children:first> |
458 | </r:aggregate></code></pre> |
459 | } |
460 | tag "aggregate:children:first" do |tag| |
461 | options = aggregate_children(tag) |
462 | children = Page.find(:all, options) |
463 | if first = children.first |
464 | tag.locals.page = first |
465 | tag.expand |
466 | end |
467 | end |
468 | |
469 | desc %{ |
470 | Renders the last child of the aggregated pages. Accepts the |
471 | same options as @<r:children:each />@. |
472 | |
473 | *Usage*: |
474 | |
475 | <pre><code><r:aggregate urls="/section1; /section2; /section3"> |
476 | <r:children:last> |
477 | ... |
478 | </r:children:last> |
479 | </r:aggregate></code></pre> |
480 | } |
481 | tag "aggregate:children:last" do |tag| |
482 | options = aggregate_children(tag) |
483 | children = Page.find(:all, options) |
484 | if last = children.last |
485 | tag.locals.page = last |
486 | tag.expand |
487 | end |
488 | end |
489 | |
490 | desc %{ |
491 | Renders one of the passed values based on a global cycle counter. Use the @reset@ |
492 | attribute to reset the cycle to the beginning. Use the @name@ attribute to track |
493 | multiple cycles; the default is @cycle@. |
494 | |
495 | *Usage:* |
496 | |
497 | <pre><code><r:cycle values="first, second, third" [reset="true|false"] [name="cycle"] /></code></pre> |
498 | } |
499 | tag 'cycle' do |tag| |
500 | raise TagError, "`cycle' tag must contain a `values' attribute." unless tag.attr['values'] |
501 | cycle = (tag.globals.cycle ||= {}) |
502 | values = tag.attr['values'].split(",").collect(&:strip) |
503 | cycle_name = tag.attr['name'] || 'cycle' |
504 | current_index = (cycle[cycle_name] ||= 0) |
505 | current_index = 0 if tag.attr['reset'] == 'true' |
506 | cycle[cycle_name] = (current_index + 1) % values.size |
507 | values[current_index] |
508 | end |
509 | |
510 | desc %{ |
511 | Renders the main content of a page. Use the @part@ attribute to select a specific |
512 | page part. By default the @part@ attribute is set to body. Use the @inherit@ |
513 | attribute to specify that if a page does not have a content part by that name that |
514 | the tag should render the parent's content part. By default @inherit@ is set to |
515 | @false@. Use the @contextual@ attribute to force a part inherited from a parent |
516 | part to be evaluated in the context of the child page. By default 'contextual' |
517 | is set to true. |
518 | |
519 | *Usage:* |
520 | |
521 | <pre><code><r:content [part="part_name"] [inherit="true|false"] [contextual="true|false"] /></code></pre> |
522 | } |
523 | tag 'content' do |tag| |
524 | page = tag.locals.page |
525 | part_name = tag_part_name(tag) |
526 | # Prevent simple and deep recursive rendering of the same page part |
527 | rendering_parts = (tag.locals.rendering_parts ||= Hash.new {|h,k| h[k] = []}) |
528 | if rendering_parts[page.id].include?(part_name) |
529 | raise TagError.new(%{Recursion error: already rendering the `#{part_name}' part.}) |
530 | else |
531 | rendering_parts[page.id] << part_name |
532 | end |
533 | boolean_attr = proc do |attribute_name, default| |
534 | attribute = (tag.attr[attribute_name] || default).to_s |
535 | raise TagError.new(%{`#{attribute_name}' attribute of `content' tag must be set to either "true" or "false"}) unless attribute =~ /true|false/i |
536 | (attribute.downcase == 'true') ? true : false |
537 | end |
538 | inherit = boolean_attr['inherit', false] |
539 | part_page = page |
540 | if inherit |
541 | while (part_page.part(part_name).nil? and (not part_page.parent.nil?)) do |
542 | part_page = part_page.parent |
543 | end |
544 | end |
545 | contextual = boolean_attr['contextual', true] |
546 | part = part_page.part(part_name) |
547 | tag.locals.page = part_page unless contextual |
548 | result = tag.globals.page.render_snippet(part) unless part.nil? |
549 | rendering_parts[page.id].delete(part_name) |
550 | result |
551 | end |
552 | |
553 | desc %{ |
554 | Renders the containing elements if all of the listed parts exist on a page. |
555 | By default the @part@ attribute is set to @body@, but you may list more than one |
556 | part by separating them with a comma. Setting the optional @inherit@ to true will |
557 | search ancestors independently for each part. By default @inherit@ is set to @false@. |
558 | |
559 | When listing more than one part, you may optionally set the @find@ attribute to @any@ |
560 | so that it will render the containing elements if any of the listed parts are found. |
561 | By default the @find@ attribute is set to @all@. |
562 | |
563 | *Usage:* |
564 | |
565 | <pre><code><r:if_content [part="part_name, other_part"] [inherit="true"] [find="any"]>...</r:if_content></code></pre> |
566 | } |
567 | tag 'if_content' do |tag| |
568 | part_name = tag_part_name(tag) |
569 | parts_arr = part_name.split(',') |
570 | inherit = boolean_attr_or_error(tag, 'inherit', 'false') |
571 | find = attr_or_error(tag, :attribute_name => 'find', :default => 'all', :values => 'any, all') |
572 | expandable = true |
573 | one_found = false |
574 | parts_arr.each do |name| |
575 | part_page = tag.locals.page |
576 | name.strip! |
577 | if inherit |
578 | while (part_page.part(name).nil? and (not part_page.parent.nil?)) do |
579 | part_page = part_page.parent |
580 | end |
581 | end |
582 | expandable = false if part_page.part(name).nil? |
583 | one_found ||= true if !part_page.part(name).nil? |
584 | end |
585 | expandable = true if (find == 'any' and one_found) |
586 | tag.expand if expandable |
587 | end |
588 | |
589 | desc %{ |
590 | The opposite of the @if_content@ tag. It renders the contained elements if all of the |
591 | specified parts do not exist. Setting the optional @inherit@ to true will search |
592 | ancestors independently for each part. By default @inherit@ is set to @false@. |
593 | |
594 | When listing more than one part, you may optionally set the @find@ attribute to @any@ |
595 | so that it will not render the containing elements if any of the listed parts are found. |
596 | By default the @find@ attribute is set to @all@. |
597 | |
598 | *Usage:* |
599 | |
600 | <pre><code><r:unless_content [part="part_name, other_part"] [inherit="false"] [find="any"]>...</r:unless_content></code></pre> |
601 | } |
602 | tag 'unless_content' do |tag| |
603 | part_name = tag_part_name(tag) |
604 | parts_arr = part_name.split(',') |
605 | inherit = boolean_attr_or_error(tag, 'inherit', false) |
606 | find = attr_or_error(tag, :attribute_name => 'find', :default => 'all', :values => 'any, all') |
607 | expandable, all_found = true, true |
608 | parts_arr.each do |name| |
609 | part_page = tag.locals.page |
610 | name.strip! |
611 | if inherit |
612 | while (part_page.part(name).nil? and (not part_page.parent.nil?)) do |
613 | part_page = part_page.parent |
614 | end |
615 | end |
616 | expandable = false if !part_page.part(name).nil? |
617 | all_found = false if part_page.part(name).nil? |
618 | end |
619 | if all_found == false and find == 'all' |
620 | expandable = true |
621 | end |
622 | tag.expand if expandable |
623 | end |
624 | |
625 | desc %{ |
626 | Renders the containing elements only if the page's url matches the regular expression |
627 | given in the @matches@ attribute. If the @ignore_case@ attribute is set to false, the |
628 | match is case sensitive. By default, @ignore_case@ is set to true. |
629 | |
630 | *Usage:* |
631 | |
632 | <pre><code><r:if_url matches="regexp" [ignore_case="true|false"]>...</r:if_url></code></pre> |
633 | } |
634 | tag 'if_url' do |tag| |
635 | raise TagError.new("`if_url' tag must contain a `matches' attribute.") unless tag.attr.has_key?('matches') |
636 | regexp = build_regexp_for(tag, 'matches') |
637 | unless tag.locals.page.url.match(regexp).nil? |
638 | tag.expand |
639 | end |
640 | end |
641 | |
642 | desc %{ |
643 | The opposite of the @if_url@ tag. |
644 | |
645 | *Usage:* |
646 | |
647 | <pre><code><r:unless_url matches="regexp" [ignore_case="true|false"]>...</r:unless_url></code></pre> |
648 | } |
649 | tag 'unless_url' do |tag| |
650 | raise TagError.new("`unless_url' tag must contain a `matches' attribute.") unless tag.attr.has_key?('matches') |
651 | regexp = build_regexp_for(tag, 'matches') |
652 | if tag.locals.page.url.match(regexp).nil? |
653 | tag.expand |
654 | end |
655 | end |
656 | |
657 | desc %{ |
658 | Renders the contained elements if the current contextual page is either the actual page or one of its parents. |
659 | |
660 | This is typically used inside another tag (like <r:children:each>) to add conditional mark-up if the child element is or descends from the current page. |
661 | |
662 | *Usage:* |
663 | |
664 | <pre><code><r:if_ancestor_or_self>...</r:if_ancestor_or_self></code></pre> |
665 | } |
666 | tag "if_ancestor_or_self" do |tag| |
667 | tag.expand if (tag.globals.page.ancestors + [tag.globals.page]).include?(tag.locals.page) |
668 | end |
669 | |
670 | desc %{ |
671 | Renders the contained elements unless the current contextual page is either the actual page or one of its parents. |
672 | |
673 | This is typically used inside another tag (like <r:children:each>) to add conditional mark-up unless the child element is or descends from the current page. |
674 | |
675 | *Usage:* |
676 | |
677 | <pre><code><r:unless_ancestor_or_self>...</r:unless_ancestor_or_self></code></pre> |
678 | } |
679 | tag "unless_ancestor_or_self" do |tag| |
680 | tag.expand unless (tag.globals.page.ancestors + [tag.globals.page]).include?(tag.locals.page) |
681 | end |
682 | |
683 | desc %{ |
684 | Renders the contained elements if the current contextual page is also the actual page. |
685 | |
686 | This is typically used inside another tag (like <r:children:each>) to add conditional mark-up if the child element is the current page. |
687 | |
688 | *Usage:* |
689 | |
690 | <pre><code><r:if_self>...</r:if_self></code></pre> |
691 | } |
692 | tag "if_self" do |tag| |
693 | tag.expand if tag.locals.page == tag.globals.page |
694 | end |
695 | |
696 | desc %{ |
697 | Renders the contained elements unless the current contextual page is also the actual page. |
698 | |
699 | This is typically used inside another tag (like <r:children:each>) to add conditional mark-up unless the child element is the current page. |
700 | |
701 | *Usage:* |
702 | |
703 | <pre><code><r:unless_self>...</r:unless_self></code></pre> |
704 | } |
705 | tag "unless_self" do |tag| |
706 | tag.expand unless tag.locals.page == tag.globals.page |
707 | end |
708 | |
709 | desc %{ |
710 | Renders the name of the author of the current page. |
711 | } |
712 | tag 'author' do |tag| |
713 | page = tag.locals.page |
714 | if author = page.created_by |
715 | author.name |
716 | end |
717 | end |
718 | |
719 | desc %{ |
720 | Renders the Gravatar of the author of the current page or the named user. |
721 | |
722 | *Usage:* |
723 | |
724 | <pre><code><r:gravatar /></code></pre> |
725 | |
726 | or |
727 | |
728 | <pre><code><r:gravatar [name="User Name"] |
729 | [rating="G | PG | R | X"] |
730 | [size="32px"] /></code></pre> |
731 | } |
732 | tag 'gravatar' do |tag| |
733 | page = tag.locals.page |
734 | name = (tag.attr['name'] || page.created_by.name) |
735 | rating = (tag.attr['rating'] || 'G') |
736 | size = (tag.attr['size'] || '32px') |
737 | email = User.find_by_name(name).email |
738 | if email != '' |
739 | url = 'http://www.gravatar.com/avatar.php?' |
740 | url << "gravatar_id=#{Digest::MD5.new.update(email)}" |
741 | url << "&rating=#{rating}" |
742 | url << "&size=#{size.to_i}" |
743 | url |
744 | else |
745 | "#{request.protocol}#{request.host_with_port}/images/admin/avatar_#{([size.to_i] * 2).join('x')}.png" |
746 | end |
747 | end |
748 | |
749 | desc %{ |
750 | Renders the date based on the current page (by default when it was published or created). |
751 | The format attribute uses the same formating codes used by the Ruby @strftime@ function. By |
752 | default it's set to @%A, %B %d, %Y@. The @for@ attribute selects which date to render. Valid |
753 | options are @published_at@, @created_at@, @updated_at@, and @now@. @now@ will render the |
754 | current date/time, regardless of the page. |
755 | |
756 | *Usage:* |
757 | |
758 | <pre><code><r:date [format="%A, %B %d, %Y"] [for="published_at"]/></code></pre> |
759 | } |
760 | tag 'date' do |tag| |
761 | page = tag.locals.page |
762 | format = (tag.attr['format'] || '%A, %B %d, %Y') |
763 | time_attr = tag.attr['for'] |
764 | date = if time_attr |
765 | case |
766 | when time_attr == 'now' |
767 | Time.zone.now |
768 | when ['published_at', 'created_at', 'updated_at'].include?(time_attr) |
769 | page[time_attr] |
770 | else |
771 | raise TagError, "Invalid value for 'for' attribute." |
772 | end |
773 | else |
774 | page.published_at || page.created_at |
775 | end |
776 | date.strftime(format) |
777 | end |
778 | |
779 | desc %{ |
780 | Renders a link to the page. When used as a single tag it uses the page's title |
781 | for the link name. When used as a double tag the part in between both tags will |
782 | be used as the link text. The link tag passes all attributes over to the HTML |
783 | @a@ tag. This is very useful for passing attributes like the @class@ attribute |
784 | or @id@ attribute. If the @anchor@ attribute is passed to the tag it will |
785 | append a pound sign (<code>#</code>) followed by the value of the attribute to |
786 | the @href@ attribute of the HTML @a@ tag--effectively making an HTML anchor. |
787 | |
788 | *Usage:* |
789 | |
790 | <pre><code><r:link [anchor="name"] [other attributes...] /></code></pre> |
791 | |
792 | or |
793 | |
794 | <pre><code><r:link [anchor="name"] [other attributes...]>link text here</r:link></code></pre> |
795 | } |
796 | tag 'link' do |tag| |
797 | options = tag.attr.dup |
798 | anchor = options['anchor'] ? "##{options.delete('anchor')}" : '' |
799 | attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip |
800 | attributes = " #{attributes}" unless attributes.empty? |
801 | text = tag.double? ? tag.expand : tag.render('title') |
802 | %{<a href="#{tag.render('url')}#{anchor}"#{attributes}>#{text}</a>} |
803 | end |
804 | |
805 | desc %{ |
806 | Renders a trail of breadcrumbs to the current page. The separator attribute |
807 | specifies the HTML fragment that is inserted between each of the breadcrumbs. By |
808 | default it is set to @>@. The boolean nolinks attribute can be specified to render |
809 | breadcrumbs in plain text, without any links (useful when generating title tag). |
810 | |
811 | *Usage:* |
812 | |
813 | <pre><code><r:breadcrumbs [separator="separator_string"] [nolinks="true"] /></code></pre> |
814 | } |
815 | tag 'breadcrumbs' do |tag| |
816 | page = tag.locals.page |
817 | breadcrumbs = [page.breadcrumb] |
818 | nolinks = (tag.attr['nolinks'] == 'true') |
819 | page.ancestors.each do |ancestor| |
820 | tag.locals.page = ancestor |
821 | if nolinks |
822 | breadcrumbs.unshift tag.render('breadcrumb') |
823 | else |
824 | breadcrumbs.unshift %{<a href="#{tag.render('url')}">#{tag.render('breadcrumb')}</a>} |
825 | end |
826 | end |
827 | separator = tag.attr['separator'] || ' > ' |
828 | breadcrumbs.join(separator) |
829 | end |
830 | |
831 | desc %{ |
832 | Renders the snippet specified in the @name@ attribute within the context of a page. |
833 | |
834 | *Usage:* |
835 | |
836 | <pre><code><r:snippet name="snippet_name" /></code></pre> |
837 | |
838 | When used as a double tag, the part in between both tags may be used within the |
839 | snippet itself, being substituted in place of @<r:yield/>@. |
840 | |
841 | *Usage:* |
842 | |
843 | <pre><code><r:snippet name="snippet_name">Lorem ipsum dolor...</r:snippet></code></pre> |
844 | } |
845 | tag 'snippet' do |tag| |
846 | if name = tag.attr['name'] |
847 | if snippet = Snippet.find_by_name(name.strip) |
848 | tag.locals.yield = tag.expand if tag.double? |
849 | tag.globals.page.render_snippet(snippet) |
850 | else |
851 | raise TagError.new('snippet not found') |
852 | end |
853 | else |
854 | raise TagError.new("`snippet' tag must contain `name' attribute") |
855 | end |
856 | end |
857 | |
858 | desc %{ |
859 | Used within a snippet as a placeholder for substitution of child content, when |
860 | the snippet is called as a double tag. |
861 | |
862 | *Usage (within a snippet):* |
863 | |
864 | <pre><code> |
865 | <div id="outer"> |
866 | <p>before</p> |
867 | <r:yield/> |
868 | <p>after</p> |
869 | </div> |
870 | </code></pre> |
871 | |
872 | If the above snippet was named "yielding", you could call it from any Page, |
873 | Layout or Snippet as follows: |
874 | |
875 | <pre><code><r:snippet name="yielding">Content within</r:snippet></code></pre> |
876 | |
877 | Which would output the following: |
878 | |
879 | <pre><code> |
880 | <div id="outer"> |
881 | <p>before</p> |
882 | Content within |
883 | <p>after</p> |
884 | </div> |
885 | </code></pre> |
886 | |
887 | When called in the context of a Page or a Layout, @<r:yield/>@ outputs nothing. |
888 | } |
889 | tag 'yield' do |tag| |
890 | tag.locals.yield |
891 | end |
892 | |
893 | desc %{ |
894 | Inside this tag all page related tags refer to the page found at the @url@ attribute. |
895 | @url@s may be relative or absolute paths. |
896 | |
897 | *Usage:* |
898 | |
899 | <pre><code><r:find url="value_to_find">...</r:find></code></pre> |
900 | } |
901 | tag 'find' do |tag| |
902 | url = tag.attr['url'] |
903 | raise TagError.new("`find' tag must contain `url' attribute") unless url |
904 | |
905 | found = Page.find_by_url(absolute_path_for(tag.locals.page.url, url)) |
906 | if page_found?(found) |
907 | tag.locals.page = found |
908 | tag.expand |
909 | end |
910 | end |
911 | |
912 | desc %{ |
913 | Randomly renders one of the options specified by the @option@ tags. |
914 | |
915 | *Usage:* |
916 | |
917 | <pre><code><r:random> |
918 | <r:option>...</r:option> |
919 | <r:option>...</r:option> |
920 | ... |
921 | <r:random> |
922 | </code></pre> |
923 | } |
924 | tag 'random' do |tag| |
925 | tag.locals.random = [] |
926 | tag.expand |
927 | options = tag.locals.random |
928 | option = options[rand(options.size)] |
929 | option if option |
930 | end |
931 | tag 'random:option' do |tag| |
932 | items = tag.locals.random |
933 | items << tag.expand |
934 | end |
935 | |
936 | desc %{ |
937 | Nothing inside a set of comment tags is rendered. |
938 | |
939 | *Usage:* |
940 | |
941 | <pre><code><r:comment>...</r:comment></code></pre> |
942 | } |
943 | tag 'comment' do |tag| |
944 | end |
945 | |
946 | desc %{ |
947 | Escapes angle brackets, etc. for rendering in an HTML document. |
948 | |
949 | *Usage:* |
950 | |
951 | <pre><code><r:escape_html>...</r:escape_html></code></pre> |
952 | } |
953 | tag "escape_html" do |tag| |
954 | CGI.escapeHTML(tag.expand) |
955 | end |
956 | |
957 | desc %{ |
958 | Outputs the published date using the format mandated by RFC 1123. (Ideal for RSS feeds.) |
959 | |
960 | *Usage:* |
961 | |
962 | <pre><code><r:rfc1123_date /></code></pre> |
963 | } |
964 | tag "rfc1123_date" do |tag| |
965 | page = tag.locals.page |
966 | if date = page.published_at || page.created_at |
967 | CGI.rfc1123_date(date.to_time) |
968 | end |
969 | end |
970 | |
971 | desc %{ |
972 | Renders a list of links specified in the @urls@ attribute according to three |
973 | states: |
974 | |
975 | * @normal@ specifies the normal state for the link |
976 | * @here@ specifies the state of the link when the url matches the current |
977 | page's URL |
978 | * @selected@ specifies the state of the link when the current page matches |
979 | is a child of the specified url |
980 | # @if_last@ renders its contents within a @normal@, @here@ or |
981 | @selected@ tag if the item is the last in the navigation elements |
982 | # @if_first@ renders its contents within a @normal@, @here@ or |
983 | @selected@ tag if the item is the first in the navigation elements |
984 | |
985 | The @between@ tag specifies what should be inserted in between each of the links. |
986 | |
987 | *Usage:* |
988 | |
989 | <pre><code><r:navigation urls="[Title: url | Title: url | ...]"> |
990 | <r:normal><a href="<r:url />"><r:title /></a></r:normal> |
991 | <r:here><strong><r:title /></strong></r:here> |
992 | <r:selected><strong><a href="<r:url />"><r:title /></a></strong></r:selected> |
993 | <r:between> | </r:between> |
994 | </r:navigation> |
995 | </code></pre> |
996 | } |
997 | tag 'navigation' do |tag| |
998 | hash = tag.locals.navigation = {} |
999 | tag.expand |
1000 | raise TagError.new("`navigation' tag must include a `normal' tag") unless hash.has_key? :normal |
1001 | result = [] |
1002 | pairs = tag.attr['urls'].to_s.split('|').map do |pair| |
1003 | parts = pair.split(':') |
1004 | value = parts.pop |
1005 | key = parts.join(':') |
1006 | [key.strip, value.strip] |
1007 | end |
1008 | pairs.each_with_index do |(title, url), i| |
1009 | compare_url = remove_trailing_slash(url) |
1010 | page_url = remove_trailing_slash(self.url) |
1011 | hash[:title] = title |
1012 | hash[:url] = url |
1013 | tag.locals.first_child = i == 0 |
1014 | tag.locals.last_child = i == pairs.length - 1 |
1015 | case page_url |
1016 | when compare_url |
1017 | result << (hash[:here] || hash[:selected] || hash[:normal]).call |
1018 | when Regexp.compile( '^' + Regexp.quote(url)) |
1019 | result << (hash[:selected] || hash[:normal]).call |
1020 | else |
1021 | result << hash[:normal].call |
1022 | end |
1023 | end |
1024 | between = hash.has_key?(:between) ? hash[:between].call : ' ' |
1025 | result.reject { |i| i.blank? }.join(between) |
1026 | end |
1027 | [:normal, :here, :selected, :between].each do |symbol| |
1028 | tag "navigation:#{symbol}" do |tag| |
1029 | hash = tag.locals.navigation |
1030 | hash[symbol] = tag.block |
1031 | end |
1032 | end |
1033 | [:title, :url].each do |symbol| |
1034 | tag "navigation:#{symbol}" do |tag| |
1035 | hash = tag.locals.navigation |
1036 | hash[symbol] |
1037 | end |
1038 | end |
1039 | |
1040 | desc %{ |
1041 | Renders the containing elements if the element is the first |
1042 | in the navigation list |
1043 | |
1044 | *Usage:* |
1045 | |
1046 | <pre><code><r:normal><r:if_first>...</r:if_first></r:normal></code></pre> |
1047 | } |
1048 | tag 'navigation:if_first' do |tag| |
1049 | tag.expand if tag.locals.first_child |
1050 | end |
1051 | |
1052 | desc %{ |
1053 | Renders the containing elements unless the element is the first |
1054 | in the navigation list |
1055 | |
1056 | *Usage:* |
1057 | |
1058 | <pre><code><r:normal><r:unless_first>...</r:unless_first></r:normal></code></pre> |
1059 | } |
1060 | tag 'navigation:unless_first' do |tag| |
1061 | tag.expand unless tag.locals.first_child |
1062 | end |
1063 | |
1064 | desc %{ |
1065 | Renders the containing elements unless the element is the last |
1066 | in the navigation list |
1067 | |
1068 | *Usage:* |
1069 | |
1070 | <pre><code><r:normal><r:unless_last>...</r:unless_last></r:normal></code></pre> |
1071 | } |
1072 | tag 'navigation:unless_last' do |tag| |
1073 | tag.expand unless tag.locals.last_child |
1074 | end |
1075 | |
1076 | desc %{ |
1077 | Renders the containing elements if the element is the last |
1078 | in the navigation list |
1079 | |
1080 | *Usage:* |
1081 | |
1082 | <pre><code><r:normal><r:if_last>...</r:if_last></r:normal></code></pre> |
1083 | } |
1084 | tag 'navigation:if_last' do |tag| |
1085 | tag.expand if tag.locals.last_child |
1086 | end |
1087 | |
1088 | desc %{ |
1089 | Renders the containing elements only if Radiant in is development mode. |
1090 | |
1091 | *Usage:* |
1092 | |
1093 | <pre><code><r:if_dev>...</r:if_dev></code></pre> |
1094 | } |
1095 | tag 'if_dev' do |tag| |
1096 | tag.expand if dev?(tag.globals.page.request) |
1097 | end |
1098 | |
1099 | desc %{ |
1100 | The opposite of the @if_dev@ tag. |
1101 | |
1102 | *Usage:* |
1103 | |
1104 | <pre><code><r:unless_dev>...</r:unless_dev></code></pre> |
1105 | } |
1106 | tag 'unless_dev' do |tag| |
1107 | tag.expand unless dev?(tag.globals.page.request) |
1108 | end |
1109 | |
1110 | desc %{ |
1111 | Prints the page's status as a string. Optional attribute 'downcase' |
1112 | will cause the status to be all lowercase. |
1113 | |
1114 | *Usage:* |
1115 | |
1116 | <pre><code><r:status [downcase='true'] /></code></pre> |
1117 | } |
1118 | tag 'status' do |tag| |
1119 | status = tag.globals.page.status.name |
1120 | return status.downcase if tag.attr['downcase'] |
1121 | status |
1122 | end |
1123 | |
1124 | desc %{ |
1125 | The namespace for 'meta' attributes. If used as a singleton tag, both the description |
1126 | and keywords fields will be output as <meta /> tags unless the attribute 'tag' is set to 'false'. |
1127 | |
1128 | *Usage:* |
1129 | |
1130 | <pre><code> <r:meta [tag="false"] /> |
1131 | <r:meta> |
1132 | <r:description [tag="false"] /> |
1133 | <r:keywords [tag="false"] /> |
1134 | </r:meta> |
1135 | </code></pre> |
1136 | } |
1137 | tag 'meta' do |tag| |
1138 | if tag.double? |
1139 | tag.expand |
1140 | else |
1141 | tag.render('description', tag.attr) + |
1142 | tag.render('keywords', tag.attr) |
1143 | end |
1144 | end |
1145 | |
1146 | desc %{ |
1147 | Emits the page description field in a meta tag, unless attribute |
1148 | 'tag' is set to 'false'. |
1149 | |
1150 | *Usage:* |
1151 | |
1152 | <pre><code> <r:meta:description [tag="false"] /> </code></pre> |
1153 | } |
1154 | tag 'meta:description' do |tag| |
1155 | show_tag = tag.attr['tag'] != 'false' || false |
1156 | description = CGI.escapeHTML(tag.locals.page.description) |
1157 | if show_tag |
1158 | "<meta name=\"description\" content=\"#{description}\" />" |
1159 | else |
1160 | description |
1161 | end |
1162 | end |
1163 | |
1164 | desc %{ |
1165 | Emits the page keywords field in a meta tag, unless attribute |
1166 | 'tag' is set to 'false'. |
1167 | |
1168 | *Usage:* |
1169 | |
1170 | <pre><code> <r:meta:keywords [tag="false"] /> </code></pre> |
1171 | } |
1172 | tag 'meta:keywords' do |tag| |
1173 | show_tag = tag.attr['tag'] != 'false' || false |
1174 | keywords = CGI.escapeHTML(tag.locals.page.keywords) |
1175 | if show_tag |
1176 | "<meta name=\"keywords\" content=\"#{keywords}\" />" |
1177 | else |
1178 | keywords |
1179 | end |
1180 | end |
1181 | |
1182 | private |
1183 | def children_find_options(tag) |
1184 | attr = tag.attr.symbolize_keys |
1185 | |
1186 | options = {} |
1187 | |
1188 | [:limit, :offset].each do |symbol| |
1189 | if number = attr[symbol] |
1190 | if number =~ /^\d{1,4}$/ |
1191 | options[symbol] = number.to_i |
1192 | else |
1193 | raise TagError.new("`#{symbol}' attribute of `each' tag must be a positive number between 1 and 4 digits") |
1194 | end |
1195 | end |
1196 | end |
1197 | |
1198 | by = (attr[:by] || 'published_at').strip |
1199 | order = (attr[:order] || 'asc').strip |
1200 | order_string = '' |
1201 | if self.attributes.keys.include?(by) |
1202 | order_string << by |
1203 | else |
1204 | raise TagError.new("`by' attribute of `each' tag must be set to a valid field name") |
1205 | end |
1206 | if order =~ /^(asc|desc)$/i |
1207 | order_string << " #{$1.upcase}" |
1208 | else |
1209 | raise TagError.new(%{`order' attribute of `each' tag must be set to either "asc" or "desc"}) |
1210 | end |
1211 | options[:order] = order_string |
1212 | |
1213 | status = (attr[:status] || ( dev?(tag.globals.page.request) ? 'all' : 'published')).downcase |
1214 | unless status == 'all' |
1215 | stat = Status[status] |
1216 | unless stat.nil? |
1217 | options[:conditions] = ["(virtual = ?) and (status_id = ?)", false, stat.id] |
1218 | else |
1219 | raise TagError.new(%{`status' attribute of `each' tag must be set to a valid status}) |
1220 | end |
1221 | else |
1222 | options[:conditions] = ["virtual = ?", false] |
1223 | end |
1224 | options |
1225 | end |
1226 | |
1227 | def aggregate_children(tag) |
1228 | options = children_find_options(tag) |
1229 | parent_ids = tag.locals.parent_ids |
1230 | |
1231 | conditions = options[:conditions] |
1232 | conditions.first << " AND parent_id IN (?)" |
1233 | conditions << parent_ids |
1234 | options |
1235 | end |
1236 | |
1237 | def pagination_find_options(tag) |
1238 | attr = tag.attr.symbolize_keys |
1239 | if attr[:paginated] == 'true' |
1240 | pagination_parameters.merge(attr.slice(:per_page)) |
1241 | else |
1242 | false |
1243 | end |
1244 | end |
1245 | |
1246 | def will_paginate_options(tag) |
1247 | attr = tag.attr.symbolize_keys |
1248 | if attr[:paginated] == 'true' |
1249 | attr.slice(:class, :previous_label, :next_label, :inner_window, :outer_window, :separator, :per_page).merge({:renderer => Radiant::Pagination::LinkRenderer.new(tag.globals.page.url)}) |
1250 | else |
1251 | {} |
1252 | end |
1253 | end |
1254 | |
1255 | def remove_trailing_slash(string) |
1256 | (string =~ %r{^(.*?)/$}) ? $1 : string |
1257 | end |
1258 | |
1259 | def tag_part_name(tag) |
1260 | tag.attr['part'] || 'body' |
1261 | end |
1262 | |
1263 | def build_regexp_for(tag, attribute_name) |
1264 | ignore_case = tag.attr.has_key?('ignore_case') && tag.attr['ignore_case']=='false' ? nil : true |
1265 | begin |
1266 | regexp = Regexp.new(tag.attr['matches'], ignore_case) |
1267 | rescue RegexpError => e |
1268 | raise TagError.new("Malformed regular expression in `#{attribute_name}' argument of `#{tag.name}' tag: #{e.message}") |
1269 | end |
1270 | regexp |
1271 | end |
1272 | |
1273 | def relative_url_for(url, request) |
1274 | File.join(ActionController::Base.relative_url_root || '', url) |
1275 | end |
1276 | |
1277 | def absolute_path_for(base_path, new_path) |
1278 | if new_path.first == '/' |
1279 | new_path |
1280 | else |
1281 | File.expand_path(File.join(base_path, new_path)) |
1282 | end |
1283 | end |
1284 | |
1285 | def page_found?(page) |
1286 | page && !(FileNotFoundPage === page) |
1287 | end |
1288 | |
1289 | def boolean_attr_or_error(tag, attribute_name, default) |
1290 | attribute = attr_or_error(tag, :attribute_name => attribute_name, :default => default.to_s, :values => 'true, false') |
1291 | (attribute.to_s.downcase == 'true') ? true : false |
1292 | end |
1293 | |
1294 | def attr_or_error(tag, options = {}) |
1295 | attribute_name = options[:attribute_name].to_s |
1296 | default = options[:default] |
1297 | values = options[:values].split(',').map!(&:strip) |
1298 | |
1299 | attribute = (tag.attr[attribute_name] || default).to_s |
1300 | raise TagError.new(%{'#{attribute_name}' attribute of #{tag} tag must be one of: #{values.join(',')}}) unless values.include?(attribute) |
1301 | return attribute |
1302 | end |
1303 | |
1304 | def dev?(request) |
1305 | return false if request.nil? |
1306 | if dev_host = Radiant::Config['dev.host'] |
1307 | dev_host == request.host |
1308 | else |
1309 | request.host =~ /^dev\./ |
1310 | end |
1311 | end |
1312 | |
1313 | end |