radiant-clipped-extension 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +230 -0
  3. data/Rakefile +109 -0
  4. data/app/controllers/admin/assets_controller.rb +60 -0
  5. data/app/controllers/admin/page_attachments_controller.rb +18 -0
  6. data/app/helpers/admin/assets_helper.rb +16 -0
  7. data/app/models/asset.rb +224 -0
  8. data/app/models/asset_tags.rb +384 -0
  9. data/app/models/asset_type.rb +170 -0
  10. data/app/models/old_page_attachment.rb +26 -0
  11. data/app/models/page_attachment.rb +21 -0
  12. data/app/views/admin/assets/_asset.html.haml +12 -0
  13. data/app/views/admin/assets/_asset_table.html.haml +30 -0
  14. data/app/views/admin/assets/_errors.html.haml +3 -0
  15. data/app/views/admin/assets/_form.html.haml +20 -0
  16. data/app/views/admin/assets/_page_assets.html.haml +12 -0
  17. data/app/views/admin/assets/_search.html.haml +17 -0
  18. data/app/views/admin/assets/_search_results.html.haml +17 -0
  19. data/app/views/admin/assets/edit.html.haml +40 -0
  20. data/app/views/admin/assets/index.html.haml +19 -0
  21. data/app/views/admin/assets/new.html.haml +24 -0
  22. data/app/views/admin/assets/refresh.html.haml +14 -0
  23. data/app/views/admin/assets/remove.html.haml +16 -0
  24. data/app/views/admin/configuration/_edit.html.haml +8 -0
  25. data/app/views/admin/configuration/_show.html.haml +12 -0
  26. data/app/views/admin/page_attachments/_attachment.html.haml +25 -0
  27. data/app/views/admin/page_parts/_page_part.html.haml +21 -0
  28. data/app/views/admin/pages/_asset_popups.html.haml +42 -0
  29. data/app/views/admin/pages/_assets.html.haml +11 -0
  30. data/app/views/admin/removed/_assets_bucket.html.haml +8 -0
  31. data/app/views/admin/removed/_assets_container.html.haml +58 -0
  32. data/app/views/admin/removed/_bucket.html.haml +11 -0
  33. data/app/views/admin/removed/_bucket_asset.html.haml +9 -0
  34. data/app/views/admin/removed/_show_bucket_link.html.haml +4 -0
  35. data/app/views/admin/removed/_upload_to_page.html.haml +12 -0
  36. data/app/views/admin/removed/bucket/_iframe.html.haml +1 -0
  37. data/artwork/icons.png +0 -0
  38. data/clipped_extension.rb +89 -0
  39. data/config/initializers/interpolation.rb +6 -0
  40. data/config/initializers/processors.rb +43 -0
  41. data/config/initializers/radiant_config.rb +34 -0
  42. data/config/locales/en.yml +108 -0
  43. data/config/locales/nl.yml +105 -0
  44. data/config/routes.rb +8 -0
  45. data/cucumber.yml +1 -0
  46. data/db/migrate/001_create_assets.rb +12 -0
  47. data/db/migrate/002_create_paperclip_attributes.rb +13 -0
  48. data/db/migrate/003_create_user_observer.rb +13 -0
  49. data/db/migrate/004_create_page_attachments.rb +19 -0
  50. data/db/migrate/005_rename_users.rb +13 -0
  51. data/db/migrate/20090316132151_disable_file_types.rb +20 -0
  52. data/db/migrate/20110513205050_asset_uuid.rb +11 -0
  53. data/db/migrate/20110606111250_update_configuration.rb +28 -0
  54. data/db/migrate/20110609101438_dimensions.rb +13 -0
  55. data/features/support/env.rb +11 -0
  56. data/features/support/paths.rb +22 -0
  57. data/lib/clipped_admin_ui.rb +32 -0
  58. data/lib/page_asset_associations.rb +13 -0
  59. data/lib/paperclip/frame_grab.rb +73 -0
  60. data/lib/paperclip/geometry_transformation.rb +79 -0
  61. data/lib/radiant-clipped-extension.rb +2 -0
  62. data/lib/radiant-clipped-extension/version.rb +3 -0
  63. data/lib/tasks/clipped_extension_tasks.rake +126 -0
  64. data/lib/tasks/paperclip_tasks.rake +79 -0
  65. data/public/flash/ZeroClipboard.swf +0 -0
  66. data/public/images/admin/assets/add.png +0 -0
  67. data/public/images/admin/assets/archive_icon.png +0 -0
  68. data/public/images/admin/assets/audio_icon.png +0 -0
  69. data/public/images/admin/assets/audio_thumbnail.png +0 -0
  70. data/public/images/admin/assets/c_icon.png +0 -0
  71. data/public/images/admin/assets/copy.png +0 -0
  72. data/public/images/admin/assets/css_icon.png +0 -0
  73. data/public/images/admin/assets/database_icon.png +0 -0
  74. data/public/images/admin/assets/delete.png +0 -0
  75. data/public/images/admin/assets/document_icon.png +0 -0
  76. data/public/images/admin/assets/document_thumbnail.png +0 -0
  77. data/public/images/admin/assets/flash_icon.png +0 -0
  78. data/public/images/admin/assets/flash_thumbnail.png +0 -0
  79. data/public/images/admin/assets/font_icon.png +0 -0
  80. data/public/images/admin/assets/gzip_icon.png +0 -0
  81. data/public/images/admin/assets/html_icon.png +0 -0
  82. data/public/images/admin/assets/image_icon.png +0 -0
  83. data/public/images/admin/assets/image_thumbnail.png +0 -0
  84. data/public/images/admin/assets/java_icon.png +0 -0
  85. data/public/images/admin/assets/page_edit.png +0 -0
  86. data/public/images/admin/assets/perl_icon.png +0 -0
  87. data/public/images/admin/assets/php_icon.png +0 -0
  88. data/public/images/admin/assets/presentation_icon.png +0 -0
  89. data/public/images/admin/assets/python_icon.png +0 -0
  90. data/public/images/admin/assets/reorder_assets.png +0 -0
  91. data/public/images/admin/assets/ruby_icon.png +0 -0
  92. data/public/images/admin/assets/script_icon.png +0 -0
  93. data/public/images/admin/assets/spreadsheet_icon.png +0 -0
  94. data/public/images/admin/assets/tar_icon.png +0 -0
  95. data/public/images/admin/assets/unknown_icon.png +0 -0
  96. data/public/images/admin/assets/unknown_thumbnail.png +0 -0
  97. data/public/images/admin/assets/video_icon.png +0 -0
  98. data/public/images/admin/assets/video_thumbnail.png +0 -0
  99. data/public/images/admin/assets/xml_icon.png +0 -0
  100. data/public/images/admin/assets/zip_icon.png +0 -0
  101. data/public/javascripts/admin/assets.js +296 -0
  102. data/public/stylesheets/sass/admin/assets.sass +209 -0
  103. data/radiant-clipped-extension.gemspec +33 -0
  104. data/spec/controllers/admin/assets_controller_spec.rb +50 -0
  105. data/spec/controllers/admin/page_attachments_controller_spec.rb +50 -0
  106. data/spec/datasets/assets_dataset.rb +35 -0
  107. data/spec/fixtures/test.flv +0 -0
  108. data/spec/lib/asset_tags_spec.rb +101 -0
  109. data/spec/lib/frame_grab_spec.rb +17 -0
  110. data/spec/lib/geometry_transformation_spec.rb +63 -0
  111. data/spec/models/asset_spec.rb +72 -0
  112. data/spec/models/asset_type_spec.rb +63 -0
  113. data/spec/spec.opts +6 -0
  114. data/spec/spec_helper.rb +36 -0
  115. data/wireframes/edit-page-assets-2.bmml +453 -0
  116. data/wireframes/edit-page-assets-2.png +0 -0
  117. data/wireframes/edit-page-assets-3.bmml +454 -0
  118. data/wireframes/edit-page-assets-3.png +0 -0
  119. data/wireframes/edit-page-assets.bmml +433 -0
  120. data/wireframes/edit-page-assets.png +0 -0
  121. data/wireframes/edit-page.bmml +174 -0
  122. data/wireframes/edit-page.png +0 -0
  123. metadata +249 -0
@@ -0,0 +1,224 @@
1
+ class Asset < ActiveRecord::Base
2
+
3
+ has_many :page_attachments, :dependent => :destroy
4
+ has_many :pages, :through => :page_attachments
5
+ has_site if respond_to? :has_site
6
+
7
+ belongs_to :created_by, :class_name => 'User'
8
+ belongs_to :updated_by, :class_name => 'User'
9
+
10
+ default_scope :order => "created_at DESC"
11
+
12
+ named_scope :latest, lambda { |limit|
13
+ { :order => "created_at DESC", :limit => limit }
14
+ }
15
+
16
+ named_scope :of_types, lambda { |types|
17
+ mimes = AssetType.slice(*types).map(&:mime_types).flatten
18
+ { :conditions => ["asset_content_type IN (#{mimes.map{'?'}.join(',')})", *mimes] }
19
+ }
20
+
21
+ named_scope :matching, lambda { |term|
22
+ { :conditions => ["LOWER(assets.asset_file_name) LIKE (:term) OR LOWER(title) LIKE (:term) OR LOWER(caption) LIKE (:term)", {:term => "%#{term.downcase}%" }] }
23
+ }
24
+
25
+ named_scope :except, lambda { |assets|
26
+ if assets.any?
27
+ assets = assets.split(',') if assets.is_a?(String)
28
+ asset_ids = assets.first.is_a?(Asset) ? assets.map(&:id) : assets
29
+ { :conditions => ["assets.id NOT IN(#{asset_ids.map{ '?' }.join(',')})", *asset_ids] }
30
+ else
31
+ {}
32
+ end
33
+ }
34
+
35
+ has_attached_file :asset,
36
+ :styles => lambda { |attachment|
37
+ AssetType.from(attachment.instance_read(:content_type)).paperclip_styles
38
+ },
39
+ :processors => lambda { |asset|
40
+ asset.paperclip_processors
41
+ },
42
+ :whiny => false,
43
+ :storage => Radiant.config["paperclip.storage"] == "s3" ? :s3 : :filesystem,
44
+ :s3_credentials => {
45
+ :access_key_id => Radiant.config["paperclip.s3.key"],
46
+ :secret_access_key => Radiant.config["paperclip.s3.secret"]
47
+ },
48
+ :s3_host_alias => Radiant.config["paperclip.s3.host_alias"] || Radiant.config["assets.s3.bucket"],
49
+ :bucket => Radiant.config["paperclip.s3.bucket"],
50
+ :url => Radiant.config["paperclip.url"],
51
+ :path => Radiant.config["paperclip.path"]
52
+
53
+ before_save :assign_title
54
+ before_save :assign_uuid
55
+ after_post_process :get_dimensions
56
+
57
+ validates_attachment_presence :asset, :message => "You must choose a file to upload!"
58
+ if Radiant.config["paperclip.skip_filetype_validation"] != "true" && Radiant.config['paperclip.content_types']
59
+ validates_attachment_content_type :asset, :content_type => Radiant.config["paperclip.content_types"].gsub(' ','').split(',')
60
+ end
61
+ validates_attachment_size :asset, :less_than => ( Radiant.config["assets.max_asset_size"] || 5 ).to_i.megabytes
62
+
63
+ def asset_type
64
+ AssetType.from(asset.content_type)
65
+ end
66
+ delegate :paperclip_processors, :paperclip_styles, :style_dimensions, :style_format, :to => :asset_type
67
+
68
+ def thumbnail(style_name='original')
69
+ return asset.url if style_name.to_sym == :original
70
+ return asset.url(style_name.to_sym) if has_style?(style_name)
71
+ return asset_type.icon(style_name)
72
+ end
73
+
74
+ def has_style?(style_name)
75
+ paperclip_styles.keys.include?(style_name.to_sym)
76
+ end
77
+
78
+ def basename
79
+ File.basename(asset_file_name, ".*") if asset_file_name
80
+ end
81
+
82
+ def extension(style_name='original')
83
+ if style_name == 'original'
84
+ return original_extension
85
+ elsif style = asset.styles[style_name.to_sym]
86
+ return style.format
87
+ else
88
+ return original_extension
89
+ end
90
+ end
91
+
92
+ def original_extension
93
+ return asset_file_name.split('.').last.downcase if asset_file_name
94
+ end
95
+
96
+ def attached_to?(page)
97
+ pages.include?(page)
98
+ end
99
+
100
+ def original_geometry
101
+ @original_geometry ||= Paperclip::Geometry.new(original_width, original_height)
102
+ end
103
+
104
+ def geometry(style_name='original')
105
+ if style_name == 'original'
106
+ original_geometry
107
+ elsif style = asset.styles[style_name.to_sym]
108
+ original_geometry * style.geometry
109
+ end
110
+ end
111
+
112
+ def aspect(style_name='original')
113
+ image? && geometry(style_name).aspect
114
+ end
115
+
116
+ def orientation(style_name='original')
117
+ if image?
118
+ this_aspect = aspect(style_name)
119
+ case
120
+ when this_aspect < 1.0 then 'vertical'
121
+ when this_aspect > 1.0 then 'horizontal'
122
+ else 'square'
123
+ end
124
+ end
125
+ end
126
+
127
+ def width(style_name='original')
128
+ image? ? geometry(style_name).width.to_i : 0
129
+ end
130
+
131
+ def height(style_name='original')
132
+ image? ? geometry(style_name).height.to_i : 0
133
+ end
134
+
135
+ def square?(style_name='original')
136
+ image? && geometry(style_name).square?
137
+ end
138
+
139
+ def vertical?(style_name='original')
140
+ image? && geometry(style_name).vertical?
141
+ end
142
+
143
+ def horizontal?(style_name='original')
144
+ image? && geometry(style_name).horizontal?
145
+ end
146
+
147
+ def dimensions_known?
148
+ !original_width.blank? && !original_height.blank?
149
+ end
150
+
151
+ private
152
+
153
+ def read_dimensions
154
+ if image? && !dimensions_known?
155
+ geometry = Paperclip::Geometry.from_file(asset.path)
156
+ self.update_attributes(
157
+ :original_width => geometry.width,
158
+ :original_height => geometry.height,
159
+ :original_extension => extension
160
+ )
161
+ geometry
162
+ end
163
+ end
164
+
165
+ def assign_title
166
+ self.title = basename if title.blank?
167
+ end
168
+
169
+ def assign_uuid
170
+ self.uuid = UUIDTools::UUID.timestamp_create.to_s if uuid.blank?
171
+ end
172
+
173
+ class << self
174
+ def known_types
175
+ AssetType.known_types
176
+ end
177
+
178
+ # searching and pagination moved to the controller
179
+
180
+ def find_all_by_asset_types(asset_types, *args)
181
+ with_asset_types(asset_types) { find *args }
182
+ end
183
+
184
+ def count_with_asset_types(asset_types, *args)
185
+ with_asset_types(asset_types) { count *args }
186
+ end
187
+
188
+ def with_asset_types(asset_types, &block)
189
+ with_scope(:find => { :conditions => AssetType.conditions_for(asset_types) }, &block)
190
+ end
191
+ end
192
+
193
+ # called from AssetType to set type_condition? methods on Asset
194
+ def self.define_class_method(name, &block)
195
+ eigenclass.send :define_method, name, &block
196
+ end
197
+
198
+ # returns the return value of class << self block, which is self (as defined within that block)
199
+ def self.eigenclass
200
+ class << self; self; end
201
+ end
202
+
203
+ # for backwards compatibility
204
+ def self.thumbnail_sizes
205
+ AssetType.find(:image).paperclip_styles
206
+ end
207
+
208
+ def self.thumbnail_names
209
+ thumbnail_sizes.keys
210
+ end
211
+
212
+ # this is a convenience for image-pickers
213
+ def self.thumbnail_options
214
+ asset_sizes = thumbnail_sizes.collect{|k,v|
215
+ size_id = k
216
+ size_description = "#{k}: "
217
+ size_description << (v.is_a?(Array) ? v.join(' as ') : v)
218
+ [size_description, size_id]
219
+ }.sort_by{|pair| pair.last.to_s}
220
+ asset_sizes.unshift ['Original (as uploaded)', 'original']
221
+ asset_sizes
222
+ end
223
+
224
+ end
@@ -0,0 +1,384 @@
1
+ module AssetTags
2
+ include Radiant::Taggable
3
+
4
+ class TagError < StandardError; end
5
+
6
+ %w{top_padding width height caption asset_file_name asset_content_type asset_file_size id filename image flash thumbnail url link extension if_content_type page:title page:url}.each do |name|
7
+ deprecated_tag "assets:#{name}", :substitute => "asset:#{name}", :deadline => '2.0'
8
+ end
9
+
10
+ Asset.known_types.each do |known_type|
11
+ deprecated_tag "assets:if_#{known_type}", :substitute => "asset:if_#{known_type}", :deadline => '2.0'
12
+ deprecated_tag "assets:unless_#{known_type}", :substitute => "asset:unless_#{known_type}", :deadline => '2.0'
13
+ end
14
+
15
+ desc %{
16
+ The namespace for referencing images and assets.
17
+
18
+ *Usage:*
19
+ <pre><code><r:asset [name="asset name"]>...</r:asset></code></pre>
20
+ }
21
+ tag 'asset' do |tag|
22
+ tag.locals.asset = find_asset unless tag.attr.empty?
23
+ tag.expand
24
+ end
25
+
26
+ desc %{
27
+ Cycles through all assets attached to the current page.
28
+ This tag does not require the name atttribute, nor do any of its children.
29
+ Use the @limit@ and @offset@ attribute to render a specific number of assets.
30
+ Use @by@ and @order@ attributes to control the order of assets.
31
+ Use @extensions@ attribute to specify which assets to be rendered.
32
+
33
+ *Usage:*
34
+ <pre><code><r:assets:each [limit=0] [offset=0] [order="asc|desc"] [by="position|title|..."] [extensions="png|pdf|doc"]>...</r:assets:each></code></pre>
35
+ }
36
+ tag 'assets' do |tag|
37
+ tag.expand
38
+ end
39
+ tag 'assets:each' do |tag|
40
+ options = tag.attr.dup
41
+ result = []
42
+ tag.locals.assets = tag.locals.page.assets.scoped(assets_find_options(tag))
43
+ tag.render('asset_list', tag.attr.dup, &tag.block)
44
+ end
45
+
46
+ # General purpose paginated asset lister. Very useful dryness.
47
+ # Tag.locals.assets must be defined but can be empty.
48
+
49
+ tag 'asset_list' do |tag|
50
+ raise TagError, "r:asset_list: no assets to list" unless tag.locals.assets
51
+ options = tag.attr.symbolize_keys
52
+ result = []
53
+ paging = pagination_find_options(tag)
54
+ assets = paging ? tag.locals.assets.paginate(paging) : tag.locals.assets.all
55
+ assets.each do |asset|
56
+ tag.locals.asset = asset
57
+ result << tag.expand
58
+ end
59
+ if paging && assets.total_pages > 1
60
+ tag.locals.paginated_list = assets
61
+ result << tag.render('pagination', tag.attr.dup)
62
+ end
63
+ result
64
+ end
65
+
66
+ desc %{
67
+ References the first asset attached to the current page.
68
+
69
+ *Usage:*
70
+ <pre><code><r:assets:first>...</r:assets:first></code></pre>
71
+ }
72
+ tag 'assets:first' do |tag|
73
+ if tag.locals.asset = tag.locals.page.assets.first
74
+ tag.expand
75
+ end
76
+ end
77
+
78
+ desc %{
79
+ References the last asset attached to the current page.
80
+
81
+ *Usage:*
82
+ <pre><code><r:assets:last>...</r:assets:last></code></pre>
83
+ }
84
+ tag 'assets:last' do |tag|
85
+ p "tag.locals.page.assets is #{tag.locals.page.assets.join(',')} and tag.locals.page.assets.last is #{tag.locals.page.assets.last}"
86
+
87
+ if tag.locals.asset = tag.locals.page.assets.last
88
+ tag.expand
89
+ end
90
+ end
91
+
92
+ desc %{
93
+ Renders the contained elements only if the current contextual page has one or
94
+ more assets. The @min_count@ attribute specifies the minimum number of required
95
+ assets. You can also filter by extensions with the @extensions@ attribute.
96
+
97
+ *Usage:*
98
+ <pre><code><r:if_assets [min_count="n"]>...</r:if_assets></code></pre>
99
+ }
100
+ tag 'if_assets' do |tag|
101
+ count = tag.attr['min_count'] && tag.attr['min_count'].to_i || 1
102
+ assets = tag.locals.page.assets.count(:conditions => assets_find_options(tag)[:conditions])
103
+ tag.expand if assets >= count
104
+ end
105
+
106
+ desc %{
107
+ The opposite of @<r:if_assets/>@.
108
+ }
109
+ tag 'unless_assets' do |tag|
110
+ count = tag.attr['min_count'] && tag.attr['min_count'].to_i || 1
111
+ assets = tag.locals.page.assets.count(:conditions => assets_find_options(tag)[:conditions])
112
+ tag.expand unless assets >= count
113
+ end
114
+
115
+ # Resets the page Url and title within the asset tag
116
+ [:title, :url].each do |method|
117
+ tag "asset:page:#{method.to_s}" do |tag|
118
+ tag.locals.page.send(method)
119
+ end
120
+ end
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+ desc %{
129
+ Renders the value for a top padding for the image. Put the image in a
130
+ container with specified height and using this tag you can vertically
131
+ align the image within it's container.
132
+
133
+ *Usage*:
134
+ <pre><code><r:asset:top_padding container = "140" [size="icon"]/></code></pre>
135
+
136
+ *Working Example*:
137
+ <pre><code>
138
+ <ul>
139
+ <r:asset:each>
140
+ <li style="height:140px">
141
+ <img style="padding-top:<r:top_padding size='category' container='140' />px"
142
+ src="<r:url />" alt="<r:title />" />
143
+ </li>
144
+ </r:asset:each>
145
+ </ul>
146
+ </code></pre>
147
+ }
148
+ tag 'asset:top_padding' do |tag|
149
+ asset, options = asset_and_options(tag)
150
+ raise TagError, 'Asset is not an image' unless asset.image?
151
+ raise TagError, "'container' attribute required" unless options['container']
152
+ size = options['size'] ? options.delete('size') : 'icon'
153
+ container = options.delete('container')
154
+ img_height = asset.height(size)
155
+ (container.to_i - img_height.to_i)/2
156
+ end
157
+
158
+ ['height','width'].each do |dimension|
159
+ desc %{
160
+ Renders the #{dimension} of the asset.
161
+ }
162
+ tag "asset:#{dimension}" do |tag|
163
+ asset, options = asset_and_options(tag)
164
+ unless asset.dimensions_known?
165
+ raise TagError, "Can't determine #{dimension} for this Asset. It may not be a supported type."
166
+ end
167
+ size = options['size'] ? options.delete('size') : 'original'
168
+ asset.send(dimension, size)
169
+ end
170
+ end
171
+
172
+ desc %{
173
+ Returns a string representing the orientation of the asset, which must be an image. Can be 'horizontal', 'vertical' or 'square'.
174
+ }
175
+ tag 'asset:orientation' do |tag|
176
+ asset, options = asset_and_options(tag)
177
+ raise TagError, 'Asset is not an image' unless asset.image?
178
+ size = options['size'] ? options.delete('size') : 'original'
179
+ asset.orientation(size)
180
+ end
181
+
182
+ desc %{
183
+ Returns the aspect ratio of the asset, which must be an image.
184
+ }
185
+ tag 'asset:aspect' do |tag|
186
+ asset, options = asset_and_options(tag)
187
+ raise TagError, 'Asset is not an image' unless asset.image?
188
+ size = options['size'] ? options.delete('size') : 'original'
189
+ asset.aspect(size)
190
+ end
191
+
192
+ desc %{
193
+ Renders the containing elements only if the asset's content type matches
194
+ the regular expression given in the @matches@ attribute. If the
195
+ @ignore_case@ attribute is set to false, the match is case sensitive. By
196
+ default, @ignore_case@ is set to true.
197
+
198
+ The @name@ or @id@ attribute is required on the parent tag unless this tag is used in @asset:each@.
199
+
200
+ *Usage:*
201
+ <pre><code><r:asset:each><r:if_content_type matches="regexp" [ignore_case=true|false"]>...</r:if_content_type></r:asset:each></code></pre>
202
+ }
203
+ tag 'asset:if_content_type' do |tag|
204
+ options = tag.attr.dup
205
+ # XXX build_regexp_for comes from StandardTags
206
+ regexp = build_regexp_for(tag,options)
207
+ asset_content_type = tag.locals.asset.asset_content_type
208
+ tag.expand unless asset_content_type.match(regexp).nil?
209
+ end
210
+
211
+ #TODO: could use better docs for Asset#other? case explaining what types it covers
212
+ Asset.known_types.each do |known_type|
213
+ desc %{
214
+ Renders the contents only of the asset is of the type #{known_type}
215
+ }
216
+ tag "asset:if_#{known_type}" do |tag|
217
+ tag.expand if find_asset(tag, tag.attr.dup).send("#{known_type}?".to_sym)
218
+ end
219
+
220
+ desc %{
221
+ Renders the contents only of the asset is not of the type #{known_type}
222
+ }
223
+ tag "asset:unless_#{known_type}" do |tag|
224
+ tag.expand unless find_asset(tag, tag.attr.dup).send("#{known_type}?".to_sym)
225
+ end
226
+ end
227
+
228
+ [:title, :caption, :asset_file_name, :extension, :asset_content_type, :asset_file_size, :id].each do |method|
229
+ desc %{
230
+ Renders the @#{method.to_s}@ attribute of the asset
231
+ }
232
+ tag "asset:#{method.to_s}" do |tag|
233
+ asset, options = asset_and_options(tag)
234
+ asset.send(method) rescue nil
235
+ end
236
+ end
237
+
238
+ tag 'asset:name' do |tag|
239
+ tag.render('asset:title', tag.attr.dup)
240
+ end
241
+
242
+ tag 'asset:filename' do |tag|
243
+ asset, options = asset_and_options(tag)
244
+ asset.asset_file_name rescue nil
245
+ end
246
+
247
+ desc %{
248
+ Renders an image tag for the asset.
249
+
250
+ Using the optional @size@ attribute, different sizes can be display.
251
+ "thumbnail" and "icon" sizes are built in, but custom ones can be set
252
+ using by changing assets.addition_thumbnails in the Radiant::Config
253
+ settings.
254
+
255
+ *Usage:*
256
+ <pre><code><r:asset:image [name="asset name" or id="asset id"] [size="icon|thumbnail|whatever"]></code></pre>
257
+ }
258
+ tag 'asset:image' do |tag|
259
+ tag.locals.asset, options = asset_and_options(tag)
260
+ if tag.locals.asset.image?
261
+ size = options['size'] ? options.delete('size') : 'original'
262
+ alt = "alt='#{tag.locals.asset.title}'" unless tag.attr['alt']
263
+ attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
264
+ attributes << alt unless alt.nil?
265
+ url = tag.locals.asset.thumbnail(size)
266
+ %{<img src="#{url}" #{attributes unless attributes.empty?} />} rescue nil
267
+ end
268
+ end
269
+
270
+ desc %{
271
+ Embeds a flash-movie in a cross-browser-compatible fashion using only HTML
272
+ If no width and height attributes are given it will use the intrinsic
273
+ dimensions of the swf file
274
+
275
+ *Usage:*
276
+ <pre><code><r:asset:flash [name="asset name" or id="asset id"] [width="100"] [height="100"]>Fallback content</flash></code></pre>
277
+
278
+ *Example with text fallback:*
279
+ <pre><code><r:asset:flash name="flash_movie">
280
+ Sorry, you need to have flash installed, <a href="http://adobe.com/flash">get it here</a>
281
+ </flash></code></pre>
282
+
283
+ *Example with image fallback and explicit dimensions:*
284
+ <pre><code><r:asset:flash name="flash_movie" width="300" height="200">
285
+ <r:asset:image name="flash_screenshot" />
286
+ </flash></code></pre>
287
+ }
288
+ tag 'asset:flash' do |tag|
289
+ asset, options = asset_and_options(tag)
290
+ if tag.locals.asset.flash?
291
+ url = asset.thumbnail('original')
292
+ dimensions = [(tag.attr['width'] || asset.width),(tag.attr['height'] || asset.height)]
293
+ swf_embed_markup url, dimensions, tag.expand
294
+ end
295
+ end
296
+
297
+ desc %{
298
+ Renders the url for the asset. If the asset is an image, the <code>size</code> attribute can be used to
299
+ generate the url for that size.
300
+
301
+ *Usage:*
302
+ <pre><code><r:url [name="asset name" or id="asset id"] [size="icon|thumbnail"]></code></pre>
303
+ }
304
+ tag 'asset:url' do |tag|
305
+ asset, options = asset_and_options(tag)
306
+ size = options['size'] ? options.delete('size') : 'original'
307
+ asset.thumbnail(size) rescue nil
308
+ end
309
+
310
+ desc %{
311
+ Renders a link to the asset. If the asset is an image, the <code>size</code> attribute can be used to
312
+ generate a link to that size.
313
+
314
+ *Usage:*
315
+ <pre><code><r:asset:link [name="asset name" or id="asset id"] [size="icon|thumbnail"] /></code></pre>
316
+ }
317
+ tag 'asset:link' do |tag|
318
+ asset, options = asset_and_options(tag)
319
+ size = options['size'] ? options.delete('size') : 'original'
320
+ text = options['text'] || asset.title
321
+ anchor = options['anchor'] ? "##{options.delete('anchor')}" : ''
322
+ attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
323
+ attributes = " #{attributes}" unless attributes.empty?
324
+ text = tag.double? ? tag.expand : text
325
+ url = asset.thumbnail(size)
326
+ %{<a href="#{url }#{anchor}"#{attributes}>#{text}</a>} rescue nil
327
+ end
328
+
329
+ private
330
+ def asset_and_options(tag)
331
+ options = tag.attr.dup
332
+ [find_asset(tag, options), options]
333
+ end
334
+
335
+ def find_asset(tag, options)
336
+ return tag.locals.asset if tag.locals.asset
337
+ if title = options.delete('name') || options.delete('title')
338
+ Asset.find_by_title(title)
339
+ elsif id = options.delete('id')
340
+ Asset.find_by_id(id)
341
+ else
342
+ raise TagError, "'name' or 'id' attribute required for unenclosed r:asset tag" unless tag.locals.asset
343
+ end
344
+ end
345
+
346
+ def assets_find_options(tag)
347
+ attr = tag.attr.symbolize_keys
348
+ extensions = attr[:extensions] && attr[:extensions].split('|') || []
349
+ conditions = unless extensions.blank?
350
+ # this is soon to be removed in favour of asset types
351
+ [ extensions.map { |ext| "assets.asset_file_name LIKE ?"}.join(' OR '),
352
+ *extensions.map { |ext| "%.#{ext}" } ]
353
+ else
354
+ nil
355
+ end
356
+
357
+ by = attr[:by] || 'page_attachments.position'
358
+ order = attr[:order] || 'asc'
359
+
360
+ options = {
361
+ :order => "#{by} #{order}",
362
+ :limit => attr[:limit] || nil,
363
+ :offset => attr[:offset] || nil,
364
+ :conditions => conditions
365
+ }
366
+ end
367
+
368
+ def swf_embed_markup(url, dimensions, fallback_content)
369
+ width, height = dimensions
370
+ %{<!--[if !IE]> -->
371
+ <object type="application/x-shockwave-flash" data="#{url}" width="#{width}" height="#{height}">
372
+ <!-- <![endif]-->
373
+ <!--[if IE]>
374
+ <object width="#{width}" height="#{height}"
375
+ classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
376
+ codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0">
377
+ <param name="movie" value="#{url}" />
378
+ <!-->
379
+ #{fallback_content}
380
+ </object>
381
+ <!-- <![endif]-->}
382
+ end
383
+ end
384
+