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  }