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.test.js b/test/nuxeo-replace-blob.test.js
new file mode 100644
index 0000000000..25759a7f70
--- /dev/null
+++ b/test/nuxeo-replace-blob.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);
+ });
+ });
+});