Skip to content

Commit dc43428

Browse files
authored
Merge pull request #632 from matias49/oembed
Provide oembed endpoint
2 parents b862d6b + 99a52d7 commit dc43428

File tree

5 files changed

+155
-48
lines changed

5 files changed

+155
-48
lines changed

app/Controllers/Controller.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,38 @@ public function getUserCreateValidator(Request $request)
151151
->alertIf($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `email` = ?', param($request, 'email'))->fetch()->count != 0, 'email_taken')
152152
->alertIf($this->database->query('SELECT COUNT(*) AS `count` FROM `users` WHERE `username` = ?', param($request, 'username'))->fetch()->count != 0, 'username_taken');
153153
}
154+
155+
/**
156+
* @param $userCode
157+
* @param $mediaCode
158+
*
159+
* @param bool $withTags
160+
* @return mixed
161+
*/
162+
protected function getMedia($userCode, $mediaCode, $withTags = false)
163+
{
164+
$mediaCode = pathinfo($mediaCode)['filename'];
165+
166+
$media = $this->database->query(
167+
'SELECT `uploads`.*, `users`.*, `users`.`id` AS `userId`, `uploads`.`id` AS `mediaId` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1',
168+
[
169+
$userCode,
170+
$mediaCode,
171+
]
172+
)->fetch();
173+
174+
if (!$withTags || !$media) {
175+
return $media;
176+
}
177+
178+
$media->tags = [];
179+
foreach ($this->database->query(
180+
'SELECT `tags`.`id`, `tags`.`name` FROM `uploads_tags` INNER JOIN `tags` ON `uploads_tags`.`tag_id` = `tags`.`id` WHERE `uploads_tags`.`upload_id` = ?',
181+
$media->mediaId
182+
) as $tag) {
183+
$media->tags[$tag->id] = $tag->name;
184+
}
185+
186+
return $media;
187+
}
154188
}

app/Controllers/MediaController.php

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,14 @@ public function show(
9292
return $this->streamMedia($request, $response, $filesystem, $media);
9393
}
9494

95+
$url = route('public', ['userCode' => $media->user_code, 'mediaCode' => $media->code]);
9596
return view()->render($response, 'upload/public.twig', [
9697
'delete_token' => $token,
9798
'media' => $media,
9899
'type' => $type,
99-
'url' => urlFor(glue($userCode, $mediaCode)),
100+
'url' => $url,
101+
'raw_url' => route('public.raw', ['userCode' => $media->user_code, 'mediaCode' => $media->code, 'ext' => $media->extension]),
102+
'oembed' => route('oembed', [], '?url=' . urlencode($url)),
100103
'copy_raw' => $this->session->get('copy_raw', false),
101104
]);
102105
}
@@ -365,40 +368,6 @@ protected function deleteMedia(Request $request, string $storagePath, int $id, i
365368
}
366369
}
367370

368-
/**
369-
* @param $userCode
370-
* @param $mediaCode
371-
*
372-
* @param bool $withTags
373-
* @return mixed
374-
*/
375-
protected function getMedia($userCode, $mediaCode, $withTags = false)
376-
{
377-
$mediaCode = pathinfo($mediaCode)['filename'];
378-
379-
$media = $this->database->query(
380-
'SELECT `uploads`.*, `users`.*, `users`.`id` AS `userId`, `uploads`.`id` AS `mediaId` FROM `uploads` INNER JOIN `users` ON `uploads`.`user_id` = `users`.`id` WHERE `user_code` = ? AND `uploads`.`code` = ? LIMIT 1',
381-
[
382-
$userCode,
383-
$mediaCode,
384-
]
385-
)->fetch();
386-
387-
if (!$withTags || !$media) {
388-
return $media;
389-
}
390-
391-
$media->tags = [];
392-
foreach ($this->database->query(
393-
'SELECT `tags`.`id`, `tags`.`name` FROM `uploads_tags` INNER JOIN `tags` ON `uploads_tags`.`tag_id` = `tags`.`id` WHERE `uploads_tags`.`upload_id` = ?',
394-
$media->mediaId
395-
) as $tag) {
396-
$media->tags[$tag->id] = $tag->name;
397-
}
398-
399-
return $media;
400-
}
401-
402371
/**
403372
* @param Request $request
404373
* @param Response $response
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace App\Controllers;
4+
5+
use League\Flysystem\FileNotFoundException;
6+
use Psr\Http\Message\ResponseInterface as Response;
7+
use Psr\Http\Message\ServerRequestInterface as Request;
8+
use Slim\Exception\HttpNotFoundException;
9+
use Slim\Exception\HttpUnauthorizedException;
10+
11+
class OembedController extends Controller
12+
{
13+
private $width = 720;
14+
private $height = 480;
15+
16+
/**
17+
* Provides the oEmbed response for a given media sent as 'url' in the query string
18+
* @see https://oembed.com/
19+
*
20+
* @param Request $request
21+
* @param Response $response
22+
* @param string $userCode
23+
* @param string $mediaCode
24+
*
25+
* @return Response
26+
* @throws HttpNotFoundException
27+
* @throws HttpUnauthorizedException
28+
* @throws FileNotFoundException
29+
*
30+
*/
31+
public function oembed(Request $request, Response $response): Response
32+
{
33+
if ($this->getSetting('image_embeds') !== 'on') {
34+
throw new HttpUnauthorizedException($request);
35+
}
36+
37+
$query = $request->getQueryParams();
38+
if (empty($query['url']) || ($path = parse_url(urldecode($query['url']), PHP_URL_PATH)) === null) {
39+
throw new HttpNotFoundException($request);
40+
}
41+
42+
// The url of the media should end with (userCode)/(mediaCode).
43+
if (!preg_match_all('/\/([0-9a-zA-Z]+)\/([0-9a-zA-Z.]+)$/', $path, $matches, PREG_SET_ORDER, 0)) {
44+
throw new HttpNotFoundException($request);
45+
}
46+
47+
$media = $this->getMedia($matches[0][1], $matches[0][2]);
48+
if (!$media || (!$media->published && $this->session->get('user_id') !== $media->user_id && !$this->session->get(
49+
'admin',
50+
false
51+
))) {
52+
throw new HttpNotFoundException($request);
53+
}
54+
$media->extension = pathinfo($media->filename, PATHINFO_EXTENSION);
55+
56+
$url = route('public.raw', ['userCode' => $media->user_code, 'mediaCode' => $media->code, 'ext' => $media->extension]);
57+
58+
$this->setOEmbedSizes($query);
59+
60+
// Providing default oEmbed return.
61+
$oembedData = [
62+
'version' => '1.0',
63+
'type' => 'link',
64+
'title' => $media->filename,
65+
'url' => $url,
66+
'width' => $this->width,
67+
'height' => $this->height,
68+
];
69+
70+
$mime = $this->storage->getMimetype($media->storage_path);
71+
$type = explode('/', $mime)[0];
72+
if ($type === 'image') {
73+
$oembedData = array_merge($oembedData, [
74+
'type' => 'photo',
75+
]);
76+
} elseif ($type === 'video') {
77+
$oembedData = array_merge($oembedData, [
78+
'type' => 'video',
79+
'html' => "<iframe src='{$url}' width='{$this->width}' height='{$this->height}'></iframe>",
80+
]);
81+
}
82+
return json($response, $oembedData);
83+
}
84+
85+
/**
86+
* @param array $query
87+
* @return void
88+
*/
89+
private function setOEmbedSizes(array $query): void
90+
{
91+
if (!empty($query['maxwidth']) && ($maxwidth = intval($query['maxwidth'])) > 0) {
92+
$this->width = min($this->width, $maxwidth);
93+
}
94+
if (!empty($query['maxheight']) && ($maxheight = intval($query['maxheight'])) > 0) {
95+
$this->height = min($this->height, $maxheight);
96+
}
97+
}
98+
}

app/routes.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use App\Controllers\DashboardController;
99
use App\Controllers\ExportController;
1010
use App\Controllers\MediaController;
11+
use App\Controllers\OembedController;
1112
use App\Controllers\ProfileController;
1213
use App\Controllers\SettingController;
1314
use App\Controllers\TagController;
@@ -91,3 +92,5 @@
9192
$app->post('/{userCode}/{mediaCode}/delete/{token}', [MediaController::class, 'deleteByToken'])->setName('public.delete')->add(CheckForMaintenanceMiddleware::class);
9293
$app->get('/{userCode}/{mediaCode}/raw[.{ext}]', [MediaController::class, 'getRaw'])->setName('public.raw');
9394
$app->get('/{userCode}/{mediaCode}/download', [MediaController::class, 'download'])->setName('public.download');
95+
96+
$app->get('/oembed', [OembedController::class, 'oembed'])->setName('oembed');

resources/templates/upload/public.twig

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
{% block head %}
66
{% if type == 'image' %}
7-
<link rel="preload" href="{{ url }}/raw" as="{{ type }}">
7+
<link rel="preload" href="{{ raw_url }}" as="{{ type }}">
88
{% endif %}
99
{% endblock %}
1010

@@ -13,22 +13,25 @@
1313
<meta property="og:type" content="website"/>
1414
<meta id="embed-title" property="og:title" content="{{ media.filename }} ({{ media.size }})">
1515
<meta id="embed-desc" property="og:description" content="{{ lang('date') }}: {{ media.timestamp }}">
16+
<link rel="alternate" type="application/json+oembed"
17+
href="{{ oembed }}"
18+
title="{{ media.filename }}" />
1619
{% if type == 'image' %}
17-
<meta id="embed-image" property="og:image" content="{{ url }}/raw">
18-
<meta id="discord" name="twitter:image" content="{{ url }}/raw">
19-
<meta id="image-src" name="twitter:image:src" content="{{ url }}/raw">
20+
<meta id="embed-image" property="og:image" content="{{ raw_url }}">
21+
<meta id="discord" name="twitter:image" content="{{ raw_url }}">
22+
<meta id="image-src" name="twitter:image:src" content="{{ raw_url }}">
2023
{% elseif type == 'video' %}
2124
<meta name="twitter:card" content="player" />
2225
<meta name="twitter:title" content="{{ media.filename }} ({{ media.size }})" />
2326
<meta name="twitter:image" content="0" />
24-
<meta name="twitter:player:stream" content="{{ url }}/raw" />
27+
<meta name="twitter:player:stream" content="{{ raw_url }}" />
2528
<meta name="twitter:player:width" content="720" />
2629
<meta name="twitter:player:height" content="480" />
2730
<meta name="twitter:player:stream:content_type" content="{{ media.mimetype }}" />
2831

29-
<meta property="og:url" content="{{ url }}/raw" />
30-
<meta property="og:video" content="{{ url }}/raw" />
31-
<meta property="og:video:secure_url" content="{{ url }}/raw" />
32+
<meta property="og:url" content="{{ raw_url }}" />
33+
<meta property="og:video" content="{{ raw_url }}" />
34+
<meta property="og:video:secure_url" content="{{ raw_url }}" />
3235
<meta property="og:video:type" content="{{ media.mimetype }}" />
3336
<meta property="og:video:width" content="720" />
3437
<meta property="og:video:height" content="480" />
@@ -46,7 +49,7 @@
4649
<div class="collapse navbar-collapse" id="navbarCollapse">
4750
<div class="ml-auto">
4851
<a href="javascript:void(0)" class="btn btn-success my-2 my-sm-0 btn-clipboard" data-toggle="tooltip" title="{{ lang('copy_link') }}" data-clipboard-text="{{ urlFor(glue(media.user_code, media.code) ~ (copy_raw ? '/raw.' ~ media.extension : '.' ~ media.extension)) }}"><i class="fas fa-link fa-lg fa-fw"></i></a>
49-
<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>
52+
<a href="{{ raw_url }}" 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>
5053
<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>
5154
{% if session.get('logged') %}
5255
<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>
@@ -76,7 +79,7 @@
7679
{% set typeMatched = true %}
7780
<div class="row mb-2">
7881
<div class="col-md-12">
79-
<img src="{{ url }}/raw" class="img-thumbnail rounded mx-auto d-block" alt="{{ media.filename }}">
82+
<img src="{{ raw_url }}" class="img-thumbnail rounded mx-auto d-block" alt="{{ media.filename }}">
8083
</div>
8184
</div>
8285
{% elseif type is same as ('text') %}
@@ -90,7 +93,7 @@
9093
{% set typeMatched = true %}
9194
<div class="media-player media-audio">
9295
<audio id="player" autoplay controls loop preload="auto">
93-
<source src="{{ url }}/raw" type="{{ media.mimetype }}">
96+
<source src="{{ raw_url }}" type="{{ media.mimetype }}">
9497
Your browser does not support HTML5 audio.
9598
<a href="{{ url }}/download" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
9699
</audio>
@@ -99,14 +102,14 @@
99102
{% set typeMatched = true %}
100103
<div class="media-player">
101104
<video id="player" autoplay controls loop preload="auto">
102-
<source src="{{ url }}/raw" type="{{ media.mimetype }}">
105+
<source src="{{ raw_url }}" type="{{ media.mimetype }}">
103106
Your browser does not support HTML5 video.
104107
<a href="{{ url }}/download" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
105108
</video>
106109
</div>
107110
{% elseif media.mimetype is same as ('application/pdf') %}
108111
{% set typeMatched = true %}
109-
<object type="{{ media.mimetype }}" data="{{ url }}/raw" class="pdf-viewer">
112+
<object type="{{ media.mimetype }}" data="{{ raw_url }}" class="pdf-viewer">
110113
Your browser does not support PDF previews.
111114
<a href="{{ url }}/download" class="btn btn-dark btn-lg"><i class="fas fa-cloud-download-alt fa-fw"></i> Download</a>
112115
</object>

0 commit comments

Comments
 (0)