diff --git a/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.html b/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.html
new file mode 100644
index 00000000000..e4966768477
--- /dev/null
+++ b/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+ {{ variant === 'full' && showDisclaimer ? ('item.page.cc.license.disclaimer' | translate) : '' }}
+ {{ name }}
+
+
+
+
+
diff --git a/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.spec.ts b/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.spec.ts
new file mode 100644
index 00000000000..7a29fed2757
--- /dev/null
+++ b/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.spec.ts
@@ -0,0 +1,298 @@
+import {
+ ChangeDetectionStrategy,
+ NO_ERRORS_SCHEMA,
+} from '@angular/core';
+import {
+ ComponentFixture,
+ TestBed,
+ waitForAsync,
+} from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import {
+ TranslateLoader,
+ TranslateModule,
+} from '@ngx-translate/core';
+import { Item } from 'src/app/core/shared/item.model';
+import {
+ MetadataMap,
+ MetadataValue,
+} from 'src/app/core/shared/metadata.models';
+import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils';
+import { createPaginatedList } from 'src/app/shared/testing/utils.test';
+
+import { APP_CONFIG } from '../../../../../../config/app-config.interface';
+import { environment } from '../../../../../../environments/environment';
+import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
+import { ItemPageCcLicenseFieldComponent } from './item-page-cc-license-field.component';
+
+
+interface TestInstance {
+ metadata: {
+ 'dc.rights.uri'?: string;
+ 'dc.rights'?: string;
+ };
+ componentInputs?: {
+ variant?: 'small' | 'full';
+ showName?: boolean;
+ showDisclaimer?: boolean;
+ };
+}
+
+
+interface TestCase {
+ testInstance: TestInstance;
+ expected: {
+ render: boolean,
+ showImage: boolean,
+ showName: boolean,
+ showDisclaimer: boolean
+ };
+}
+
+
+const licenseNameMock = 'CC LICENSE NAME';
+
+
+const testCases: TestCase[] = [
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': undefined, 'dc.rights': undefined },
+ },
+ expected: {
+ render: false,
+ showName: false,
+ showImage: false,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': null, 'dc.rights': null },
+ },
+ expected: {
+ render: false,
+ showName: false,
+ showImage: false,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': 'https://creativecommons.org/licenses/by/4.0', 'dc.rights': null },
+ },
+ expected: {
+ render: false,
+ showName: false,
+ showImage: false,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': null, 'dc.rights': licenseNameMock },
+ },
+ expected: {
+ render: false,
+ showName: false,
+ showImage: false,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': 'https://creativecommons.org/licenses/by/4.0', 'dc.rights': licenseNameMock },
+ },
+ expected: {
+ render: true,
+ showName: true,
+ showImage: true,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': 'https://creativecommons.org/', 'dc.rights': licenseNameMock },
+ },
+ expected: {
+ render: true,
+ showName: true,
+ showImage: false,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': 'https://creativecommons.org/', 'dc.rights': licenseNameMock },
+ componentInputs: { variant: 'full' },
+ },
+ expected: {
+ render: true,
+ showName: true,
+ showImage: false,
+ showDisclaimer: true,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': 'https://creativecommons.org/', 'dc.rights': licenseNameMock },
+ componentInputs: { showName: false },
+ },
+ expected: {
+ render: true,
+ showName: true,
+ showImage: false,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': 'https://creativecommons.org/licenses/by/4.0', 'dc.rights': licenseNameMock },
+ componentInputs: { showName: false },
+ },
+ expected: {
+ render: true,
+ showName: false,
+ showImage: true,
+ showDisclaimer: false,
+ },
+ },
+ {
+ testInstance: {
+ metadata: { 'dc.rights.uri': 'https://creativecommons.org/licenses/by/4.0', 'dc.rights': licenseNameMock },
+ componentInputs: { variant: 'full', showDisclaimer: false },
+ },
+ expected: {
+ render: true,
+ showName: true,
+ showImage: true,
+ showDisclaimer: false,
+ },
+ },
+];
+
+
+// Updates the component fixture with parameters from the test instance
+function configureFixture(
+ fixture: ComponentFixture,
+ testInstance: TestInstance,
+) {
+ const item = Object.assign(new Item(), {
+ bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
+ metadata: new MetadataMap(),
+ });
+
+ for (const [key, value] of Object.entries(testInstance.metadata)) {
+ item.metadata[key] = [
+ {
+ language: 'en_US',
+ value: value,
+ },
+ ] as MetadataValue[];
+ }
+
+ let component: ItemPageCcLicenseFieldComponent = fixture.componentInstance;
+ for (const [key, value] of Object.entries(testInstance.componentInputs ?? {})) {
+ component[key] = value;
+ }
+ component.item = item;
+
+ fixture.detectChanges();
+}
+
+
+describe('ItemPageCcLicenseFieldComponent', () => {
+ let fixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ void TestBed.configureTestingModule({
+ imports: [
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: TranslateLoaderMock,
+ },
+ }),
+ ItemPageCcLicenseFieldComponent,
+ ],
+ providers: [{ provide: APP_CONFIG, useValue: environment }],
+ schemas: [NO_ERRORS_SCHEMA],
+ })
+ .overrideComponent(ItemPageCcLicenseFieldComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default },
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(waitForAsync(() => {
+ fixture = TestBed.createComponent(ItemPageCcLicenseFieldComponent);
+ }));
+
+ testCases.forEach((testCase) => {
+ describe('', () => {
+ beforeEach(async () => {
+ configureFixture(fixture, testCase.testInstance);
+
+ // Waits the image to be loaded or to cause an error when loading
+ let imgEl = fixture.debugElement.query(By.css('img'));
+ if (imgEl) {
+ await new Promise((resolve, reject) => {
+ imgEl.nativeElement.addEventListener('load', () => resolve());
+ imgEl.nativeElement.addEventListener('error', () => resolve());
+ });
+ }
+
+ // Executes again because the 'img' element could have been updated due to a loading error
+ fixture.detectChanges();
+ });
+
+ it('should render or not the component',
+ () => {
+ const componentEl = fixture.debugElement.query(By.css('.item-page-field'));
+ expect(Boolean(componentEl)).toBe(testCase.expected.render);
+ },
+ );
+
+ it('should show/hide CC license name',
+ () => {
+ const nameEl = fixture.debugElement.query(de => de.nativeElement.id === 'cc-name');
+ expect(Boolean(nameEl)).toBe(testCase.expected.showName);
+ if (nameEl && testCase.expected.showName) {
+ expect(nameEl.nativeElement.innerHTML).toContain(licenseNameMock);
+ }
+ },
+ );
+
+ it('should show CC license image',
+ () => {
+ const imgEl = fixture.debugElement.query(By.css('img'));
+ expect(Boolean(imgEl)).toBe(testCase.expected.showImage);
+ },
+ );
+
+ it('should use name fallback when CC image fails loading',
+ () => {
+ const nameEl = fixture.debugElement.query(de => de.nativeElement.id === 'cc-name');
+ expect(Boolean(nameEl)).toBe(testCase.expected.showName);
+ if (nameEl && testCase.expected.showName) {
+ expect(nameEl.nativeElement.innerHTML).toContain(licenseNameMock);
+ }
+ },
+ );
+
+ it('should show or not CC license disclaimer',
+ () => {
+ const disclaimerEl = fixture.debugElement.query(By.css('span'));
+ if (testCase.expected.showDisclaimer) {
+ expect(disclaimerEl).toBeTruthy();
+ expect(disclaimerEl.nativeElement.innerHTML).toContain('item.page.cc.license.disclaimer');
+ } else if (testCase.expected.render) {
+ expect(disclaimerEl).toBeTruthy();
+ expect(disclaimerEl.nativeElement.innerHTML).not.toContain('item.page.cc.license.disclaimer');
+ } else {
+ expect(disclaimerEl).toBeFalsy();
+ }
+ },
+ );
+ });
+ });
+});
diff --git a/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.ts b/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.ts
new file mode 100644
index 00000000000..5d19fe4b4e2
--- /dev/null
+++ b/src/app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component.ts
@@ -0,0 +1,73 @@
+import {
+ NgClass,
+ NgIf,
+ NgStyle,
+} from '@angular/common';
+import {
+ Component,
+ Input,
+ OnInit,
+} from '@angular/core';
+import { TranslateModule } from '@ngx-translate/core';
+import { Item } from 'src/app/core/shared/item.model';
+import { MetadataFieldWrapperComponent } from 'src/app/shared/metadata-field-wrapper/metadata-field-wrapper.component';
+
+@Component({
+ selector: 'ds-item-page-cc-license-field',
+ templateUrl: './item-page-cc-license-field.component.html',
+ standalone: true,
+ imports: [NgIf, NgClass, NgStyle, TranslateModule, MetadataFieldWrapperComponent],
+})
+/**
+ * Displays the item's Creative Commons license image in it's simple item page
+ */
+export class ItemPageCcLicenseFieldComponent implements OnInit {
+ /**
+ * The item to display the CC license image for
+ */
+ @Input() item: Item;
+
+ /**
+ * 'full' variant shows image, a disclaimer (optional) and name (always), better for the item page content.
+ * 'small' variant shows image and name (optional), better for the item page sidebar
+ */
+ @Input() variant?: 'small' | 'full' = 'small';
+
+ /**
+ * Filed name containing the CC license URI, as configured in the back-end, in the 'dspace.cfg' file, propertie
+ * 'cc.license.uri'
+ */
+ @Input() ccLicenseUriField? = 'dc.rights.uri';
+
+ /**
+ * Filed name containing the CC license name, as configured in the back-end, in the 'dspace.cfg' file, propertie
+ * 'cc.license.name'
+ */
+ @Input() ccLicenseNameField? = 'dc.rights';
+
+ /**
+ * Shows the CC license name with the image. Always show if image fails to load
+ */
+ @Input() showName? = true;
+
+ /**
+ * Shows the disclaimer in the 'full' variant of the component
+ */
+ @Input() showDisclaimer? = true;
+
+ uri: string;
+ name: string;
+ showImage = true;
+ imgSrc: string;
+
+ ngOnInit() {
+ this.uri = this.item.firstMetadataValue(this.ccLicenseUriField);
+ this.name = this.item.firstMetadataValue(this.ccLicenseNameField);
+
+ // Extracts the CC license code from the URI
+ const regex = /.*creativecommons.org\/(licenses|publicdomain)\/([^/]+)/gm;
+ const matches = regex.exec(this.uri ?? '') ?? [];
+ const ccCode = matches.length > 2 ? matches[2] : null;
+ this.imgSrc = ccCode ? `assets/images/cc-licenses/${ccCode}.png` : null;
+ }
+}
diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html
index cd1c0c445ce..f7e44ab14c4 100644
--- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html
+++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html
@@ -86,6 +86,8 @@
[fields]="['datacite.relation.isReferencedBy']"
[label]="'item.page.dataset'">
+
+
{{"item.page.link.full" | translate}}
diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts
index 74b4b50875b..a9ef9b2eadf 100644
--- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts
+++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts
@@ -21,6 +21,7 @@ import { ThemedMediaViewerComponent } from '../../../media-viewer/themed-media-v
import { MiradorViewerComponent } from '../../../mirador-viewer/mirador-viewer.component';
import { ThemedFileSectionComponent } from '../../field-components/file-section/themed-file-section.component';
import { ItemPageAbstractFieldComponent } from '../../field-components/specific-field/abstract/item-page-abstract-field.component';
+import { ItemPageCcLicenseFieldComponent } from '../../field-components/specific-field/cc-license/item-page-cc-license-field.component';
import { ItemPageDateFieldComponent } from '../../field-components/specific-field/date/item-page-date-field.component';
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
import { ThemedItemPageTitleFieldComponent } from '../../field-components/specific-field/title/themed-item-page-field.component';
@@ -39,8 +40,26 @@ import { ItemComponent } from '../shared/item.component';
templateUrl: './untyped-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
- imports: [NgIf, ThemedResultsBackButtonComponent, MiradorViewerComponent, ThemedItemPageTitleFieldComponent, DsoEditMenuComponent, MetadataFieldWrapperComponent, ThemedThumbnailComponent, ThemedMediaViewerComponent, ThemedFileSectionComponent, ItemPageDateFieldComponent, ThemedMetadataRepresentationListComponent, GenericItemPageFieldComponent, ItemPageAbstractFieldComponent, ItemPageUriFieldComponent, CollectionsComponent, RouterLink, AsyncPipe, TranslateModule],
+ imports: [
+ NgIf,
+ ThemedResultsBackButtonComponent,
+ MiradorViewerComponent,
+ ThemedItemPageTitleFieldComponent,
+ DsoEditMenuComponent,
+ MetadataFieldWrapperComponent,
+ ThemedThumbnailComponent,
+ ThemedMediaViewerComponent,
+ ThemedFileSectionComponent,
+ ItemPageDateFieldComponent,
+ ThemedMetadataRepresentationListComponent,
+ GenericItemPageFieldComponent,
+ ItemPageAbstractFieldComponent,
+ ItemPageUriFieldComponent,
+ CollectionsComponent,
+ RouterLink,
+ AsyncPipe,
+ TranslateModule,
+ ItemPageCcLicenseFieldComponent,
+ ],
})
-export class UntypedItemComponent extends ItemComponent {
-
-}
+export class UntypedItemComponent extends ItemComponent {}
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index fa956491c69..6a4857f4e00 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -6715,4 +6715,8 @@
"search.filters.filter.notifyEndorsement.placeholder": "Notify Endorsement",
"search.filters.filter.notifyEndorsement.label": "Search Notify Endorsement",
+
+ "item.page.cc.license.title": "Creative Commons license",
+
+ "item.page.cc.license.disclaimer": "Except where otherwised noted, this item's license is described as",
}
diff --git a/src/assets/images/cc-licenses/by-nc-nd.png b/src/assets/images/cc-licenses/by-nc-nd.png
new file mode 100644
index 00000000000..5a8dbd0652e
Binary files /dev/null and b/src/assets/images/cc-licenses/by-nc-nd.png differ
diff --git a/src/assets/images/cc-licenses/by-nc-sa.png b/src/assets/images/cc-licenses/by-nc-sa.png
new file mode 100644
index 00000000000..b9a55533c0c
Binary files /dev/null and b/src/assets/images/cc-licenses/by-nc-sa.png differ
diff --git a/src/assets/images/cc-licenses/by-nc.png b/src/assets/images/cc-licenses/by-nc.png
new file mode 100644
index 00000000000..25e284099a0
Binary files /dev/null and b/src/assets/images/cc-licenses/by-nc.png differ
diff --git a/src/assets/images/cc-licenses/by-nd.png b/src/assets/images/cc-licenses/by-nd.png
new file mode 100644
index 00000000000..fc3d26789a0
Binary files /dev/null and b/src/assets/images/cc-licenses/by-nd.png differ
diff --git a/src/assets/images/cc-licenses/by-sa.png b/src/assets/images/cc-licenses/by-sa.png
new file mode 100644
index 00000000000..8770732928c
Binary files /dev/null and b/src/assets/images/cc-licenses/by-sa.png differ
diff --git a/src/assets/images/cc-licenses/by.png b/src/assets/images/cc-licenses/by.png
new file mode 100644
index 00000000000..c8473a24786
Binary files /dev/null and b/src/assets/images/cc-licenses/by.png differ
diff --git a/src/assets/images/cc-licenses/zero.png b/src/assets/images/cc-licenses/zero.png
new file mode 100644
index 00000000000..4ff09a0bb26
Binary files /dev/null and b/src/assets/images/cc-licenses/zero.png differ
diff --git a/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts
index aeae9a519a5..6fbb035d2f2 100644
--- a/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts
+++ b/src/themes/custom/app/item-page/simple/item-types/untyped-item/untyped-item.component.ts
@@ -17,6 +17,7 @@ import { ThemedMediaViewerComponent } from '../../../../../../../app/item-page/m
import { MiradorViewerComponent } from '../../../../../../../app/item-page/mirador-viewer/mirador-viewer.component';
import { ThemedFileSectionComponent } from '../../../../../../../app/item-page/simple/field-components/file-section/themed-file-section.component';
import { ItemPageAbstractFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/abstract/item-page-abstract-field.component';
+import { ItemPageCcLicenseFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/cc-license/item-page-cc-license-field.component';
import { ItemPageDateFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/date/item-page-date-field.component';
import { GenericItemPageFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component';
import { ThemedItemPageTitleFieldComponent } from '../../../../../../../app/item-page/simple/field-components/specific-field/title/themed-item-page-field.component';
@@ -36,12 +37,34 @@ import { ThemedThumbnailComponent } from '../../../../../../../app/thumbnail/the
@Component({
selector: 'ds-untyped-item',
// styleUrls: ['./untyped-item.component.scss'],
- styleUrls: ['../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component.scss'],
+ styleUrls: [
+ '../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component.scss',
+ ],
// templateUrl: './untyped-item.component.html',
- templateUrl: '../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component.html',
+ templateUrl:
+ '../../../../../../../app/item-page/simple/item-types/untyped-item/untyped-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
- imports: [NgIf, ThemedResultsBackButtonComponent, MiradorViewerComponent, ThemedItemPageTitleFieldComponent, DsoEditMenuComponent, MetadataFieldWrapperComponent, ThemedThumbnailComponent, ThemedMediaViewerComponent, ThemedFileSectionComponent, ItemPageDateFieldComponent, ThemedMetadataRepresentationListComponent, GenericItemPageFieldComponent, ItemPageAbstractFieldComponent, ItemPageUriFieldComponent, CollectionsComponent, RouterLink, AsyncPipe, TranslateModule],
+ imports: [
+ NgIf,
+ ThemedResultsBackButtonComponent,
+ MiradorViewerComponent,
+ ThemedItemPageTitleFieldComponent,
+ DsoEditMenuComponent,
+ MetadataFieldWrapperComponent,
+ ThemedThumbnailComponent,
+ ThemedMediaViewerComponent,
+ ThemedFileSectionComponent,
+ ItemPageDateFieldComponent,
+ ThemedMetadataRepresentationListComponent,
+ GenericItemPageFieldComponent,
+ ItemPageAbstractFieldComponent,
+ ItemPageUriFieldComponent,
+ CollectionsComponent,
+ RouterLink,
+ AsyncPipe,
+ TranslateModule,
+ ItemPageCcLicenseFieldComponent,
+ ],
})
-export class UntypedItemComponent extends BaseComponent {
-}
+export class UntypedItemComponent extends BaseComponent {}