module Caboose PageBarGenerator.class_eval do def all_records return model_with_includes.where(where) end end class ProductsController < Caboose::ApplicationController # @route GET /admin/products/stubs def admin_stubs title = params[:title] ? params[:title].strip.downcase.split(' ') : nil render :json => [] and return if title.nil? || title.length == 0 where = ["site_id = ?"] vars = [@site.id] title.each do |str| where << 'lower(title) like ?' vars << "%#{str}%" end where = where.join(' and ') query = ["select id, title, option1, option2, option3 from store_products where #{where} order by title limit 20"] vars.each{ |v| query << v } rows = ActiveRecord::Base.connection.select_rows(ActiveRecord::Base.send(:sanitize_sql_array, query)) arr = rows.collect do |row| has_options = row[2] || row[3] || row[4] ? true : false variant_id = nil if !has_options v = Variant.where(:product_id => row[0].to_i, :status => 'Active').first variant_id = v.id if v end { :id => row[0], :title => row[1], :variant_id => variant_id } end render :json => arr end # @route GET /products/:id/info def info p = Product.find(params[:id]) render :json => { :product => p, :option1_values => p.option1_values_with_media(true), :option2_values => p.option2_values_with_media(true), :option3_values => p.option3_values_with_media(true) } end # @route GET /products # @route GET /products/:id # @route_constraints { :id => /.*/ } def index # If id exists, is an integer and a product exists with the specified id then get the product if params[:id] if params[:id] == 'sales' products = Caboose::Product.where(:site_id => @site.id, :status => Caboose::Product::STATUS_ACTIVE, :on_sale => true).all @sale_categories = {} products.each do |p| cat = 'Uncategorized' if p.categories.count > 0 cats = p.categories.last.ancestry.collect{ |a| a.name } cats.shift cat = cats.join(' > ') end @sale_categories[cat] = [] if @sale_categories[cat].nil? @sale_categories[cat] << p end add_ga_event('Products', 'View', 'Sales') render 'caboose/products/sales' and return elsif params[:id].to_i > 0 && Product.exists?(params[:id]) @product = Product.find(params[:id]) render 'caboose/products/not_available' and return if @product.status == 'Inactive' || @product.site_id != @site.id @category = @product.categories.first @review = Review.new @reviews = Review.where(:product_id => @product.id).limit(10).reorder("id DESC") || nil @logged_in_user = logged_in_user add_ga_event('Products', 'View', "Product #{@product.id}") render 'caboose/products/details' and return end end # Filter params from url url_without_params = request.fullpath.split('?').first # Find the category cat = Category.where(:site_id => @site.id, :url => url_without_params).first if cat.nil? cat = Category.where(:site_id => @site.id, :url => '/products').first cat = Category.create(:site_id => @site.id, :url => '/products') if cat.nil? end # Set category ID params['category_id'] = cat.id # If this is the top-most category, collect all it's immediate children IDs params['category_id'] = cat.children.collect { |child| child.id } if cat.id == 1 # Shove the original category ID into the first position if the param is an array params['category_id'].unshift(category.id) if params['category_id'].is_a?(Array) # Otherwise looking at a category or search parameters @pager = Caboose::Pager.new(params, { 'site_id' => @site.id, 'on_sale' => '', 'category_id' => '', 'vendor_id' => '', 'vendor_name' => '', 'vendor_status' => 'Active', 'status' => 'Active', 'variant_status' => 'Active', 'price_gte' => '', 'price_lte' => '', 'alternate_id' => '', 'search_like' => '', 'cm_category_id' => cat.id # This filters the CategoryMembership object that we'll be sorting on }, { 'model' => 'Caboose::Product', #'sort' => if params[:sort] then params[:sort] else 'store_products.sort_order' end, #'sort' => if params[:sort] then params[:sort] else 'store_category_memberships.sort_order' end, 'sort' => 'store_category_memberships.sort_order', 'base_url' => url_without_params, 'items_per_page' => 15, 'use_url_params' => false, 'abbreviations' => { 'search_like' => 'title_concat_store_products.alternate_id_concat_vendor_name_concat_category_name_like', }, 'includes' => { 'cm_category_id' => [ 'category_memberships' , 'category_id' ], 'category_id' => [ 'categories' , 'id' ], 'category_name' => [ 'categories' , 'name' ], 'vendor_id' => [ 'vendor' , 'id' ], 'vendor_name' => [ 'vendor' , 'name' ], 'vendor_status' => [ 'vendor' , 'status' ], 'price_gte' => [ 'variants' , 'price' ], 'price_lte' => [ 'variants' , 'price' ], 'variant_status' => [ 'variants' , 'status' ] } }) @sort_options = [ { :name => 'Default', :value => 'store_products.sort_order' }, { :name => 'Price (Low to High)', :value => 'store_variants.price ASC' }, { :name => 'Price (High to Low)', :value => 'store_variants.price DESC' }, { :name => 'Alphabetical (A-Z)', :value => 'store_products.title ASC' }, { :name => 'Alphabetical (Z-A)', :value => 'store_products.title DESC' }, ] SearchFilter.delete_all @filter = SearchFilter.find_from_url(request.fullpath, @pager, ['page']) @products = @pager.items @category = if @filter['category_id'] then Category.find(@filter['category_id'].to_i) else nil end @pager.set_item_count add_ga_event('Products', 'View', "Category #{cat.id}") end def show end #============================================================================= # Admin actions #============================================================================= # @route PUT /admin/products/update-vendor-status/:id def admin_update_vendor_status vendor = Vendor.find(params[:id]) vendor.status = params[:status] render :json => vendor.save end # @route GET /admin/products/alternate-ids def admin_alternate_ids return if !user_is_allowed('products', 'view') query = ["select P.id as product_id, V.id as variant_id, P.title, P.option1, V.option1 as option1_value, P.option2, V.option2 as option2_value, P.option3, V.option3 as option3_value, V.alternate_id from store_variants V left join store_products P on V.product_id = P.id where P.site_id = ? order by title, P.option1, V.option1", @site.id] rows = ActiveRecord::Base.connection.select_rows(ActiveRecord::Base.send(:sanitize_sql_array, query)) @rows = rows.collect{ |row| Caboose::StdClass.new({ :product_id => row[0], :variant_id => row[1], :title => row[2], :option1 => row[3], :option1_value => row[4], :option2 => row[5], :option2_value => row[6], :option3 => row[7], :option3_value => row[8], :alternate_id => row[9] })} render :layout => 'caboose/admin' end # @route GET /admin/products def admin_index return if !user_is_allowed('products', 'view') # Temporary patch for vendor name sorting; Fix this params[:sort] = 'store_vendors.name' if params[:sort] == 'vendor' @gen = Caboose::PageBarGenerator.new(params, { 'site_id' => @site.id, 'vendor_name' => '', 'search_like' => '', 'category_id' => '', 'category_name' => '', 'vendor_id' => '', 'vendor_status' => '', 'price_gte' => '', 'price_lte' => '', 'variant_status' => '', 'price' => params[:filters] && params[:filters][:missing_prices] ? 0 : '' }, { 'model' => 'Caboose::Product', 'sort' => 'title', 'desc' => false, 'base_url' => '/admin/products', 'items_per_page' => 25, 'use_url_params' => false, 'abbreviations' => { 'search_like' => 'store_products.title_concat_vendor_name_like' }, 'includes' => { 'category_id' => [ 'categories' , 'id' ], 'category_name' => [ 'categories' , 'name' ], 'vendor_id' => [ 'vendor' , 'id' ], 'vendor_name' => [ 'vendor' , 'name' ], 'vendor_status' => [ 'vendor' , 'status' ], 'price_gte' => [ 'variants' , 'price' ], 'price_lte' => [ 'variants' , 'price' ], 'price' => [ 'variants' , 'price' ], 'variant_status' => [ 'variants' , 'status' ] } }) # Make a copy of all the items; so it can be filtered more @all_products = @gen.all_records # Apply any extra filters if params[:filters] @all_products = @all_products.includes(:product_images).where('store_product_images.id IS NULL') if params[:filters][:missing_images] @all_products = @all_products.where('vendor_id IS NULL') if params[:filters][:no_vendor] end # Get the correct page of the results @products = @all_products.limit(@gen.limit).offset(@gen.offset) @category_options = Category.options(@site.id) render :layout => 'caboose/admin' end # @route GET /admin/products/json def admin_json return if !user_is_allowed('products', 'view') # Temporary patch for vendor name sorting; Fix this params[:sort] = 'store_vendors.name' if params[:sort] == 'vendor' pager = Caboose::PageBarGenerator.new(params, { 'site_id' => @site.id, 'vendor_name' => '', 'search_like' => '', 'category_id' => '', 'status' => 'Active', 'price' => params[:filters] && params[:filters][:missing_prices] ? 0 : '' }, { 'model' => 'Caboose::Product', 'sort' => 'title', 'desc' => false, 'base_url' => '/admin/products', 'items_per_page' => 25, 'use_url_params' => false, 'abbreviations' => { 'search_like' => 'store_products.title_concat_vendor_name_like' }, 'includes' => { 'category_id' => [ 'categories' , 'id' ], 'vendor_name' => [ 'vendor' , 'name' ], 'price' => [ 'variants' , 'price' ] } }) render :json => { :pager => pager, :models => pager.items } end # @route GET /admin/products/:id/json def admin_json_single p = Product.find(params[:id]) render :json => p end # @route GET /admin/products/:id # @route GET /admin/products/:id/general def admin_edit_general return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) render :layout => 'caboose/admin' end # @route GET /admin/products/:id/description def admin_edit_description return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) render :layout => 'caboose/admin' end # @route GET /admin/products/:id/options def admin_edit_options return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) render :layout => 'caboose/admin' end # @route GET /admin/products/:id/categories def admin_edit_categories return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) @top_categories = Category.where(:parent_id => 1).reorder('name').all @selected_ids = @product.categories.collect{ |cat| cat.id } render :layout => 'caboose/admin' end # @route GET /admin/products/:id/images def admin_edit_images return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) config = YAML.load(File.read(Rails.root.join('config', 'aws.yml')))[Rails.env] access_key = config['access_key_id'] secret_key = config['secret_access_key'] bucket = config['bucket'] policy = { "expiration" => 1.hour.from_now.utc.xmlschema, "conditions" => [ { "bucket" => "#{bucket}-uploads" }, { "acl" => "public-read" }, [ "starts-with", "$key", '' ], [ 'starts-with', '$name', '' ], [ 'starts-with', '$Filename', '' ], ] } @policy = Base64.encode64(policy.to_json).gsub(/\n/,'') @signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), secret_key, @policy)).gsub("\n","") @s3_upload_url = "https://#{bucket}-uploads.s3.amazonaws.com/" @aws_access_key_id = access_key @top_media_category = @product.media_category.parent @media_category = @product.media_category render :layout => 'caboose/admin' end # @route POST /admin/products/:id/images def admin_add_image return if !user_is_allowed('products', 'edit') product_id = params[:id] if (params[:new_image].nil?) render :text => "" else img = ProductImage.new img.product_id = product_id img.image = params[:new_image] img.square_offset_x = 0 img.square_offset_y = 0 img.square_scale_factor = 1.00 img.save render :text => "" end end # @route GET /admin/products/:id/collections def admin_edit_collections return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) render :layout => 'caboose/admin' end # @route GET /admin/products/:id/seo def admin_edit_seo return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) render :layout => 'caboose/admin' end # @route GET /admin/products/:id/delete def admin_delete_form return if !user_is_allowed('products', 'edit') @product = Product.find(params[:id]) render :layout => 'caboose/admin' end # @route PUT /admin/products/:id def admin_update return if !user_is_allowed('products', 'edit') resp = Caboose::StdClass.new({'attributes' => {}}) product = Product.find(params[:id]) save = true params.each do |name,value| case name when 'site_id' then product.site_id = value when 'vendor_id' then product.vendor_id = value when 'alternate_id' then product.alternate_id = value when 'title' product.title = value c = MediaCategory.where(:id => product.media_category_id).last c.name = value c.save when 'caption' then product.caption = value when 'featured' then product.featured = value when 'description' then product.description = value when 'vendor_id' then product.vendor_id = value when 'handle' then product.handle = value when 'seo_title' then product.seo_title = value when 'seo_description' then product.seo_description = value when 'status' then product.status = value when 'category_id' then product.toggle_category(value[0], value[1]) when 'stackable_group_id' then product.stackable_group_id = value when 'allow_gift_wrap' then product.allow_gift_wrap = value when 'gift_wrap_price' then product.gift_wrap_price = value when 'option1' then product.option1 = value when 'option2' then product.option2 = value when 'option3' then product.option3 = value when 'option1_media' then product.option1_media = value when 'option2_media' then product.option2_media = value when 'option3_media' then product.option3_media = value when 'default1' product.default1 = value Variant.where(:product_id => product.id, :option1 => nil).each do |p| p.option1 = value p.save end when 'default2' product.default2 = value Variant.where(:product_id => product.id, :option2 => nil).each do |p| p.option2 = value p.save end when 'default3' product.default3 = value Variant.where(:product_id => product.id, :option3 => nil).each do |p| p.option3 = value p.save end when 'date_available' if value.strip.length == 0 product.date_available = nil else begin product.date_available = DateTime.parse(value) rescue resp.error = "Invalid date" save = false end end end end resp.success = save && product.save render :json => resp end # @route_priority 1 # @route GET /admin/products/new def admin_new return if !user_is_allowed('products', 'add') render :layout => 'caboose/admin' end # @route POST /admin/products def admin_add return if !user_is_allowed('products', 'add') resp = Caboose::StdClass.new name = params[:name] pd = @site.product_default vd = @site.variant_default if name.length == 0 resp.error = "The title cannot be empty." else p = Product.new(:site_id => @site.id, :title => name) mc = MediaCategory.where(:site_id => @site.id).where("parent_id IS NULL").exists? ? MediaCategory.where(:site_id => @site.id).where("parent_id IS NULL").last : MediaCategory.create(:name => "Media", :site_id => @site.id) pc = MediaCategory.where(:name => "Products", :site_id => @site.id).exists? ? MediaCategory.where(:name => "Products", :site_id => @site.id).last : MediaCategory.create(:name => "Products", :site_id => @site.id, :parent_id => mc.id) c = MediaCategory.create(:site_id => @site.id, :name => name, :parent_id => pc.id) p.media_category_id = c.id p.vendor_id = pd.vendor_id p.option1 = pd.option1 p.option2 = pd.option2 p.option3 = pd.option3 p.status = pd.status p.on_sale = pd.on_sale p.allow_gift_wrap = pd.allow_gift_wrap p.gift_wrap_price = pd.gift_wrap_price p.save v = Variant.new v.product_id = p.id v.option1 = p.default1 if p.option1 v.option2 = p.default2 if p.option2 v.option3 = p.default3 if p.option3 v.cost = vd.cost v.price = vd.price v.available = vd.available v.quantity_in_stock = vd.quantity_in_stock v.ignore_quantity = vd.ignore_quantity v.allow_backorder = vd.allow_backorder v.weight = vd.weight v.length = vd.length v.width = vd.width v.height = vd.height v.volume = vd.volume v.cylinder = vd.cylinder v.requires_shipping = vd.requires_shipping v.taxable = vd.taxable v.shipping_unit_value = vd.shipping_unit_value v.flat_rate_shipping = vd.flat_rate_shipping v.flat_rate_shipping_package_id = vd.flat_rate_shipping_package_id v.flat_rate_shipping_method_id = vd.flat_rate_shipping_method_id v.flat_rate_shipping_single = vd.flat_rate_shipping_single v.flat_rate_shipping_combined = vd.flat_rate_shipping_combined v.status = vd.status v.downloadable = vd.downloadable v.is_bundle = vd.is_bundle v.save resp.new_id = p.id resp.new_variant_id = v.id resp.success = true resp.redirect = "/admin/products/#{p.id}/general" end render :json => resp end # @route DELETE /admin/products/:id def admin_delete return if !user_is_allowed('products', 'delete') p = Product.find(params[:id]) p.status = 'Deleted' p.save render :json => Caboose::StdClass.new({ :redirect => '/admin/products' }) end # @route_priority 3 # @route GET /admin/products/status-options def admin_status_options arr = ['Active', 'Inactive', 'Deleted'] render :json => arr.collect{ |status| { :value => status, :text => status }} end # @route GET /products/stackable-group-options def admin_stackable_group_options arr = ['Active', 'Inactive', 'Deleted'] render :json => arr.collect{ |status| { :value => status, :text => status }} end # @route_priority 2 # @route GET /admin/products/combine def admin_combine_select_products end # @route_priority 4 # @route GET /admin/products/combine-step2 def admin_combine_assign_title end # @route_priority 5 # @route POST /admin/products/combine def admin_combine product_ids = params[:product_ids] p = Product.new p.title = params[:title] p.description = params[:description] p.option1 = params[:option1] p.option2 = params[:option2] p.option3 = params[:option3] p.default1 = params[:default1] p.default2 = params[:default2] p.default3 = params[:default3] p.status = 'Active' p.save product_ids.each do |pid| p = Product.find(pid) p.variants.each do |v| end end end # @route_priority 6 # @route GET /admin/products/sort def admin_sort #@products = Product.active #@vendors = Vendor.active #@categories = Category.all render :layout => 'caboose/admin' end # @route PUT /admin/categories/:category_id/products/sort-order def admin_update_sort_order cat_id = params[:category_id] params[:product_ids].each_with_index do |product_id, i| cm = CategoryMembership.where(:category_id => cat_id, :product_id => product_id).first cm.sort_order = i cm.save end render :json => { :success => true } end #============================================================================= # API actions #============================================================================= # @route GET /api/products def api_index render :json => Product.where(:status => 'Active') end # @route GET /api/products/keyword def api_keyword query = params[:query] resp = Caboose::StdClass.new({'products' => {}}) if query && !query.blank? resp.products = Product.select('title, id').where(:site_id => @site.id).where('title ILIKE (?)',"%#{query}%").order(:title).limit(30) end render :json => resp end # @route GET /api/products/:id def api_details p = Product.where(:id => params[:id]).first render :json => p ? p : { :error => 'Invalid product ID' } end # @route GET /api/products/:id/variants def api_variants p = Product.where(:id => params[:id]).first render :json => p ? p.variants : { :error => 'Invalid product ID' } end end end