diff --git a/app/helpers/spree/base_helper.rb b/app/helpers/spree/base_helper.rb new file mode 100644 index 0000000000..cdd6ea23e1 --- /dev/null +++ b/app/helpers/spree/base_helper.rb @@ -0,0 +1,196 @@ +module Spree + module BaseHelper + + # Defined because Rails' current_page? helper is not working when Spree is mounted at root. + def current_spree_page?(url) + path = request.fullpath.gsub(/^\/\//, '/') + if url.is_a?(String) + return path == url + elsif url.is_a?(Hash) + return path == spree.url_for(url) + end + return false + end + + def link_to_cart(text = nil) + return "" if current_spree_page?(spree.cart_path) + + text = text ? h(text) : Spree.t('cart') + css_class = nil + + if current_order.nil? or current_order.item_count.zero? + text = "#{text}: (#{Spree.t('empty')})" + css_class = 'empty' + else + text = "#{text}: (#{current_order.item_count}) #{current_order.display_total.to_html}".html_safe + css_class = 'full' + end + + link_to text, spree.cart_path, :class => "cart-info #{css_class}" + end + + # human readable list of variant options + def variant_options(v, options={}) + v.options_text + end + + def meta_data_tags + object = instance_variable_get('@'+controller_name.singularize) + meta = {} + + if object.kind_of? ActiveRecord::Base + meta[:keywords] = object.meta_keywords if object[:meta_keywords].present? + meta[:description] = object.meta_description if object[:meta_description].present? + end + + if meta[:description].blank? && object.kind_of?(Spree::Product) + meta[:description] = strip_tags(truncate(object.description, length: 160, separator: ' ')) + end + + meta.reverse_merge!({ + keywords: Spree::Config[:default_meta_keywords], + description: Spree::Config[:default_meta_description] + }) + + meta.map do |name, content| + tag('meta', name: name, content: content) + end.join("\n") + end + + def body_class + @body_class ||= content_for?(:sidebar) ? 'two-col' : 'one-col' + @body_class + end + + def logo(image_path=Spree::Config[:logo]) + link_to image_tag(image_path), spree.root_path + end + + def flash_messages(opts = {}) + opts[:ignore_types] = [:commerce_tracking].concat(Array(opts[:ignore_types]) || []) + + flash.each do |msg_type, text| + unless opts[:ignore_types].include?(msg_type) + concat(content_tag :div, text, class: "flash #{msg_type}") + end + end + nil + end + + def breadcrumbs(taxon, separator=" » ") + return "" if current_page?("/") || taxon.nil? + separator = raw(separator) + crumbs = [content_tag(:li, link_to(Spree.t(:home), spree.root_path) + separator)] + if taxon + crumbs << content_tag(:li, link_to(Spree.t(:products), products_path) + separator) + crumbs << taxon.ancestors.collect { |ancestor| content_tag(:li, link_to(ancestor.name , seo_url(ancestor)) + separator) } unless taxon.ancestors.empty? + crumbs << content_tag(:li, content_tag(:span, link_to(taxon.name , seo_url(taxon)))) + else + crumbs << content_tag(:li, content_tag(:span, Spree.t(:products))) + end + crumb_list = content_tag(:ul, raw(crumbs.flatten.map{|li| li.mb_chars}.join), class: 'inline') + content_tag(:nav, crumb_list, id: 'breadcrumbs', class: 'sixteen columns') + end + + def taxons_tree(root_taxon, current_taxon, max_level = 1) + return '' if max_level < 1 || root_taxon.children.empty? + content_tag :ul, class: 'taxons-list' do + root_taxon.children.map do |taxon| + css_class = (current_taxon && current_taxon.self_and_ancestors.include?(taxon)) ? 'current' : nil + content_tag :li, class: css_class do + link_to(taxon.name, seo_url(taxon)) + + taxons_tree(taxon, current_taxon, max_level - 1) + end + end.join("\n").html_safe + end + end + + def available_countries + checkout_zone = Zone.find_by(name: Spree::Config[:checkout_zone]) + + if checkout_zone && checkout_zone.kind == 'country' + countries = checkout_zone.country_list + else + countries = Country.all + end + + countries.collect do |country| + country.name = Spree.t(country.iso, scope: 'country_names', default: country.name) + country + end.sort { |a, b| a.name <=> b.name } + end + + def seo_url(taxon) + return spree.nested_taxons_path(taxon.permalink) + end + + def gem_available?(name) + Gem::Specification.find_by_name(name) + rescue Gem::LoadError + false + rescue + Gem.available?(name) + end + + def display_price(product_or_variant) + product_or_variant.price_in(current_currency).display_price.to_html + end + + def pretty_time(time) + [I18n.l(time.to_date, format: :long), + time.strftime("%l:%M %p")].join(" ") + end + + def method_missing(method_name, *args, &block) + if image_style = image_style_from_method_name(method_name) + define_image_method(image_style) + self.send(method_name, *args) + else + super + end + end + + def link_to_tracking(shipment, options = {}) + return unless shipment.tracking + + if shipment.tracking_url + link_to(shipment.tracking, shipment.tracking_url, options) + else + content_tag(:span, shipment.tracking) + end + end + + private + # Returns style of image or nil + def image_style_from_method_name(method_name) + if style = method_name.to_s.sub(/_image$/, '') + possible_styles = Spree::Image.attachment_definitions[:attachment][:styles] + style if style.in? possible_styles.with_indifferent_access + end + end + + def create_product_image_tag(image, product, options, style) + options.reverse_merge! alt: image.alt.blank? ? product.name : image.alt + image_tag image.attachment.url(style), options + end + + def define_image_method(style) + self.class.send :define_method, "#{style}_image" do |product, *options| + options = options.first || {} + if product.images.empty? + if !product.is_a?(Spree::Variant) && !product.variant_images.empty? + create_product_image_tag(product.variant_images.first, product, options, style) + else + if product.is_a?(Variant) && !product.product.variant_images.empty? + create_product_image_tag(product.product.variant_images.first, product, options, style) + else + image_tag "noimage/#{style}.png", options + end + end + else + create_product_image_tag(product.images.first, product, options, style) + end + end + end + end +end diff --git a/app/models/spree/log_entry.rb b/app/models/spree/log_entry.rb new file mode 100644 index 0000000000..04a6d686c7 --- /dev/null +++ b/app/models/spree/log_entry.rb @@ -0,0 +1,16 @@ +module Spree + class LogEntry < ActiveRecord::Base + belongs_to :source, polymorphic: true + + # Fix for #1767 + # If a payment fails, we want to make sure we keep the record of it failing + after_rollback :save_anyway + + def save_anyway + log = Spree::LogEntry.new + log.source = source + log.details = details + log.save! + end + end +end diff --git a/spec/helpers/spree/base_helper_spec.rb b/spec/helpers/spree/base_helper_spec.rb new file mode 100644 index 0000000000..1b101e77f8 --- /dev/null +++ b/spec/helpers/spree/base_helper_spec.rb @@ -0,0 +1,143 @@ +require 'spec_helper' + +describe Spree::BaseHelper do + include Spree::BaseHelper + + context "available_countries" do + let(:country) { create(:country) } + + before do + 3.times { create(:country) } + end + + context "with no checkout zone defined" do + before do + Spree::Config[:checkout_zone] = nil + end + + it "return complete list of countries" do + available_countries.count.should == Spree::Country.count + end + end + + context "with a checkout zone defined" do + context "checkout zone is of type country" do + before do + @country_zone = create(:zone, :name => "CountryZone") + @country_zone.members.create(:zoneable => country) + Spree::Config[:checkout_zone] = @country_zone.name + end + + it "return only the countries defined by the checkout zone" do + available_countries.should == [country] + end + end + + context "checkout zone is of type state" do + before do + state_zone = create(:zone, :name => "StateZone") + state = create(:state, :country => country) + state_zone.members.create(:zoneable => state) + Spree::Config[:checkout_zone] = state_zone.name + end + + it "return complete list of countries" do + available_countries.count.should == Spree::Country.count + end + end + end + end + + # Regression test for #1436 + context "defining custom image helpers" do + let(:product) { mock_model(Spree::Product, :images => [], :variant_images => []) } + before do + Spree::Image.class_eval do + attachment_definitions[:attachment][:styles].merge!({:very_strange => '1x1'}) + end + end + + it "should not raise errors when style exists" do + expect { very_strange_image(product) }.not_to raise_error + end + + it "should raise NoMethodError when style is not exists" do + expect { another_strange_image(product) }.to raise_error(NoMethodError) + end + + end + + # Regression test for #2034 + context "flash_message" do + let(:flash) { {:notice => "ok", :foo => "foo", :bar => "bar"} } + + it "should output all flash content" do + flash_messages + html = Nokogiri::HTML(helper.output_buffer) + html.css(".notice").text.should == "ok" + html.css(".foo").text.should == "foo" + html.css(".bar").text.should == "bar" + end + + it "should output flash content except one key" do + flash_messages(:ignore_types => :bar) + html = Nokogiri::HTML(helper.output_buffer) + html.css(".notice").text.should == "ok" + html.css(".foo").text.should == "foo" + html.css(".bar").text.should be_empty + end + + it "should output flash content except some keys" do + flash_messages(:ignore_types => [:foo, :bar]) + html = Nokogiri::HTML(helper.output_buffer) + html.css(".notice").text.should == "ok" + html.css(".foo").text.should be_empty + html.css(".bar").text.should be_empty + helper.output_buffer.should == "