mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge branch 'w2p-90263_issue-8205_no-embargoed-files-on-google-scholar-meta-tag-7.0' into w2p-90263_issue-8205_no-embargoed-files-on-google-scholar-meta-tag
This commit is contained in:
@@ -3,7 +3,7 @@ import { Meta, Title } from '@angular/platform-browser';
|
|||||||
import { NavigationEnd, Router } from '@angular/router';
|
import { NavigationEnd, Router } from '@angular/router';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of as observableOf, of } from 'rxjs';
|
||||||
|
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
@@ -23,6 +23,7 @@ import { DSONameService } from '../breadcrumbs/dso-name.service';
|
|||||||
import { HardRedirectService } from '../services/hard-redirect.service';
|
import { HardRedirectService } from '../services/hard-redirect.service';
|
||||||
import { getMockStore } from '@ngrx/store/testing';
|
import { getMockStore } from '@ngrx/store/testing';
|
||||||
import { AddMetaTagAction, ClearMetaTagAction } from './meta-tag.actions';
|
import { AddMetaTagAction, ClearMetaTagAction } from './meta-tag.actions';
|
||||||
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
|
||||||
describe('MetadataService', () => {
|
describe('MetadataService', () => {
|
||||||
let metadataService: MetadataService;
|
let metadataService: MetadataService;
|
||||||
@@ -38,6 +39,7 @@ describe('MetadataService', () => {
|
|||||||
let rootService: RootDataService;
|
let rootService: RootDataService;
|
||||||
let translateService: TranslateService;
|
let translateService: TranslateService;
|
||||||
let hardRedirectService: HardRedirectService;
|
let hardRedirectService: HardRedirectService;
|
||||||
|
let authorizationService: AuthorizationDataService;
|
||||||
|
|
||||||
let router: Router;
|
let router: Router;
|
||||||
let store;
|
let store;
|
||||||
@@ -76,6 +78,9 @@ describe('MetadataService', () => {
|
|||||||
hardRedirectService = jasmine.createSpyObj( {
|
hardRedirectService = jasmine.createSpyObj( {
|
||||||
getCurrentOrigin: 'https://request.org',
|
getCurrentOrigin: 'https://request.org',
|
||||||
});
|
});
|
||||||
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
|
isAuthorized: observableOf(true)
|
||||||
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
store = getMockStore({ initialState });
|
store = getMockStore({ initialState });
|
||||||
@@ -92,7 +97,8 @@ describe('MetadataService', () => {
|
|||||||
undefined,
|
undefined,
|
||||||
rootService,
|
rootService,
|
||||||
store,
|
store,
|
||||||
hardRedirectService
|
hardRedirectService,
|
||||||
|
authorizationService
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -300,6 +306,24 @@ describe('MetadataService', () => {
|
|||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
describe('bitstream not download allowed', () => {
|
||||||
|
it('should not have citation_pdf_url', fakeAsync(() => {
|
||||||
|
(bundleDataService.findByItemAndName as jasmine.Spy).and.returnValue(mockBundleRD$([MockBitstream3]));
|
||||||
|
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
|
|
||||||
|
(metadataService as any).processRouteChange({
|
||||||
|
data: {
|
||||||
|
value: {
|
||||||
|
dso: createSuccessfulRemoteDataObject(ItemMock),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tick();
|
||||||
|
expect(meta.addTag).not.toHaveBeenCalledWith(jasmine.objectContaining({ name: 'citation_pdf_url' }));
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('no primary Bitstream', () => {
|
describe('no primary Bitstream', () => {
|
||||||
it('should link to first and only Bitstream regardless of format', fakeAsync(() => {
|
it('should link to first and only Bitstream regardless of format', fakeAsync(() => {
|
||||||
(bundleDataService.findByItemAndName as jasmine.Spy).and.returnValue(mockBundleRD$([MockBitstream3]));
|
(bundleDataService.findByItemAndName as jasmine.Spy).and.returnValue(mockBundleRD$([MockBitstream3]));
|
||||||
|
@@ -18,7 +18,11 @@ import { BitstreamFormat } from '../shared/bitstream-format.model';
|
|||||||
import { Bitstream } from '../shared/bitstream.model';
|
import { Bitstream } from '../shared/bitstream.model';
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../shared/operators';
|
import {
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getDownloadableBitstream
|
||||||
|
} from '../shared/operators';
|
||||||
import { RootDataService } from '../data/root-data.service';
|
import { RootDataService } from '../data/root-data.service';
|
||||||
import { getBitstreamDownloadRoute } from '../../app-routing-paths';
|
import { getBitstreamDownloadRoute } from '../../app-routing-paths';
|
||||||
import { BundleDataService } from '../data/bundle-data.service';
|
import { BundleDataService } from '../data/bundle-data.service';
|
||||||
@@ -32,6 +36,7 @@ import { createSelector, select, Store } from '@ngrx/store';
|
|||||||
import { AddMetaTagAction, ClearMetaTagAction } from './meta-tag.actions';
|
import { AddMetaTagAction, ClearMetaTagAction } from './meta-tag.actions';
|
||||||
import { coreSelector } from '../core.selectors';
|
import { coreSelector } from '../core.selectors';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base selector function to select the metaTag section in the store
|
* The base selector function to select the metaTag section in the store
|
||||||
@@ -82,6 +87,7 @@ export class MetadataService {
|
|||||||
private rootService: RootDataService,
|
private rootService: RootDataService,
|
||||||
private store: Store<CoreState>,
|
private store: Store<CoreState>,
|
||||||
private hardRedirectService: HardRedirectService,
|
private hardRedirectService: HardRedirectService,
|
||||||
|
private authorizationService: AuthorizationDataService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +302,6 @@ export class MetadataService {
|
|||||||
).pipe(
|
).pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
switchMap((bundle: Bundle) =>
|
switchMap((bundle: Bundle) =>
|
||||||
|
|
||||||
// First try the primary bitstream
|
// First try the primary bitstream
|
||||||
bundle.primaryBitstream.pipe(
|
bundle.primaryBitstream.pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
@@ -307,13 +312,14 @@ export class MetadataService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
getDownloadableBitstream(this.authorizationService),
|
||||||
// return the bundle as well so we can use it again if there's no primary bitstream
|
// return the bundle as well so we can use it again if there's no primary bitstream
|
||||||
map((bitstream: Bitstream) => [bundle, bitstream])
|
map((bitstream: Bitstream) => [bundle, bitstream])
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
switchMap(([bundle, primaryBitstream]: [Bundle, Bitstream]) => {
|
switchMap(([bundle, primaryBitstream]: [Bundle, Bitstream]) => {
|
||||||
if (hasValue(primaryBitstream)) {
|
if (hasValue(primaryBitstream)) {
|
||||||
// If there was a primary bitstream, emit its link
|
// If there was a downloadable primary bitstream, emit its link
|
||||||
return [getBitstreamDownloadRoute(primaryBitstream)];
|
return [getBitstreamDownloadRoute(primaryBitstream)];
|
||||||
} else {
|
} else {
|
||||||
// Otherwise consider the regular bitstreams in the bundle
|
// Otherwise consider the regular bitstreams in the bundle
|
||||||
@@ -321,8 +327,8 @@ export class MetadataService {
|
|||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
switchMap((bitstreamRd: RemoteData<PaginatedList<Bitstream>>) => {
|
switchMap((bitstreamRd: RemoteData<PaginatedList<Bitstream>>) => {
|
||||||
if (hasValue(bitstreamRd.payload) && bitstreamRd.payload.totalElements === 1) {
|
if (hasValue(bitstreamRd.payload) && bitstreamRd.payload.totalElements === 1) {
|
||||||
// If there's only one bitstream in the bundle, emit its link
|
// If there's only one bitstream in the bundle, emit its link if its downloadable
|
||||||
return [getBitstreamDownloadRoute(bitstreamRd.payload.page[0])];
|
return this.getBitLinkIfDownloadable(bitstreamRd.payload.page[0], bitstreamRd);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise check all bitstreams to see if one matches the format whitelist
|
// Otherwise check all bitstreams to see if one matches the format whitelist
|
||||||
return this.getFirstAllowedFormatBitstreamLink(bitstreamRd);
|
return this.getFirstAllowedFormatBitstreamLink(bitstreamRd);
|
||||||
@@ -342,6 +348,20 @@ export class MetadataService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBitLinkIfDownloadable(bitstream: Bitstream, bitstreamRd: RemoteData<PaginatedList<Bitstream>>): Observable<string> {
|
||||||
|
return observableOf(bitstream).pipe(
|
||||||
|
getDownloadableBitstream(this.authorizationService),
|
||||||
|
switchMap((bit: Bitstream) => {
|
||||||
|
if (hasValue(bit)) {
|
||||||
|
return [getBitstreamDownloadRoute(bit)];
|
||||||
|
} else {
|
||||||
|
// Otherwise check all bitstreams to see if one matches the format whitelist
|
||||||
|
return this.getFirstAllowedFormatBitstreamLink(bitstreamRd);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For Items with more than one Bitstream (and no primary Bitstream), link to the first Bitstream with a MIME type
|
* For Items with more than one Bitstream (and no primary Bitstream), link to the first Bitstream with a MIME type
|
||||||
* included in {@linkcode CITATION_PDF_URL_MIMETYPES}
|
* included in {@linkcode CITATION_PDF_URL_MIMETYPES}
|
||||||
@@ -388,9 +408,14 @@ export class MetadataService {
|
|||||||
// for the link at the end
|
// for the link at the end
|
||||||
map((format: BitstreamFormat) => [bitstream, format])
|
map((format: BitstreamFormat) => [bitstream, format])
|
||||||
)),
|
)),
|
||||||
// Filter out only pairs with whitelisted formats
|
// Check if bitstream downloadable
|
||||||
filter(([, format]: [Bitstream, BitstreamFormat]) =>
|
switchMap(([bitstream, format]: [Bitstream, BitstreamFormat]) => observableOf(bitstream).pipe(
|
||||||
hasValue(format) && this.CITATION_PDF_URL_MIMETYPES.includes(format.mimetype)),
|
getDownloadableBitstream(this.authorizationService),
|
||||||
|
map((bit: Bitstream) => [bit, format])
|
||||||
|
)),
|
||||||
|
// Filter out only pairs with whitelisted formats and non-null bitstreams, null from download check
|
||||||
|
filter(([bitstream, format]: [Bitstream, BitstreamFormat]) =>
|
||||||
|
hasValue(format) && hasValue(bitstream) && this.CITATION_PDF_URL_MIMETYPES.includes(format.mimetype)),
|
||||||
// We only need 1
|
// We only need 1
|
||||||
take(1),
|
take(1),
|
||||||
// Emit the link of the match
|
// Emit the link of the match
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Router, UrlTree } from '@angular/router';
|
import { Router, UrlTree } from '@angular/router';
|
||||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
debounceTime,
|
debounceTime,
|
||||||
filter,
|
filter,
|
||||||
@@ -27,6 +27,9 @@ import { getForbiddenRoute, getPageNotFoundRoute } from '../../app-routing-paths
|
|||||||
import { getEndUserAgreementPath } from '../../info/info-routing-paths';
|
import { getEndUserAgreementPath } from '../../info/info-routing-paths';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
|
import { Bitstream } from './bitstream.model';
|
||||||
|
import { FeatureID } from '../data/feature-authorization/feature-id';
|
||||||
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
|
||||||
export const DEBOUNCE_TIME_OPERATOR = new InjectionToken<<T>(dueTime: number) => (source: Observable<T>) => Observable<T>>('debounceTime', {
|
export const DEBOUNCE_TIME_OPERATOR = new InjectionToken<<T>(dueTime: number) => (source: Observable<T>) => Observable<T>>('debounceTime', {
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -355,3 +358,21 @@ export const metadataFieldsToString = () =>
|
|||||||
return fieldSchemaArray.map((fieldSchema: { field: MetadataField, schema: MetadataSchema }) => fieldSchema.schema.prefix + '.' + fieldSchema.field.toString());
|
return fieldSchemaArray.map((fieldSchema: { field: MetadataField, schema: MetadataSchema }) => fieldSchema.schema.prefix + '.' + fieldSchema.field.toString());
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator to check if the given bitstream is downloadable
|
||||||
|
*/
|
||||||
|
export const getDownloadableBitstream = (authService: AuthorizationDataService) =>
|
||||||
|
(source: Observable<Bitstream>): Observable<Bitstream | null> =>
|
||||||
|
source.pipe(
|
||||||
|
switchMap((bit: Bitstream) => {
|
||||||
|
if (hasValue(bit)) {
|
||||||
|
return authService.isAuthorized(FeatureID.CanDownload, bit.self).pipe(
|
||||||
|
map((canDownload: boolean) => {
|
||||||
|
return canDownload ? bit : null;
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return observableOf(null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
Reference in New Issue
Block a user