2014-05-06 22:00:30 +01:00
< ? php
2021-04-19 19:51:05 +01:00
// {{{ License
2018-07-18 05:31:24 +01:00
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
2021-04-19 19:51:05 +01:00
// }}}
2018-07-18 05:31:24 +01:00
/**
2019-07-06 04:31:02 +01:00
* OEmbed and OpenGraph implementation for GNU social
2018-07-18 05:31:24 +01:00
*
* @ package GNUsocial
2021-04-14 16:27:37 +01:00
*
2021-02-19 10:34:21 +00:00
* @ author Mikael Nordfeldth
2018-07-18 05:31:24 +01:00
* @ author Stephen Paul Weber
* @ author hannes
* @ author Mikael Nordfeldth
2021-02-19 10:34:21 +00:00
* @ author Miguel Dantas
2021-08-12 00:41:57 +01:00
* @ author Hugo Sales < hugo @ hsal . es >
2021-04-19 19:51:05 +01:00
* @ author Diogo Peralta Cordeiro < mail @ diogo . site >
2021-02-19 10:34:21 +00:00
* @ copyright 2014 - 2021 Free Software Foundation , Inc http :// www . fsf . org
2018-07-18 05:31:24 +01:00
* @ license https :// www . gnu . org / licenses / agpl . html GNU AGPL v3 or later
*/
2021-04-14 16:27:37 +01:00
namespace Plugin\Embed ;
2018-07-18 05:31:24 +01:00
2021-04-25 22:23:46 +01:00
use App\Core\Cache ;
use App\Core\DB\DB ;
use App\Core\Event ;
2021-04-27 21:56:50 +01:00
use App\Core\GSFile ;
2021-04-25 22:23:46 +01:00
use App\Core\HTTPClient ;
2021-08-12 00:41:57 +01:00
use function App\Core\I18n\_m ;
2021-04-25 22:23:46 +01:00
use App\Core\Log ;
2021-04-19 19:51:05 +01:00
use App\Core\Modules\Plugin ;
2021-04-25 22:23:46 +01:00
use App\Core\Router\RouteLoader ;
use App\Core\Router\Router ;
use App\Entity\Attachment ;
2021-08-13 20:09:20 +01:00
use App\Entity\Link ;
2021-08-12 00:41:57 +01:00
use App\Entity\Note ;
2021-04-27 21:56:50 +01:00
use App\Util\Common ;
2021-08-14 15:04:30 +01:00
use App\Util\Exception\ClientException ;
2021-04-25 22:23:46 +01:00
use App\Util\Exception\DuplicateFoundException ;
use App\Util\Exception\NotFoundException ;
2021-08-12 00:41:57 +01:00
use App\Util\Exception\ServerException ;
2021-04-28 16:03:17 +01:00
use App\Util\Formatting ;
2021-04-27 21:56:50 +01:00
use App\Util\TemporaryFile ;
use Embed\Embed as LibEmbed ;
2021-05-01 22:03:54 +01:00
use Exception ;
2021-04-25 22:23:46 +01:00
use Symfony\Component\HttpFoundation\Request ;
2019-07-07 13:26:10 +01:00
2018-07-18 05:31:24 +01:00
/**
2019-07-06 04:31:02 +01:00
* Base class for the Embed plugin that does most of the heavy lifting to get
2018-07-18 05:31:24 +01:00
* and display representations for remote content .
*
2021-02-19 10:34:21 +00:00
* @ copyright 2014 - 2021 Free Software Foundation , Inc http :// www . fsf . org
2018-07-18 05:31:24 +01:00
* @ license https :// www . gnu . org / licenses / agpl . html GNU AGPL v3 or later
*/
2021-04-19 19:51:05 +01:00
class Embed extends Plugin
2014-05-06 22:00:30 +01:00
{
2021-08-12 00:41:57 +01:00
public function version () : string
{
return '3.0.0' ;
}
2021-04-25 22:23:46 +01:00
/**
* Settings which can be set in social . local . yaml
* WARNING , these are _regexps_ ( slashes added later ) . Always escape your dots and end ( '$' ) your strings
*/
2021-08-12 00:41:57 +01:00
public array $domain_whitelist = [
2019-07-06 04:31:02 +01:00
// hostname => service provider
2021-04-27 21:56:50 +01:00
'.*' => '' , // Default to allowing any host
2019-07-06 04:31:02 +01:00
];
2019-07-06 16:52:30 +01:00
2021-08-14 15:04:30 +01:00
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 ;
}
2018-07-18 05:31:24 +01:00
/**
* This code executes when GNU social creates the page routing , and we hook
2019-07-06 04:31:02 +01:00
* on this event to add our action handler for Embed .
2018-07-18 05:31:24 +01:00
*
2021-08-12 00:41:57 +01:00
* @ param $m RouteLoader the router that was initialized .
2021-04-14 16:27:37 +01:00
*
2019-07-12 03:13:40 +01:00
* @ throws Exception
2021-04-14 16:27:37 +01:00
*
2021-08-12 00:41:57 +01:00
* @ return bool
2021-07-22 20:49:12 +01:00
*
2021-08-13 19:36:34 +01:00
*
2018-07-18 05:31:24 +01:00
*/
2021-05-01 22:45:47 +01:00
public function onAddRoute ( RouteLoader $m ) : bool
2014-05-06 22:00:30 +01:00
{
2021-04-25 22:23:46 +01:00
$m -> connect ( 'oembed' , 'main/oembed' , Controller\Embed :: class );
$m -> connect ( 'embed' , 'main/embed' , Controller\Embed :: class );
return Event :: next ;
2014-05-06 22:00:30 +01:00
}
2021-04-25 22:23:46 +01:00
/**
* Insert oembed and opengraph tags in all HTML head elements
*/
2021-08-13 19:36:34 +01:00
public function onShowHeadElements ( Request $request , array & $result ) : bool
2014-05-06 22:00:30 +01:00
{
2021-04-25 22:23:46 +01:00
$matches = [];
2021-04-28 21:15:43 +01:00
preg_match ( ',/?([^/]+)/?(.*),' , $request -> getPathInfo (), $matches );
2021-08-13 19:36:34 +01:00
$url = match ( $matches [ 1 ]) {
'attachment' => " { $matches [ 1 ] } / { $matches [ 2 ] } " ,
default => null ,
};
2014-05-06 22:00:30 +01:00
2021-08-13 19:36:34 +01:00
if ( is_null ( $url )) {
2019-07-14 23:35:11 +01:00
foreach ([ 'xml' , 'json' ] as $format ) {
2021-04-25 22:23:46 +01:00
$result [] = [
'link' => [
2021-07-22 20:49:12 +01:00
'rel' => 'alternate' ,
'type' => " application/ { $format } +oembed " ,
'href' => Router :: url ( 'embed' , [ 'format' => $format , 'url' => $url ]),
2021-04-14 16:27:37 +01:00
'title' => 'oEmbed' ,
2021-07-22 20:49:12 +01:00
], ];
2019-07-14 23:35:11 +01:00
}
}
2021-04-25 22:23:46 +01:00
return Event :: next ;
2016-03-16 23:31:45 +00:00
}
2021-08-13 19:36:34 +01:00
/**
* Show this attachment enhanced with the corresponding Embed data , if available
*
* @ param array $vars
* @ param array $res
*
* @ return bool
*/
2021-08-13 20:09:20 +01:00
public function onViewLink ( array $vars , array & $res ) : bool
2021-08-13 19:36:34 +01:00
{
2021-08-13 20:09:20 +01:00
$link = $vars [ 'link' ];
2021-08-13 19:36:34 +01:00
try {
2021-08-13 20:09:20 +01:00
$embed = Cache :: get ( 'attachment-embed-' . $link -> getId (),
fn () => DB :: findOneBy ( 'attachment_embed' , [ 'link_id' => $link -> getId ()]));
2021-08-13 19:36:34 +01:00
} catch ( DuplicateFoundException $e ) {
Log :: warning ( $e );
return Event :: next ;
} catch ( NotFoundException ) {
2021-08-13 20:09:20 +01:00
Log :: debug ( " Embed doesn't have a representation for the link id= { $link -> getId () } . Must have been stored before the plugin was enabled. " );
2021-08-13 19:36:34 +01:00
return Event :: next ;
}
2021-08-14 15:04:30 +01:00
$attributes = $embed -> getImageHTMLAttributes ();
2021-08-13 19:36:34 +01:00
$res [] = Formatting :: twigRenderFile ( 'embed/embedView.html.twig' ,
2021-08-13 20:09:20 +01:00
[ 'embed' => $embed , 'attributes' => $attributes , 'link' => $link ]);
2021-08-13 19:36:34 +01:00
return Event :: stop ;
}
2014-05-06 22:00:30 +01:00
/**
2021-04-25 22:23:46 +01:00
* Save embedding information for an Attachment , if applicable .
2014-05-06 22:00:30 +01:00
*
2021-08-13 20:09:20 +01:00
* @ param Link $link
* @ param Note $note
2014-05-06 22:00:30 +01:00
*
2021-08-13 20:09:20 +01:00
*@ throws DuplicateFoundException
2014-05-06 22:00:30 +01:00
*
2021-08-12 00:41:57 +01:00
* @ return bool
2021-08-13 20:09:20 +01:00
*
2014-05-06 22:00:30 +01:00
*/
2021-08-13 20:09:20 +01:00
public function onNewLinkFromNote ( Link $link , Note $note ) : bool
2014-05-06 22:00:30 +01:00
{
2021-08-12 00:41:57 +01:00
// Only handle text mime
2021-08-13 20:09:20 +01:00
$mimetype = $link -> getMimetype ();
2021-08-13 19:36:34 +01:00
if ( ! ( Formatting :: startsWith ( $mimetype , 'text/html' ) || Formatting :: startsWith ( $mimetype , 'application/xhtml+xml' ))) {
2021-08-12 00:41:57 +01:00
return Event :: next ;
}
// Ignore if already handled
2021-08-13 20:09:20 +01:00
$attachment_embed = DB :: find ( 'attachment_embed' , [ 'link_id' => $link -> getId ()]);
2021-08-12 00:41:57 +01:00
if ( ! is_null ( $attachment_embed )) {
return Event :: next ;
}
2021-08-13 19:36:34 +01:00
// If an attachment already exist, do not create an Embed for it. Some other plugin must have done things
2021-08-13 20:09:20 +01:00
$link_to_attachment = DB :: find ( 'link_to_attachment' , [ 'link_id' => $link -> getId ()]);
if ( ! is_null ( $link_to_attachment )) {
$attachment_id = $link_to_attachment -> getAttachmentId ();
2021-08-12 00:41:57 +01:00
try {
2021-08-13 19:36:34 +01:00
$attachment = DB :: findOneBy ( 'attachment' , [ 'id' => $attachment_id ]);
$attachment -> livesIncrementAndGet ();
return Event :: next ;
} catch ( DuplicateFoundException | NotFoundException $e ) {
Log :: error ( $e );
2014-05-06 22:00:30 +01:00
}
}
2021-08-13 19:36:34 +01:00
// Create an Embed representation for this URL
2021-08-13 20:09:20 +01:00
$embed_data = $this -> getEmbedLibMetadata ( $link -> getUrl ());
$embed_data [ 'link_id' ] = $link -> getId ();
$img_data = $this -> downloadThumbnail ( $embed_data [ 'thumbnail_url' ]);
2021-08-13 19:36:34 +01:00
switch ( $img_data ) {
case null : // URL isn't usable
$embed_data [ 'thumbnail_url' ] = null ;
// no break
case false : // Thumbnail isn't acceptable
2021-08-13 20:09:20 +01:00
DB :: persist ( $attachment = Attachment :: create ([ 'mimetype' => $link -> getMimetype ()]));
2021-08-13 19:36:34 +01:00
Event :: handle ( 'AttachmentStoreNew' , [ & $attachment ]);
break ;
default : // String is valid image data
$temp_file = new TemporaryFile ();
$temp_file -> write ( $img_data );
2021-08-14 15:04:30 +01:00
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 ]);
}
2021-08-13 19:36:34 +01:00
}
$embed_data [ 'attachment_id' ] = $attachment -> getId ();
DB :: persist ( Entity\AttachmentEmbed :: create ( $embed_data ));
DB :: flush ();
return Event :: stop ;
2014-05-06 22:00:30 +01:00
}
2021-04-25 22:23:46 +01:00
/**
2021-08-12 00:41:57 +01:00
* Perform an oEmbed or OpenGraph lookup for the given $url .
*
* Some known hosts are whitelisted with API endpoints where we
* know they exist but autodiscovery data isn ' t available .
*
* Throws exceptions on failure .
*
* @ param string $url
*
* @ return array
2021-04-25 22:23:46 +01:00
*/
2021-08-13 19:36:34 +01:00
private function getEmbedLibMetadata ( string $url ) : array
2014-05-06 22:00:30 +01:00
{
2021-08-13 19:36:34 +01:00
Log :: info ( " Trying to find Embed data for { $url } with 'oscarotero/Embed' " );
$embed = new LibEmbed ();
$info = $embed -> get ( $url );
$metadata [ 'title' ] = $info -> title ;
$metadata [ 'description' ] = $info -> description ;
$metadata [ 'author_name' ] = $info -> authorName ;
$metadata [ 'author_url' ] = ( string ) $info -> authorUrl ;
$metadata [ 'provider_name' ] = $info -> providerName ;
$metadata [ 'provider_url' ] = ( string ) $info -> providerUrl ;
if ( ! is_null ( $info -> image )) {
$thumbnail_url = ( string ) $info -> image ;
} else {
$thumbnail_url = ( string ) $info -> favicon ;
2014-05-06 22:00:30 +01:00
}
2021-04-28 16:03:17 +01:00
2021-08-13 19:36:34 +01:00
// Check thumbnail URL validity
$metadata [ 'thumbnail_url' ] = $thumbnail_url ;
return self :: normalizeEmbedLibMetadata ( $metadata );
2021-08-12 00:41:57 +01:00
}
2021-04-28 16:03:17 +01:00
2021-08-12 00:41:57 +01:00
/**
* Normalize fetched info .
*
* @ param array $metadata
*
* @ return array
*/
2021-08-13 19:36:34 +01:00
private static function normalizeEmbedLibMetadata ( array $metadata ) : array
2021-08-12 00:41:57 +01:00
{
if ( isset ( $metadata [ 'thumbnail_url' ])) {
// sometimes sites serve the path, not the full URL, for images
// let's "be liberal in what you accept from others"!
// add protocol and host if the thumbnail_url starts with /
if ( $metadata [ 'thumbnail_url' ][ 0 ] == '/' ) {
$thumbnail_url_parsed = parse_url ( $metadata [ 'thumbnail_url' ]);
$metadata [ 'thumbnail_url' ] = " { $thumbnail_url_parsed [ 'scheme' ] } :// { $thumbnail_url_parsed [ 'host' ] } { $metadata [ 'url' ] } " ;
}
// Some wordpress opengraph implementations sometimes return a white blank image
// no need for us to save that!
if ( $metadata [ 'thumbnail_url' ] == 'https://s0.wp.com/i/blank.jpg' ) {
$metadata [ 'thumbnail_url' ] = null ;
}
}
return $metadata ;
2014-05-06 22:00:30 +01:00
}
2016-03-16 23:31:45 +00:00
2018-07-18 05:31:24 +01:00
/**
2021-08-13 19:36:34 +01:00
* Private helper that :
* - checks if given URL is valid and is in fact an image ( basic test ), returns null if not ;
* - checks if respects file quota and whitelist / blacklist , returns false if not ;
* - downloads the thumbnail , returns a string if successful .
2018-07-18 05:31:24 +01:00
*
2021-08-13 20:09:20 +01:00
* @ param string $url URL to the remote thumbnail
2021-04-14 16:27:37 +01:00
*
2021-08-13 19:36:34 +01:00
* @ return null | bool | string
2018-07-18 05:31:24 +01:00
*/
2021-08-13 20:09:20 +01:00
private function downloadThumbnail ( string $url ) : bool | string | null
2015-01-25 01:34:40 +00:00
{
2021-08-13 19:36:34 +01:00
// Is this a valid URL?
2021-08-13 20:09:20 +01:00
if ( ! Common :: isValidHttpUrl ( $url )) {
Log :: debug ( " Invalid URL ( { $url } ) in Embed->downloadThumbnail. " );
2021-08-13 19:36:34 +01:00
return null ;
2015-01-25 01:34:40 +00:00
}
2021-08-13 19:36:34 +01:00
// Is this URL trusted?
2021-08-13 20:09:20 +01:00
if ( ! $this -> allowedLink ( $url )) {
Log :: info ( " Blocked URL ( { $url } ) in Embed->downloadThumbnail. " );
2021-04-25 22:23:46 +01:00
return false ;
2018-07-18 05:31:24 +01:00
}
2021-08-13 19:36:34 +01:00
// Validate if the URL really does point to a remote image
2021-08-13 20:09:20 +01:00
$head = HTTPClient :: head ( $url );
2021-08-13 19:36:34 +01:00
$headers = $head -> getHeaders ();
$headers = array_change_key_case ( $headers , CASE_LOWER );
if ( empty ( $headers [ 'content-type' ]) || GSFile :: mimetypeMajor ( $headers [ 'content-type' ][ 0 ]) !== 'image' ) {
2021-08-13 20:09:20 +01:00
Log :: debug ( " URL ( { $url } ) doesn't point to an image (content-type: " . ( ! empty ( $headers [ 'content-type' ][ 0 ]) ? $headers [ 'content-type' ][ 0 ] : 'not available' ) . ') in Embed->downloadThumbnail.' );
2021-08-13 19:36:34 +01:00
return null ;
2019-08-19 01:40:31 +01:00
}
2021-08-13 19:36:34 +01:00
// Does it respect the file quota?
$file_size = $headers [ 'content-length' ][ 0 ];
$max_size = Common :: config ( 'attachments' , 'file_quota' );
if ( $file_size > $max_size ) {
Log :: debug ( " Went to download remote thumbnail of size { $file_size } but the upload limit is { $max_size } so we aborted in Embed->downloadThumbnail. " );
return false ;
2018-07-18 05:31:24 +01:00
}
2021-08-13 19:36:34 +01:00
// Download and return the file
2021-08-13 20:09:20 +01:00
Log :: debug ( " Downloading remote thumbnail from URL: { $url } in Embed->downloadThumbnail. " );
return HTTPClient :: get ( $url ) -> getContent ();
2021-04-27 21:56:50 +01:00
}
/**
2021-08-12 00:41:57 +01:00
* Event raised when GNU social polls the plugin for information about it .
* Adds this plugin ' s version information to $versions array
2021-04-27 21:56:50 +01:00
*
2021-08-12 00:41:57 +01:00
* @ param & $versions array inherited from parent
2021-04-27 21:56:50 +01:00
*
2021-08-13 19:36:34 +01:00
* @ throws ServerException
*
2021-08-12 00:41:57 +01:00
* @ return bool true hook value
2021-04-27 21:56:50 +01:00
*/
2021-08-12 00:41:57 +01:00
public function onPluginVersion ( array & $versions ) : bool
2021-04-27 21:56:50 +01:00
{
2021-08-12 00:41:57 +01:00
$versions [] = [
'name' => 'Embed' ,
'version' => $this -> version (),
'author' => 'Mikael Nordfeldth, Hugo Sales, Diogo Peralta Cordeiro' ,
'homepage' => GNUSOCIAL_PROJECT_URL ,
'description' => // TRANS: Plugin description.
_m ( 'Plugin for using and representing oEmbed, OpenGraph and other data.' ),
];
return Event :: next ;
2014-05-06 22:00:30 +01:00
}
}