flamingo.me/flamingo-commerce/v3@v3.11.0/product/interfaces/controller/controller.go (about) 1 package controller 2 3 import ( 4 "context" 5 "net/url" 6 7 "flamingo.me/flamingo/v3/framework/web" 8 "github.com/pkg/errors" 9 "golang.org/x/text/cases" 10 "golang.org/x/text/language" 11 12 "flamingo.me/flamingo-commerce/v3/product/application" 13 "flamingo.me/flamingo-commerce/v3/product/domain" 14 ) 15 16 type ( 17 // View demonstrates a product view controller 18 View struct { 19 Responder *web.Responder `inject:""` 20 domain.ProductService `inject:""` 21 URLService *application.URLService `inject:""` 22 23 Template string `inject:"config:commerce.product.view.template"` 24 Router *web.Router `inject:""` 25 } 26 27 // productViewData is used for product rendering 28 productViewData struct { 29 // simple / configurable / configurable_with_variant 30 RenderContext string 31 Product domain.BasicProduct 32 VariantSelected bool 33 VariantSelection variantSelection 34 BackURL string 35 } 36 37 // variantSelection for templating 38 variantSelection struct { 39 Attributes []viewVariantAttribute 40 Variants []viewVariant 41 } 42 43 viewVariantAttribute struct { 44 Key string 45 Title string 46 CodeLabel string 47 Options []viewVariantOption 48 } 49 50 viewVariantOption struct { 51 Key string 52 Title string 53 Combinations map[string][]string 54 Selected bool 55 Unit string 56 } 57 58 viewVariant struct { 59 Attributes map[string]string 60 Marketplacecode string 61 Title string 62 URL string 63 InStock bool 64 } 65 ) 66 67 func (vc *View) variantSelection(configurable domain.ConfigurableProduct, activeVariant *domain.Variant) variantSelection { 68 var variants variantSelection 69 combinations := make(map[string]map[string]map[string]map[string]bool) 70 titles := make(map[string]map[string]string) 71 units := make(map[string]map[string]string) 72 73 combinationsOrder := make(map[string][]string) 74 75 for _, attribute := range configurable.VariantVariationAttributes { 76 // attribute -> value -> combinableAttribute -> combinableValue -> true 77 combinations[attribute] = make(map[string]map[string]map[string]bool) 78 titles[attribute] = make(map[string]string) 79 units[attribute] = make(map[string]string) 80 81 for _, variant := range configurable.Variants { 82 titles[attribute][variant.Attributes[attribute].Value()] = variant.Attributes[attribute].Label 83 units[attribute][variant.Attributes[attribute].Value()] = variant.Attributes[attribute].UnitCode 84 85 for _, subattribute := range configurable.VariantVariationAttributes { 86 if _, ok := variant.Attributes[attribute]; !ok { 87 continue 88 } 89 if combinations[attribute][variant.Attributes[attribute].Value()] == nil { 90 combinations[attribute][variant.Attributes[attribute].Value()] = make(map[string]map[string]bool) 91 combinationsOrder[attribute] = append(combinationsOrder[attribute], variant.Attributes[attribute].Value()) 92 } 93 94 // titles[variant.Attributes[subattribute].Value()] = variant.Attributes[subattribute].Label 95 if subattribute != attribute { 96 if _, ok := variant.Attributes[subattribute]; ok { 97 if combinations[attribute][variant.Attributes[attribute].Value()][subattribute] == nil { 98 combinations[attribute][variant.Attributes[attribute].Value()][subattribute] = make(map[string]bool) 99 } 100 combinations[attribute][variant.Attributes[attribute].Value()][subattribute][variant.Attributes[subattribute].Value()] = true 101 } 102 } 103 } 104 } 105 } 106 107 for _, code := range configurable.VariantVariationAttributes { 108 attribute := combinations[code] 109 110 codeLabel := "" 111 if len(configurable.Variants) > 0 { 112 codeLabel = configurable.Variants[0].BaseData().Attributes.Attribute(code).CodeLabel 113 } 114 115 viewVariantAttribute := viewVariantAttribute{ 116 Key: code, 117 Title: cases.Title(language.Und).String(code), 118 CodeLabel: codeLabel, 119 } 120 121 options := append([]string{}, configurable.VariantVariationAttributesSorting[viewVariantAttribute.Key]...) 122 options = append(options, combinationsOrder[viewVariantAttribute.Key]...) 123 knownOption := make(map[string]struct{}, len(options)) 124 125 for _, optionCode := range options { 126 option, ok := attribute[optionCode] 127 if _, known := knownOption[optionCode]; !ok || known { 128 continue 129 } 130 knownOption[optionCode] = struct{}{} 131 132 combinations := make(map[string][]string) 133 for cattr, cvalues := range option { 134 for cvalue := range cvalues { 135 combinations[cattr] = append(combinations[cattr], cvalue) 136 } 137 } 138 139 var selected bool 140 if activeVariant != nil && activeVariant.Attributes[code].Value() == optionCode { 141 selected = true 142 } 143 144 label, ok := titles[code][optionCode] 145 if !ok || label == "" { 146 label = cases.Title(language.Und).String(optionCode) 147 } 148 viewVariantAttribute.Options = append(viewVariantAttribute.Options, viewVariantOption{ 149 Key: optionCode, 150 Title: label, 151 Selected: selected, 152 Combinations: combinations, 153 Unit: units[code][optionCode], 154 }) 155 } 156 157 variants.Attributes = append(variants.Attributes, viewVariantAttribute) 158 } 159 160 for _, variant := range configurable.Variants { 161 urlName := web.URLTitle(variant.BasicProductData.Title) 162 variantURL, _ := vc.Router.URL("product.view", map[string]string{"marketplacecode": configurable.MarketPlaceCode, "variantcode": variant.MarketPlaceCode, "name": urlName}) 163 164 attributes := make(map[string]string) 165 166 for _, attr := range variants.Attributes { 167 if !variant.HasAttribute(attr.Key) { 168 continue 169 } 170 attributes[attr.Key] = variant.Attributes[attr.Key].Value() 171 } 172 173 variants.Variants = append(variants.Variants, viewVariant{ 174 Title: variant.Title, 175 Marketplacecode: variant.MarketPlaceCode, 176 URL: variantURL.String(), 177 Attributes: attributes, 178 InStock: variant.IsInStock(), 179 }) 180 } 181 182 return variants 183 } 184 185 // Get Response for Product matching sku param 186 func (vc *View) Get(c context.Context, r *web.Request) web.Result { 187 product, err := vc.ProductService.Get(c, r.Params["marketplacecode"]) 188 skipnamecheck := r.Params["skipnamecheck"] 189 190 // catch error 191 if err != nil { 192 switch errors.Cause(err).(type) { 193 case domain.ProductNotFound: 194 return vc.Responder.NotFound(err) 195 196 default: 197 return vc.Responder.ServerError(err) 198 } 199 } 200 201 var viewData productViewData 202 203 // 1. Handle Configurables 204 if product.Type() == domain.TypeConfigurable { 205 configurableProduct := product.(domain.ConfigurableProduct) 206 var activeVariant *domain.Variant 207 208 viewData = productViewData{} 209 variantCode, ok := r.Params["variantcode"] 210 211 if !ok { 212 // Redirect if url is not canonical 213 redirect := vc.getRedirectIfRequired(configurableProduct, r, skipnamecheck) 214 if redirect != nil { 215 return redirect 216 } 217 // 1.A. No variant selected 218 viewData.VariantSelected = false 219 viewData.RenderContext = "configurable" 220 viewData.Product = configurableProduct 221 } else { 222 configurableProductWithActiveVariant, err := configurableProduct.GetConfigurableWithActiveVariant(variantCode) 223 if err != nil { 224 return vc.Responder.NotFound(err) 225 } 226 activeVariant = &configurableProductWithActiveVariant.ActiveVariant 227 // Redirect if url is not canonical 228 redirect := vc.getRedirectIfRequired(configurableProductWithActiveVariant, r, skipnamecheck) 229 if redirect != nil { 230 return redirect 231 } 232 viewData.VariantSelected = true 233 viewData.RenderContext = "configurable_with_activevariant" 234 viewData.Product = configurableProductWithActiveVariant 235 } 236 viewData.VariantSelection = vc.variantSelection(configurableProduct, activeVariant) 237 238 } else { 239 // Redirect if url is not canonical 240 redirect := vc.getRedirectIfRequired(product, r, skipnamecheck) 241 if redirect != nil { 242 return redirect 243 } 244 245 // 2. Handle Simples 246 simpleProduct := product.(domain.SimpleProduct) 247 viewData = productViewData{Product: simpleProduct, RenderContext: "simple"} 248 } 249 250 backURL, err := r.Query1("backurl") 251 if err == nil { 252 viewData.BackURL = backURL 253 } 254 255 return vc.Responder.Render(vc.Template, viewData) 256 } 257 258 func (vc *View) getRedirectIfRequired(product domain.BasicProduct, r *web.Request, skipnamecheck string) *web.URLRedirectResponse { 259 currentNameParameter := r.Params["name"] 260 var allParams url.Values 261 if r.QueryAll() != nil { 262 allParams = url.Values(r.QueryAll()) 263 } 264 265 if skipnamecheck != "" { 266 return nil 267 } 268 // Redirect if url is not canonical 269 if vc.URLService.GetNameParam(product, "") != currentNameParameter { 270 if redirectURL, err := vc.URLService.Get(product, ""); err == nil { 271 newURL, _ := url.Parse(redirectURL) 272 if len(allParams) > 0 { 273 newURL.RawQuery = allParams.Encode() 274 } 275 return vc.Responder.URLRedirect(newURL).Permanent() 276 } 277 } 278 return nil 279 }