Sfoglia il codice sorgente

Merge branch 'main' into fix-security-issues

Charles Shen 1 anno fa
parent
commit
27fb9b9dcf

+ 2 - 2
example/admin/config.go

@@ -500,7 +500,7 @@ func NewConfig() Config {
 
 	b.GetWebBuilder().RegisterEventFunc(noteMarkAllAsRead, markAllAsRead(db))
 
-	note.Configure(db, b, m, pm)
+	note.Configure(db, b, m)
 
 	if err := db.AutoMigrate(&UserUnreadNote{}); err != nil {
 		panic(err)
@@ -517,7 +517,7 @@ func NewConfig() Config {
 	microsite_views.Configure(b, db, ab, PublishStorage, publisher, mm)
 	l10nM, l10nVM := configL10nModel(b)
 	_ = l10nM
-	publish_view.Configure(b, db, ab, publisher, m, l, pm, product, category, l10nVM)
+	publish_view.Configure(b, db, ab, publisher, m, l, product, category, l10nVM)
 
 	initLoginBuilder(db, b, ab)
 

+ 1 - 1
go.mod

@@ -24,7 +24,7 @@ require (
 	github.com/ory/ladon v1.2.0
 	github.com/pquerna/otp v1.4.0
 	github.com/qor/oss v0.0.0-20230717083721-c04686f83630
-	github.com/qor5/ui v1.0.1-0.20230822092653-afb0ab3fb124
+	github.com/qor5/ui v1.0.1-0.20230913083355-743825ff29b1
 	github.com/qor5/web v1.2.5-0.20230905084205-145cb859a65f
 	github.com/qor5/x v1.2.1-0.20230907054212-50b1a850acf6
 	github.com/sunfmin/reflectutils v1.0.3

+ 2 - 2
go.sum

@@ -288,8 +288,8 @@ github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/qor/oss v0.0.0-20230717083721-c04686f83630 h1:CRi4xF7B8aGX/y48NCjarNdCIYZ9ZYRr3SLzzTEccOU=
 github.com/qor/oss v0.0.0-20230717083721-c04686f83630/go.mod h1:FDxJAVwmZ1j8ITcKJExFlzkTYuUor1dBKZgNVWqEqlM=
-github.com/qor5/ui v1.0.1-0.20230822092653-afb0ab3fb124 h1:zFsaGk50v8uI8FQxIIdnTyqRM32sMj0ZWCOyd5lxwRg=
-github.com/qor5/ui v1.0.1-0.20230822092653-afb0ab3fb124/go.mod h1:bgBqjIytHRdfTsiZea8df/ltAcyQyuHiLbecgo8Iwgw=
+github.com/qor5/ui v1.0.1-0.20230913083355-743825ff29b1 h1:6ZIyg13zG0ki2yE2XcFN20RkwCMUjIJkYHCGIIqrq5c=
+github.com/qor5/ui v1.0.1-0.20230913083355-743825ff29b1/go.mod h1:bgBqjIytHRdfTsiZea8df/ltAcyQyuHiLbecgo8Iwgw=
 github.com/qor5/web v1.2.5-0.20230905084205-145cb859a65f h1:xQwS/HKx9unrQ964UGkKiiXZPOpnLaQbRQ9DpaPJVKc=
 github.com/qor5/web v1.2.5-0.20230905084205-145cb859a65f/go.mod h1:4VXydGmy5Uwz8rEeKjcmCetciJo8TpU0mnN7Ca5kMR0=
 github.com/qor5/x v1.2.1-0.20230907054212-50b1a850acf6 h1:GyPeYULwjUPGR6fT/lZicJ8dkoKL5cu/hRNefxX+V7g=

+ 1 - 1
l10n/l10n.go

@@ -6,7 +6,7 @@ type Locale struct {
 }
 
 // GetLocale get model's locale
-func (l *Locale) GetLocale() string {
+func (l Locale) GetLocale() string {
 	return l.LocaleCode
 }
 

+ 37 - 1
l10n/views/config.go

@@ -1,9 +1,11 @@
 package views
 
 import (
+	"errors"
 	"fmt"
 	"net/url"
 	"reflect"
+	"time"
 
 	"github.com/qor5/admin/activity"
 	"github.com/qor5/admin/l10n"
@@ -31,6 +33,8 @@ func Configure(b *presets.Builder, db *gorm.DB, lb *l10n.Builder, ab *activity.A
 			l10nONModel.L10nON()
 		}
 		m.Listing().Field("Locale")
+		m.Editing().Field("Locale")
+
 		searcher := m.Listing().Searcher
 		m.Listing().SearchFunc(func(model interface{}, params *presets.SearchParams, ctx *web.EventContext) (r interface{}, totalCount int, err error) {
 			if localeCode := ctx.R.Context().Value(l10n.LocaleCode); localeCode != nil {
@@ -58,6 +62,25 @@ func Configure(b *presets.Builder, db *gorm.DB, lb *l10n.Builder, ab *activity.A
 			}
 		})
 
+		deleter := m.Editing().Deleter
+		m.Editing().DeleteFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
+			if err = deleter(obj, id, ctx); err != nil {
+				return
+			}
+			locale := obj.(presets.SlugDecoder).PrimaryColumnValuesBySlug(id)["locale_code"]
+			locale = fmt.Sprintf("%s(del:%d)", locale, time.Now().UnixMilli())
+
+			withoutKeys := []string{}
+			if ctx.R.URL.Query().Get("all_versions") == "true" {
+				withoutKeys = append(withoutKeys, "version")
+			}
+
+			if err = utils.PrimarySluggerWhere(db.Unscoped(), obj, id, withoutKeys...).Update("locale_code", locale).Error; err != nil {
+				return
+			}
+			return
+		})
+
 		rmb := m.Listing().RowMenu()
 		rmb.RowMenuItem("Localize").ComponentFunc(localizeRowMenuItemFunc(m.Info(), "", url.Values{}))
 
@@ -70,9 +93,22 @@ func Configure(b *presets.Builder, db *gorm.DB, lb *l10n.Builder, ab *activity.A
 	b.FieldDefaults(presets.WRITE).
 		FieldType(l10n.Locale{}).
 		ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
-			return nil
+			var value string
+			id, err := reflectutils.Get(obj, "ID")
+			if err == nil && len(fmt.Sprint(id)) > 0 && fmt.Sprint(id) != "0" {
+				value = field.Value(obj).(l10n.Locale).GetLocale()
+			} else {
+				value = lb.GetCorrectLocaleCode(ctx.R)
+			}
+
+			return h.Input("").Type("hidden").Value(value).Attr(web.VFieldName("LocaleCode")...)
 		}).
 		SetterFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {
+			value := field.Value(obj).(l10n.Locale).GetLocale()
+			if !utils.Contains(lb.GetSupportLocaleCodesFromRequest(ctx.R), value) {
+				return errors.New("Incorrect locale.")
+			}
+
 			return nil
 		})
 

+ 3 - 0
l10n/views/events.go

@@ -154,6 +154,9 @@ func doLocalizeTo(db *gorm.DB, mb *presets.ModelBuilder, lb *l10n.Builder, ab *a
 			if ab == nil {
 				return
 			}
+			if _, ok := ab.GetModelBuilder(fromObj); !ok {
+				return
+			}
 			if len(toObjs) > 0 {
 				if err = ab.AddCustomizedRecord(LocalizeFrom, false, ctx.R.Context(), fromObj); err != nil {
 					return

+ 2 - 1
media/views/cropper.go

@@ -3,11 +3,11 @@ package views
 import (
 	"encoding/json"
 	"fmt"
-	"github.com/qor5/admin/presets"
 	"time"
 
 	"github.com/qor5/admin/media"
 	"github.com/qor5/admin/media/media_library"
+	"github.com/qor5/admin/presets"
 	"github.com/qor5/ui/cropper"
 	. "github.com/qor5/ui/vuetify"
 	"github.com/qor5/web"
@@ -143,6 +143,7 @@ func cropImage(db *gorm.DB) web.EventFunc {
 				return r, nil
 			}
 
+			mb.Url = m.File.Url
 			mb.FileSizes = m.File.FileSizes
 			if thumb == media.DefaultSizeKey {
 				mb.Width = int(cropValue.Width)

+ 3 - 3
media/vips/vips.go

@@ -83,7 +83,7 @@ func (bimgImageHandler) Handle(m media.Media, file media.FileInterface, option *
 			return err
 		}
 		for key, _ := range m.GetSizes() {
-			if key == "original" {
+			if key == media.DefaultSizeKey {
 				continue
 			}
 			img := copyImage(buffer.Bytes())
@@ -106,7 +106,7 @@ func (bimgImageHandler) Handle(m media.Media, file media.FileInterface, option *
 		img := copyImage(buffer.Bytes())
 		bimgOption := bimg.Options{Quality: quality, Palette: true, Compression: PNGCompression}
 		// Crop original image if specified
-		if cropOption := m.GetCropOption("original"); cropOption != nil {
+		if cropOption := m.GetCropOption(media.DefaultSizeKey); cropOption != nil {
 			options := bimg.Options{
 				Quality:    100, // Don't compress twice
 				Top:        cropOption.Min.Y,
@@ -137,7 +137,7 @@ func (bimgImageHandler) Handle(m media.Media, file media.FileInterface, option *
 
 	// Handle size images
 	for key, size := range m.GetSizes() {
-		if key == "original" {
+		if key == media.DefaultSizeKey {
 			continue
 		}
 		img := copyImage(buffer.Bytes())

+ 14 - 51
pagebuilder/builder.go

@@ -255,6 +255,19 @@ func (b *Builder) Configure(pb *presets.Builder, db *gorm.DB, l10nB *l10n.Builde
 				dmb = templateM
 			}
 			obj = dmb.NewModel()
+			if sd, ok := obj.(presets.SlugDecoder); ok {
+				vs, err := presets.RecoverPrimaryColumnValuesBySlug(sd, id)
+				if err != nil {
+					return pb.DefaultNotFoundPageFunc(ctx)
+				}
+				if _, err := strconv.Atoi(vs["id"]); err != nil {
+					return pb.DefaultNotFoundPageFunc(ctx)
+				}
+			} else {
+				if _, err := strconv.Atoi(id); err != nil {
+					return pb.DefaultNotFoundPageFunc(ctx)
+				}
+			}
 			obj, err = dmb.Detailing().GetFetchFunc()(obj, id, ctx)
 			if err != nil {
 				if errors.Is(err, presets.ErrRecordNotFound) {
@@ -311,7 +324,7 @@ func (b *Builder) Configure(pb *presets.Builder, db *gorm.DB, l10nB *l10n.Builde
 			editAction := web.POST().
 				EventFunc(actions.Edit).
 				URL(web.Var("\""+b.prefix+"/\"+arr[0]")).
-				Query(presets.ParamOverlay, actions.Dialog).
+				Query(presets.ParamOverlay, actions.Drawer).
 				Query(presets.ParamID, web.Var("arr[1]")).
 				Go()
 
@@ -537,38 +550,6 @@ function(e){
 		return nil
 	})
 
-	eb.Field("EditContainer").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
-		msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
-		p := obj.(*Page)
-		if p.ID == 0 {
-			return nil
-		}
-		if p.GetStatus() == publish.StatusDraft {
-			var href = fmt.Sprintf("%s/editors/%d?version=%s", b.prefix, p.ID, p.GetVersion())
-			if locale, isLocalizable := l10n.IsLocalizableFromCtx(ctx.R.Context()); isLocalizable && l10nON {
-				href = fmt.Sprintf("%s/editors/%d?version=%s&locale=%s", b.prefix, p.ID, p.GetVersion(), locale)
-			}
-			return h.Div(
-				VBtn(msgr.EditPageContent).
-					Target("_blank").
-					Href(href).
-					Color("secondary"),
-			)
-		} else {
-			var href = fmt.Sprintf("%s/preview?id=%d&version=%s", b.prefix, p.ID, p.GetVersion())
-			if locale, isLocalizable := l10n.IsLocalizableFromCtx(ctx.R.Context()); isLocalizable && l10nON {
-				href = fmt.Sprintf("%s/preview?id=%d&version=%s&locale=%s", b.prefix, p.ID, p.GetVersion(), locale)
-			}
-			return h.Div(
-				VBtn(msgr.Preview).
-					Target("_blank").
-					Href(href).
-					Color("secondary"),
-			)
-		}
-		return nil
-	})
-
 	eb.SaveFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
 		localeCode, _ := l10n.IsLocalizableFromCtx(ctx.R.Context())
 		p := obj.(*Page)
@@ -1690,24 +1671,6 @@ func (b *Builder) ConfigTemplate(pb *presets.Builder, db *gorm.DB) (pm *presets.
 	dp.Field("Overview").ComponentFunc(templateSettings(db, pm))
 
 	eb := pm.Editing("Name", "Description")
-	eb.Field("EditContainer").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
-		msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
-		m := obj.(*Template)
-		if m.ID == 0 {
-			return nil
-		}
-
-		var href = fmt.Sprintf("%s/editors/%d?tpl=1", b.prefix, m.ID)
-		if locale, isLocalizable := l10n.IsLocalizableFromCtx(ctx.R.Context()); isLocalizable && l10nON {
-			href = fmt.Sprintf("%s/editors/%d?tpl=1&locale=%s", b.prefix, m.ID, locale)
-		}
-		return h.Div(
-			VBtn(msgr.EditPageContent).
-				Target("_blank").
-				Href(href).
-				Color("secondary"),
-		)
-	})
 
 	eb.SaveFunc(func(obj interface{}, id string, ctx *web.EventContext) (err error) {
 		this := obj.(*Template)

+ 3 - 5
pagebuilder/editor.go

@@ -431,7 +431,7 @@ func (b *Builder) renderContainersList(ctx *web.EventContext, pageID uint, pageV
 									web.Plaid().
 										URL(web.Var("item.url")).
 										EventFunc(actions.Edit).
-										Query(presets.ParamOverlay, actions.Dialog).
+										Query(presets.ParamOverlay, actions.Drawer).
 										Query(presets.ParamID, web.Var("item.model_id")).
 										Go(),
 								).Class("my-2"),
@@ -461,7 +461,6 @@ func (b *Builder) renderContainersList(ctx *web.EventContext, pageID uint, pageV
 												EventFunc(RenameContainerDialogEvent).
 												Query(paramContainerID, web.Var("item.param_id")).
 												Query(paramContainerName, web.Var("item.display_name")).
-												Query(presets.ParamOverlay, actions.Dialog).
 												Go(),
 										),
 										VListItem(
@@ -501,7 +500,6 @@ func (b *Builder) renderContainersList(ctx *web.EventContext, pageID uint, pageV
 								Query(paramPageID, pageID).
 								Query(paramPageVersion, pageVersion).
 								Query(paramLocale, locale).
-								Query(presets.ParamOverlay, actions.Dialog).
 								Go(),
 						),
 					),
@@ -529,7 +527,7 @@ func (b *Builder) AddContainer(ctx *web.EventContext) (r web.EventResponse, err
 		r.VarsScript = web.Plaid().
 			URL(b.ContainerByName(containerName).mb.Info().ListingHref()).
 			EventFunc(actions.Edit).
-			Query(presets.ParamOverlay, actions.Dialog).
+			Query(presets.ParamOverlay, actions.Drawer).
 			Query(presets.ParamID, fmt.Sprint(newModelID)).
 			Go()
 	}
@@ -1077,7 +1075,7 @@ func (b *Builder) pageEditorLayout(in web.PageFunc, config *presets.LayoutConfig
 		action := web.POST().
 			EventFunc(actions.Edit).
 			URL(web.Var("\""+b.prefix+"/\"+arr[0]")).
-			Query(presets.ParamOverlay, actions.Dialog).
+			Query(presets.ParamOverlay, actions.Drawer).
 			Query(presets.ParamID, web.Var("arr[1]")).
 			// Query(presets.ParamOverlayAfterUpdateScript,
 			// 	web.Var(

+ 1 - 1
pagebuilder/helper.go

@@ -10,7 +10,7 @@ import (
 )
 
 var (
-	directoryRe = regexp.MustCompile(`^([\/]{1}[a-z0-9.-]+)+(\/?){1}$|^([\/]{1})$`)
+	directoryRe = regexp.MustCompile(`^([\/]{1}[a-zA-Z0-9._-]+)+(\/?){1}$|^([\/]{1})$`)
 )
 
 const (

+ 74 - 0
pagebuilder/helper_test.go

@@ -0,0 +1,74 @@
+package pagebuilder
+
+import (
+	"path"
+	"testing"
+)
+
+func TestSlugReg(t *testing.T) {
+	cases := []string{
+		"/apple",
+		"/Apple",
+		"/fruit/Apple",
+		"/fruit/Apple/Red",
+		"/fruit/bAnAnA/yElLoW",
+		"/vegetable/Carrot/Orange",
+		"/animal/Dog/Brown",
+		"/animal/Cat/Gray",
+		"/vehicle/Car/Red",
+		"/City/New-York",
+		"/Country/United-States/New-York",
+		"/fruit/apple",
+		"/fruit/apple/red",
+		"/fruit/banana/yellow",
+		"/vegetable/carrot/orange",
+		"/animal/dog/brown",
+		"/animal/cat/gray",
+		"/vehicle/car/red",
+		"/city/new-york",
+		"/country/united-states/new-york",
+		"/user/john_doe",
+		"/product/12345",
+		"/page/home",
+		"/file/file-name",
+		"/images/image_01",
+		"/folder/sub_folder",
+		"/route/route-1",
+		"/data/data123",
+		"/user/user12345/profile",
+	}
+
+	casesNotMatch := []string{
+		"apple",
+		"*apple",
+		"$fruit/apple",
+		"/fruit/apple&banana",
+		"#vegetable/carrot",
+		"/animal/dog:",
+		"/animal/dog#cat",
+		"%20images/image01",
+		`/user\john`,
+		"/product?item=123",
+		`/my$folder`,
+		`/usr#bin`,
+		`/home/user&docs`,
+		`/var/www/my folder`,
+		`/home/user/documents/with space`,
+		`/home/user*docs`,
+		`/usr?bin`,
+		`/var/www/my\tfolder`,
+		`/home/user/documents\nwithnewline`,
+	}
+
+	for _, c := range cases {
+		if !directoryRe.MatchString(path.Clean(c)) {
+			t.Errorf("directoryRe.MatchString(%q) = false, want true", c)
+		}
+	}
+
+	for _, c := range casesNotMatch {
+		if directoryRe.MatchString(path.Clean(c)) {
+			t.Errorf("directoryRe.MatchString(%q) = true, want false", c)
+		}
+	}
+}

+ 0 - 4
pagebuilder/messages.go

@@ -6,7 +6,6 @@ const I18nPageBuilderKey i18n.ModuleKey = "I18nPageBuilderKey"
 
 type Messages struct {
 	Category                       string
-	EditPageContent                string
 	Preview                        string
 	Containers                     string
 	AddContainers                  string
@@ -30,7 +29,6 @@ type Messages struct {
 
 var Messages_en_US = &Messages{
 	Category:                       "Category",
-	EditPageContent:                "Edit Page Content",
 	Preview:                        "Preview",
 	Containers:                     "Containers",
 	AddContainers:                  "Add Containers",
@@ -54,7 +52,6 @@ var Messages_en_US = &Messages{
 
 var Messages_zh_CN = &Messages{
 	Category:                       "目录",
-	EditPageContent:                "编辑页面内容",
 	Preview:                        "预览",
 	Containers:                     "组件",
 	AddContainers:                  "增加组件",
@@ -78,7 +75,6 @@ var Messages_zh_CN = &Messages{
 
 var Messages_ja_JP = &Messages{
 	Category:                       "カテゴリー",
-	EditPageContent:                "ページコンテナを編集する",
 	Preview:                        "プレビュー",
 	Containers:                     "コンテナ",
 	AddContainers:                  "コンテナを追加する",

+ 12 - 0
presets/api.go

@@ -1,6 +1,7 @@
 package presets
 
 import (
+	"fmt"
 	"net/http"
 	"net/url"
 
@@ -62,6 +63,17 @@ type SlugDecoder interface {
 	PrimaryColumnValuesBySlug(slug string) map[string]string
 }
 
+func RecoverPrimaryColumnValuesBySlug(dec SlugDecoder, slug string) (r map[string]string, err error) {
+	defer func() {
+		if e := recover(); e != nil {
+			r = nil
+			err = fmt.Errorf("wrong slug: %v", slug)
+		}
+	}()
+	r = dec.PrimaryColumnValuesBySlug(slug)
+	return r, nil
+}
+
 type SlugEncoder interface {
 	PrimarySlug() string
 }

+ 33 - 14
presets/field.go

@@ -511,33 +511,40 @@ func (b *FieldsBuilder) getField(name string) (r *FieldBuilder) {
 	return
 }
 
-func (b *FieldsBuilder) Only(vs ...interface{}) (r *FieldsBuilder) {
-	if len(vs) == 0 {
-		return b
-	}
-
-	r = b.Clone()
-
-	r.fieldsLayout = vs
-	for _, iv := range vs {
+func (b *FieldsBuilder) getFieldNamesFromLayout() []string {
+	var ns []string
+	for _, iv := range b.fieldsLayout {
 		switch t := iv.(type) {
 		case string:
-			r.appendFieldAfterClone(b, t)
+			ns = append(ns, t)
 		case []string:
 			for _, n := range t {
-				r.appendFieldAfterClone(b, n)
+				ns = append(ns, n)
 			}
 		case *FieldsSection:
 			for _, row := range t.Rows {
 				for _, n := range row {
-					r.appendFieldAfterClone(b, n)
+					ns = append(ns, n)
 				}
 			}
 		default:
 			panic("unknown fields layout, must be string/[]string/*FieldsSection")
 		}
 	}
+	return ns
+}
 
+func (b *FieldsBuilder) Only(vs ...interface{}) (r *FieldsBuilder) {
+	if len(vs) == 0 {
+		return b
+	}
+
+	r = b.Clone()
+
+	r.fieldsLayout = vs
+	for _, fn := range r.getFieldNamesFromLayout() {
+		r.appendFieldAfterClone(b, fn)
+	}
 	return
 }
 
@@ -596,12 +603,24 @@ func (b *FieldsBuilder) toComponentWithFormValueKey(info *ModelInfo, obj interfa
 		edit = true
 	}
 
-	layout := b.fieldsLayout
-	if layout == nil {
+	var layout []interface{}
+	if b.fieldsLayout == nil {
 		layout = make([]interface{}, 0, len(b.fields))
 		for _, f := range b.fields {
 			layout = append(layout, f.name)
 		}
+	} else {
+		layout = b.fieldsLayout[:]
+		layoutFM := make(map[string]struct{})
+		for _, fn := range b.getFieldNamesFromLayout() {
+			layoutFM[fn] = struct{}{}
+		}
+		for _, f := range b.fields {
+			if _, ok := layoutFM[f.name]; ok {
+				continue
+			}
+			layout = append(layout, f.name)
+		}
 	}
 	for _, iv := range layout {
 		var comp h.HTMLComponent