[Embed] Add UI element and fix some bugs

This commit is contained in:
Diogo Peralta Cordeiro 2021-08-14 15:04:30 +01:00
parent a43f1a641a
commit d23312aff9
Signed by: diogo
GPG Key ID: 18D2D35001FBFAB0
3 changed files with 186 additions and 33 deletions

View File

@ -49,6 +49,7 @@ use App\Entity\Attachment;
use App\Entity\Link;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\DuplicateFoundException;
use App\Util\Exception\NotFoundException;
use App\Util\Exception\ServerException;
@ -81,6 +82,144 @@ class Embed extends Plugin
'.*' => '', // Default to allowing any host
];
public bool $store_image = true; // Whether to maintain a copy of the original media or only a thumbnail of it
public ?int $thumbnail_width;
public ?int $thumbnail_height;
public ?int $max_size;
public ?bool $smart_crop;
private function getStoreImage(): bool
{
return $this->store_image;
}
private function getMaxSize(): int
{
return $this->max_size ?? Common::config('attachments', 'file_quota');
}
private function getSmartCrop(): bool
{
return $this->smart_crop ?? Common::config('thumbnail', 'smart_crop');
}
/**
* Add common favicon, embed sizes and social media image sizes
* (Commented out ancient sizes)
* TODO: This is a temporary "solution" until we handle different sizes properly, discuss with Eliseu
*
* @param array $sizes
*
* @return bool
*/
public function onGetAllowedThumbnailSizes(array &$sizes): bool
{
$sizes[] = ['width' => 16, 'height' => 16]; // Standard favicon size for browsers
$sizes[] = ['width' => 24, 'height' => 24]; // IE9 pinned site size for user interface
$sizes[] = ['width' => 32, 'height' => 32]; // Standard for most desktop browsers, facebook small photo thumbnail
$sizes[] = ['width' => 36, 'height' => 36]; // Facebook big photo thumbnail
// $sizes[] = ['width' => 48, 'height' => 48]; // Windows site (mid-size standard favicon)
$sizes[] = ['width' => 55, 'height' => 55]; // Pinterest small thumb
// $sizes[] = ['width' => 57, 'height' => 57]; // Standard iOS home screen (iPod Touch, iPhone first generation to 3G)
// $sizes[] = ['width' => 60, 'height' => 60]; // iPhone touch up to iOS 7
// $sizes[] = ['width' => 64, 'height' => 64]; // Windows site, Safari Reader List sidebar in HiDPI/Retina
// $sizes[] = ['width' => 70, 'height' => 70]; // Win 8.1 Metro tile
// $sizes[] = ['width' => 72, 'height' => 72]; // iPad touch up to iOS 6
// $sizes[] = ['width' => 76, 'height' => 76]; // iPad home screen icon up to iOS 7
// $sizes[] = ['width' => 96, 'height' => 96]; // GoogleTV icon
$sizes[] = ['width' => 110, 'height' => 110]; // Instagram profile picture
// $sizes[] = ['width' => 114, 'height' => 114]; // iPhone retina touch up to iOS6
$sizes[] = ['width' => 116, 'height' => 116]; // Facebook page small square shared link
// $sizes[] = ['width' => 120, 'height' => 120]; // iPhone retina touch up to iOS6
$sizes[] = ['width' => 128, 'height' => 128]; // Chrome Web Store icon and Small Windows 8 Star Screen Icon, Facebook profile picture smartphone
// $sizes[] = ['width' => 144, 'height' => 144]; // IE10 Metro tile for pinned site, Apple iPad retina iOS 6 to iOS 7
$sizes[] = ['width' => 150, 'height' => 150]; // Win 8.1 Metro tile (standard MS tile)
$sizes[] = ['width' => 154, 'height' => 154]; // Facebook feed small square shared link
$sizes[] = ['width' => 152, 'height' => 152]; // Apple iPad iOS 10 retina touch icon
$sizes[] = ['width' => 161, 'height' => 161]; // Instagram thumbnails
$sizes[] = ['width' => 165, 'height' => 165]; // Pinterest profile picture
$sizes[] = ['width' => 167, 'height' => 167]; // Apple iPad Retina touch icon
$sizes[] = ['width' => 170, 'height' => 170]; // Facebook Profile Picture desktop
$sizes[] = ['width' => 180, 'height' => 180]; // Apple iPhone Retina touch icon, Facebook Profile Picture
$sizes[] = ['width' => 192, 'height' => 192]; // Google Developer Web App Manifest Recommendation
// $sizes[] = ['width' => 195, 'height' => 195]; // Opera Speed Dial icon
$sizes[] = ['width' => 196, 'height' => 196]; // Chrome for Android home screen icon
$sizes[] = ['width' => 200, 'height' => 200]; // GitHub profile photo
$sizes[] = ['width' => 222, 'height' => 150]; // Pinterest large thumb
// $sizes[] = ['width' => 228, 'height' => 228]; // Opera Coast icon
$sizes[] = ['width' => 250, 'height' => 250]; // Google My Business minimum
// $sizes[] = ['width' => 260, 'height' => 260];
$sizes[] = ['width' => 300, 'height' => 300]; // Instagram personal profile image
// $sizes[] = ['width' => 310, 'height' => 150]; // Win 8.1 wide Metro tile
// $sizes[] = ['width' => 310, 'height' => 310]; // Win 8.1 big Metro tile
$sizes[] = ['width' => 360, 'height' => 360];
$sizes[] = ['width' => 400, 'height' => 150]; // Facebook small cover photo, facebook small fundraiser image
// $sizes[] = ['width' => 400, 'height' => 300];
$sizes[] = ['width' => 400, 'height' => 400]; // Twitter profile photo
$sizes[] = ['width' => 470, 'height' => 174]; // Facebook event small image
$sizes[] = ['width' => 470, 'height' => 246]; // Facebook feed small rectangular link
$sizes[] = ['width' => 480, 'height' => 360]; // YouTube video thumbnail
$sizes[] = ['width' => 484, 'height' => 252]; // Facebook page small rectangular link
$sizes[] = ['width' => 510, 'height' => 510]; // Instagram feed image
$sizes[] = ['width' => 512, 'height' => 512]; // Android Chrome big
// $sizes[] = ['width' => 560, 'height' => 315];
// $sizes[] = ['width' => 560, 'height' => 340];
$sizes[] = ['width' => 600, 'height' => 900]; // Pinterest expanded pin
$sizes[] = ['width' => 612, 'height' => 612]; // Instagram small photo
// $sizes[] = ['width' => 640, 'height' => 385];
$sizes[] = ['width' => 700, 'height' => 800]; // Twitter two image post
$sizes[] = ['width' => 720, 'height' => 720]; // Google My Business
$sizes[] = ['width' => 800, 'height' => 300]; // Facebook fundraiser image
$sizes[] = ['width' => 800, 'height' => 800]; // Youtube channel profile image
$sizes[] = ['width' => 820, 'height' => 312]; // Facebook cover photo
// $sizes[] = ['width' => 853, 'height' => 505];
$sizes[] = ['width' => 900, 'height' => 600]; // Instagram company photo
$sizes[] = ['width' => 943, 'height' => 943]; // Big safari pinned tab
$sizes[] = ['width' => 1024, 'height' => 768]; // Standard 4:3 ratio photo
$sizes[] = ['width' => 1080, 'height' => 720]; // Standard 3:2 ratio photo
$sizes[] = ['width' => 1080, 'height' => 1080]; // Standard 1:1 ratio photo, Instagram photo
$sizes[] = ['width' => 1080, 'height' => 1350]; // Instagram portrait photo
$sizes[] = ['width' => 1080, 'height' => 1920]; // Instagram story
$sizes[] = ['width' => 1128, 'height' => 191]; // Instagram company cover image
$sizes[] = ['width' => 1128, 'height' => 376]; // Instagram Main image
$sizes[] = ['width' => 1200, 'height' => 600]; // Twitter four images
$sizes[] = ['width' => 1200, 'height' => 627]; // Instagram shared link
$sizes[] = ['width' => 1200, 'height' => 628]; // Facebook and Twitter shared link
$sizes[] = ['width' => 1200, 'height' => 630]; // Facebook shared image
$sizes[] = ['width' => 1200, 'height' => 686]; // Twitter three images
$sizes[] = ['width' => 1200, 'height' => 675]; // Twitter shared image
$sizes[] = ['width' => 1280, 'height' => 720]; // Standard small 16:9 ratio photo, YouTube HD
$sizes[] = ['width' => 1500, 'height' => 1500]; // Twitter header photo
$sizes[] = ['width' => 1584, 'height' => 396]; // Instagram personal background image
$sizes[] = ['width' => 1920, 'height' => 1005]; // Facebook event image
$sizes[] = ['width' => 1920, 'height' => 1080]; // Standard big 16:9 ratio photo
$sizes[] = ['width' => 2048, 'height' => 1152]; // YouTube channel cover photo
return Event::next;
}
/**
* @param string $url
*
* @return bool true if allowed by the lists, false otherwise
*/
private function allowedLink(string $url): bool
{
return true;
if ($this->check_whitelist ?? false) {
return false; // indicates "no check made"
}
$host = parse_url($url, PHP_URL_HOST);
foreach ($this->domain_whitelist as $regex => $provider) {
if (preg_match("/{$regex}/", $host)) {
return $provider; // we trust this source, return provider name
}
}
return false;
}
/**
* This code executes when GNU social creates the page routing, and we hook
* on this event to add our action handler for Embed.
@ -148,7 +287,7 @@ class Embed extends Plugin
return Event::next;
}
$attributes = $embed->getImageHTMLAttributes(['class' => 'u-photo embed']);
$attributes = $embed->getImageHTMLAttributes();
$res[] = Formatting::twigRenderFile('embed/embedView.html.twig',
['embed' => $embed, 'attributes' => $attributes, 'link' => $link]);
@ -209,8 +348,13 @@ class Embed extends Plugin
default: // String is valid image data
$temp_file = new TemporaryFile();
$temp_file->write($img_data);
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($temp_file);
$embed_data['attachment_id'] = $attachment->getId();
try {
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($temp_file);
$embed_data['attachment_id'] = $attachment->getId();
} catch (ClientException) {
DB::persist($attachment = Attachment::create(['mimetype' => $link->getMimetype()]));
Event::handle('AttachmentStoreNew', [&$attachment]);
}
}
$embed_data['attachment_id'] = $attachment->getId();
DB::persist(Entity\AttachmentEmbed::create($embed_data));
@ -282,28 +426,6 @@ class Embed extends Plugin
return $metadata;
}
/**
* @param string $url
*
* @return bool true if allowed by the lists, false otherwise
*/
private function allowedLink(string $url): bool
{
return true;
if ($this->check_whitelist ?? false) {
return false; // indicates "no check made"
}
$host = parse_url($url, PHP_URL_HOST);
foreach ($this->domain_whitelist as $regex => $provider) {
if (preg_match("/{$regex}/", $host)) {
return $provider; // we trust this source, return provider name
}
}
return false;
}
/**
* Private helper that:
* - checks if given URL is valid and is in fact an image (basic test), returns null if not;

View File

@ -32,7 +32,9 @@
namespace Plugin\Embed\Entity;
use App\Core\DB\DB;
use App\Core\Entity;
use App\Entity\Attachment;
use DateTimeInterface;
/**
@ -55,6 +57,8 @@ class AttachmentEmbed extends Entity
private ?string $author_name;
private ?string $author_url;
private ?string $thumbnail_url;
private ?int $thumbnail_width;
private ?int $thumbnail_height;
private \DateTimeInterface $modified;
public function setLinkId(int $link_id): self
@ -175,6 +179,25 @@ class AttachmentEmbed extends Entity
// @codeCoverageIgnoreEnd
// }}} Autocode
/**
* Generate the Embed thumbnail HTML attributes
*
* @return string[] ['class' => "string", 'has_attachment' => "bool", 'height' => "int|null", 'width' => "int|null"]
*/
public function getImageHTMLAttributes(): array
{
$attr = ['class' => 'u-photo embed'];
$attachment = DB::find('attachment', ['id' => $this->getAttachmentId()]);
if (is_null($attachment) || is_null($attachment->getWidth()) || is_null($attachment->getHeight())) {
$attr['has_attachment'] = false;
} else {
$attr['has_attachment'] = true;
$attr['width'] = $attachment->getWidth();
$attr['height'] = $attachment->getHeight();
}
return $attr;
}
public static function schemaDef()
{
return [
@ -182,12 +205,13 @@ class AttachmentEmbed extends Entity
'fields' => [
'link_id' => ['type' => 'int', 'not null' => true, 'description' => 'Embed for that URL/file'],
'attachment_id' => ['type' => 'int', 'not null' => true, 'description' => 'Attachment relation, used to show previews'],
'provider_name' => ['type' => 'text', 'description' => 'name of this Embed provider'],
'provider_name' => ['type' => 'text', 'description' => 'Name of this Embed provider'],
'provider_url' => ['type' => 'text', 'description' => 'URL of this Embed provider'],
'title' => ['type' => 'text', 'description' => 'title of Embed resource when available'],
'author_name' => ['type' => 'text', 'description' => 'author name for this Embed resource'],
'author_url' => ['type' => 'text', 'description' => 'author URL for this Embed resource'],
'thumbnail_url' => ['type' => 'text', 'description' => 'URL for this Embed resource when applicable (photo, link)'],
'title' => ['type' => 'text', 'description' => 'Title of Embed resource when available'],
'description' => ['type' => 'text', 'description' => 'Description of Embed resource when available'],
'author_name' => ['type' => 'text', 'description' => 'Author name for this Embed resource'],
'author_url' => ['type' => 'text', 'description' => 'Author URL for this Embed resource'],
'thumbnail_url' => ['type' => 'text', 'description' => 'URL for this Embed resource when applicable (image)'],
'modified' => ['type' => 'timestamp', 'not null' => true, 'description' => 'date this record was modified'],
],
'primary key' => ['link_id'],

View File

@ -1,7 +1,14 @@
<article class="h-entry embed">
<header>
{% if attributes != false %}
<img class="u-photo embed" width="{{attributes['width']}}" height="{{attributes['height']}}" src="{{attributes['src']}}" />
{% if attributes['has_attachment'] != false %}
{% set thumbnail_parameters = {
'id': embed.getAttachmentId(),
'w': attributes['width'],
'h': attributes['height']
} %}
<img alt="{{embed.getTitle() | escape}}" class="{{attributes['class']}}"
width="{{attributes['width']}}" height="{{attributes['height']}}"
src="{{ path('attachment_thumbnail', thumbnail_parameters) }}" />
{% endif %}
<h5 class="p-name embed">
<a class="u-url" href="{{link.getUrl()}}">{{embed.getTitle() | escape}}</a>
@ -28,6 +35,6 @@
</div>
</header>
<div class="p-summary embed">
{{ embed.getHtml() | escape }}
{{ embed.getDescription() | escape }}
</div>
</article>