diff --git a/elements/nuxeo-document-actions/nuxeo-replace-blob-button.js b/elements/nuxeo-document-actions/nuxeo-replace-blob-button.js index f7d7cc1113..e143503607 100644 --- a/elements/nuxeo-document-actions/nuxeo-replace-blob-button.js +++ b/elements/nuxeo-document-actions/nuxeo-replace-blob-button.js @@ -184,9 +184,44 @@ Polymer({ !this.isImmutable(doc) && !this.hasType(doc, 'Root') && !this.isTrashed(doc) && - !(doc.isRecord && this.xpath !== 'file:content') && - !(this.isUnderRetentionOrLegalHold(doc) && this.xpath === 'file:content') && - !(this.hasFacet(doc, 'ColdStorage') && this.hasContent(doc, 'coldstorage:coldContent')) + !(this.hasFacet(doc, 'ColdStorage') && this.hasContent(doc, 'coldstorage:coldContent')) && + !this._isPropUnderRetention(doc) ); }, + + _isPropUnderRetention(doc) { + if (doc && doc.isUnderRetentionOrLegalHold && doc.retainedProperties && doc.retainedProperties.length > 0) { + const { retainedProperties } = doc; + /* if retained property is multivalued attachment, and all files are to be retained, denoted by ‘*’, + then return true. + if retained property is multivalued attachment, but only a single file is to be retained, + then return true only for that file */ + return retainedProperties.find( + (prop) => + this._transformXpathRegex(prop, this.xpath) || // xpath = docname:files/*/file + prop.startsWith(this.xpath) || // xpath = docname:files/1/file + (prop.includes(this.xpath.split('/')[0]) && !prop.includes('/')), // xpath = docname:files + ); + } + return false; + }, + + _transformXpathRegex(prop, xpath) { + const transformedArray = []; + const splitter = '/'; + const star = '*'; + if (prop.includes(star)) { + let xpathArray = xpath.split(splitter); + + for (let i = 0; i < xpathArray.length; i++) { + if (!Number.isNaN(parseInt(xpathArray[i], 10))) { + xpathArray[i] = star; + } + transformedArray.push(xpathArray[i]); + } + xpathArray = transformedArray; + xpath = xpathArray.join(splitter); + } + return prop === xpath; + }, }); diff --git a/elements/nuxeo-document-attachments/nuxeo-document-attachments.js b/elements/nuxeo-document-attachments/nuxeo-document-attachments.js index 1695f54735..c87935a697 100644 --- a/elements/nuxeo-document-attachments/nuxeo-document-attachments.js +++ b/elements/nuxeo-document-attachments/nuxeo-document-attachments.js @@ -143,10 +143,19 @@ Polymer({ this.hasPermission(doc, 'WriteProperties') && !this.isImmutable(doc) && !this.hasType(doc, 'Root') && - !this.isTrashed(doc) + !this.isTrashed(doc) && + !this._isPropUnderRetention(doc) ); }, + _isPropUnderRetention(doc) { + if (doc && doc.isUnderRetentionOrLegalHold && doc.retainedProperties && doc.retainedProperties.length > 0) { + return doc.retainedProperties.some((prop) => prop.startsWith(this.xpath)); + } + + return false; + }, + _computeFiles() { if (this._hasFiles(this._attachments)) { return this._attachments; diff --git a/test/nuxeo-document-attachments.test.js b/test/nuxeo-document-attachments.test.js new file mode 100644 index 0000000000..b44b18c46c --- /dev/null +++ b/test/nuxeo-document-attachments.test.js @@ -0,0 +1,56 @@ +/** + @license + ©2023 Hyland Software, Inc. and its affiliates. All rights reserved. +All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { fixture, html } from '@nuxeo/testing-helpers'; +import '../elements/nuxeo-document-attachments/nuxeo-document-attachments'; + +suite('nuxeo-document-attachments', () => { + let element; + setup(async () => { + element = await fixture( + html` + + `, + ); + sinon.stub(element, 'hasPermission').returns(true); + sinon.stub(element, 'isImmutable').returns(false); + sinon.stub(element, 'hasType').returns(false); + sinon.stub(element, 'isTrashed').returns(false); + sinon.spy(element, '_isDropzoneAvailable'); + }); + + suite('should return whether property is under retention', () => { + const document = { + isUnderRetentionOrLegalHold: true, + retainedProperties: [ + 'checkext:single', + 'checkext:field1/2/item', + 'files:files/*/file', + 'checkext:multiple', + 'file:content', + ], + }; + test('when xpath = checkext:single, for dropzone', () => { + element.xpath = 'checkext:single'; + expect(element._isDropzoneAvailable(document)).to.eql(false); + }); + test('when xpath = checkext:multiple, for dropzone', () => { + element.xpath = 'checkext:multiple'; + expect(element._isDropzoneAvailable(document)).to.eql(false); + }); + }); +}); diff --git a/test/nuxeo-replace-blob-button.test.js b/test/nuxeo-replace-blob-button.test.js new file mode 100644 index 0000000000..25759a7f70 --- /dev/null +++ b/test/nuxeo-replace-blob-button.test.js @@ -0,0 +1,78 @@ +/** + @license + ©2023 Hyland Software, Inc. and its affiliates. All rights reserved. +All Hyland product names are registered or unregistered trademarks of Hyland Software, Inc. or its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ +import { fixture, html } from '@nuxeo/testing-helpers'; +import '../elements/nuxeo-document-actions/nuxeo-replace-blob-button'; + +suite('nuxeo-replace-blob-button', () => { + let element; + setup(async () => { + element = await fixture( + html` + + `, + ); + sinon.stub(element, 'hasPermission').returns(true); + sinon.stub(element, 'isImmutable').returns(false); + sinon.stub(element, 'hasType').returns(false); + sinon.stub(element, 'isTrashed').returns(false); + sinon.stub(element, 'hasFacet').returns(false); + sinon.stub(element, 'hasContent').returns(false); + sinon.spy(element, '_isAvailable'); + }); + + suite('should return whether property is under retention', () => { + const document = { + isUnderRetentionOrLegalHold: true, + retainedProperties: [ + 'checkext:single', + 'checkext:field1/2/item', + 'files:files/*/file', + 'checkext:multiple', + 'file:content', + ], + }; + test('when xpath = checkext:single, for document blob', () => { + element.xpath = 'checkext:single'; + expect(element._isAvailable(document)).to.eql(false); + }); + test('when xpath = checkext:multiple/0, for document attachement', () => { + element.xpath = 'checkext:multiple/0'; + expect(element._isAvailable(document)).to.eql(false); + }); + test('when xpath = checkext:multiple/1, for document attachement', () => { + element.xpath = 'checkext:multiple/1'; + expect(element._isAvailable(document)).to.eql(false); + }); + test('when xpath = checkext:field1/0, for custom property - document attachment', () => { + element.xpath = 'checkext:field1/0'; + expect(element._isAvailable(document)).to.eql(true); + }); + test('when xpath = checkext:field1/2, for custom property - document attachment', () => { + element.xpath = 'checkext:field1/2'; + expect(element._isAvailable(document)).to.eql(false); + }); + test('when xpath = files:files/0/file, for document attachement', () => { + element.xpath = 'files:files/0/file'; + expect(element._isAvailable(document)).to.eql(false); + }); + test('when xpath = file:content, for document viewer', () => { + element.xpath = 'file:content'; + expect(element._isAvailable(document)).to.eql(false); + }); + }); +});