[Embed] Add UI element and fix some bugs
This commit is contained in:
parent
061c953eac
commit
1906d4f276
@ -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);
|
||||
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;
|
||||
|
@ -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'],
|
||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user