mirror of
https://github.com/Karaka-Management/oms-Media.git
synced 2026-02-09 22:18:42 +00:00
many small fixes
This commit is contained in:
parent
d4e46af1c7
commit
0e8c3a12cf
|
|
@ -85,13 +85,6 @@
|
|||
"type": "VARCHAR(255)",
|
||||
"null": false
|
||||
},
|
||||
"media_type": {
|
||||
"name": "media_type",
|
||||
"type": "INT",
|
||||
"null": true,
|
||||
"foreignTable": "media_type",
|
||||
"foreignKey": "media_type_id"
|
||||
},
|
||||
"media_description": {
|
||||
"name": "media_description",
|
||||
"type": "TEXT",
|
||||
|
|
|
|||
|
|
@ -336,6 +336,8 @@ final class Installer extends InstallerAbstract
|
|||
|
||||
$module->apiMediaUpload($request, $response);
|
||||
|
||||
$uploadedIds = $response->get('')['response'];
|
||||
|
||||
if ($data['create_collection']) {
|
||||
$response = new HttpResponse();
|
||||
$request = new HttpRequest(new HttpUri(''));
|
||||
|
|
@ -344,6 +346,7 @@ final class Installer extends InstallerAbstract
|
|||
$request->setData('name', (string) ($data['name'] ?? ''));
|
||||
$request->setData('virtualpath', (string) ($data['virtualPath'] ?? '/'));
|
||||
$request->setData('path', (string) ($data['path'] ?? '/Modules/Media/Files/' . ((string) ($data['name'] ?? ''))));
|
||||
$request->setData('media-list', \json_encode($uploadedIds));
|
||||
|
||||
$module->apiCollectionCreate($request, $response);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ use Modules\Media\Models\Collection;
|
|||
use Modules\Media\Models\CollectionMapper;
|
||||
use Modules\Media\Models\Media;
|
||||
use Modules\Media\Models\MediaContent;
|
||||
use Modules\Media\Models\MediaContentMapper;
|
||||
use Modules\Media\Models\MediaMapper;
|
||||
use Modules\Media\Models\MediaType;
|
||||
use Modules\Media\Models\MediaTypeL11nMapper;
|
||||
|
|
@ -47,11 +48,13 @@ use phpOMS\Message\RequestAbstract;
|
|||
use phpOMS\Message\ResponseAbstract;
|
||||
use phpOMS\Model\Html\Head;
|
||||
use phpOMS\Model\Message\FormValidation;
|
||||
use phpOMS\Security\Guard;
|
||||
use phpOMS\System\File\FileUtils;
|
||||
use phpOMS\System\File\Local\Directory;
|
||||
use phpOMS\System\MimeType;
|
||||
use phpOMS\Utils\ImageUtils;
|
||||
use phpOMS\Utils\Parser\Markdown\Markdown;
|
||||
use phpOMS\Utils\StringUtils;
|
||||
use phpOMS\Views\View;
|
||||
|
||||
/**
|
||||
|
|
@ -167,6 +170,79 @@ final class ApiController extends Controller
|
|||
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Media', 'Media successfully created.', $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a media file and replace the existing media file
|
||||
*
|
||||
* @param array $files Files
|
||||
* @param array $media Media files to update
|
||||
* @param bool $sameNameIfPossible Use exact same file name as original file name if the extension is the same.
|
||||
*
|
||||
* @return Media[]
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function replaceUploadFiles(
|
||||
array $files,
|
||||
array $media,
|
||||
bool $sameNameIfPossible = false
|
||||
) : array
|
||||
{
|
||||
if (empty($files) || \count($files) !== \count($media)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$nCounter = -1;
|
||||
foreach ($files as $file) {
|
||||
++$nCounter;
|
||||
|
||||
// set output dir same as existing media
|
||||
$outputDir = \dirname($media[$nCounter]->getAbsolutePath());
|
||||
|
||||
// set upload name (either same as old file name or new file name)
|
||||
$mediaFilename = \basename($media[$nCounter]->getAbsolutePath());
|
||||
$uploadFilename = \basename($file['tmp_name']);
|
||||
|
||||
$splitMediaFilename = \explode('.', $mediaFilename);
|
||||
$splitUploadFilename = \explode('.', $uploadFilename);
|
||||
|
||||
$mediaExtension = ($c = \count($splitMediaFilename)) > 1 ? $splitMediaFilename[$c - 1] : '';
|
||||
$uploadExtension = ($c = \count($splitUploadFilename)) > 1 ? $splitUploadFilename[$c - 1] : '';
|
||||
|
||||
if ($sameNameIfPossible && $mediaExtension === $uploadExtension) {
|
||||
$uploadFilename = $mediaFilename;
|
||||
}
|
||||
|
||||
// remove old file
|
||||
\unlink($media[$nCounter]->getAbsolutePath());
|
||||
|
||||
// upload file
|
||||
$upload = new UploadFile();
|
||||
$upload->outputDir = $outputDir;
|
||||
$upload->preserveFileName = $sameNameIfPossible;
|
||||
|
||||
$status = $upload->upload([$file], [$uploadFilename], true);
|
||||
$stat = \reset($status);
|
||||
|
||||
// update media data
|
||||
$media[$nCounter]->setPath(self::normalizeDbPath($stat['path']) . '/' . $stat['filename']);
|
||||
$media[$nCounter]->size = $stat['size'];
|
||||
$media[$nCounter]->extension = $stat['extension'];
|
||||
|
||||
MediaMapper::update()->execute($media[$nCounter]);
|
||||
|
||||
if (!empty($media[$nCounter]?->content->content)) {
|
||||
$media[$nCounter]->content->content = self::loadFileContent(
|
||||
$media[$nCounter]->getAbsolutePath(),
|
||||
$media[$nCounter]->extension
|
||||
);
|
||||
|
||||
MediaContentMapper::update()->execute($media[$nCounter]->content);
|
||||
}
|
||||
}
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a media file
|
||||
*
|
||||
|
|
@ -189,10 +265,10 @@ final class ApiController extends Controller
|
|||
* @since 1.0.0
|
||||
*/
|
||||
public function uploadFiles(
|
||||
array $names,
|
||||
array $fileNames,
|
||||
array $files,
|
||||
int $account,
|
||||
array $names = [],
|
||||
array $fileNames = [],
|
||||
array $files = [],
|
||||
int $account = 0,
|
||||
string $basePath = '/Modules/Media/Files',
|
||||
string $virtualPath = '',
|
||||
string $password = '',
|
||||
|
|
@ -338,25 +414,35 @@ final class ApiController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$app?->eventManager->triggerSimilar('PRE:Module:Media-media-create', '', $media);
|
||||
MediaMapper::create()->execute($media);
|
||||
|
||||
if ($app !== null) {
|
||||
$app->moduleManager->get('Admin')->createAccountModelPermission(
|
||||
new AccountPermission(
|
||||
$account,
|
||||
$app->unitId,
|
||||
$app->appName,
|
||||
self::NAME,
|
||||
self::NAME,
|
||||
PermissionCategory::MEDIA,
|
||||
$media->getId(),
|
||||
null,
|
||||
PermissionType::READ | PermissionType::MODIFY | PermissionType::DELETE | PermissionType::PERMISSION
|
||||
),
|
||||
$app?->eventManager->triggerSimilar('POST:Module:Media-media-create', '',
|
||||
[
|
||||
$account,
|
||||
null, $media,
|
||||
StringUtils::intHash(MediaMapper::class), 'Media-media-create',
|
||||
self::NAME,
|
||||
(string) $media->getId(),
|
||||
'',
|
||||
$ip
|
||||
);
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
$app?->moduleManager->get('Admin')->createAccountModelPermission(
|
||||
new AccountPermission(
|
||||
$account,
|
||||
$app->unitId,
|
||||
$app->appName,
|
||||
self::NAME,
|
||||
self::NAME,
|
||||
PermissionCategory::MEDIA,
|
||||
$media->getId(),
|
||||
null,
|
||||
PermissionType::READ | PermissionType::MODIFY | PermissionType::DELETE | PermissionType::PERMISSION
|
||||
),
|
||||
$account,
|
||||
$ip
|
||||
);
|
||||
|
||||
return $media;
|
||||
}
|
||||
|
|
@ -539,8 +625,8 @@ final class ApiController extends Controller
|
|||
return;
|
||||
}
|
||||
|
||||
$reference = $this->createReferenceFromRequest($request);
|
||||
$this->createModel($request->header->account, $reference, ReferenceMapper::class, 'reference', $request->getOrigin());
|
||||
$ref = $this->createReferenceFromRequest($request);
|
||||
$this->createModel($request->header->account, $ref, ReferenceMapper::class, 'media_reference', $request->getOrigin());
|
||||
|
||||
// get parent collection
|
||||
// create relation
|
||||
|
|
@ -555,9 +641,17 @@ final class ApiController extends Controller
|
|||
$parentCollectionId = $parentCollection->getId();
|
||||
}
|
||||
|
||||
CollectionMapper::writer()->createRelationTable('sources', [$parentCollectionId], $reference->getId());
|
||||
$this->createModelRelation(
|
||||
$request->header->account,
|
||||
$parentCollectionId,
|
||||
$ref->getId(),
|
||||
CollectionMapper::class,
|
||||
'sources',
|
||||
'',
|
||||
$request->getOrigin()
|
||||
);
|
||||
|
||||
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Reference', 'Reference successfully created.', $reference);
|
||||
$this->fillJsonResponse($request, $response, NotificationLevel::OK, 'Reference', 'Reference successfully created.', $ref);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -607,7 +701,20 @@ final class ApiController extends Controller
|
|||
// Collection add = directly pointing to other media element (disadvantage = we don't know if we are allowed to modify/delete)
|
||||
public function apiCollectionAdd(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
|
||||
{
|
||||
$collection = (int) $request->getData('collection');
|
||||
$media = $request->getDataJson('media-list');
|
||||
|
||||
foreach ($media as $file) {
|
||||
$this->createModelRelation(
|
||||
$request->header->account,
|
||||
$collection,
|
||||
$file,
|
||||
CollectionMapper::class,
|
||||
'sources',
|
||||
'',
|
||||
$request->getOrigin()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -695,8 +802,6 @@ final class ApiController extends Controller
|
|||
$mediaCollection->setVirtualPath($virtualPath);
|
||||
$mediaCollection->setPath($outputDir);
|
||||
|
||||
CollectionMapper::create()->execute($mediaCollection);
|
||||
|
||||
if (((bool) ($request->getData('create_directory') ?? false))
|
||||
&& !\is_dir($dirPath)
|
||||
) {
|
||||
|
|
@ -791,8 +896,16 @@ final class ApiController extends Controller
|
|||
$childCollection->setVirtualPath('/'. \ltrim($temp, '/'));
|
||||
$childCollection->setPath('/Modules/Media/Files' . $temp);
|
||||
|
||||
CollectionMapper::create()->execute($childCollection);
|
||||
CollectionMapper::writer()->createRelationTable('sources', [$childCollection->getId()], $parentCollection->getId());
|
||||
$this->createModel($account, $childCollection, CollectionMapper::class, 'collection', '127.0.0.1');
|
||||
$this->createModelRelation(
|
||||
$account,
|
||||
$parentCollection->getId(),
|
||||
$childCollection->getId(),
|
||||
CollectionMapper::class,
|
||||
'sources',
|
||||
'',
|
||||
'127.0.0.1'
|
||||
);
|
||||
|
||||
$parentCollection = $childCollection;
|
||||
$temp .= '/' . $paths[$i];
|
||||
|
|
@ -891,13 +1004,18 @@ final class ApiController extends Controller
|
|||
*/
|
||||
public function apiMediaExport(RequestAbstract $request, ResponseAbstract $response, mixed $data = null) : void
|
||||
{
|
||||
if (((int) $request->getData('id')) !== 0) {
|
||||
$filePath = '';
|
||||
$media = null;
|
||||
|
||||
if ($request->hasData('id')) {
|
||||
/** @var Media $media */
|
||||
$media = MediaMapper::get()->where('id', (int) $request->getData('id'))->execute();
|
||||
$media = MediaMapper::get()->where('id', (int) $request->getData('id'))->execute();
|
||||
$filePath = $media->getAbsolutePath();
|
||||
} else {
|
||||
$path = \urldecode($request->getData('path'));
|
||||
$media = new NullMedia();
|
||||
if (\is_file(__DIR__ . '/../../../' . \ltrim($path, '\\/'))) {
|
||||
|
||||
if (\is_file($filePath = __DIR__ . '/../../../' . \ltrim($path, '\\/'))) {
|
||||
$name = \explode('.', \basename($path));
|
||||
|
||||
$media->name = $name[0];
|
||||
|
|
@ -908,6 +1026,43 @@ final class ApiController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
if (!($media instanceof NullMedia)) {
|
||||
if ($request->header->account !== $media->createdBy->getId()
|
||||
&& !$this->app->accountManager->get($request->header->account)->hasPermission(
|
||||
PermissionType::READ,
|
||||
$this->app->unitId,
|
||||
$this->app->appName,
|
||||
self::NAME,
|
||||
PermissionCategory::MEDIA,
|
||||
$media->getId()
|
||||
)
|
||||
) {
|
||||
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
|
||||
$response->header->status = RequestStatusCode::R_403;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset($data, $data['guard'])) {
|
||||
if (!isset($data)) {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$data['guard'] = __DIR__ . '/../Files';
|
||||
}
|
||||
} else {
|
||||
if (!isset($data, $data['guard'])) {
|
||||
$this->fillJsonResponse($request, $response, NotificationLevel::HIDDEN, '', '', []);
|
||||
$response->header->status = RequestStatusCode::R_403;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Guard::isSafePath($filePath, $data['guard'])) {
|
||||
$response->header->status = RequestStatusCode::R_403;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($media->hasPassword()
|
||||
&& !$media->comparePassword((string) $request->getData('password'))
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ class MediaMapper extends DataMapperFactory
|
|||
->with('createdBy')
|
||||
->with('source')
|
||||
->with('tags')
|
||||
->with('tags/content')
|
||||
->with('tags/title')
|
||||
->where('virtualPath', $virtualPath)
|
||||
->where('status', $status);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
<div class="box" id="<?= $this->getId(); ?>-tags" data-limit="0" data-active="true">
|
||||
<template id="<?= $this->getId(); ?>-tagTemplate">
|
||||
<span class="tag red" data-tpl-value="/id" data-value="" data-uuid="" data-name="<?= $this->printHtml($this->name); ?>">
|
||||
<i class="fa fa-times"></i>
|
||||
<i class="fa fa-times close"></i>
|
||||
<span style="display: none;" data-name="type_prefix" data-tpl-value="/type_prefix" data-value=""></span>
|
||||
<span data-tpl-text="/id" data-name="id" data-tpl-value="/id" data-value=""></span>
|
||||
<span data-tpl-text="/name/0" data-tpl-value="/name/0" data-value=""></span>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ $next = empty($this->media)
|
|||
<td data-label="<?= $this->getHtml('Created'); ?>"><a href="<?= $url; ?>"><?= $this->printHtml($value->createdAt->format('Y-m-d')); ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($count === 0) : ?>
|
||||
<tr><td colspan="6" class="empty"><?= $this->getHtml('Empty', '0', '0'); ?>
|
||||
<tr><td colspan="7" class="empty"><?= $this->getHtml('Empty', '0', '0'); ?>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@ use \phpOMS\Uri\UriFactory;
|
|||
|
||||
?>
|
||||
|
||||
<section id="mediaFile" class="portlet">
|
||||
<div class="portlet-body">
|
||||
<div id="media" class="tabview tab-2 m-editor">
|
||||
<section id="mediaFile" class="portlet col-simple">
|
||||
<div class="portlet-body col-simple">
|
||||
<div id="media" class="tabview tab-2 m-editor col-simple">
|
||||
<ul class="tab-links">
|
||||
<li><label tabindex="0" for="media-c-tab-1"><?= $this->getHtml('Preview', 'Media'); ?></label>
|
||||
<li><label tabindex="0" for="media-c-tab-2"><?= $this->getHtml('Content', 'Media'); ?></label>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-content col-simple">
|
||||
<input type="radio" id="media-c-tab-1" name="tabular-1" checked>
|
||||
<div class="tab">
|
||||
<iframe style="min-height: 600px;" data-form="iUiSettings" data-name="iframeHelper" id="iHelperFrame" src="<?= UriFactory::build('{/backend}Resources/mozilla/Pdf/web/viewer.html?{?}&file=' . \urlencode(($this->media->isAbsolute ? '' : '/../../../../') . $this->media->getPath())); ?>" allowfullscreen></iframe>
|
||||
<div class="tab col-simple">
|
||||
<iframe class="col-simple" id="iHelperFrame" src="<?= UriFactory::build('Resources/mozilla/Pdf/web/viewer.html?{?}&file=' . \urlencode(($this->media->isAbsolute ? '' : '/../../../../') . $this->media->getPath())); ?>" allowfullscreen></iframe>
|
||||
</div>
|
||||
<input type="radio" id="media-c-tab-2" name="tabular-1">
|
||||
<div class="tab">
|
||||
|
|
|
|||
|
|
@ -18,25 +18,3 @@ use phpOMS\Utils\Parser\Presentation\PresentationParser;
|
|||
|
||||
Autoloader::addPath(__DIR__ . '/../../../../../../Resources/');
|
||||
?>
|
||||
<section id="mediaFile" class="portlet">
|
||||
<div class="portlet-body">
|
||||
<div id="media" class="tabview tab-2 m-editor">
|
||||
<ul class="tab-links">
|
||||
<li><label tabindex="0" for="media-c-tab-1"><?= $this->getHtml('Preview', 'Media'); ?></label>
|
||||
<li><label tabindex="0" for="media-c-tab-2">Status</label>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<input type="radio" id="media-c-tab-1" name="tabular-1" checked>
|
||||
<div class="tab">
|
||||
<iframe src="<?= UriFactory::build('{/api}media/export?id=' . $this->media->getId()); ?>&type=html"></iframe>
|
||||
</div>
|
||||
<input type="radio" id="media-c-tab-2" name="tabular-1" checked>
|
||||
<div class="tab">
|
||||
<?php
|
||||
echo PresentationParser::parsePresentation($this->media->getPath(), 'html');
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -19,20 +19,20 @@ use phpOMS\Uri\UriFactory;
|
|||
|
||||
Autoloader::addPath(__DIR__ . '/../../../../../../Resources/');
|
||||
?>
|
||||
<section id="mediaFile" class="portlet">
|
||||
<div class="portlet-body">
|
||||
<div id="media" class="tabview tab-2 m-editor">
|
||||
<section id="mediaFile" class="portlet col-simple">
|
||||
<div class="portlet-body col-simple">
|
||||
<div id="media" class="tabview tab-2 m-editor col-simple">
|
||||
<ul class="tab-links">
|
||||
<li><label tabindex="0" for="media-c-tab-1"><?= $this->getHtml('Preview', 'Media'); ?></label>
|
||||
<li><label tabindex="0" for="media-c-tab-2">CSV</label>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-content col-simple">
|
||||
<input type="radio" id="media-c-tab-1" name="tabular-1" checked>
|
||||
<div class="tab">
|
||||
<iframe src="<?= UriFactory::build('{/api}media/export?id=' . $this->media->getId()); ?>&type=html"></iframe>
|
||||
<div class="tab col-simple">
|
||||
<iframe class="col-simple" src="<?= UriFactory::build('{/api}media/export?id=' . $this->media->getId()); ?>&type=html"></iframe>
|
||||
</div>
|
||||
<input type="radio" id="media-c-tab-2" name="tabular-1" checked>
|
||||
<div class="tab">
|
||||
<div class="tab col-simple">
|
||||
<?php
|
||||
$reader = IOFactory::createReaderforFile(($this->media->isAbsolute ? '' : __DIR__ . '/../../../../../../') . $this->media->getPath());
|
||||
$reader->setReadDataOnly(true);
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ declare(strict_types=1);
|
|||
use phpOMS\Uri\UriFactory;
|
||||
|
||||
?>
|
||||
<section id="mediaFile" class="portlet">
|
||||
<div class="portlet-body">
|
||||
<iframe src="<?= UriFactory::build('{/api}media/export?id=' . $this->media->getId()); ?>&type=html"></iframe>
|
||||
<section id="mediaFile" class="portlet col-simple">
|
||||
<div class="portlet-body col-simple">
|
||||
<iframe class="col-simple" src="<?= UriFactory::build('{/api}media/export?id=' . $this->media->getId()); ?>&type=html"></iframe>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Karaka
|
||||
*
|
||||
* PHP Version 8.1
|
||||
*
|
||||
* @package Modules\Media
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 1.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
return ['Media' => [
|
||||
]];
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Karaka
|
||||
*
|
||||
* PHP Version 8.1
|
||||
*
|
||||
* @package Modules\Media
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 1.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
return ['Media' => [
|
||||
]];
|
||||
|
|
@ -19,6 +19,7 @@ return ['Media' => [
|
|||
'Author' => 'Author',
|
||||
'Back' => 'Back',
|
||||
'Changed' => 'Changed',
|
||||
'Content' => 'Content',
|
||||
'Changedby' => 'Changed by',
|
||||
'CreateCollection' => 'Create Collection',
|
||||
'CreateFile' => 'Create File',
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ echo $this->getData('nav')->render();
|
|||
$media = $media->class === MediaClass::REFERENCE ? $media->source : $media;
|
||||
?>
|
||||
|
||||
<div class="row" style="height: calc(100% - 85px);">
|
||||
<div class="col-xs-12">
|
||||
<div class="row col-simple">
|
||||
<div class="col-xs-12 col-simple">
|
||||
<?= $view->render($media); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user