diff --git a/domain/document/document.go b/domain/document/document.go new file mode 100644 index 00000000..beafd0c4 --- /dev/null +++ b/domain/document/document.go @@ -0,0 +1,147 @@ +// Copyright 2016 Documize Inc. . All rights reserved. +// +// This software (Documize Community Edition) is licensed under +// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html +// +// You can operate outside the AGPL restrictions by purchasing +// Documize Enterprise Edition and obtaining a commercial license +// by contacting . +// +// https://documize.com + +package document + +import ( + "github.com/documize/community/core/uniqueid" + "github.com/documize/community/domain" + "github.com/documize/community/model/category" + "github.com/documize/community/model/doc" + "github.com/documize/community/model/page" + "github.com/documize/community/model/workflow" + "github.com/pkg/errors" +) + +// FilterCategoryProtected removes documents that cannot be seen by user due to +// document cateogory viewing permissions. +func FilterCategoryProtected(docs []doc.Document, cats []category.Category, members []category.Member, viewDrafts bool) (filtered []doc.Document) { + filtered = []doc.Document{} + + for _, doc := range docs { + hasCategory := false + canSeeCategory := false + skip := false + + // drafts included if user can see them + if doc.Lifecycle == workflow.LifecycleDraft && !viewDrafts { + skip = true + } + + // archived never included + if doc.Lifecycle == workflow.LifecycleArchived { + skip = true + } + + OUTER: + + for _, m := range members { + if m.DocumentID == doc.RefID { + hasCategory = true + for _, cat := range cats { + if cat.RefID == m.CategoryID { + canSeeCategory = true + continue OUTER + } + } + } + } + + if !skip && (!hasCategory || canSeeCategory) { + filtered = append(filtered, doc) + } + } + + return +} + +// CopyDocument clones an existing document +func CopyDocument(ctx domain.RequestContext, s domain.Store, documentID string) (newDocumentID string, err error) { + + doc, err := s.Document.Get(ctx, documentID) + if err != nil { + err = errors.Wrap(err, "unable to fetch existing document") + return + } + + newDocumentID = uniqueid.Generate() + doc.RefID = newDocumentID + doc.ID = 0 + doc.Versioned = false + doc.VersionID = "" + doc.GroupID = "" + doc.Template = false + + // Duplicate pages and associated meta + pages, err := s.Page.GetPages(ctx, documentID) + if err != nil { + err = errors.Wrap(err, "unable to get existing pages") + return + } + + var pageModel []page.NewPage + + for _, p := range pages { + p.DocumentID = newDocumentID + p.ID = 0 + + meta, err2 := s.Page.GetPageMeta(ctx, p.RefID) + if err2 != nil { + err = errors.Wrap(err, "unable to get existing pages meta") + return + } + + pageID := uniqueid.Generate() + p.RefID = pageID + meta.PageID = pageID + meta.DocumentID = newDocumentID + + m := page.NewPage{} + m.Page = p + m.Meta = meta + + pageModel = append(pageModel, m) + } + + // Duplicate attachments + attachments, _ := s.Attachment.GetAttachments(ctx, documentID) + for i, a := range attachments { + a.DocumentID = newDocumentID + a.RefID = uniqueid.Generate() + a.ID = 0 + attachments[i] = a + } + + // Now create the template: document, attachments, pages and their meta + err = s.Document.Add(ctx, doc) + if err != nil { + err = errors.Wrap(err, "unable to add copied document") + return + } + + for _, a := range attachments { + err = s.Attachment.Add(ctx, a) + if err != nil { + err = errors.Wrap(err, "unable to add copied attachment") + return + } + } + + for _, m := range pageModel { + err = s.Page.Add(ctx, m) + if err != nil { + err = errors.Wrap(err, "unable to add copied page") + return + } + } + + return +} diff --git a/domain/document/endpoint.go b/domain/document/endpoint.go index d34269b3..2c01baba 100644 --- a/domain/document/endpoint.go +++ b/domain/document/endpoint.go @@ -153,45 +153,10 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) { // sort by title sort.Sort(doc.ByTitle(documents)) - // remove documents that cannot be seen due to lack of - // category view/access permission - filtered := []doc.Document{} + // remove documents that cannot be seen due to lack of category view/access permission cats, err := h.Store.Category.GetBySpace(ctx, spaceID) members, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID) - - for _, doc := range documents { - hasCategory := false - canSeeCategory := false - skip := false - - // drafts included if user can see them - if doc.Lifecycle == workflow.LifecycleDraft && !viewDrafts { - skip = true - } - - // archived never included - if doc.Lifecycle == workflow.LifecycleArchived { - skip = true - } - - OUTER: - - for _, m := range members { - if m.DocumentID == doc.RefID { - hasCategory = true - for _, cat := range cats { - if cat.RefID == m.CategoryID { - canSeeCategory = true - continue OUTER - } - } - } - } - - if !skip && (!hasCategory || canSeeCategory) { - filtered = append(filtered, doc) - } - } + filtered := FilterCategoryProtected(documents, cats, members, viewDrafts) response.WriteJSON(w, filtered) } @@ -259,6 +224,24 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { return } + // Record document being marked as archived + if d.Lifecycle != oldDoc.Lifecycle && d.Lifecycle == workflow.LifecycleArchived { + h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ + LabelID: d.LabelID, + DocumentID: documentID, + SourceType: activity.SourceTypeDocument, + ActivityType: activity.TypeArchived}) + } + + // Record document being marked as draft + if d.Lifecycle != oldDoc.Lifecycle && d.Lifecycle == workflow.LifecycleDraft { + h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ + LabelID: d.LabelID, + DocumentID: documentID, + SourceType: activity.SourceTypeDocument, + ActivityType: activity.TypeDraft}) + } + ctx.Transaction.Commit() h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate) diff --git a/domain/permission/permission.go b/domain/permission/permission.go index 38dec60e..c3f82c22 100644 --- a/domain/permission/permission.go +++ b/domain/permission/permission.go @@ -175,7 +175,7 @@ func CanViewDrafts(ctx domain.RequestContext, s domain.Store, spaceID string) bo return false } for _, role := range roles { - if role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow && + if role.OrgID == ctx.OrgID && role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow && pm.ContainsPermission(role.Action, pm.DocumentLifecycle) { return true } @@ -184,6 +184,25 @@ func CanViewDrafts(ctx domain.RequestContext, s domain.Store, spaceID string) bo return false } +// CanManageVersion returns if the user has permission to manage versions in space. +func CanManageVersion(ctx domain.RequestContext, s domain.Store, spaceID string) bool { + roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID) + if err == sql.ErrNoRows { + err = nil + } + if err != nil { + return false + } + for _, role := range roles { + if role.OrgID == ctx.OrgID && role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow && + pm.ContainsPermission(role.Action, pm.DocumentVersion) { + return true + } + } + + return false +} + // HasPermission returns if user can perform specified actions. func HasPermission(ctx domain.RequestContext, s domain.Store, spaceID string, actions ...pm.Action) bool { roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID) diff --git a/gui/app/models/document.js b/gui/app/models/document.js index aa47c46b..21650e56 100644 --- a/gui/app/models/document.js +++ b/gui/app/models/document.js @@ -28,9 +28,9 @@ export default Model.extend({ approval: attr('number', { defaultValue: 0 }), lifecycle: attr('number', { defaultValue: 1 }), versioned: attr('boolean'), - versionID: attr('string'), + versionId: attr('string'), versionOrder: attr('number', { defaultValue: 0 }), - groupID: attr('string'), + groupId: attr('string'), // client-side property selected: attr('boolean', { defaultValue: false }), diff --git a/gui/vendor/sortable.js b/gui/vendor/sortable.js index 7617360d..94610f30 100644 --- a/gui/vendor/sortable.js +++ b/gui/vendor/sortable.js @@ -109,7 +109,7 @@ scrollOffsetX, scrollOffsetY - ; + ; // Delect scrollEl if (scrollParentEl !== rootEl) { @@ -160,7 +160,7 @@ scrollOffsetY = vy ? vy * speed : 0; scrollOffsetX = vx ? vx * speed : 0; - if ('function' === typeof(scrollCustomFn)) { + if ('function' === typeof (scrollCustomFn)) { return scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt); } @@ -202,7 +202,7 @@ var originalGroup = options.group; if (!originalGroup || typeof originalGroup != 'object') { - originalGroup = {name: originalGroup}; + originalGroup = { name: originalGroup }; } group.name = originalGroup.name; @@ -212,7 +212,7 @@ options.group = group; } - ; + ; /** @@ -261,7 +261,7 @@ fallbackClass: 'sortable-fallback', fallbackOnBody: false, fallbackTolerance: 0, - fallbackOffset: {x: 0, y: 0} + fallbackOffset: { x: 0, y: 0 } }; @@ -555,7 +555,7 @@ _onTouchMove: function (/**TouchEvent*/evt) { if (tapEvt) { - var options = this.options, + var options = this.options, fallbackTolerance = options.fallbackTolerance, fallbackOffset = options.fallbackOffset, touch = evt.touches ? evt.touches[0] : evt, @@ -781,7 +781,7 @@ halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, nextSibling = target.nextElementSibling, after = false - ; + ; if (floating) { var elTop = dragEl.offsetTop, @@ -795,7 +795,7 @@ } else { after = tgTop > elTop; } - } else if (!isMovingBetweenSortable) { + } else if (!isMovingBetweenSortable) { after = (nextSibling !== dragEl) && !isLong || halfway && isLong; } @@ -963,30 +963,30 @@ this._nulling(); }, - _nulling: function() { + _nulling: function () { rootEl = - dragEl = - parentEl = - ghostEl = - nextEl = - cloneEl = - lastDownEl = + dragEl = + parentEl = + ghostEl = + nextEl = + cloneEl = + lastDownEl = - scrollEl = - scrollParentEl = + scrollEl = + scrollParentEl = - tapEvt = - touchEvt = + tapEvt = + touchEvt = - moved = - newIndex = + moved = + newIndex = - lastEl = - lastCSS = + lastEl = + lastCSS = - putSortable = - activeGroup = - Sortable.active = null; + putSortable = + activeGroup = + Sortable.active = null; savedInputChecked.forEach(function (el) { el.checked = true; @@ -1455,7 +1455,7 @@ }; } })); - } catch (err) {} + } catch (err) { } // Export utils Sortable.utils = { @@ -1489,3 +1489,6 @@ Sortable.version = '1.6.0'; return Sortable; }); + + +// http://rubaxa.github.io/Sortable/ diff --git a/model/activity/activity.go b/model/activity/activity.go index cf10ed26..92bf54ad 100644 --- a/model/activity/activity.go +++ b/model/activity/activity.go @@ -95,6 +95,9 @@ const ( // TypeSentSecureLink records user sending secure document link to email address(es) TypeSentSecureLink Type = 12 + + // TypeDraft records user marking space/document as draft + TypeDraft Type = 13 ) // TypeName returns one-work descriptor for activity type