radiant-paperclipped-extension 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/.gitmodules +3 -0
  2. data/HELP_admin.markdown +69 -0
  3. data/LICENSE +21 -0
  4. data/README.md +137 -0
  5. data/Rakefile +110 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/admin/assets_controller.rb +124 -0
  8. data/app/helpers/admin/assets_helper.rb +5 -0
  9. data/app/models/asset.rb +279 -0
  10. data/app/models/asset_page_tags.rb +101 -0
  11. data/app/models/asset_tags.rb +272 -0
  12. data/app/models/old_page_attachment.rb +26 -0
  13. data/app/models/page_attachment.rb +8 -0
  14. data/app/views/admin/assets/_asset.html.haml +19 -0
  15. data/app/views/admin/assets/_asset_table.html.haml +49 -0
  16. data/app/views/admin/assets/_assets_bucket.html.haml +8 -0
  17. data/app/views/admin/assets/_assets_container.html.haml +72 -0
  18. data/app/views/admin/assets/_bucket.html.haml +11 -0
  19. data/app/views/admin/assets/_bucket_asset.html.haml +9 -0
  20. data/app/views/admin/assets/_errors.html.haml +3 -0
  21. data/app/views/admin/assets/_form.html.haml +20 -0
  22. data/app/views/admin/assets/_page_assets.html.haml +12 -0
  23. data/app/views/admin/assets/_search_results.html.haml +17 -0
  24. data/app/views/admin/assets/_show_bucket_link.html.haml +4 -0
  25. data/app/views/admin/assets/_upload_to_page.html.haml +16 -0
  26. data/app/views/admin/assets/edit.html.haml +53 -0
  27. data/app/views/admin/assets/index.html.haml +43 -0
  28. data/app/views/admin/assets/new.html.haml +27 -0
  29. data/app/views/admin/assets/remove.html.haml +21 -0
  30. data/app/views/admin/bucket/_iframe.html.haml +1 -0
  31. data/config/locales/en.yml +60 -0
  32. data/config/locales/nl.yml +60 -0
  33. data/config/locales/pl.yml +60 -0
  34. data/config/routes.rb +20 -0
  35. data/db/migrate/001_create_assets.rb +12 -0
  36. data/db/migrate/002_create_paperclip_attributes.rb +13 -0
  37. data/db/migrate/003_create_user_observer.rb +13 -0
  38. data/db/migrate/004_create_page_attachments.rb +19 -0
  39. data/db/migrate/005_rename_users.rb +13 -0
  40. data/db/migrate/006_add_default_configs.rb +29 -0
  41. data/db/migrate/007_add_default_content_types.rb +29 -0
  42. data/db/migrate/20090316132151_disable_file_types.rb +20 -0
  43. data/lib/assets_admin_ui.rb +38 -0
  44. data/lib/mime_type_ext.rb +7 -0
  45. data/lib/tasks/assets_extension_tasks.rake +123 -0
  46. data/lib/tasks/paperclip_tasks.rake +79 -0
  47. data/lib/url_additions.rb +10 -0
  48. data/paperclipped_extension.rb +56 -0
  49. data/psds/file_type_icons.psd +0 -0
  50. data/psds/file_type_icons_.psd +0 -0
  51. data/public/images/assets/_page_assets.html.haml +26 -0
  52. data/public/images/assets/add-to-bucket.png +0 -0
  53. data/public/images/assets/add.png +0 -0
  54. data/public/images/assets/audio_icon.png +0 -0
  55. data/public/images/assets/audio_thumbnail.png +0 -0
  56. data/public/images/assets/delete.png +0 -0
  57. data/public/images/assets/doc_icon.png +0 -0
  58. data/public/images/assets/doc_thumbnail.png +0 -0
  59. data/public/images/assets/edit.png +0 -0
  60. data/public/images/assets/movie_icon.png +0 -0
  61. data/public/images/assets/movie_thumbnail.png +0 -0
  62. data/public/images/assets/new-asset.png +0 -0
  63. data/public/images/assets/page_edit.png +0 -0
  64. data/public/images/assets/pdf_icon.png +0 -0
  65. data/public/images/assets/pdf_thumbnail.png +0 -0
  66. data/public/images/assets/reorder_assets.png +0 -0
  67. data/public/javascripts/admin/assets.js +173 -0
  68. data/public/stylesheets/admin/assets.css +163 -0
  69. data/spec/controllers/admin/assets_controller_spec.rb +10 -0
  70. data/spec/models/asset_spec.rb +68 -0
  71. data/spec/spec.opts +6 -0
  72. data/spec/spec_helper.rb +27 -0
  73. data/vendor/plugins/acts_as_list/README +23 -0
  74. data/vendor/plugins/acts_as_list/init.rb +3 -0
  75. data/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
  76. data/vendor/plugins/acts_as_list/test/list_test.rb +332 -0
  77. data/vendor/plugins/paperclip/LICENSE +26 -0
  78. data/vendor/plugins/paperclip/README.rdoc +179 -0
  79. data/vendor/plugins/paperclip/Rakefile +76 -0
  80. data/vendor/plugins/paperclip/cucumber/paperclip_steps.rb +6 -0
  81. data/vendor/plugins/paperclip/generators/paperclip/USAGE +5 -0
  82. data/vendor/plugins/paperclip/generators/paperclip/paperclip_generator.rb +27 -0
  83. data/vendor/plugins/paperclip/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  84. data/vendor/plugins/paperclip/init.rb +1 -0
  85. data/vendor/plugins/paperclip/lib/generators/paperclip/USAGE +8 -0
  86. data/vendor/plugins/paperclip/lib/generators/paperclip/paperclip_generator.rb +31 -0
  87. data/vendor/plugins/paperclip/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  88. data/vendor/plugins/paperclip/lib/paperclip.rb +397 -0
  89. data/vendor/plugins/paperclip/lib/paperclip/attachment.rb +326 -0
  90. data/vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb +61 -0
  91. data/vendor/plugins/paperclip/lib/paperclip/geometry.rb +115 -0
  92. data/vendor/plugins/paperclip/lib/paperclip/interpolations.rb +108 -0
  93. data/vendor/plugins/paperclip/lib/paperclip/iostream.rb +59 -0
  94. data/vendor/plugins/paperclip/lib/paperclip/matchers.rb +33 -0
  95. data/vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
  96. data/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +74 -0
  97. data/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
  98. data/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
  99. data/vendor/plugins/paperclip/lib/paperclip/processor.rb +49 -0
  100. data/vendor/plugins/paperclip/lib/paperclip/railtie.rb +24 -0
  101. data/vendor/plugins/paperclip/lib/paperclip/storage.rb +247 -0
  102. data/vendor/plugins/paperclip/lib/paperclip/style.rb +90 -0
  103. data/vendor/plugins/paperclip/lib/paperclip/thumbnail.rb +78 -0
  104. data/vendor/plugins/paperclip/lib/paperclip/upfile.rb +52 -0
  105. data/vendor/plugins/paperclip/lib/paperclip/version.rb +3 -0
  106. data/vendor/plugins/paperclip/lib/tasks/paperclip.rake +79 -0
  107. data/vendor/plugins/paperclip/paperclip.gemspec +34 -0
  108. data/vendor/plugins/paperclip/rails/init.rb +2 -0
  109. data/vendor/plugins/paperclip/shoulda_macros/paperclip.rb +119 -0
  110. data/vendor/plugins/paperclip/test/.gitignore +1 -0
  111. data/vendor/plugins/paperclip/test/attachment_test.rb +758 -0
  112. data/vendor/plugins/paperclip/test/database.yml +4 -0
  113. data/vendor/plugins/paperclip/test/fixtures/12k.png +0 -0
  114. data/vendor/plugins/paperclip/test/fixtures/50x50.png +0 -0
  115. data/vendor/plugins/paperclip/test/fixtures/5k.png +0 -0
  116. data/vendor/plugins/paperclip/test/fixtures/bad.png +1 -0
  117. data/vendor/plugins/paperclip/test/fixtures/s3.yml +8 -0
  118. data/vendor/plugins/paperclip/test/fixtures/text.txt +0 -0
  119. data/vendor/plugins/paperclip/test/fixtures/twopage.pdf +0 -0
  120. data/vendor/plugins/paperclip/test/geometry_test.rb +177 -0
  121. data/vendor/plugins/paperclip/test/helper.rb +148 -0
  122. data/vendor/plugins/paperclip/test/integration_test.rb +483 -0
  123. data/vendor/plugins/paperclip/test/interpolations_test.rb +124 -0
  124. data/vendor/plugins/paperclip/test/iostream_test.rb +78 -0
  125. data/vendor/plugins/paperclip/test/matchers/have_attached_file_matcher_test.rb +24 -0
  126. data/vendor/plugins/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb +37 -0
  127. data/vendor/plugins/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
  128. data/vendor/plugins/paperclip/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  129. data/vendor/plugins/paperclip/test/paperclip_test.rb +317 -0
  130. data/vendor/plugins/paperclip/test/processor_test.rb +10 -0
  131. data/vendor/plugins/paperclip/test/storage_test.rb +343 -0
  132. data/vendor/plugins/paperclip/test/style_test.rb +141 -0
  133. data/vendor/plugins/paperclip/test/thumbnail_test.rb +227 -0
  134. data/vendor/plugins/paperclip/test/upfile_test.rb +36 -0
  135. data/vendor/plugins/responds_to_parent/MIT-LICENSE +20 -0
  136. data/vendor/plugins/responds_to_parent/README +42 -0
  137. data/vendor/plugins/responds_to_parent/Rakefile +22 -0
  138. data/vendor/plugins/responds_to_parent/init.rb +2 -0
  139. data/vendor/plugins/responds_to_parent/lib/parent_selector_assertion.rb +144 -0
  140. data/vendor/plugins/responds_to_parent/lib/responds_to_parent.rb +46 -0
  141. data/vendor/plugins/responds_to_parent/test/assert_select_parent_test.rb +318 -0
  142. data/vendor/plugins/responds_to_parent/test/responds_to_parent_test.rb +115 -0
  143. metadata +226 -0
@@ -0,0 +1,5 @@
1
+ module Admin::AssetsHelper
2
+ def automatic_size_descriptions
3
+ Asset.thumbnail_sizes.collect{|k,v| "#{k} (#{(v.to_s).match(/\d+x\d+/)})"}.join(', ')
4
+ end
5
+ end
@@ -0,0 +1,279 @@
1
+ require 'mime_type_ext'
2
+
3
+ class Asset < ActiveRecord::Base
4
+ @@known_types = []
5
+ cattr_accessor :known_types
6
+
7
+ # type declaration machinery is consolidated here so that other extensions can add more types
8
+ # for example: Asset.register_type(:gps, %w{application/gpx+xml application/tcx+xml})
9
+ # the main Asset register_type() calls are in the class definition below after validation
10
+
11
+ def self.register_type(type, mimes)
12
+ constname = type.to_s.upcase.to_sym
13
+ Mime.send(:remove_const, constname) if Mime.const_defined?(constname)
14
+ Mime::Type.register mimes.shift, type, mimes # Mime::Type.register 'image/png', :image, %w[image/x-png image/jpeg image/pjpeg image/jpg image/gif]
15
+
16
+ self.class.send :define_method, "#{type}?".intern do |content_type|
17
+ Mime::Type.lookup_by_extension(type.to_s) == content_type.to_s
18
+ end
19
+
20
+ define_method "#{type}?".intern do
21
+ self.class.send "#{type}?".intern, asset_content_type
22
+ end
23
+
24
+ self.class.send :define_method, "#{type}_condition".intern do
25
+ types = Mime::Type.lookup_by_extension(type.to_s).all_types
26
+ send(:sanitize_sql, ['asset_content_type IN (?)', types])
27
+ end
28
+
29
+ self.class.send :define_method, "not_#{type}_condition".intern do
30
+ types = Mime::Type.lookup_by_extension(type.to_s).all_types
31
+ send(:sanitize_sql, ['NOT asset_content_type IN (?)', types])
32
+ end
33
+
34
+ named_scope type.to_s.pluralize.intern, :conditions => self.send("#{type}_condition".intern) do
35
+ def paged (options={})
36
+ paginate({:per_page => 20, :page => 1}.merge(options))
37
+ end
38
+ end
39
+
40
+ named_scope "not_#{type.to_s.pluralize}".intern, :conditions => self.send("not_#{type}_condition".intern) do
41
+ def paged (options={})
42
+ paginate({:per_page => 20, :page => 1}.merge(options))
43
+ end
44
+ end
45
+
46
+ known_types.push(type)
47
+ end
48
+
49
+ def self.known_type?(type)
50
+ known_types.include?(type)
51
+ end
52
+
53
+ # 'other' here means 'document', really: anything that is not image, audio or video.
54
+
55
+ def other?
56
+ self.class.other?(asset_content_type)
57
+ end
58
+
59
+ def self.other?(content_type)
60
+ !self.mime_types_not_considered_other.include? content_type.to_s
61
+ end
62
+
63
+ # the lambda delays interpolation, allowing extensions to affect the other_condition
64
+ named_scope :others, lambda {{:conditions => self.other_condition}}
65
+ known_types.push(:other)
66
+
67
+ named_scope :newest_first, { :order => 'created_at DESC'}
68
+
69
+ def self.other_condition
70
+ send(:sanitize_sql, ['asset_content_type NOT IN (?)', self.mime_types_not_considered_other])
71
+ end
72
+
73
+ def self.mime_types_not_considered_other
74
+ Mime::IMAGE.all_types + Mime::AUDIO.all_types + Mime::MOVIE.all_types
75
+ end
76
+
77
+ class << self
78
+ def search(search, filter, page)
79
+ unless search.blank?
80
+
81
+ search_cond_sql = []
82
+ search_cond_sql << 'LOWER(asset_file_name) LIKE (:term)'
83
+ search_cond_sql << 'LOWER(title) LIKE (:term)'
84
+ search_cond_sql << 'LOWER(caption) LIKE (:term)'
85
+
86
+ cond_sql = search_cond_sql.join(" or ")
87
+
88
+ @conditions = [cond_sql, {:term => "%#{search.downcase}%" }]
89
+ else
90
+ @conditions = []
91
+ end
92
+
93
+ options = { :conditions => @conditions,
94
+ :order => 'created_at DESC',
95
+ :page => page,
96
+ :per_page => 10 }
97
+
98
+ @file_types = filter.blank? ? [] : filter.keys
99
+ if not @file_types.empty?
100
+ options[:total_entries] = count_by_conditions
101
+ Asset.paginate_by_content_types(@file_types, :all, options )
102
+ else
103
+ Asset.paginate(:all, options)
104
+ end
105
+ end
106
+
107
+ def find_all_by_content_types(types, *args)
108
+ with_content_types(types) { find *args }
109
+ end
110
+
111
+ def with_content_types(types, &block)
112
+ with_scope(:find => { :conditions => types_to_conditions(types).join(' OR ') }, &block)
113
+ end
114
+
115
+ def count_by_conditions
116
+ type_conditions = @file_types.blank? ? nil : Asset.types_to_conditions(@file_types.dup).join(" OR ")
117
+ @count_by_conditions ||= @conditions.empty? ? Asset.count(:all, :conditions => type_conditions) : Asset.count(:all, :conditions => @conditions)
118
+ end
119
+
120
+ def types_to_conditions(types)
121
+ types.collect! { |t| '(' + send("#{t}_condition") + ')' }
122
+ end
123
+
124
+ def thumbnail_sizes
125
+ if Radiant::Config.table_exists? && Radiant::Config["assets.additional_thumbnails"]
126
+ thumbnails = additional_thumbnails
127
+ else
128
+ thumbnails = {}
129
+ end
130
+ thumbnails[:icon] = ['42x42#', :png]
131
+ thumbnails[:thumbnail] = ['100x100>', :png]
132
+ thumbnails
133
+ end
134
+
135
+ def thumbnail_names
136
+ thumbnail_sizes.keys
137
+ end
138
+
139
+ # returns a descriptive list suitable for use as options in a select box
140
+
141
+ def thumbnail_options
142
+ asset_sizes = thumbnail_sizes.collect{|k,v|
143
+ size_id = k
144
+ size_description = "#{k}: "
145
+ size_description << (v.is_a?(Array) ? v.join(' as ') : v)
146
+ [size_description, size_id]
147
+ }.sort_by{|pair| pair.last.to_s}
148
+ asset_sizes.unshift ['Original (as uploaded)', 'original']
149
+ asset_sizes
150
+ end
151
+
152
+ # this is just a pointer that can be alias-chained in other extensions to add to or replace the list of thumbnail mechanisms
153
+ # its invocation is delayed with a lambda in has_attached_file so that it isn't called when the extension loads, but when an attachment initializes:
154
+ # that way we can be sure that all the related extensions have loaded and all the alias_chains are in place.
155
+
156
+ def thumbnail_definitions
157
+ thumbnail_sizes
158
+ end
159
+
160
+ private
161
+ def additional_thumbnails
162
+ Radiant::Config["assets.additional_thumbnails"].gsub(' ','').split(',').collect{|s| s.split('=')}.inject({}) {|ha, (k, v)| ha[k.to_sym] = v; ha}
163
+ end
164
+ end
165
+
166
+ # order_by 'title'
167
+
168
+ has_attached_file :asset,
169
+ :processors => lambda {|instance| instance.choose_processors }, # this allows us to set processors per file type, and to add more in other extensions
170
+ :styles => lambda { thumbnail_definitions }, # and this lets extensions add thumbnailers (and also usefully defers the call)
171
+ :whiny => false,
172
+ :storage => Radiant::Config["assets.storage"] == "s3" ? :s3 : :filesystem,
173
+ :s3_credentials => {
174
+ :access_key_id => Radiant::Config["assets.s3.key"],
175
+ :secret_access_key => Radiant::Config["assets.s3.secret"]
176
+ },
177
+ :bucket => Radiant::Config["assets.s3.bucket"],
178
+ :url => Radiant::Config["assets.url"] ? Radiant::Config["assets.url"] : "/:class/:id/:basename:no_original_style.:extension",
179
+ :path => Radiant::Config["assets.path"] ? Radiant::Config["assets.path"] : ":rails_root/public/:class/:id/:basename:no_original_style.:extension"
180
+
181
+ has_many :page_attachments, :dependent => :destroy
182
+ has_many :pages, :through => :page_attachments
183
+
184
+ belongs_to :created_by, :class_name => 'User'
185
+ belongs_to :updated_by, :class_name => 'User'
186
+
187
+ validates_attachment_presence :asset
188
+ validates_attachment_content_type :asset,
189
+ :content_type => Radiant::Config["assets.content_types"].gsub(' ','').split(',') if Radiant::Config.table_exists? && Radiant::Config["assets.content_types"] && Radiant::Config["assets.skip_filetype_validation"] == nil
190
+ validates_attachment_size :asset,
191
+ :less_than => Radiant::Config["assets.max_asset_size"].to_i.megabytes if Radiant::Config.table_exists? && Radiant::Config["assets.max_asset_size"]
192
+
193
+ before_save :assign_title
194
+
195
+ register_type :image, %w[image/png image/x-png image/jpeg image/pjpeg image/jpg image/gif]
196
+ register_type :video, %w[video/mpeg video/mp4 video/ogg video/quicktime video/x-ms-wmv video/x-flv]
197
+ register_type :audio, %w[audio/mpeg audio/mpg audio/ogg application/ogg audio/x-ms-wma audio/vnd.rn-realaudio audio/x-wav]
198
+ register_type :swf, %w[application/x-shockwave-flash]
199
+ register_type :pdf, %w[application/pdf]
200
+
201
+ # alias for backwards-compatibility: movie can be video or swf
202
+ register_type :movie, Mime::SWF.all_types + Mime::VIDEO.all_types
203
+
204
+ def thumbnail(size='original')
205
+ return asset.url if size == 'original'
206
+ case
207
+ when self.pdf? : "/images/assets/pdf_#{size.to_s}.png"
208
+ when self.movie? : "/images/assets/movie_#{size.to_s}.png"
209
+ when self.video? : "/images/assets/movie_#{size.to_s}.png"
210
+ when self.swf? : "/images/assets/movie_#{size.to_s}.png" #TODO: special icon for swf-files
211
+ when self.audio? : "/images/assets/audio_#{size.to_s}.png"
212
+ when self.other? : "/images/assets/doc_#{size.to_s}.png"
213
+ else
214
+ self.asset.url(size.to_sym)
215
+ end
216
+ end
217
+
218
+ def generate_style(name, args={})
219
+ size = args[:size]
220
+ format = args[:format] || :jpg
221
+ asset = self.asset
222
+ unless asset.exists?(name.to_sym)
223
+ self.asset.styles[name.to_sym] = { :geometry => size, :format => format, :whiny => true, :convert_options => "", :processors => [:thumbnail] }
224
+ self.asset.reprocess!
225
+ end
226
+ end
227
+
228
+ # this has been added to support other extensions that want to add processors
229
+ # eg paperclipped_gps uses gpsbabel in the same way as paperclipped uses imagemagick
230
+ # I also mean to add a video thumbnailer using ffmpeg
231
+
232
+ def choose_processors
233
+ [:thumbnail]
234
+ end
235
+
236
+ def basename
237
+ File.basename(asset_file_name, ".*") if asset_file_name
238
+ end
239
+
240
+ def extension
241
+ asset_file_name.split('.').last.downcase if asset_file_name
242
+ end
243
+
244
+ def dimensions(size='original')
245
+ @dimensions ||= {}
246
+ @dimensions[size] ||= supported_by_image_spec? && begin
247
+ image_spec = ImageSpec.new(filesystem_path(size))
248
+ [image_spec.width, image_spec.height]
249
+ rescue
250
+ [0, 0]
251
+ end
252
+ end
253
+
254
+ def dimensions_known?
255
+ defined?(ImageSpec) && supported_by_image_spec?
256
+ end
257
+
258
+ def width(size='original')
259
+ supported_by_image_spec? && self.dimensions(size)[0]
260
+ end
261
+
262
+ def height(size='original')
263
+ supported_by_image_spec? && self.dimensions(size)[1]
264
+ end
265
+
266
+ private
267
+ def filesystem_path(size='original')
268
+ "#{RAILS_ROOT}/public#{thumbnail(size)}"
269
+ end
270
+
271
+ def supported_by_image_spec?
272
+ image? || swf?
273
+ end
274
+
275
+ def assign_title
276
+ self.title = basename if title.blank?
277
+ end
278
+
279
+ end
@@ -0,0 +1,101 @@
1
+ module AssetPageTags
2
+ include Radiant::Taggable
3
+ include AssetTags
4
+
5
+ desc %{
6
+ Cycles through all assets attached to the current page.
7
+ This tag does not require the title atttribute, nor do any of its children.
8
+ Use the @limit@ and @offset@ attribute to render a specific number of assets.
9
+ Use @by@ and @order@ attributes to control the order of assets.
10
+ Use @extensions@ attribute to specify which assets to be rendered.
11
+
12
+ *Usage:*
13
+ <pre><code><r:assets:each [limit=0] [offset=0] [order="asc|desc"] [by="position|title|..."] [extensions="png|pdf|doc"]>...</r:assets:each></code></pre>
14
+ }
15
+ tag 'assets:each' do |tag|
16
+ options = tag.attr.dup
17
+ result = []
18
+ assets = tag.locals.page.assets.find(:all, assets_find_options(tag))
19
+ tag.locals.assets = assets
20
+ assets.each do |asset|
21
+ tag.locals.asset = asset
22
+ result << tag.expand
23
+ end
24
+ result
25
+ end
26
+
27
+ desc %{
28
+ References the first asset attached to the current page.
29
+
30
+ *Usage:*
31
+ <pre><code><r:assets:first>...</r:assets:first></code></pre>
32
+ }
33
+ tag 'assets:first' do |tag|
34
+ attachments = tag.locals.page.page_attachments
35
+ if first = attachments.first
36
+ tag.locals.asset = first.asset
37
+ tag.expand
38
+ end
39
+ end
40
+
41
+ desc %{
42
+ Renders the contained elements only if the current asset is the first
43
+ asset attached to the current page.
44
+
45
+ *Usage:*
46
+ <pre><code><r:if_first>...</r:if_first></code></pre>
47
+ }
48
+ tag 'assets:if_first' do |tag|
49
+ attachments = tag.locals.assets
50
+ asset = tag.locals.asset
51
+ if asset == attachments.first
52
+ tag.expand
53
+ end
54
+ end
55
+
56
+
57
+ desc %{
58
+ Renders the contained elements only if the current asset is not the first
59
+ asset attached to the current page.
60
+
61
+ *Usage:*
62
+ <pre><code><r:unless_first>...</r:unless_first></code></pre>
63
+ }
64
+ tag 'assets:unless_first' do |tag|
65
+ attachments = tag.locals.assets
66
+ asset = tag.locals.asset
67
+ if asset != attachments.first
68
+ tag.expand
69
+ end
70
+ end
71
+
72
+ desc %{
73
+ Renders the contained elements only if the current contextual page has one or
74
+ more assets. The @min_count@ attribute specifies the minimum number of required
75
+ assets. You can also filter by extensions with the @extensions@ attribute.
76
+
77
+ *Usage:*
78
+ <pre><code><r:if_assets [min_count="n"] [extensions="pdf|jpg"]>...</r:if_assets></code></pre>
79
+ }
80
+ tag 'if_assets' do |tag|
81
+ count = tag.attr['min_count'] && tag.attr['min_count'].to_i || 1
82
+ assets = tag.locals.page.assets.count(:conditions => assets_find_options(tag)[:conditions])
83
+ tag.expand if assets >= count
84
+ end
85
+
86
+ desc %{
87
+ The opposite of @<r:if_assets/>@.
88
+ }
89
+ tag 'unless_assets' do |tag|
90
+ count = tag.attr['min_count'] && tag.attr['min_count'].to_i || 1
91
+ assets = tag.locals.page.assets.count(:conditions => assets_find_options(tag)[:conditions])
92
+ tag.expand unless assets >= count
93
+ end
94
+
95
+ # Resets the page Url and title within the asset tag
96
+ [:title, :url].each do |method|
97
+ tag "assets:page:#{method.to_s}" do |tag|
98
+ tag.locals.page.send(method)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,272 @@
1
+ module AssetTags
2
+ include Radiant::Taggable
3
+
4
+ class TagError < StandardError; end
5
+
6
+ desc %{
7
+ The namespace for referencing images and assets. You may specify the @title@
8
+ attribute on this tag for all contained tags to refer to that asset.
9
+
10
+ *Usage:*
11
+ <pre><code><r:assets [title="asset_title"]>...</r:assets></code></pre>
12
+ }
13
+ tag 'assets' do |tag|
14
+ tag.locals.asset = Asset.find_by_title(tag.attr['title']) || Asset.find(tag.attr['id']) unless tag.attr.empty?
15
+ tag.expand
16
+ end
17
+
18
+ desc %{
19
+ Renders the value for a top padding for the image. Put the image in a
20
+ container with specified height and using this tag you can vertically
21
+ align the image within it's container.
22
+
23
+ *Usage*:
24
+ <pre><code><r:assets:top_padding container = "140" [size="icon"]/></code></pre>
25
+
26
+ *Working Example*:
27
+ <pre><code>
28
+ <ul>
29
+ <r:assets:each>
30
+ <li style="height:140px">
31
+ <img style="padding-top:<r:top_padding size='category' container='140' />px"
32
+ src="<r:url />" alt="<r:title />" />
33
+ </li>
34
+ </r:assets:each>
35
+ </ul>
36
+ </code></pre>
37
+ }
38
+ tag 'assets:top_padding' do |tag|
39
+ asset, options = asset_and_options(tag)
40
+ raise TagError, 'Asset is not an image' unless asset.image?
41
+ raise TagError, "'container' attribute required" unless options['container']
42
+ size = options['size'] ? options.delete('size') : 'icon'
43
+ container = options.delete('container')
44
+ img_height = asset.height(size)
45
+ (container.to_i - img_height.to_i)/2
46
+ end
47
+
48
+ ['height','width'].each do |dimension|
49
+ desc %{
50
+ Renders the #{dimension} of the asset.
51
+ }
52
+ tag "assets:#{dimension}" do |tag|
53
+ asset, options = asset_and_options(tag)
54
+ unless asset.dimensions_known?
55
+ raise TagError, "Can't determine #{dimension} for this Asset. It may not be a supported type."
56
+ end
57
+ size = options['size'] ? options.delete('size') : 'original'
58
+ asset.send(dimension, size)
59
+ end
60
+ end
61
+
62
+ desc %{
63
+ Renders the containing elements only if the asset's content type matches
64
+ the regular expression given in the @matches@ attribute. If the
65
+ @ignore_case@ attribute is set to false, the match is case sensitive. By
66
+ default, @ignore_case@ is set to true.
67
+
68
+ The @title@ attribute is required on the parent tag unless this tag is used in @assets:each@.
69
+
70
+ *Usage:*
71
+ <pre><code><r:assets:each><r:if_content_type matches="regexp" [ignore_case=true|false"]>...</r:if_content_type></r:assets:each></code></pre>
72
+ }
73
+ tag 'assets:if_content_type' do |tag|
74
+ options = tag.attr.dup
75
+ # XXX build_regexp_for comes from StandardTags
76
+ # XXX its cool if I use it, right?
77
+ regexp = build_regexp_for(tag,options)
78
+ asset_content_type = tag.locals.asset.asset_content_type
79
+ tag.expand unless asset_content_type.match(regexp).nil?
80
+ end
81
+
82
+ #TODO: could use better docs for Asset#other? case explaining what types it covers
83
+ Asset.known_types.each do |known_type|
84
+ desc %{
85
+ Renders the contents only of the asset is of the type #{known_type}
86
+ }
87
+ tag "assets:if_#{known_type}" do |tag|
88
+ tag.expand if find_asset(tag, tag.attr.dup).send("#{known_type}?".to_sym)
89
+ end
90
+
91
+ desc %{
92
+ Renders the contents only of the asset is not of the type #{known_type}
93
+ }
94
+ tag "assets:unless_#{known_type}" do |tag|
95
+ tag.expand unless find_asset(tag, tag.attr.dup).send("#{known_type}?".to_sym)
96
+ end
97
+ end
98
+
99
+ [:title, :caption, :asset_file_name, :asset_content_type, :asset_file_size, :id].each do |method|
100
+ desc %{
101
+ Renders the @#{method.to_s}@ attribute of the asset
102
+ }
103
+ tag "assets:#{method.to_s}" do |tag|
104
+ asset, options = asset_and_options(tag)
105
+ asset.send(method) rescue nil
106
+ end
107
+ end
108
+
109
+ tag 'assets:filename' do |tag|
110
+ asset, options = asset_and_options(tag)
111
+ asset.asset_file_name rescue nil
112
+ end
113
+
114
+ desc %{
115
+ Renders an image tag for the asset.
116
+
117
+ Using the optional @size@ attribute, different sizes can be display.
118
+ “thumbnail” and “icon” sizes are built in, but custom ones can be set
119
+ using by changing assets.addition_thumbnails in the Radiant::Config
120
+ settings.
121
+
122
+ *Usage:*
123
+ <pre><code><r:assets:image [title="asset_title"] [size="icon|thumbnail"]></code></pre>
124
+ }
125
+ tag 'assets:image' do |tag|
126
+ asset, options = asset_and_options(tag)
127
+ raise TagError, 'Asset is not an image' unless asset.image?
128
+ size = options['size'] ? options.delete('size') : 'original'
129
+ geometry = options['geometry'] ? options.delete('geometry') : nil
130
+ #This is very experimental and will generate new sizes on the fly
131
+ asset.generate_style(size, { :size => geometry }) if geometry
132
+
133
+ alt = " alt='#{asset.title}'" unless tag.attr['alt'] rescue nil
134
+ attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
135
+ attributes << alt unless alt.nil?
136
+ url = asset.thumbnail(size)
137
+ %{<img src="#{url}" #{attributes unless attributes.empty?} />} rescue nil
138
+ end
139
+
140
+ desc %{
141
+ Embeds a flash-movie in a cross-browser-compatible fashion using only HTML
142
+ If no width and height attributes are given it will use the intrinsic
143
+ dimensions of the swf file
144
+
145
+ *Usage:*
146
+ <pre><code><r:assets:flash [title="asset_title"] [width="100"] [height="100"]>Fallback content</flash></code></pre>
147
+
148
+ *Example with text fallback:*
149
+ <pre><code><r:assets:flash title="flash_movie">
150
+ Sorry, you need to have flash installed, <a href="http://adobe.com/flash">get it here</a>
151
+ </flash></code></pre>
152
+
153
+ *Example with image fallback and explicit dimensions:*
154
+ <pre><code><r:assets:flash title="flash_movie" width="300" height="200">
155
+ <r:assets:image title="flash_screenshot" />
156
+ </flash></code></pre>
157
+ }
158
+ tag 'assets:flash' do |tag|
159
+ asset, options = asset_and_options(tag)
160
+ raise TagError, 'Must be flash' unless asset.swf?
161
+ url = asset.thumbnail('original')
162
+ dimensions = [(tag.attr['width'] || asset.width),(tag.attr['height'] || asset.height)]
163
+ swf_embed_markup url, dimensions, tag.expand
164
+ end
165
+
166
+ tag 'assets:thumbnail' do |tag|
167
+ asset, options = asset_and_options(tag)
168
+ asset.generate_thumbnail('test', ['24x24#',nil])
169
+ asset.save
170
+ end
171
+
172
+ desc %{
173
+ Renders the url for the asset. If the asset is an image, the <code>size</code> attribute can be used to
174
+ generate the url for that size.
175
+
176
+ *Usage:*
177
+ <pre><code><r:url [title="asset_title"] [size="icon|thumbnail"]></code></pre>
178
+ }
179
+ tag 'assets:url' do |tag|
180
+ asset, options = asset_and_options(tag)
181
+ size = options['size'] ? options.delete('size') : 'original'
182
+ asset.thumbnail(size) rescue nil
183
+ end
184
+
185
+ desc %{
186
+ Renders a link to the asset. If the asset is an image, the <code>size</code> attribute can be used to
187
+ generate a link to that size.
188
+
189
+ *Usage:*
190
+ <pre><code><r:assets:link [title="asset_title"] [size="icon|thumbnail"] /></code></pre>
191
+ }
192
+ tag 'assets:link' do |tag|
193
+ asset, options = asset_and_options(tag)
194
+ size = options['size'] ? options.delete('size') : 'original'
195
+ text = options['text'] || asset.title
196
+ anchor = options['anchor'] ? "##{options.delete('anchor')}" : ''
197
+ attributes = options.inject('') { |s, (k, v)| s << %{#{k.downcase}="#{v}" } }.strip
198
+ attributes = " #{attributes}" unless attributes.empty?
199
+ text = tag.double? ? tag.expand : text
200
+ url = asset.thumbnail(size)
201
+ %{<a href="#{url }#{anchor}"#{attributes}>#{text}</a>} rescue nil
202
+ end
203
+
204
+ desc %{
205
+ Renders the extension of the asset, as extracted from its filename.
206
+
207
+ *Usage*:
208
+ <pre><code>
209
+ <ul>
210
+ <r:assets:each extensions="doc|pdf">
211
+ <li class="<r:extension/>">
212
+ <r:link/>
213
+ </li>
214
+ </r:assets:each>
215
+ </ul>
216
+ </code></pre>
217
+ }
218
+ tag 'assets:extension' do |tag|
219
+ raise TagError, 'must be nested inside an assets or assets:each tag' unless tag.locals.asset
220
+ asset = tag.locals.asset
221
+ asset.asset_file_name[/\.(\w+)$/, 1]
222
+ end
223
+
224
+ private
225
+ def asset_and_options(tag)
226
+ options = tag.attr.dup
227
+ [find_asset(tag, options), options]
228
+ end
229
+
230
+ def find_asset(tag, options)
231
+ raise TagError, "'title' attribute required" unless title = options.delete('title') or id = options.delete('id') or tag.locals.asset
232
+ tag.locals.asset || Asset.find_by_title(title) || Asset.find(id)
233
+ end
234
+
235
+ def assets_find_options(tag)
236
+ attr = tag.attr.symbolize_keys
237
+ extensions = attr[:extensions] && attr[:extensions].split('|') || []
238
+ conditions = unless extensions.blank?
239
+ [ extensions.map { |ext| "assets.asset_file_name LIKE ?"}.join(' OR '),
240
+ *extensions.map { |ext| "%.#{ext}" } ]
241
+ else
242
+ nil
243
+ end
244
+
245
+ by = attr[:by] || 'page_attachments.position'
246
+ order = attr[:order] || 'asc'
247
+
248
+ options = {
249
+ :order => "#{by} #{order}",
250
+ :limit => attr[:limit] || nil,
251
+ :offset => attr[:offset] || nil,
252
+ :conditions => conditions
253
+ }
254
+ end
255
+
256
+ def swf_embed_markup(url, dimensions, fallback_content)
257
+ width, height = dimensions
258
+ %{<!--[if !IE]> -->
259
+ <object type="application/x-shockwave-flash" data="#{url}" width="#{width}" height="#{height}">
260
+ <!-- <![endif]-->
261
+ <!--[if IE]>
262
+ <object width="#{width}" height="#{height}"
263
+ classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
264
+ codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0">
265
+ <param name="movie" value="#{url}" />
266
+ <!-->
267
+ #{fallback_content}
268
+ </object>
269
+ <!-- <![endif]-->}
270
+ end
271
+ end
272
+