spree_core 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/app/assets/javascripts/admin/admin.js.erb +27 -2
  2. data/app/assets/javascripts/admin/taxon_autocomplete.js.erb +25 -23
  3. data/app/assets/stylesheets/store/screen.css.scss +13 -0
  4. data/app/controllers/spree/admin/images_controller.rb +0 -11
  5. data/app/controllers/spree/admin/option_types_controller.rb +3 -14
  6. data/app/controllers/spree/admin/option_values_controller.rb +11 -0
  7. data/app/controllers/spree/admin/product_properties_controller.rb +12 -0
  8. data/app/controllers/spree/admin/resource_controller.rb +10 -0
  9. data/app/controllers/spree/admin/taxons_controller.rb +1 -1
  10. data/app/controllers/spree/admin/variants_controller.rb +0 -11
  11. data/app/controllers/spree/products_controller.rb +9 -3
  12. data/app/helpers/spree/admin/base_helper.rb +2 -1
  13. data/app/helpers/spree/admin/products_helper.rb +1 -1
  14. data/app/helpers/spree/checkout_helper.rb +15 -0
  15. data/app/models/spree/calculator/per_item.rb +3 -1
  16. data/app/models/spree/image.rb +1 -1
  17. data/app/models/spree/line_item.rb +1 -1
  18. data/app/models/spree/order.rb +25 -4
  19. data/app/models/spree/order/checkout.rb +4 -1
  20. data/app/models/spree/payment.rb +1 -1
  21. data/app/models/spree/payment/processing.rb +1 -4
  22. data/app/models/spree/preferences/preferable_class_methods.rb +6 -9
  23. data/app/models/spree/preferences/store.rb +22 -11
  24. data/app/models/spree/product.rb +1 -1
  25. data/app/models/spree/shipment.rb +19 -15
  26. data/app/models/spree/taxonomy.rb +2 -0
  27. data/app/views/spree/admin/image_settings/edit.html.erb +7 -0
  28. data/app/views/spree/admin/orders/edit.html.erb +1 -1
  29. data/app/views/spree/admin/products/_form.html.erb +6 -1
  30. data/app/views/spree/admin/shared/_order_tabs.html.erb +1 -1
  31. data/app/views/spree/admin/shared/_translations.html.erb +1 -1
  32. data/app/views/spree/admin/shipping_methods/_form.html.erb +1 -1
  33. data/app/views/spree/admin/taxonomies/_list.html.erb +2 -1
  34. data/app/views/spree/checkout/_address.html.erb +47 -47
  35. data/app/views/spree/checkout/payment/_gateway.html.erb +2 -2
  36. data/app/views/spree/layouts/spree_application.html.erb +1 -1
  37. data/app/views/spree/orders/_line_item.html.erb +1 -1
  38. data/app/views/spree/products/_thumbnails.html.erb +17 -15
  39. data/app/views/spree/products/show.html.erb +1 -1
  40. data/app/views/spree/shared/_footer.html.erb +1 -1
  41. data/app/views/spree/shared/_google_analytics.html.erb +8 -8
  42. data/app/views/spree/shared/_header.html.erb +1 -1
  43. data/app/views/spree/shared/_main_nav_bar.html.erb +1 -1
  44. data/app/views/spree/shared/_nav_bar.html.erb +1 -1
  45. data/app/views/spree/shared/_order_details.html.erb +3 -1
  46. data/app/views/spree/shared/_sidebar.html.erb +1 -1
  47. data/app/views/spree/taxons/show.html.erb +1 -1
  48. data/config/locales/en.yml +6 -15
  49. data/config/routes.rb +7 -0
  50. data/db/migrate/20121124203911_add_position_to_taxonomies.rb +5 -0
  51. data/lib/spree/core/controller_helpers.rb +1 -0
  52. data/lib/spree/core/testing_support/factories/payment_factory.rb +0 -18
  53. data/lib/spree/core/validators/email.rb +1 -1
  54. data/lib/spree/core/version.rb +1 -1
  55. data/lib/tasks/taxon.rake +1 -1
  56. metadata +147 -54
@@ -29,6 +29,7 @@ module Spree
29
29
 
30
30
  def capture!
31
31
  return true if completed?
32
+ started_processing!
32
33
  protect_from_connection_error do
33
34
  check_environment
34
35
 
@@ -165,10 +166,6 @@ module Spree
165
166
  end
166
167
  end
167
168
 
168
- def record_log(response)
169
- log_entries.create({:details => response.to_yaml}, :without_protection => true)
170
- end
171
-
172
169
  def gateway_error(error)
173
170
  if error.is_a? ActiveMerchant::Billing::Response
174
171
  text = error.params['message'] || error.params['response_reason_text'] || error.message
@@ -10,16 +10,13 @@ module Spree::Preferences
10
10
  # cache_key will be nil for new objects, then if we check if there
11
11
  # is a pending preference before going to default
12
12
  define_method preference_getter_method(name) do
13
- if preference_cache_key(name) && preference_store.exist?(preference_cache_key(name))
14
- preference_store.get preference_cache_key(name)
13
+
14
+ # perference_cache_key will only be nil/false for new records
15
+ #
16
+ if preference_cache_key(name)
17
+ preference_store.get(preference_cache_key(name), default)
15
18
  else
16
- if get_pending_preference(name)
17
- get_pending_preference(name)
18
- elsif Spree::Preference.table_exists? && preference = Spree::Preference.find_by_name(name)
19
- preference.value
20
- else
21
- send self.class.preference_default_getter_method(name)
22
- end
19
+ get_pending_preference(name) || default
23
20
  end
24
21
  end
25
22
  alias_method prefers_getter_method(name), preference_getter_method(name)
@@ -27,25 +27,36 @@ module Spree::Preferences
27
27
  should_persist? && Spree::Preference.where(:key => key).exists?
28
28
  end
29
29
 
30
- def get(key)
30
+ def get(key,fallback=nil)
31
31
  # return the retrieved value, if it's in the cache
32
- if (val = @cache.read(key)).present?
32
+ # use unless nil? incase the value is actually boolean false
33
+ #
34
+ unless (val = @cache.read(key)).nil?
33
35
  return val
34
36
  end
35
37
 
36
- return nil unless should_persist?
38
+ if should_persist?
39
+ # If it's not in the cache, maybe it's in the database, but
40
+ # has been cleared from the cache
37
41
 
38
- # If it's not in the cache, maybe it's in the database, but
39
- # has been cleared from the cache
42
+ # does it exist in the database?
43
+ if Spree::Preference.table_exists? && preference = Spree::Preference.find_by_key(key)
44
+ # it does exist, so let's put it back into the cache
45
+ @cache.write(preference.key, preference.value)
40
46
 
41
- # does it exist in the database?
42
- if preference = Spree::Preference.find_by_key(key)
43
- # it does exist, so let's put it back into the cache
44
- @cache.write(preference.key, preference.value)
47
+ # and return the value
48
+ return preference.value
49
+ end
50
+ end
45
51
 
46
- # and return the value
47
- preference.value
52
+ unless fallback.nil?
53
+ # cache fallback so we won't hit the db above on
54
+ # subsequent queries for the same key
55
+ #
56
+ @cache.write(key, fallback)
48
57
  end
58
+
59
+ return fallback
49
60
  end
50
61
 
51
62
  def delete(key)
@@ -37,7 +37,7 @@ module Spree
37
37
  has_many :variants,
38
38
  :class_name => 'Spree::Variant',
39
39
  :conditions => { :is_master => false, :deleted_at => nil },
40
- :order => :position
40
+ :order => "#{::Spree::Variant.quoted_table_name}.position ASC"
41
41
 
42
42
  has_many :variants_including_master,
43
43
  :class_name => 'Spree::Variant',
@@ -28,7 +28,7 @@ module Spree
28
28
 
29
29
  scope :with_state, lambda { |s| where(:state => s) }
30
30
  scope :shipped, with_state('shipped')
31
- scope :ready, with_state('ready')
31
+ scope :ready, with_state('ready')
32
32
  scope :pending, with_state('pending')
33
33
 
34
34
  def to_param
@@ -47,12 +47,16 @@ module Spree
47
47
  def cost
48
48
  adjustment ? adjustment.amount : 0
49
49
  end
50
+
50
51
  alias_method :amount, :cost
51
52
 
52
53
  # shipment state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
53
54
  state_machine :initial => 'pending', :use_transactions => false do
54
55
  event :ready do
55
- transition :from => 'pending', :to => 'ready'
56
+ transition :from => 'pending', :to => 'ready', :if => lambda { |shipment|
57
+ # Fix for #2040
58
+ shipment.determine_state(shipment.order) == 'ready'
59
+ }
56
60
  end
57
61
  event :pend do
58
62
  transition :from => 'ready', :to => 'pending'
@@ -92,12 +96,24 @@ module Spree
92
96
  after_ship if new_state == 'shipped' and old_state != 'shipped'
93
97
  end
94
98
 
99
+ # Determines the appropriate +state+ according to the following logic:
100
+ #
101
+ # pending unless order is complete and +order.payment_state+ is +paid+
102
+ # shipped if already shipped (ie. does not change the state)
103
+ # ready all other cases
104
+ def determine_state(order)
105
+ return 'pending' unless order.can_ship?
106
+ return 'pending' if inventory_units.any? &:backordered?
107
+ return 'shipped' if state == 'shipped'
108
+ order.paid? ? 'ready' : 'pending'
109
+ end
110
+
95
111
  private
96
112
  def generate_shipment_number
97
113
  return number unless number.blank?
98
114
  record = true
99
115
  while record
100
- random = "H#{Array.new(11){rand(9)}.join}"
116
+ random = "H#{Array.new(11) { rand(9) }.join}"
101
117
  record = self.class.where(:number => random).first
102
118
  end
103
119
  self.number = random
@@ -113,18 +129,6 @@ module Spree
113
129
  end
114
130
  end
115
131
 
116
- # Determines the appropriate +state+ according to the following logic:
117
- #
118
- # pending unless order is complete and +order.payment_state+ is +paid+
119
- # shipped if already shipped (ie. does not change the state)
120
- # ready all other cases
121
- def determine_state(order)
122
- return 'pending' unless order.complete?
123
- return 'pending' if inventory_units.any? &:backordered?
124
- return 'shipped' if state == 'shipped'
125
- order.paid? ? 'ready' : 'pending'
126
- end
127
-
128
132
  # Determines whether or not inventory units should be associated with the shipment. This is always +false+ when
129
133
  # +Spree::Config[:track_inventory_levels]+ is set to +false.+ Otherwise its +true+ whenever the order is completed
130
134
  # (and not canceled.)
@@ -10,6 +10,8 @@ module Spree
10
10
 
11
11
  after_save :set_name
12
12
 
13
+ default_scope :order => "#{self.table_name}.position"
14
+
13
15
  private
14
16
  def set_name
15
17
  if root
@@ -13,6 +13,13 @@
13
13
  </label>
14
14
  </p>
15
15
 
16
+ <p data-hook="attachment_url">
17
+ <label>
18
+ <%= label_tag 'preferences[attachment_url]', t(:attachment_url) %>
19
+ <%= preference_field_tag 'preferences[attachment_url]', Spree::Config[:attachment_url], :type => :string, :size => 60 %>
20
+ </label>
21
+ </p>
22
+
16
23
  <p data-hook="attachment_default_url">
17
24
  <label>
18
25
  <%= label_tag 'preferences[attachment_default_url]', t(:attachment_default_url) %>
@@ -4,7 +4,7 @@
4
4
  <%= event_links %>
5
5
  </div>
6
6
  <div class="toolbar">
7
- <%= button_link_to t(:resend), resend_admin_order_url(@order), :method => :post, :icon => 'send-email' if @order.user %>
7
+ <%= button_link_to t(:resend), resend_admin_order_url(@order), :method => :post, :icon => 'send-email' %>
8
8
  </div>
9
9
  </div>
10
10
 
@@ -35,7 +35,12 @@
35
35
  <%= f.field_container :available_on do %>
36
36
  <%= f.label :available_on, t(:available_on) %><br />
37
37
  <%= f.error_message_on :available_on %>
38
- <%= f.text_field :available_on, :class => 'datepicker' %>
38
+ <% if @product.available_on? %>
39
+ <% available_on = l(@product.available_on, :format => t('spree.date_picker.format')) %>
40
+ <% else %>
41
+ <% available_on = nil %>
42
+ <% end %>
43
+ <%= f.text_field :available_on, :value => available_on, :class => 'datepicker' %>
39
44
  <% end %>
40
45
 
41
46
  <% unless @product.has_variants? %>
@@ -6,7 +6,7 @@
6
6
  <h5 id="order_status" data-hook><%= t(:status) %>: <%= t(@order.state, :scope => :order_state) %></h5>
7
7
  <h5 id="order_total" data-hook><%= t(:total) %>: <%= money @order.total %></h5>
8
8
  <% if @order.completed? %>
9
- <h5 id="shipment_status"><%= t(:shipment) %>: <%= t(@order.shipment_state, :scope => :shipment_state, :default => [:missing, "none"]) %></h5>
9
+ <h5 id="shipment_status"><%= t(:shipment) %>: <%= t(@order.shipment_state, :scope => :shipment_states, :default => [:missing, "none"]) %></h5>
10
10
  <h5 id="payment_status"><%= t(:payment) %>: <%= t(@order.payment_state, :scope => :payment_states, :default => [:missing, "none"]) %></h5>
11
11
  <h5 id="date_completed" style="width:100%; float:none;" data-hook><%= t(:date_completed) %>: <%= I18n.l(@order.completed? ? @order.completed_at : @order.created_at) %></h5>
12
12
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  Spree.translations = <%==
3
- { :date_picker => I18n.t(:format,
3
+ { :date_picker => I18n.t(:js_format,
4
4
  :scope => 'spree.date_picker',
5
5
  :default => 'yy/mm/dd'),
6
6
  :abbr_day_names => I18n.t(:abbr_day_names, :scope => :date),
@@ -18,7 +18,7 @@
18
18
  <% end %>
19
19
  </div>
20
20
 
21
- <div data-hook"admin_shipping_method_form_availability_fields">
21
+ <div data-hook="admin_shipping_method_form_availability_fields">
22
22
  <fieldset class="categories">
23
23
  <legend><%= t(:availability) %></legend>
24
24
  <%= f.field_container :shipping_category do %>
@@ -1,4 +1,4 @@
1
- <table class="index" id='listing_taxonomies' data-hook>
1
+ <table class="index sortable" id='listing_taxonomies' data-hook data-sortable-link="<%= update_positions_admin_taxonomies_url %>">
2
2
  <tr data-hook="taxonomies_header">
3
3
  <th><%= t(:name) %></th>
4
4
  <th></th>
@@ -6,6 +6,7 @@
6
6
  <% @taxonomies.each do |taxonomy| %>
7
7
  <tr id="<%= spree_dom_id taxonomy %>" data-hook="taxonomies_row">
8
8
  <td>
9
+ <span class="handle"></span>
9
10
  <%= taxonomy.name %>
10
11
  </td>
11
12
  <td class="actions">
@@ -38,29 +38,30 @@
38
38
  </span>
39
39
  </p>
40
40
 
41
- <% if Spree::Config[:address_requires_state] %>
42
- <p class="field" id="bstate">
43
- <% have_states = !@order.bill_address.country.states.empty? %>
44
- <%= bill_form.label :state, t(:state) %><span class="required">*</span><br />
45
- <% state_elements = [
46
- bill_form.collection_select(:state_id, @order.bill_address.country.states,
47
- :id, :name,
48
- {:include_blank => true},
49
- {:class => have_states ? 'required' : 'hidden',
50
- :disabled => !have_states}) +
51
- bill_form.text_field(:state_name,
52
- :class => !have_states ? 'required' : 'hidden',
53
- :disabled => have_states)
54
- ].join.gsub('"', "'").gsub("\n", "")
55
- %>
56
- <%= javascript_tag do -%>
57
- document.write("<%== state_elements %>");
58
- <% end -%>
59
- </p>
60
- <noscript>
61
- <%= bill_form.text_field :state_name, :class => 'required' %>
62
- </noscript>
63
- <% end %>
41
+ <p class="field" id="bstate">
42
+ <% have_states = !@order.bill_address.country.states.empty? %>
43
+
44
+ <%= bill_form.label :state, t(:state) %><%= state_required_label %><br />
45
+
46
+ <% state_elements = [
47
+ bill_form.collection_select(:state_id, @order.bill_address.country.states,
48
+ :id, :name,
49
+ {:include_blank => true},
50
+ {:class => have_states ? state_required_class : 'hidden',
51
+ :disabled => !have_states}) +
52
+ bill_form.text_field(:state_name,
53
+ :class => !have_states ? state_required_class : 'hidden',
54
+ :disabled => have_states)
55
+ ].join.gsub('"', "'").gsub("\n", "")
56
+ %>
57
+ <%= javascript_tag do -%>
58
+ document.write("<%== state_elements %>");
59
+ <% end -%>
60
+ </p>
61
+
62
+ <noscript>
63
+ <%= bill_form.text_field :state_name, :class => state_required_class %>
64
+ </noscript>
64
65
 
65
66
  <p class="field" id="bzipcode">
66
67
  <%= bill_form.label :zipcode, t(:zip) %><span class="required">*</span><br />
@@ -86,7 +87,7 @@
86
87
  <%= form.fields_for :ship_address do |ship_form| %>
87
88
  <legend><%= t(:shipping_address) %></legend>
88
89
  <p class="field checkbox" data-hook="use_billing">
89
- <%= check_box_tag 'order[use_billing]', '1', (!(@order.bill_address.empty? && @order.ship_address.empty?) && @order.bill_address.same_as?(@order.ship_address)) %>
90
+ <%= check_box_tag 'order[use_billing]', '1', (!(@order.bill_address.empty? && @order.ship_address.empty?) && @order.bill_address.same_as?(@order.ship_address)) %>
90
91
  <%= label_tag :order_use_billing, t(:use_billing_address), :id => 'use_billing' %>
91
92
  </p>
92
93
  <div class="inner" data-hook="shipping_inner">
@@ -125,29 +126,28 @@
125
126
  </span>
126
127
  </p>
127
128
 
128
- <% if Spree::Config[:address_requires_state] %>
129
- <p class="field" id="sstate">
130
- <% have_states = !@order.ship_address.country.states.empty? %>
131
- <%= ship_form.label :state, t(:state) %><span class="required">*</span><br />
132
- <% state_elements = [
133
- ship_form.collection_select(:state_id, @order.ship_address.country.states,
134
- :id, :name,
135
- {:include_blank => true},
136
- {:class => have_states ? 'required' : 'hidden',
137
- :disabled => !have_states}) +
138
- ship_form.text_field(:state_name,
139
- :class => !have_states ? 'required' : 'hidden',
140
- :disabled => have_states)
141
- ].join.gsub('"', "'").gsub("\n", "")
142
- %>
143
- <%= javascript_tag do -%>
144
- document.write("<%== state_elements %>");
145
- <% end %>
146
- </p>
147
- <noscript>
148
- <%= ship_form.text_field :state_name, :class => 'required' %>
149
- </noscript>
150
- <% end %>
129
+ <p class="field" id="sstate">
130
+ <% have_states = !@order.ship_address.country.states.empty? %>
131
+ <%= ship_form.label :state, t(:state) %><%= state_required_label %><br />
132
+ <% state_elements = [
133
+ ship_form.collection_select(:state_id, @order.ship_address.country.states,
134
+ :id, :name,
135
+ {:include_blank => true},
136
+ {:class => have_states ? state_required_class : 'hidden',
137
+ :disabled => !have_states}) +
138
+ ship_form.text_field(:state_name,
139
+ :class => !have_states ? state_required_class : 'hidden',
140
+ :disabled => have_states)
141
+ ].join.gsub('"', "'").gsub("\n", "")
142
+ %>
143
+ <%= javascript_tag do -%>
144
+ document.write("<%== state_elements %>");
145
+ <% end %>
146
+ </p>
147
+
148
+ <noscript>
149
+ <%= ship_form.text_field :state_name, :class => state_required_class %>
150
+ </noscript>
151
151
 
152
152
  <p class="field" id="szipcode">
153
153
  <%= ship_form.label :zipcode, t(:zip) %><span class="required">*</span><br />
@@ -15,8 +15,8 @@
15
15
  </p>
16
16
  <p class="field" data-hook="card_expiration">
17
17
  <%= label_tag nil, t(:expiration) %><br />
18
- <%= select_month(Date.today, :prefix => param_prefix, :field_name => 'month', :use_month_numbers => true, :class => 'required') %>
19
- <%= select_year(Date.today, :prefix => param_prefix, :field_name => 'year', :start_year => Date.today.year, :end_year => Date.today.year + 15, :class => 'required') %>
18
+ <%= select_month(Date.today, { :prefix => param_prefix, :field_name => 'month', :use_month_numbers => true }, :class => 'required') %>
19
+ <%= select_year(Date.today, { :prefix => param_prefix, :field_name => 'year', :start_year => Date.today.year, :end_year => Date.today.year + 15 }, :class => 'required') %>
20
20
  <span class="required">*</span>
21
21
  </p>
22
22
  <p class="field" data-hook="card_code">
@@ -13,7 +13,7 @@
13
13
  <div id="wrapper" class="row" data-hook>
14
14
  <%= breadcrumbs(@taxon) %>
15
15
  <%= render :partial => 'spree/shared/sidebar' if content_for? :sidebar %>
16
- <div id="content" class="columns omega <%= !content_for?(:sidebar) ? "sixteen alpha" : "twelve" %>" data-hook>
16
+ <div id="content" class="columns <%= !content_for?(:sidebar) ? "sixteen" : "twelve" %>" data-hook>
17
17
  <%= flash_messages %>
18
18
  <%= yield %>
19
19
  </div>
@@ -11,7 +11,7 @@
11
11
  <%= variant.options_text %>
12
12
  <% if @order.insufficient_stock_lines.include? line_item %>
13
13
  <span class="out-of-stock">
14
- <%= variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock) %><br />
14
+ <%= variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock) %>&nbsp;&nbsp;<br />
15
15
  </span>
16
16
  <% end %>
17
17
  <%= line_item_description(variant) %>
@@ -1,19 +1,21 @@
1
- <!-- no need for thumnails unless there is more then one image -->
2
- <% if product.images.size > 1 || product.variant_images.size > 0 %>
1
+ <%# no need for thumbnails unless there is more than one image %>
2
+ <% if (@product.images + @product.variant_images).uniq.size > 1 %>
3
3
  <ul id="product-thumbnails" class="thumbnails inline" data-hook>
4
- <% product.images.each do |i| %>
5
- <li class="tmb-all" id="tmb-<%= i.id.to_s %>"><%= link_to image_tag(i.attachment.url(:mini)), i.attachment.url(:product) %></li>
4
+ <% @product.images.each do |i| %>
5
+ <li class='tmb-all' id='tmb-<%= i.id %>'>
6
+ <%= link_to(image_tag(i.attachment.url(:mini)), i.attachment.url(:product), :class => 'tmb-all', :id => "tmb-#{i.id}") %>
7
+ </li>
8
+ <% end %>
9
+
10
+ <% if @product.has_variants? %>
11
+ <% @variants.select(&:available?).each do |v| %>
12
+ <% v.images.each do |i| %>
13
+ <% next if @product.images.include?(i) %>
14
+ <li class='vtmb-<%= v.id %> vtmb' id='tmb-<%= i.id %>'>
15
+ <%= link_to(image_tag(i.attachment.url(:mini)), i.attachment.url(:product)) %>
16
+ </li>
17
+ <% end %>
18
+ <% end %>
6
19
  <% end %>
7
- <% if @product.has_variants?
8
- @variants.each do |v|
9
- if v.available?
10
- v.images.each do |i| %>
11
- <li class="vtmb-<%= v.id.to_s %> vtmb" id="tmb-<%= i.id.to_s %>"><%= link_to image_tag(i.attachment.url(:mini)), i.attachment.url(:product) %></li>
12
- <%
13
- end
14
- end
15
- end
16
- end
17
- %>
18
20
  </ul>
19
21
  <% end %>
@@ -9,7 +9,7 @@
9
9
  <%= render :partial => 'image' %>
10
10
  </div>
11
11
  <div id="thumbnails" data-hook>
12
- <%= render :partial => 'thumbnails', :locals => { :product => @product } %>
12
+ <%= render :partial => 'thumbnails' %>
13
13
  </div>
14
14
  </div>
15
15