mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-18 15:33:04 +00:00
88248: #346: Withdrawn item tombstone page
This commit is contained in:
@@ -4,19 +4,21 @@
|
|||||||
<ds-item-alerts [item]="item"></ds-item-alerts>
|
<ds-item-alerts [item]="item"></ds-item-alerts>
|
||||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||||
<div class="d-flex flex-row">
|
<div *ngIf="!item.isWithdrawn || (isAdmin$|async)" class="full-item-info">
|
||||||
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
|
<div class="d-flex flex-row">
|
||||||
<div class="pl-2">
|
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
|
||||||
<ds-dso-page-edit-button [pageRoute]="itemPageRoute$ | async" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
<div class="pl-2">
|
||||||
|
<ds-dso-page-edit-button [pageRoute]="itemPageRoute$ | async" [dso]="item"
|
||||||
|
[tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="simple-view-link my-3" *ngIf="!fromWfi">
|
||||||
<div class="simple-view-link my-3" *ngIf="!fromWfi">
|
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
|
||||||
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
|
{{"item.page.link.simple" | translate}}
|
||||||
{{"item.page.link.simple" | translate}}
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<table class="table table-responsive table-striped">
|
||||||
<table class="table table-responsive table-striped">
|
<tbody>
|
||||||
<tbody>
|
|
||||||
<ng-container *ngFor="let mdEntry of (metadata$ | async) | keyvalue">
|
<ng-container *ngFor="let mdEntry of (metadata$ | async) | keyvalue">
|
||||||
<tr *ngFor="let mdValue of mdEntry.value">
|
<tr *ngFor="let mdValue of mdEntry.value">
|
||||||
<td>{{mdEntry.key}}</td>
|
<td>{{mdEntry.key}}</td>
|
||||||
@@ -24,14 +26,16 @@
|
|||||||
<td>{{mdValue.language}}</td>
|
<td>{{mdValue.language}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<ds-item-page-full-file-section [item]="item"></ds-item-page-full-file-section>
|
<ds-item-page-full-file-section [item]="item"></ds-item-page-full-file-section>
|
||||||
<ds-item-page-collections [item]="item"></ds-item-page-collections>
|
<ds-item-page-collections [item]="item"></ds-item-page-collections>
|
||||||
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
|
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
|
||||||
<div class="button-row bottom" *ngIf="fromWfi">
|
<div class="button-row bottom" *ngIf="fromWfi">
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<button class="btn btn-outline-secondary mr-1" (click)="back()"><i class="fas fa-arrow-left"></i> {{'item.page.return' | translate}}</button>
|
<button class="btn btn-outline-secondary mr-1" (click)="back()"><i
|
||||||
|
class="fas fa-arrow-left"></i> {{'item.page.return' | translate}}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -11,12 +11,15 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
|||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { createRelationshipsObservable } from '../simple/item-types/shared/item.component.spec';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
@@ -30,6 +33,13 @@ const mockItem: Item = Object.assign(new Item(), {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mockWithdrawnItem: Item = Object.assign(new Item(), {
|
||||||
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
|
metadata: [],
|
||||||
|
relationships: createRelationshipsObservable(),
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
const metadataServiceStub = {
|
const metadataServiceStub = {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
processRemoteData: () => {
|
processRemoteData: () => {
|
||||||
@@ -44,6 +54,7 @@ describe('FullItemPageComponent', () => {
|
|||||||
let authService: AuthService;
|
let authService: AuthService;
|
||||||
let routeStub: ActivatedRouteStub;
|
let routeStub: ActivatedRouteStub;
|
||||||
let routeData;
|
let routeData;
|
||||||
|
let authorizationDataService: AuthorizationDataService;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -61,6 +72,10 @@ describe('FullItemPageComponent', () => {
|
|||||||
data: observableOf(routeData)
|
data: observableOf(routeData)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
authorizationDataService = jasmine.createSpyObj('authorizationDataService', {
|
||||||
|
isAuthorized: observableOf(false),
|
||||||
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
@@ -74,6 +89,7 @@ describe('FullItemPageComponent', () => {
|
|||||||
{ provide: ItemDataService, useValue: {} },
|
{ provide: ItemDataService, useValue: {} },
|
||||||
{ provide: MetadataService, useValue: metadataServiceStub },
|
{ provide: MetadataService, useValue: metadataServiceStub },
|
||||||
{ provide: AuthService, useValue: authService },
|
{ provide: AuthService, useValue: authService },
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationDataService },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -111,4 +127,53 @@ describe('FullItemPageComponent', () => {
|
|||||||
expect(simpleViewBtn).toBeFalsy();
|
expect(simpleViewBtn).toBeFalsy();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
describe('when the item is withdrawn and the user is an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.isAdmin$ = observableOf(true);
|
||||||
|
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockWithdrawnItem));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
|
||||||
|
expect(objectLoader.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('when the item is withdrawn and the user is not an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockWithdrawnItem));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
|
||||||
|
expect(objectLoader).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the item is not withdrawn and the user is an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.isAdmin$ = observableOf(true);
|
||||||
|
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockItem));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
|
||||||
|
expect(objectLoader.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the item is not withdrawn and the user is not an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockItem));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
|
||||||
|
expect(objectLoader.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -15,6 +15,7 @@ import { fadeInOut } from '../../shared/animations/fade';
|
|||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,8 +47,9 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
|
|||||||
router: Router,
|
router: Router,
|
||||||
items: ItemDataService,
|
items: ItemDataService,
|
||||||
authService: AuthService,
|
authService: AuthService,
|
||||||
|
authorizationService: AuthorizationDataService,
|
||||||
private _location: Location) {
|
private _location: Location) {
|
||||||
super(route, router, items, authService);
|
super(route, router, items, authService, authorizationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<ds-item-alerts [item]="item"></ds-item-alerts>
|
<ds-item-alerts [item]="item"></ds-item-alerts>
|
||||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||||
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
<ds-listable-object-component-loader *ngIf="!item.isWithdrawn || (isAdmin$|async)" [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||||
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
|
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -21,6 +21,7 @@ import {
|
|||||||
} from '../../shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
@@ -28,10 +29,18 @@ const mockItem: Item = Object.assign(new Item(), {
|
|||||||
relationships: createRelationshipsObservable()
|
relationships: createRelationshipsObservable()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mockWithdrawnItem: Item = Object.assign(new Item(), {
|
||||||
|
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
|
metadata: [],
|
||||||
|
relationships: createRelationshipsObservable(),
|
||||||
|
isWithdrawn: true
|
||||||
|
});
|
||||||
|
|
||||||
describe('ItemPageComponent', () => {
|
describe('ItemPageComponent', () => {
|
||||||
let comp: ItemPageComponent;
|
let comp: ItemPageComponent;
|
||||||
let fixture: ComponentFixture<ItemPageComponent>;
|
let fixture: ComponentFixture<ItemPageComponent>;
|
||||||
let authService: AuthService;
|
let authService: AuthService;
|
||||||
|
let authorizationDataService: AuthorizationDataService;
|
||||||
|
|
||||||
const mockMetadataService = {
|
const mockMetadataService = {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
@@ -48,6 +57,9 @@ describe('ItemPageComponent', () => {
|
|||||||
isAuthenticated: observableOf(true),
|
isAuthenticated: observableOf(true),
|
||||||
setRedirectUrl: {}
|
setRedirectUrl: {}
|
||||||
});
|
});
|
||||||
|
authorizationDataService = jasmine.createSpyObj('authorizationDataService', {
|
||||||
|
isAuthorized: observableOf(false),
|
||||||
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
@@ -63,6 +75,7 @@ describe('ItemPageComponent', () => {
|
|||||||
{ provide: MetadataService, useValue: mockMetadataService },
|
{ provide: MetadataService, useValue: mockMetadataService },
|
||||||
{ provide: Router, useValue: {} },
|
{ provide: Router, useValue: {} },
|
||||||
{ provide: AuthService, useValue: authService },
|
{ provide: AuthService, useValue: authService },
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationDataService },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -102,4 +115,53 @@ describe('ItemPageComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when the item is withdrawn and the user is an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.isAdmin$ = observableOf(true);
|
||||||
|
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockWithdrawnItem);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
|
||||||
|
expect(objectLoader.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('when the item is withdrawn and the user is not an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockWithdrawnItem);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
|
||||||
|
expect(objectLoader).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the item is not withdrawn and the user is an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.isAdmin$ = observableOf(true);
|
||||||
|
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockItem);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
|
||||||
|
expect(objectLoader.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the item is not withdrawn and the user is not an admin', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockItem);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the item', () => {
|
||||||
|
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
|
||||||
|
expect(objectLoader.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { map } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
@@ -13,6 +13,8 @@ import { getAllSucceededRemoteDataPayload, redirectOn4xx } from '../../core/shar
|
|||||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { getItemPageRoute } from '../item-page-routing-paths';
|
import { getItemPageRoute } from '../item-page-routing-paths';
|
||||||
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -48,11 +50,17 @@ export class ItemPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
itemPageRoute$: Observable<string>;
|
itemPageRoute$: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the current user is an admin or not
|
||||||
|
*/
|
||||||
|
isAdmin$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private items: ItemDataService,
|
private items: ItemDataService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private authorizationService: AuthorizationDataService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,5 +75,7 @@ export class ItemPageComponent implements OnInit {
|
|||||||
getAllSucceededRemoteDataPayload(),
|
getAllSucceededRemoteDataPayload(),
|
||||||
map((item) => getItemPageRoute(item))
|
map((item) => getItemPageRoute(item))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<div *ngIf="item && !item.isDiscoverable" class="private-warning">
|
<div *ngIf="item && !item.isDiscoverable" class="private-warning">
|
||||||
<ds-alert [type]="AlertTypeEnum.Warning" [content]="'item.alerts.private' | translate"></ds-alert>
|
<ds-alert [type]="AlertTypeEnum.Warning" [content]="'item.alerts.private' | translate"></ds-alert>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="item && item.isWithdrawn" class="withdrawn-warning">
|
<div *ngIf="item && item.isWithdrawn" class="withdrawn-warning">
|
||||||
<ds-alert [type]="AlertTypeEnum.Warning" [content]="'item.alerts.withdrawn' | translate"></ds-alert>
|
<ds-alert [type]="AlertTypeEnum.Warning">
|
||||||
</div>
|
<div class="d-flex justify-content-between flex-wrap">
|
||||||
|
<span>{{'item.alerts.withdrawn' | translate}}</span>
|
||||||
|
<a routerLink="/home" class="btn btn-primary btn-sm">{{"404.link.home-page" | translate}}</a>
|
||||||
|
</div>
|
||||||
|
</ds-alert>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user