mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #1597 from atmire/w2p-90263_issue-8205_no-embargoed-files-on-google-scholar-meta-tag
Fix for embargoed file links on Google Scholar Meta Tag "citation_pdf_url"
This commit is contained in:
@@ -3,7 +3,7 @@ import { Meta, Title } from '@angular/platform-browser';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
|
||||
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 { Item } from '../shared/item.model';
|
||||
@@ -23,6 +23,7 @@ import { DSONameService } from '../breadcrumbs/dso-name.service';
|
||||
import { HardRedirectService } from '../services/hard-redirect.service';
|
||||
import { getMockStore } from '@ngrx/store/testing';
|
||||
import { AddMetaTagAction, ClearMetaTagAction } from './meta-tag.actions';
|
||||
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||
|
||||
describe('MetadataService', () => {
|
||||
let metadataService: MetadataService;
|
||||
@@ -38,6 +39,7 @@ describe('MetadataService', () => {
|
||||
let rootService: RootDataService;
|
||||
let translateService: TranslateService;
|
||||
let hardRedirectService: HardRedirectService;
|
||||
let authorizationService: AuthorizationDataService;
|
||||
|
||||
let router: Router;
|
||||
let store;
|
||||
@@ -76,6 +78,9 @@ describe('MetadataService', () => {
|
||||
hardRedirectService = jasmine.createSpyObj( {
|
||||
getCurrentOrigin: 'https://request.org',
|
||||
});
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true)
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
store = getMockStore({ initialState });
|
||||
@@ -92,7 +97,8 @@ describe('MetadataService', () => {
|
||||
undefined,
|
||||
rootService,
|
||||
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', () => {
|
||||
it('should link to first and only Bitstream regardless of format', fakeAsync(() => {
|
||||
(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 { DSpaceObject } from '../shared/dspace-object.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 { getBitstreamDownloadRoute } from '../../app-routing-paths';
|
||||
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 { coreSelector } from '../core.selectors';
|
||||
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
|
||||
@@ -82,6 +87,7 @@ export class MetadataService {
|
||||
private rootService: RootDataService,
|
||||
private store: Store<CoreState>,
|
||||
private hardRedirectService: HardRedirectService,
|
||||
private authorizationService: AuthorizationDataService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -296,7 +302,6 @@ export class MetadataService {
|
||||
).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
switchMap((bundle: Bundle) =>
|
||||
|
||||
// First try the primary bitstream
|
||||
bundle.primaryBitstream.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
@@ -307,13 +312,14 @@ export class MetadataService {
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
getDownloadableBitstream(this.authorizationService),
|
||||
// return the bundle as well so we can use it again if there's no primary bitstream
|
||||
map((bitstream: Bitstream) => [bundle, bitstream])
|
||||
)
|
||||
),
|
||||
switchMap(([bundle, primaryBitstream]: [Bundle, Bitstream]) => {
|
||||
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)];
|
||||
} else {
|
||||
// Otherwise consider the regular bitstreams in the bundle
|
||||
@@ -321,8 +327,8 @@ export class MetadataService {
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((bitstreamRd: RemoteData<PaginatedList<Bitstream>>) => {
|
||||
if (hasValue(bitstreamRd.payload) && bitstreamRd.payload.totalElements === 1) {
|
||||
// If there's only one bitstream in the bundle, emit its link
|
||||
return [getBitstreamDownloadRoute(bitstreamRd.payload.page[0])];
|
||||
// If there's only one bitstream in the bundle, emit its link if its downloadable
|
||||
return this.getBitLinkIfDownloadable(bitstreamRd.payload.page[0], bitstreamRd);
|
||||
} else {
|
||||
// Otherwise check all bitstreams to see if one matches the format whitelist
|
||||
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
|
||||
* included in {@linkcode CITATION_PDF_URL_MIMETYPES}
|
||||
@@ -388,9 +408,14 @@ export class MetadataService {
|
||||
// for the link at the end
|
||||
map((format: BitstreamFormat) => [bitstream, format])
|
||||
)),
|
||||
// Filter out only pairs with whitelisted formats
|
||||
filter(([, format]: [Bitstream, BitstreamFormat]) =>
|
||||
hasValue(format) && this.CITATION_PDF_URL_MIMETYPES.includes(format.mimetype)),
|
||||
// Check if bitstream downloadable
|
||||
switchMap(([bitstream, format]: [Bitstream, BitstreamFormat]) => observableOf(bitstream).pipe(
|
||||
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
|
||||
take(1),
|
||||
// Emit the link of the match
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Router, UrlTree } from '@angular/router';
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||
import {
|
||||
debounceTime,
|
||||
filter,
|
||||
@@ -27,6 +27,9 @@ import { getForbiddenRoute, getPageNotFoundRoute } from '../../app-routing-paths
|
||||
import { getEndUserAgreementPath } from '../../info/info-routing-paths';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
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', {
|
||||
providedIn: 'root',
|
||||
@@ -355,3 +358,21 @@ export const metadataFieldsToString = () =>
|
||||
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