liquid_cms 0.3.0.10 → 0.3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/CHANGELOG.rdoc +12 -0
  2. data/app/controllers/cms/assets_controller.rb +32 -10
  3. data/app/controllers/cms/components_controller.rb +3 -3
  4. data/app/controllers/cms/main_controller.rb +2 -0
  5. data/app/controllers/cms/pages_controller.rb +2 -2
  6. data/app/helpers/cms/pages_helper.rb +0 -12
  7. data/app/liquid/cms_paperclip_extension.rb +1 -1
  8. data/app/liquid/drops/cms_asset_drop.rb +15 -0
  9. data/app/liquid/filters/cms_filters.rb +1 -1
  10. data/app/liquid/tags/asset_search_tag.rb +26 -0
  11. data/app/liquid/tags/cms/tag_common.rb +29 -0
  12. data/app/liquid/tags/data_tag.rb +59 -0
  13. data/app/models/cms/asset.rb +109 -2
  14. data/app/models/cms/component.rb +21 -17
  15. data/app/models/cms/page.rb +1 -3
  16. data/app/models/cms/tag.rb +21 -0
  17. data/app/models/cms/taggable.rb +78 -0
  18. data/app/models/cms/tagging.rb +6 -0
  19. data/app/views/cms/assets/_asset.html.erb +2 -3
  20. data/app/views/cms/assets/_form.html.erb +53 -2
  21. data/app/views/cms/assets/_list.html.erb +16 -1
  22. data/app/views/cms/assets/_meta_field.html.erb +15 -0
  23. data/app/views/cms/documentation/_cms_drops.html.erb +18 -0
  24. data/app/views/cms/documentation/_cms_tags.html.erb +13 -0
  25. data/app/views/cms/pages/_page.html.erb +1 -3
  26. data/app/views/cms/shared/_sidebar.html.erb +30 -28
  27. data/config/initializers/cms/liquid_cms.rb +8 -0
  28. data/config/locales/cms/en.yml +5 -0
  29. data/generators/liquid_cms/templates/migration_rev1.rb +38 -0
  30. data/lib/generators/liquid_cms/install_generator.rb +7 -9
  31. data/lib/generators/liquid_cms/templates/migration_rev1.rb +38 -0
  32. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/sidebar.css +25 -7
  33. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/simple_form.css +79 -4
  34. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/styles.css +0 -8
  35. data/lib/generators/liquid_cms/templates/public/cms/stylesheets/themes/dark.css +3 -0
  36. data/lib/liquid_cms/configuration.rb +12 -0
  37. data/lib/liquid_cms/version.rb +1 -1
  38. data/test/functional/assets_controller_test.rb +64 -16
  39. data/test/functional/components_controller_test.rb +90 -1
  40. data/test/functional/main_controller_test.rb +21 -0
  41. data/test/integration/pages_test.rb +123 -0
  42. data/test/integration/pages_test_no_context.rb +57 -0
  43. data/test/rails_app/db/migrate/20110329201435_create_liquid_cms_upgrade_rev1.rb +38 -0
  44. data/test/test_helper.rb +2 -0
  45. data/test/test_helpers/asset_helpers.rb +6 -4
  46. data/test/test_helpers/component_helpers.rb +37 -0
  47. data/test/test_helpers/file_helpers.rb +11 -0
  48. data/test/unit/asset_test.rb +114 -8
  49. data/test/unit/component_test.rb +64 -1
  50. data/test/unit/helpers/cms/common_helper_test.rb +4 -0
  51. metadata +37 -6
  52. data/app/views/cms/assets/destroy.js.rjs +0 -2
  53. data/test/rails_app/config/initializers/cms/vestal_versions.rb +0 -11
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,15 @@
1
+ == 0.3.1.0
2
+
3
+ * Enhancements
4
+ * Many improvements to assets including the addition of meta data fields, tags and the ability to specify custom image sizes.
5
+ * Assets can now be searched for using the liquid 'asset_data' tag. {% asset_data tag:'test' %}
6
+ See the built-in docs for more details on how to use "asset_data" and asset drops.
7
+ * Asset drops (accessible via the asset_data tag) give you access to the meta data and custom image information.
8
+ * limit and random ordering for collection retrieved from the asset_data tag.
9
+
10
+ * Fixes
11
+ * Optimize asset image file sizes with -strip
12
+
1
13
  == 0.3.0.10
2
14
 
3
15
  * Enhancements
@@ -6,11 +6,13 @@ class Cms::AssetsController < Cms::MainController
6
6
  end
7
7
 
8
8
  def new
9
- @asset = Cms::Asset.new
9
+ @asset = create_tagged_asset
10
10
  end
11
11
 
12
12
  def create
13
- @asset = @context.assets.build params[:cms_asset]
13
+ @asset = @context.assets.build
14
+ @asset.assign_ordered_attributes params[:cms_asset]
15
+
14
16
  if @asset.save
15
17
  flash[:notice] = t('assets.flash.created')
16
18
  redirect_to cms_root_path
@@ -25,14 +27,9 @@ class Cms::AssetsController < Cms::MainController
25
27
 
26
28
  def update
27
29
  @asset = @context.assets.find params[:id]
30
+ @asset.assign_ordered_attributes params[:cms_asset]
28
31
 
29
- success = if params[:file_content].present?
30
- @asset.write params[:file_content]
31
- else
32
- @asset.update_attributes params[:cms_asset]
33
- end
34
-
35
- if success
32
+ if @asset.save
36
33
  flash[:notice] = t('assets.flash.updated')
37
34
  redirect_to cms_root_path
38
35
  else
@@ -48,7 +45,32 @@ class Cms::AssetsController < Cms::MainController
48
45
 
49
46
  respond_to do |format|
50
47
  format.html { redirect_to cms_root_path }
51
- format.js
48
+ end
49
+ end
50
+
51
+ protected
52
+ # pre-populate an asset with tagged data and meta fields if a tag param is present
53
+ def create_tagged_asset
54
+ asset = Cms::Asset.new
55
+ curr_tag = (params[:tag] || '').strip
56
+
57
+ if curr_tag.present?
58
+ asset.tag_list = curr_tag
59
+
60
+ meta_asset = @context.assets.tagged_with(curr_tag).first :conditions => 'meta_data is not null'
61
+ if meta_asset
62
+ # remove meta values since we only want the key names
63
+ # new values will be provided for the new asset
64
+ asset.meta_data = meta_asset.meta_data.collect{|m| {:name => m[:name], :value => ''}}
65
+
66
+ # assign custom dims
67
+ asset.custom_width = meta_asset.custom_width
68
+ asset.custom_height = meta_asset.custom_height
69
+ end
70
+
71
+ asset
72
+ else
73
+ asset
52
74
  end
53
75
  end
54
76
  end
@@ -17,10 +17,10 @@ class Cms::ComponentsController < Cms::MainController
17
17
  @component = Cms::Component.new(@context, @path)
18
18
  @component.write params[:file_content]
19
19
 
20
- flash[:notice] = "Component file updated."
20
+ flash[:notice] = "The component file has been updated."
21
21
  redirect_to cms_root_path
22
22
  else
23
- flash[:error] = "Not an editable file."
23
+ flash[:error] = "Not an editable component."
24
24
  redirect_to :controller => 'cms/components', :action => 'edit', :url => @path
25
25
  end
26
26
  end
@@ -39,7 +39,7 @@ class Cms::ComponentsController < Cms::MainController
39
39
  component_zip = params[:zip_file]
40
40
 
41
41
  if component_zip.present? && File.extname(component_zip.original_filename) == '.zip'
42
- Cms::Component.new(@context).expand component_zip.path
42
+ Cms::Component.expand @context, component_zip.path
43
43
  flash[:notice] = 'The component has been uploaded.'
44
44
  else
45
45
  flash[:error] = 'The component file must be a zip archive.'
@@ -20,5 +20,7 @@ protected
20
20
  @cms_pages = @context.pages.ordered.all(:conditions => {:layout_page_id => nil})
21
21
  @cms_assets = @context.assets.ordered
22
22
  @cms_components = @context.components
23
+
24
+ @context_asset_tags = Cms::Asset.tags_for_context(@context)
23
25
  end
24
26
  end
@@ -10,7 +10,7 @@ class Cms::PagesController < Cms::MainController
10
10
  end
11
11
 
12
12
  def create
13
- @page = @context.pages.build({:updated_by => current_user}.merge(params[:cms_page] || {}))
13
+ @page = @context.pages.build(params[:cms_page])
14
14
  if @page.save
15
15
  flash[:notice] = t('pages.flash.created')
16
16
  redirect_to cms_root_path
@@ -25,7 +25,7 @@ class Cms::PagesController < Cms::MainController
25
25
 
26
26
  def update
27
27
  @page = @context.pages.find params[:id]
28
- if @page.update_attributes({:updated_by => current_user}.merge(params[:cms_page] || {}))
28
+ if @page.update_attributes(params[:cms_page])
29
29
  flash[:notice] = t('pages.flash.updated')
30
30
  redirect_to edit_cms_page_path(@page)
31
31
  else
@@ -3,18 +3,6 @@ module Cms::PagesHelper
3
3
  page.new_record? ? @context.pages.layouts : @context.pages.layouts.reject{|pg| pg == page}
4
4
  end
5
5
 
6
- def delete_page_link(page)
7
- options = {:method => :delete, :confirm => "Are you sure you want to delete the \"#{page}\" page?"}
8
-
9
- # use a remote link if there are no children since if we remove the current page list item, all the children items get removed (in the UI) as well
10
- # it's easier to just remove the item otherwise with a normal post and refresh the page
11
- if page.content_pages.empty?
12
- link_to cms_icon('delete.png', :title => 'Delete'), cms_page_path(page), {:remote => true, :indicator => dom_id(page, 'progress')}.merge(options)
13
- else
14
- link_to cms_icon('delete.png', :title => 'Delete'), cms_page_path(page), options
15
- end
16
- end
17
-
18
6
  # find the # of term matches in each page and sorts the pages by the match count (highest to lowest)
19
7
  # only shows the first SEARCH_LIMIT pages
20
8
  def page_match_order(pages, term)
@@ -7,7 +7,7 @@ module Paperclip
7
7
  all_styles = self.styles.keys + ['original']
8
8
  all_styles.each do |style|
9
9
  g = Paperclip::Geometry.from_file(self.path(style)) rescue nil
10
- h[style] = {'width' => g.width.to_i, 'height' => g.height.to_i, 'url' => self.url(style)} unless g.nil?
10
+ h[style.to_s] = {'width' => g.width.to_i, 'height' => g.height.to_i, 'url' => self.url(style)} unless g.nil?
11
11
  end
12
12
  end
13
13
  end
@@ -0,0 +1,15 @@
1
+ require 'cms_common_drop'
2
+
3
+ class Cms::AssetDrop < Cms::CommonDrop
4
+ def meta
5
+ {}.tap do |h|
6
+ (@record.meta_data || []).each do |mh|
7
+ h[mh[:name]] = mh[:value]
8
+ end
9
+ end
10
+ end
11
+
12
+ def image
13
+ @record.asset
14
+ end
15
+ end
@@ -114,7 +114,7 @@ module CmsFilters
114
114
 
115
115
  def component_url(path)
116
116
  context = @context.registers[:context]
117
- "/" + File.join(Cms::Component.base_path(context), Cms::Component.component_path(context, path))
117
+ "/" + Cms::Component.base_path(context).join(path).to_s
118
118
  end
119
119
  end
120
120
 
@@ -0,0 +1,26 @@
1
+ # Syntax
2
+ # {% asset_data tag'test', as:'sale_vehicles' %}
3
+
4
+ class AssetDataTag < Cms::DataTag
5
+ def get_data
6
+ raise Liquid::ArgumentError.new("The required 'tag' parameter is missing.") if options[:tag].blank?
7
+
8
+ collection = uses_random do |random_func|
9
+ assets = context_object.assets.tagged_with(options[:tag])
10
+
11
+ assets = if options[:random] == true
12
+ assets.order(random_func)
13
+ else
14
+ assets.order('cms_assets.created_at ASC')
15
+ end
16
+
17
+ assets = assets.limit(options[:limit]) if options[:limit]
18
+
19
+ assets.all
20
+ end
21
+
22
+ yield 'assets', collection
23
+ end
24
+ end
25
+
26
+ Liquid::Template.register_tag('asset_data', AssetDataTag)
@@ -0,0 +1,29 @@
1
+ module Cms::TagCommon
2
+ extend ActiveSupport::Memoizable
3
+
4
+ HyphenatedTagAttributes = /([\w-]+)\s*\:\s*(#{Liquid::QuotedFragment})/
5
+
6
+ def parse_options(context, markup)
7
+ begin
8
+ options = HashWithIndifferentAccess.new
9
+ return options if markup.blank?
10
+
11
+ markup.scan(HyphenatedTagAttributes) do |key, value|
12
+ options[key.to_sym] = context[value]
13
+ end
14
+
15
+ options
16
+ rescue ArgumentError => e
17
+ raise SyntaxError.new("Syntax Error in 'tag options' - Valid syntax: name:value")
18
+ end
19
+ end
20
+
21
+ def context_object(context)
22
+ context.registers[:context].object
23
+ end
24
+ memoize :context_object
25
+
26
+ def params(context)
27
+ context.registers[:controller].params.except(:controller, :action)
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ class Cms::DataTag < Liquid::Tag
2
+ module TagMethods
3
+ extend Cms::TagCommon
4
+ end
5
+
6
+ attr_reader :context
7
+ attr_reader :options
8
+
9
+ def initialize(tag_name, markup, tokens)
10
+ @markup = markup
11
+ super
12
+ end
13
+
14
+ def context_object
15
+ TagMethods.context_object(@context)
16
+ end
17
+
18
+ def params
19
+ TagMethods.params(@context)
20
+ end
21
+
22
+ def render(context)
23
+ @context = context
24
+ @options = TagMethods.parse_options(context, @markup)
25
+
26
+ get_data do |name, data|
27
+ context[@options[:as] || name] = data
28
+ end
29
+ ''
30
+ end
31
+
32
+ def uses_random(&block)
33
+ collection = []
34
+
35
+ # random sql func supported by postgresql and sqlite (perhaps others)
36
+ random_func = "random()"
37
+
38
+ begin
39
+ collection = yield random_func
40
+ rescue ActiveRecord::StatementInvalid => e
41
+ if options[:random] == true
42
+ # the random function used was invalid, so we'll try an alternative syntax for mysql (perhaps others)
43
+ mysql_func = "rand()"
44
+
45
+ if random_func != mysql_func
46
+ random_func = mysql_func
47
+ else
48
+ # set random to false and just use the default order since the alt didn't work either
49
+ options[:random] = false
50
+ end
51
+
52
+ # retry the query
53
+ retry
54
+ end
55
+ end
56
+
57
+ collection
58
+ end
59
+ end
@@ -1,22 +1,76 @@
1
1
  module Cms
2
2
  class Asset < ActiveRecord::Base
3
+ unloadable
4
+
3
5
  set_table_name 'cms_assets'
4
6
 
7
+ include Cms::Taggable
8
+ define_tag_association :assets
9
+
10
+ def self.tags_for_context(context)
11
+ common_options = {:order => 'name ASC'}
12
+ context.object ? Cms::Tag.all({:include => :assets, :conditions => {'cms_assets.context_id' => context.object.id}}.merge(common_options)) : Cms::Tag.all(common_options)
13
+ end
14
+
15
+ class Meta
16
+ attr_reader :name, :value, :errors
17
+
18
+ def initialize(data)
19
+ @name, @value = data[:name], data[:value]
20
+ @errors = ActiveModel::Errors.new(self)
21
+ end
22
+
23
+ def valid?
24
+ validate
25
+ errors.empty?
26
+ end
27
+
28
+ def validate
29
+ errors.clear
30
+ if @name.blank?
31
+ errors.add :name, 'must be set'
32
+ else
33
+ errors.add :name, "is an invalid format" if (@name =~ /^[a-z]+[a-z0-9_]*$/).nil?
34
+ end
35
+ end
36
+ end
37
+
38
+ serialize :meta_data
39
+
5
40
  has_attached_file :asset,
6
- :styles => { :tiny => '50x50>', :thumb => '100x100>' }, #:custom => Proc.new { |instance| "#{instance.photo_width}x#{instance.photo_height}>" } },
41
+ :styles => { :tiny => '50x50>', :thumb => '100x100>', :large => '200x200>', :custom => Proc.new{|instance| custom_dimensions(instance)} },
42
+ :convert_options => {:all => '-strip -quality 90'},
7
43
  :path => ":rails_root/public/cms/assets/:id/:style/:filename",
8
44
  :url => "/cms/assets/:id/:style/:filename"
9
45
 
10
46
  validates_attachment_presence :asset
47
+ validate :meta_data_check
11
48
 
12
49
  scope :ordered, :order => 'asset_file_name ASC'
13
50
 
51
+ after_save :reprocess_custom_dimensions
14
52
  before_post_process :process_check
15
53
 
16
54
  def to_s
17
55
  asset_file_name
18
56
  end
19
57
 
58
+ def assign_ordered_attributes(params)
59
+ # force the custom dimensions to be assigned first so that when the asset is assigned, the custom dims are present
60
+ # if the custom dims aren't set before the asset is assigned, the custom size won't be generated properly
61
+ # this can occur if the attribute hash is iterated with the asset coming before the dims
62
+ self.attributes = params.slice(:custom_height, :custom_width)
63
+ self.attributes = params
64
+ end
65
+
66
+ def self.context_tags(context)
67
+ if context
68
+ Tag.all :joins => 'inner join taggings on taggings.tag_id = tags.id inner join cms_assets on taggings.taggable_id = cms_assets.id', :conditions => {'cms_assets.context_id' => context.id}
69
+ else
70
+ Tag.all
71
+ end
72
+ end
73
+
20
74
  def image?
21
75
  !(asset_content_type =~ /^image.*/).blank?
22
76
  end
@@ -30,6 +84,40 @@ module Cms
30
84
  !(asset_content_type =~ /(javascript|css|xml|html)$/).blank?
31
85
  end
32
86
 
87
+ def file_content
88
+ read
89
+ end
90
+
91
+ def file_content=(content)
92
+ write content
93
+ end
94
+
95
+ def meta
96
+ return @_meta if @_meta.present?
97
+ @_meta = (meta_data || []).collect{|m| Meta.new(m)}
98
+ end
99
+
100
+ def meta=(data)
101
+ # reset the cached meta collection
102
+ @_meta = nil
103
+
104
+ # data ex:
105
+ # {"new_1301457489798"=>{"name"=>"test1", "value"=>"test1"}, "new_1301457493800"=>{"name"=>"test2", "value"=>"test2"}}
106
+ # converted to:
107
+ # [{"name"=>"test1", "value"=>"test1"}, {"name"=>"test2", "value"=>"test2"}]
108
+ # strip spaces of name and value
109
+ temp_data = data.to_a.sort{|a,b| a.first <=> b.first}.collect{|a| h = a[1]; {:name => h[:name].strip, :value => h[:value].strip} }
110
+ # remove any elements that have both name and value blank
111
+ temp_data = temp_data.reject{|d| d[:name].blank? && d[:value].blank?}
112
+
113
+ self.meta_data = temp_data
114
+ end
115
+
116
+ def to_liquid
117
+ Cms::AssetDrop.new(self)
118
+ end
119
+
120
+ protected
33
121
  def read
34
122
  return '' if !editable?
35
123
  asset.to_file(:original).read
@@ -46,9 +134,28 @@ module Cms
46
134
  end
47
135
  end
48
136
 
49
- protected
137
+ def meta_data_check
138
+ if !meta.find_all{|m| !m.valid?}.empty?
139
+ errors.add :meta_data, "is invalid"
140
+ end
141
+ end
142
+
143
+ def self.custom_dimensions(record)
144
+ if !record.custom_width.to_i.zero? && !record.custom_height.to_i.zero?
145
+ "#{record.custom_width}x#{record.custom_height}>"
146
+ else
147
+ ''
148
+ end
149
+ end
150
+
50
151
  def process_check
51
152
  image? && !icon?
52
153
  end
154
+
155
+ def reprocess_custom_dimensions
156
+ if custom_height_changed? || custom_width_changed?
157
+ asset.reprocess!
158
+ end
159
+ end
53
160
  end
54
161
  end