Merge pull request #507 from SrS2225a/master

Support For Vanity Urls
This commit is contained in:
Sergio Brighenti 2023-08-14 22:45:48 +02:00 committed by GitHub
commit 7f07ecdfad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 91 additions and 1 deletions

View file

@ -188,6 +188,43 @@ class MediaController extends Controller
return $this->streamMedia($request, $response, $this->storage, $media, 'attachment'); return $this->streamMedia($request, $response, $this->storage, $media, 'attachment');
} }
/**
* @param Request $request
* @param Response $response
* @param string $vanity
* @param string $id
*
* @return Response
* @throws HttpNotFoundException
* @throws HttpBadRequestException
*/
public function createVanity(Request $request, Response $response, int $id): Response
{
$media = $this->database->query('SELECT * FROM `uploads` WHERE `id` = ? LIMIT 1', $id)->fetch();
$vanity = param($request, 'vanity');
$vanity = preg_replace('/[^a-z0-9]+/', '-', strtolower($vanity));
//handle collisions
$collision = $this->database->query('SELECT * FROM `uploads` WHERE `code` = ? AND `id` != ? LIMIT 1', [$vanity, $id])->fetch();
if (!$media) {
throw new HttpNotFoundException($request);
}
if ($vanity === '' || $collision) {
throw new HttpBadRequestException($request);
}
$this->database->query('UPDATE `uploads` SET `code` = ? WHERE `id` = ?', [$vanity, $media->id]);
$media->code = $vanity;
$response->getBody()->write(json_encode($media));
$this->logger->info('User '.$this->session->get('username').' created a vanity link for media '.$media->id);
return $response;
}
/** /**
* @param Request $request * @param Request $request
* @param Response $response * @param Response $response

1
app/routes.php Normal file → Executable file
View file

@ -62,6 +62,7 @@ $app->group('', function (RouteCollectorProxy $group) {
$group->post('/upload/{id}/publish', [MediaController::class, 'togglePublish'])->setName('upload.publish'); $group->post('/upload/{id}/publish', [MediaController::class, 'togglePublish'])->setName('upload.publish');
$group->post('/upload/{id}/unpublish', [MediaController::class, 'togglePublish'])->setName('upload.unpublish'); $group->post('/upload/{id}/unpublish', [MediaController::class, 'togglePublish'])->setName('upload.unpublish');
$group->post('/upload/{id}/vanity', [MediaController::class, 'createVanity'])->setName('upload.vanity');
$group->get('/upload/{id}/raw', [MediaController::class, 'getRawById'])->add(AdminMiddleware::class)->setName('upload.raw'); $group->get('/upload/{id}/raw', [MediaController::class, 'getRawById'])->add(AdminMiddleware::class)->setName('upload.raw');
$group->map(['GET', 'POST'], '/upload/{id}/delete', [MediaController::class, 'delete'])->setName('upload.delete'); $group->map(['GET', 'POST'], '/upload/{id}/delete', [MediaController::class, 'delete'])->setName('upload.delete');

3
resources/lang/en.lang.php Normal file → Executable file
View file

@ -30,6 +30,8 @@ return [
'download' => 'Download', 'download' => 'Download',
'upload' => 'Upload', 'upload' => 'Upload',
'delete' => 'Delete', 'delete' => 'Delete',
'confirm' => 'Confirm',
'vanity_url' => 'Custom URL',
'publish' => 'Publish', 'publish' => 'Publish',
'hide' => 'Hide', 'hide' => 'Hide',
'files' => 'Files', 'files' => 'Files',
@ -58,7 +60,6 @@ return [
'reg_date' => 'Registration Date', 'reg_date' => 'Registration Date',
'none' => 'None', 'none' => 'None',
'open' => 'Open', 'open' => 'Open',
'confirm' => 'Confirmation',
'confirm_string' => 'Are you sure?', 'confirm_string' => 'Are you sure?',
'installed' => 'Installation completed successfully!', 'installed' => 'Installation completed successfully!',
'bad_login' => 'Wrong credentials.', 'bad_login' => 'Wrong credentials.',

View file

@ -0,0 +1,19 @@
<div class="modal fade" id="modalVanity" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ lang('vanity_url') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<input type="text" class="form-control" id="modalVanity-input" maxlength="64">
</div>
<div class="modal-footer">
<button class="btn btn-primary media-vanity" id="modalVanity-link"><i class="fas fa-check fa-fw"></i> {{ lang('confirm') }}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ lang('no') }}</button>
</div>
</div>
</div>
</div>

2
resources/templates/dashboard/grid.twig Normal file → Executable file
View file

@ -28,6 +28,7 @@
{% else %} {% else %}
<a class="btn btn-sm btn-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a> <a class="btn btn-sm btn-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
{% endif %} {% endif %}
<button class="btn btn-info btn-sm public-vanity" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('vanity_url') }}"><i class="fas fa-star"></i></button>
<button type="button" class="btn btn-sm btn-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}"> <button type="button" class="btn btn-sm btn-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button> </button>
@ -68,4 +69,5 @@
<div class="text-center text-muted"><i>{{ lang('no_media') }}</i></div> <div class="text-center text-muted"><i>{{ lang('no_media') }}</i></div>
{% endif %} {% endif %}
</div> </div>
{% include 'comp/modal_vanity.twig' %}
{% endblock %} {% endblock %}

2
resources/templates/dashboard/list.twig Normal file → Executable file
View file

@ -6,6 +6,7 @@
{% include 'comp/navbar.twig' %} {% include 'comp/navbar.twig' %}
<div class="container"> <div class="container">
{% include 'comp/alert.twig' %} {% include 'comp/alert.twig' %}
{% include 'comp/modal_vanity.twig' %}
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-body"> <div class="card-body">
@ -77,6 +78,7 @@
{% else %} {% else %}
<a href="javascript:void(0)" class="btn btn-sm btn-outline-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a> <a href="javascript:void(0)" class="btn btn-sm btn-outline-info publish-toggle" data-toggle="tooltip" title="{{ lang('publish') }}" data-id="{{ media.id }}" data-published="{{ media.published }}"><i class="fas fa-check-circle"></i></a>
{% endif %} {% endif %}
<a href="javascript:void(0)" class="btn btn-sm btn-outline-info public-vanity" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('vanity_url') }}"><i class="fas fa-star"></i></a>
<a href="javascript:void(0)" class="btn btn-sm btn-outline-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}"><i class="fas fa-trash"></i></a> <a href="javascript:void(0)" class="btn btn-sm btn-outline-danger media-delete" data-link="{{ route('upload.delete', {'id': media.id}) }}" data-id="{{ media.id }}" data-toggle="tooltip" title="{{ lang('delete') }}"><i class="fas fa-trash"></i></a>
</div> </div>
</td> </td>

2
resources/templates/upload/public.twig Normal file → Executable file
View file

@ -49,6 +49,7 @@
<a href="{{ url }}/raw" class="btn btn-secondary my-2 my-sm-0" data-toggle="tooltip" title="{{ lang('raw') }}"><i class="fas fa-file-alt fa-lg fa-fw"></i></a> <a href="{{ url }}/raw" class="btn btn-secondary my-2 my-sm-0" data-toggle="tooltip" title="{{ lang('raw') }}"><i class="fas fa-file-alt fa-lg fa-fw"></i></a>
<a href="{{ url }}/download" class="btn btn-warning my-2 my-sm-0" data-toggle="tooltip" title="{{ lang('download') }}"><i class="fas fa-cloud-download-alt fa-lg fa-fw"></i></a> <a href="{{ url }}/download" class="btn btn-warning my-2 my-sm-0" data-toggle="tooltip" title="{{ lang('download') }}"><i class="fas fa-cloud-download-alt fa-lg fa-fw"></i></a>
{% if session.get('logged') %} {% if session.get('logged') %}
<a href="javascript:void(0)" class="btn btn-info my-2 my-sm-0 public-vanity" data-link="{{ route('upload.vanity', {'id': media.mediaId}) }}" data-id="{{ media.mediaId }}" data-toggle="tooltip" title="{{ lang('vanity') }}"><i class="fas fa-star fa-lg fa-fw"></i></a>
<a href="javascript:void(0)" class="btn btn-danger my-2 my-sm-0 public-delete" data-link="{{ route('upload.delete', {'id': media.mediaId}) }}" data-toggle="tooltip" title="{{ lang('delete') }}"><i class="fas fa-trash fa-lg fa-fw"></i></a> <a href="javascript:void(0)" class="btn btn-danger my-2 my-sm-0 public-delete" data-link="{{ route('upload.delete', {'id': media.mediaId}) }}" data-toggle="tooltip" title="{{ lang('delete') }}"><i class="fas fa-trash fa-lg fa-fw"></i></a>
{% endif %} {% endif %}
</div> </div>
@ -162,4 +163,5 @@
</div> </div>
</div> </div>
{% include 'comp/modal_delete.twig' %} {% include 'comp/modal_delete.twig' %}
{% include 'comp/modal_vanity.twig' %}
{% endblock %} {% endblock %}

26
src/js/app.js Normal file → Executable file
View file

@ -29,8 +29,10 @@ var app = {
$('.user-delete').click(app.modalDelete); $('.user-delete').click(app.modalDelete);
$('.public-delete').click(app.modalDelete); $('.public-delete').click(app.modalDelete);
$('.public-vanity').click(app.modalVanity);
$('.media-delete').click(app.mediaDelete); $('.media-delete').click(app.mediaDelete);
$('.publish-toggle').click(app.publishToggle); $('.publish-toggle').click(app.publishToggle);
$('.refresh-token').click(app.refreshToken); $('.refresh-token').click(app.refreshToken);
$('#themes').mousedown(app.loadThemes); $('#themes').mousedown(app.loadThemes);
$('.checkForUpdatesButton').click(app.checkForUpdates); $('.checkForUpdatesButton').click(app.checkForUpdates);
@ -57,6 +59,30 @@ var app = {
$('#modalDelete-link').attr('href', $(this).data('link')); $('#modalDelete-link').attr('href', $(this).data('link'));
$('#modalDelete').modal('show'); $('#modalDelete').modal('show');
}, },
modalVanity: function () {
var id = $(this).data('id');
$('#modalVanity').modal('show');
$('#modalVanity-link').click(function () {
var $callerButton = $(this);
$.post(window.AppConfig.base_url + '/upload/' + id + '/vanity', {vanity: $('#modalVanity-input').val()}, function (responseData, status) {
$callerButton.tooltip('dispose');
$('#modalVanity').modal('hide');
$('#modalVanity-input').val('');
var parsedData = JSON.parse(responseData);
if ($('#media_' + id).find('.btn-group').length > 0) {
$('#media_' + id).find('.btn-group').find('a').each(function (item) {
var oldUrl = $(this).attr('href');
var newUrl = oldUrl.replace(oldUrl.substr(oldUrl.lastIndexOf('/') + 1), parsedData.code.code);
$(this).attr('href', newUrl);
});
} else {
var oldUrl = window.location.href;
var newUrl = oldUrl.replace(oldUrl.substr(oldUrl.lastIndexOf('/') + 1), parsedData.code.code);
window.location.href = newUrl;
}
});
});
},
publishToggle: function () { publishToggle: function () {
var id = $(this).data('id'); var id = $(this).data('id');
var $callerButton = $(this); var $callerButton = $(this);