diff --git a/config/preload.php b/config/preload.php new file mode 100644 index 0000000000..db3772325f --- /dev/null +++ b/config/preload.php @@ -0,0 +1,5 @@ + service provider - '^i\d*\.ytimg\.com$' => 'YouTube', + '^i\d*\.ytimg\.com$' => 'YouTube', '^i\d*\.vimeocdn\.com$' => 'Vimeo', ]; public $append_whitelist = []; // fill this array as domain_whitelist to add more trusted sources public $check_whitelist = false; // security/abuse precaution - public $thumbnail_width = 128; + public $thumbnail_width = 128; public $thumbnail_height = 128; - public $crop = true; - public $max_size = null; + public $crop = true; + public $max_size; protected $imgData = []; @@ -74,10 +75,10 @@ class EmbedPlugin extends Plugin $this->domain_whitelist = array_merge($this->domain_whitelist, $this->append_whitelist); // Load global configuration if specific not provided - $this->thumbnail_width = $this->thumbnail_width ?? common_config('thumbnail', 'width'); + $this->thumbnail_width = $this->thumbnail_width ?? common_config('thumbnail', 'width'); $this->thumbnail_height = $this->thumbnail_height ?? common_config('thumbnail', 'height'); - $this->max_size = $this->max_size ?? common_config('attachments', 'file_quota'); - $this->crop = $this->crop ?? common_config('thumbnail', 'crop'); + $this->max_size = $this->max_size ?? common_config('attachments', 'file_quota'); + $this->crop = $this->crop ?? common_config('thumbnail', 'crop'); } /** @@ -106,8 +107,10 @@ class EmbedPlugin extends Plugin * on this event to add our action handler for Embed. * * @param $m URLMapper the router that was initialized. - * @return void true if successful, the exception object if it isn't. + * * @throws Exception + * + * @return void true if successful, the exception object if it isn't. */ public function onRouterInitialized(URLMapper $m) { @@ -122,35 +125,36 @@ class EmbedPlugin extends Plugin * @param $url string the remote URL we're looking at * @param $dom DOMDocument the document we're getting metadata from * @param $metadata stdClass class representing the metadata + * * @return bool true if successful, the exception object if it isn't. */ public function onGetRemoteUrlMetadataFromDom(string $url, DOMDocument $dom, stdClass &$metadata) { try { - common_log(LOG_INFO, "Trying to find Embed data for $url with 'oscarotero/Embed'"); - $info = Embed::create($url); + common_log(LOG_INFO, "Trying to find Embed data for {$url} with 'oscarotero/Embed'"); + $info = self::create($url); - $metadata->version = '1.0'; // Yes. - $metadata->provider_name = $info->authorName; - $metadata->title = $info->title; - $metadata->html = common_purify($info->description); - $metadata->type = $info->type; - $metadata->url = $info->url; + $metadata->version = '1.0'; // Yes. + $metadata->provider_name = $info->authorName; + $metadata->title = $info->title; + $metadata->html = common_purify($info->description); + $metadata->type = $info->type; + $metadata->url = $info->url; $metadata->thumbnail_height = $info->imageHeight; - $metadata->thumbnail_width = $info->imageWidth; + $metadata->thumbnail_width = $info->imageWidth; if (substr($info->image, 0, 4) === 'data') { // Inline image - $imgData = base64_decode(substr($info->image, stripos($info->image, 'base64,') + 7)); - list($filename, , ) = $this->validateAndWriteImage($imgData); + $imgData = base64_decode(substr($info->image, stripos($info->image, 'base64,') + 7)); + list($filename) = $this->validateAndWriteImage($imgData); // Use a file URI for images, as file_embed can't store a filename $metadata->thumbnail_url = 'file://' . File_thumbnail::path($filename); } else { $metadata->thumbnail_url = $info->image; } } catch (Exception $e) { - common_log(LOG_INFO, "Failed to find Embed data for $url with 'oscarotero/Embed'" . - ", got exception: " . get_class($e)); + common_log(LOG_INFO, "Failed to find Embed data for {$url} with 'oscarotero/Embed'" . + ', got exception: ' . get_class($e)); } if (isset($metadata->thumbnail_url)) { @@ -158,15 +162,15 @@ class EmbedPlugin extends Plugin // 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->url); - $metadata->thumbnail_url = "{$thumbnail_url_parsed['scheme']}://". + $thumbnail_url_parsed = parse_url($metadata->url); + $metadata->thumbnail_url = "{$thumbnail_url_parsed['scheme']}://" . "{$thumbnail_url_parsed['host']}$metadata->thumbnail_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') { - unset($metadata->thumbnail_url); + $metadata->thumbnail_url = null; } // FIXME: this is also true of locally-installed wordpress so we should watch out for that. @@ -174,6 +178,7 @@ class EmbedPlugin extends Plugin return true; } + /** Placeholder */ public function onEndShowHeadElements(Action $action) { switch ($action->getActionName()) { @@ -199,10 +204,10 @@ class EmbedPlugin extends Plugin $action->element( 'link', [ - 'rel' =>'alternate', - 'type' => "application/$format+oembed", + 'rel' => 'alternate', + 'type' => "application/{$format}+oembed", 'href' => common_local_url('oembed', [], ['format' => $format, 'url' => $url]), - 'title' => 'oEmbed' + 'title' => 'oEmbed', ] ); } @@ -210,6 +215,7 @@ class EmbedPlugin extends Plugin return true; } + /** Placeholder */ public function onEndShowStylesheets(Action $action) { $action->cssLink($this->path('css/embed.css')); @@ -221,9 +227,9 @@ class EmbedPlugin extends Plugin * * Normally this event is called through File::saveNew() * - * @param File $file The newly inserted File object. + * @param File $file The newly inserted File object. * - * @return boolean success + * @return bool success */ public function onEndFileSaveNew(File $file) { @@ -234,17 +240,16 @@ class EmbedPlugin extends Plugin } if (isset($file->mimetype) - && (('text/html' === substr($file->mimetype, 0, 9) || - 'application/xhtml+xml' === substr($file->mimetype, 0, 21)))) { + && (('text/html' === substr($file->mimetype, 0, 9) || 'application/xhtml+xml' === substr($file->mimetype, 0, 21)))) { try { $embed_data = File_embed::getEmbed($file->url); if ($embed_data === false) { - throw new Exception("Did not get Embed data from URL $file->url"); + throw new Exception("Did not get Embed data from URL {$file->url}"); } $file->setTitle($embed_data->title); } catch (Exception $e) { common_log(LOG_WARNING, sprintf( - __METHOD__.': %s thrown when getting embed data: %s', + __METHOD__ . ': %s thrown when getting embed data: %s', get_class($e), _ve($e->getMessage()) )); @@ -256,25 +261,26 @@ class EmbedPlugin extends Plugin return true; } + /** Placeholder */ public function onEndShowAttachmentLink(HTMLOutputter $out, File $file) { $embed = File_embed::getKV('file_id', $file->getID()); if (empty($embed->author_name) && empty($embed->provider)) { return true; } - $out->elementStart('div', ['id'=>'oembed_info', 'class'=>'e-content']); + $out->elementStart('div', ['id' => 'oembed_info', 'class' => 'e-content']); foreach (['author_name' => ['class' => ' author', 'url' => 'author_url'], - 'provider' => ['class' => '', 'url' => 'provider_url']] + 'provider' => ['class' => '', 'url' => 'provider_url'], ] as $field => $options) { if (!empty($embed->{$field})) { - $out->elementStart('div', "fn vcard" . $options['class']); + $out->elementStart('div', 'fn vcard' . $options['class']); if (empty($embed->{$options['url']})) { $out->text($embed->{$field}); } else { $out->element( 'a', - ['href' => $embed->{$options['url']}, - 'class' => 'url'], + ['href' => $embed->{$options['url']}, + 'class' => 'url', ], $embed->{$field} ); } @@ -284,6 +290,7 @@ class EmbedPlugin extends Plugin return false; } + /** Placeholder */ public function onFileEnclosureMetadata(File $file, &$enclosure) { // Never treat generic HTML links as an enclosure type! @@ -301,6 +308,7 @@ class EmbedPlugin extends Plugin return true; } + /** Placeholder */ public function onStartShowAttachmentRepresentation(HTMLOutputter $out, File $file) { try { @@ -314,29 +322,29 @@ class EmbedPlugin extends Plugin return true; } - $out->elementStart('article', ['class'=>'h-entry embed']); + $out->elementStart('article', ['class' => 'h-entry embed']); $out->elementStart('header'); try { $thumb = $file->getThumbnail($this->thumbnail_width, $this->thumbnail_height); - $out->element('img', $thumb->getHtmlAttrs(['class'=>'u-photo embed'])); + $out->element('img', $thumb->getHtmlAttrs(['class' => 'u-photo embed'])); unset($thumb); } catch (FileNotFoundException $e) { // Nothing to show } catch (Exception $e) { - $out->element('div', ['class'=>'error'], $e->getMessage()); + $out->element('div', ['class' => 'error'], $e->getMessage()); } - $out->elementStart('h5', ['class'=>'p-name embed']); - $out->element('a', ['class'=>'u-url', 'href'=>$file->getUrl()], common_strip_html($embed->title)); + $out->elementStart('h5', ['class' => 'p-name embed']); + $out->element('a', ['class' => 'u-url', 'href' => $file->getUrl()], common_strip_html($embed->title)); $out->elementEnd('h5'); - $out->elementStart('div', ['class'=>'p-author embed']); + $out->elementStart('div', ['class' => 'p-author embed']); if (!empty($embed->author_name)) { // TRANS: text before the author name of embed attachment representation // FIXME: The whole "By x from y" should be i18n because of different language constructions. $out->text(_('By ')); - $attrs = ['class'=>'h-card p-author']; + $attrs = ['class' => 'h-card p-author']; if (!empty($embed->author_url)) { $attrs['href'] = $embed->author_url; - $tag = 'a'; + $tag = 'a'; } else { $tag = 'span'; } @@ -346,10 +354,10 @@ class EmbedPlugin extends Plugin // TRANS: text between the embed author name and provider url // FIXME: The whole "By x from y" should be i18n because of different language constructions. $out->text(_(' from ')); - $attrs = ['class'=>'h-card']; + $attrs = ['class' => 'h-card']; if (!empty($embed->provider_url)) { $attrs['href'] = $embed->provider_url; - $tag = 'a'; + $tag = 'a'; } else { $tag = 'span'; } @@ -357,7 +365,7 @@ class EmbedPlugin extends Plugin } $out->elementEnd('div'); $out->elementEnd('header'); - $out->elementStart('div', ['class'=>'p-summary embed']); + $out->elementStart('div', ['class' => 'p-summary embed']); $out->raw(common_purify($embed->html)); $out->elementEnd('div'); $out->elementStart('footer'); @@ -367,6 +375,7 @@ class EmbedPlugin extends Plugin return false; } + /** Placeholder */ public function onShowUnsupportedAttachmentRepresentation(HTMLOutputter $out, File $file) { try { @@ -400,12 +409,14 @@ class EmbedPlugin extends Plugin * @param $file File the file of the created thumbnail * @param &$imgPath null|string = the path to the created thumbnail (output) * @param $media string = media type - * @return bool true if it succeeds (including non-action - * states where it isn't oEmbed data, so it doesn't mess up the event handle - * for other things hooked into it), or the exception if it fails. + * * @throws FileNotFoundException * @throws NoResultException * @throws ServerException + * + * @return bool true if it succeeds (including non-action + * states where it isn't oEmbed data, so it doesn't mess up the event handle + * for other things hooked into it), or the exception if it fails. */ public function onCreateFileImageThumbnailSource(File $file, ?string &$imgPath, string $media): bool { @@ -417,7 +428,7 @@ class EmbedPlugin extends Plugin // All our remote Embed images lack a local filename property in the File object if ($file->isLocal()) { - common_debug(sprintf('File of id==%d is local (filename: %s), so nothing Embed '. + common_debug(sprintf('File of id==%d is local (filename: %s), so nothing Embed ' . 'should handle.', $file->getID(), _ve($file->filename))); return true; } @@ -428,7 +439,7 @@ class EmbedPlugin extends Plugin $thumbnail = File_thumbnail::byFile($file); } catch (NoResultException $e) { // Not Embed data, or at least nothing we either can or want to use. - common_debug('No Embed data found for file id=='.$file->getID()); + common_debug('No Embed data found for file id==' . $file->getID()); return true; } @@ -459,8 +470,11 @@ class EmbedPlugin extends Plugin } /** - * @return bool false on no check made, provider name on success - * @throws ServerException if check is made but fails + * @param mixed $url + * + * @throws ServerException if check is made but fails + * + * @return bool false on no check made, provider name on success */ protected function checkWhitelist($url) { @@ -470,7 +484,7 @@ class EmbedPlugin extends Plugin $host = parse_url($url, PHP_URL_HOST); foreach ($this->domain_whitelist as $regex => $provider) { - if (preg_match("/$regex/", $host)) { + if (preg_match("/{$regex}/", $host)) { return $provider; // we trust this source, return provider name } } @@ -483,24 +497,27 @@ class EmbedPlugin extends Plugin * the content-length variable returned. This isn't 100% foolproof but is * reliable enough for our purposes. * - * @return string|bool the file size if it succeeds, false otherwise. + * @param mixed $url + * @param null|mixed $headers + * + * @return bool|string the file size if it succeeds, false otherwise. */ private function getRemoteFileSize($url, $headers = null) { try { if ($headers === null) { if (!common_valid_http_url($url)) { - common_log(LOG_ERR, "Invalid URL in Embed::getRemoteFileSize()"); + common_log(LOG_ERR, 'Invalid URL in Embed::getRemoteFileSize()'); return false; } - $head = (new HTTPClient())->head($url); + $head = (new HTTPClient())->head($url); $headers = $head->getHeader(); $headers = array_change_key_case($headers, CASE_LOWER); } return $headers['content-length'] ?? false; } catch (Exception $err) { - common_log(LOG_ERR, __CLASS__.': getRemoteFileSize on URL : '._ve($url). - ' threw exception: '.$err->getMessage()); + common_log(LOG_ERR, __CLASS__ . ': getRemoteFileSize on URL : ' . _ve($url) . + ' threw exception: ' . $err->getMessage()); return false; } } @@ -509,16 +526,19 @@ class EmbedPlugin extends Plugin * A private helper function that uses a CURL lookup to check the mime type * of a remote URL to see it it's an image. * + * @param mixed $url + * @param null|mixed $headers + * * @return bool true if the remote URL is an image, or false otherwise. */ private function isRemoteImage($url, $headers = null) { if (empty($headers)) { if (!common_valid_http_url($url)) { - common_log(LOG_ERR, "Invalid URL in Embed::isRemoteImage()"); + common_log(LOG_ERR, 'Invalid URL in Embed::isRemoteImage()'); return false; } - $head = (new HTTPClient())->head($url); + $head = (new HTTPClient())->head($url); $headers = $head->getHeader(); $headers = array_change_key_case($headers, CASE_LOWER); } @@ -531,11 +551,11 @@ class EmbedPlugin extends Plugin * by $this->thumbnail_height * * @param $imgData - The image data to validate. Taken by reference to avoid copying - * @param string|null $url - The url where the image came from, to fetch metadata - * @param array|null $headers - The headers possible previous request to $url - * @param int|null $file_id - The id of the file this image belongs to, used for logging + * @param null|string $url - The url where the image came from, to fetch metadata + * @param null|array $headers - The headers possible previous request to $url + * @param null|int $file_id - The id of the file this image belongs to, used for logging */ - protected function validateAndWriteImage(&$imgData, ?string $url = null, ?array $headers = null, ?int $file_id = null) : array + protected function validateAndWriteImage(&$imgData, ?string $url = null, ?array $headers = null, ?int $file_id = null): array { $info = @getimagesizefromstring($imgData); // array indexes documented on php.net: @@ -546,8 +566,8 @@ class EmbedPlugin extends Plugin throw new UnsupportedMediaException(_('Image file had impossible geometry (0 width or height)')); } - $width = min($info[0], $this->thumbnail_width); - $height = min($info[1], $this->thumbnail_height); + $width = min($info[0], $this->thumbnail_width); + $height = min($info[1], $this->thumbnail_height); $filehash = hash(File::FILEHASH_ALG, $imgData); try { @@ -556,8 +576,8 @@ class EmbedPlugin extends Plugin } $filename = MediaFile::encodeFilename($original_name ?? _m('Untitled attachment'), $filehash); } catch (Exception $err) { - common_log(LOG_ERR, "Went to write a thumbnail to disk in StoreRemoteMediaPlugin::storeRemoteThumbnail " . - "but encountered error: $err"); + common_log(LOG_ERR, 'Went to write a thumbnail to disk in StoreRemoteMediaPlugin::storeRemoteThumbnail ' . + "but encountered error: {$err}"); throw $err; } @@ -581,21 +601,21 @@ class EmbedPlugin extends Plugin if ($this->crop && ($info[0] > $this->thumbnail_width || $info[1] > $this->thumbnail_height)) { try { // Temporary object, not stored in DB - $img = new ImageFile(-1, $fullpath); + $img = new ImageFile(-1, $fullpath); list($width, $height, $x, $y, $w, $h) = $img->scaleToFit($this->thumbnail_width, $this->thumbnail_height, $this->crop); // The boundary box for our resizing $box = [ 'width' => $width, 'height' => $height, - 'x' => $x, 'y' => $y, - 'w' => $w, 'h' => $h, + 'x' => $x, 'y' => $y, + 'w' => $w, 'h' => $h, ]; - $width = $box['width']; + $width = $box['width']; $height = $box['height']; $img->resizeTo($fullpath, $box); } catch (\Intervention\Image\Exception\NotReadableException $e) { - common_log(LOG_ERR, "StoreRemoteMediaPlugin::storeRemoteThumbnail was unable to decode image with Intervention: $e"); + common_log(LOG_ERR, "StoreRemoteMediaPlugin::storeRemoteThumbnail was unable to decode image with Intervention: {$e}"); // No need to interrupt processing } } @@ -606,8 +626,8 @@ class EmbedPlugin extends Plugin } catch (AlreadyFulfilledException $e) { // Carry on } catch (Exception $err) { - common_log(LOG_ERR, "Went to write a thumbnail to disk in EmbedPlugin::storeRemoteThumbnail " . - "but encountered error: $err"); + common_log(LOG_ERR, 'Went to write a thumbnail to disk in EmbedPlugin::storeRemoteThumbnail ' . + "but encountered error: {$err}"); throw $err; } finally { unset($imgData); @@ -620,8 +640,9 @@ class EmbedPlugin extends Plugin * Function to create and store a thumbnail representation of a remote image * * @param $thumbnail File_thumbnail object containing the file thumbnail + * * @return bool true if it succeeded, the exception if it fails, or false if it - * is limited by system limits (ie the file is too large.) + * is limited by system limits (ie the file is too large.) */ protected function storeRemoteFileThumbnail(File_thumbnail $thumbnail) { @@ -635,13 +656,13 @@ class EmbedPlugin extends Plugin if (substr($url, 0, 7) == 'file://') { $filename = substr($url, 7); - $info = getimagesize($filename); + $info = getimagesize($filename); $filename = basename($filename); - $width = $info[0]; - $height = $info[1]; + $width = $info[0]; + $height = $info[1]; } else { $this->checkWhitelist($url); - $head = (new HTTPClient())->head($url); + $head = (new HTTPClient())->head($url); $headers = $head->getHeader(); $headers = array_change_key_case($headers, CASE_LOWER); @@ -649,16 +670,16 @@ class EmbedPlugin extends Plugin $is_image = $this->isRemoteImage($url, $headers); if ($is_image == true) { $file_size = $this->getRemoteFileSize($url, $headers); - if (($file_size!=false) && ($file_size > $this->max_size)) { - common_debug("Went to store remote thumbnail of size " . $file_size . - " but the upload limit is " . $this->max_size . " so we aborted."); + if (($file_size != false) && ($file_size > $this->max_size)) { + common_debug('Went to store remote thumbnail of size ' . $file_size . + ' but the upload limit is ' . $this->max_size . ' so we aborted.'); return false; } } else { return false; } } catch (Exception $err) { - common_debug("Could not determine size of remote image, aborted local storage."); + common_debug('Could not determine size of remote image, aborted local storage.'); throw $err; } @@ -683,22 +704,22 @@ class EmbedPlugin extends Plugin } } catch (UnsupportedMediaException $e) { // Couldn't find anything that looks like an image, nothing to do - common_debug("Embed was not able to find an image for URL `$url`: " . $e->getMessage()); + common_debug("Embed was not able to find an image for URL `{$url}`: " . $e->getMessage()); return false; } } try { // Update our database for the thumbnail record - $orig = clone($thumbnail); + $orig = clone $thumbnail; $thumbnail->filename = $filename; - $thumbnail->width = $width; - $thumbnail->height = $height; + $thumbnail->width = $width; + $thumbnail->height = $height; // Throws exception on failure. $thumbnail->updateWithKeys($orig); } catch (Exception $err) { - common_log(LOG_ERR, "Went to write a thumbnail entry to the database in " . - "EmbedPlugin::storeRemoteThumbnail but encountered error: ".$err); + common_log(LOG_ERR, 'Went to write a thumbnail entry to the database in ' . + 'EmbedPlugin::storeRemoteThumbnail but encountered error: ' . $err); throw $err; } return true; @@ -709,17 +730,17 @@ class EmbedPlugin extends Plugin * Adds this plugin's version information to $versions array * * @param &$versions array inherited from parent + * * @return bool true hook value */ public function onPluginVersion(array &$versions): bool { $versions[] = ['name' => 'Embed', - 'version' => self::PLUGIN_VERSION, - 'author' => 'Mikael Nordfeldth', - 'homepage' => GNUSOCIAL_ENGINE_URL, - 'description' => - // TRANS: Plugin description. - _m('Plugin for using and representing oEmbed, OpenGraph and other data.')]; + 'version' => self::PLUGIN_VERSION, + 'author' => 'Mikael Nordfeldth', + 'homepage' => GNUSOCIAL_ENGINE_URL, + 'description' => // TRANS: Plugin description. + _m('Plugin for using and representing oEmbed, OpenGraph and other data.'), ]; return true; } } diff --git a/plugins/Embed/actions/oembed.php b/plugins/Embed/actions/OEmbedAction.php similarity index 72% rename from plugins/Embed/actions/oembed.php rename to plugins/Embed/actions/OEmbedAction.php index 9f44801885..6deb9bc6ea 100644 --- a/plugins/Embed/actions/oembed.php +++ b/plugins/Embed/actions/OEmbedAction.php @@ -18,6 +18,7 @@ * OembedPlugin implementation for GNU social * * @package GNUsocial + * * @author Craig Andrews * @author Mikael Nordfeldth * @author hannes @@ -26,7 +27,7 @@ * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ -defined('GNUSOCIAL') || die(); +namespace Plguin\Embed\actions; /** * Oembed provider implementation @@ -38,12 +39,13 @@ defined('GNUSOCIAL') || die(); */ class OEmbedAction extends Action { + /** Placeholder */ protected function handle() { parent::handle(); - $url = $this->trimmed('url'); - $tls = parse_url($url, PHP_URL_SCHEME) == 'https'; + $url = $this->trimmed('url'); + $tls = parse_url($url, PHP_URL_SCHEME) == 'https'; $root_url = common_root_url($tls); if (substr(strtolower($url), 0, mb_strlen($root_url)) !== strtolower($root_url)) { @@ -58,20 +60,20 @@ class OEmbedAction extends Action // $r->map will throw ClientException 404 if it fails to find a mapping $proxy_args = $r->map($path); - $oembed=array(); - $oembed['version']='1.0'; - $oembed['provider_name']=common_config('site', 'name'); - $oembed['provider_url']=common_root_url(); + $oembed = []; + $oembed['version'] = '1.0'; + $oembed['provider_name'] = common_config('site', 'name'); + $oembed['provider_url'] = common_root_url(); switch ($proxy_args['action']) { case 'shownotice': - $oembed['type']='link'; + $oembed['type'] = 'link'; try { $notice = Notice::getByID($proxy_args['notice']); } catch (NoResultException $e) { throw new ClientException($e->getMessage(), 404); } - $profile = $notice->getProfile(); + $profile = $notice->getProfile(); $authorname = $profile->getFancyName(); // TRANS: oEmbed title. %1$s is the author name, %2$s is the creation date. $oembed['title'] = sprintf( @@ -79,36 +81,34 @@ class OEmbedAction extends Action $authorname, common_exact_date($notice->created) ); - $oembed['author_name']=$authorname; - $oembed['author_url']=$profile->profileurl; - $oembed['url']=$notice->getUrl(); - $oembed['html']=$notice->getRendered(); + $oembed['author_name'] = $authorname; + $oembed['author_url'] = $profile->profileurl; + $oembed['url'] = $notice->getUrl(); + $oembed['html'] = $notice->getRendered(); // maybe add thumbnail foreach ($notice->attachments() as $attachment) { if (!$attachment instanceof File) { - common_debug('ATTACHMENTS array entry from notice id=='._ve($notice->getID()). - ' is something else than a File dataobject: '._ve($attachment)); + common_debug('ATTACHMENTS array entry from notice id==' . _ve($notice->getID()) . + ' is something else than a File dataobject: ' . _ve($attachment)); continue; } try { - $thumb = $attachment->getThumbnail(); - $thumb_url = $thumb->getUrl(); + $thumb = $attachment->getThumbnail(); + $thumb_url = $thumb->getUrl(); $oembed['thumbnail_url'] = $thumb_url; break; // only first one } catch (UseFileAsThumbnailException $e) { $oembed['thumbnail_url'] = $attachment->getUrl(); break; // we're happy with that } catch (ServerException $e) { - // } catch (ClientException $e) { - // } } break; case 'attachment': - $id = $proxy_args['attachment']; + $id = $proxy_args['attachment']; $attachment = File::getKV($id); if (empty($attachment)) { // TRANS: Client error displayed in oEmbed action when attachment not found. @@ -123,47 +123,47 @@ class OEmbedAction extends Action )) ) { // Proxy the existing oembed information - $oembed['type']=$file_oembed->type; - $oembed['provider']=$file_oembed->provider; - $oembed['provider_url']=$file_oembed->provider_url; - $oembed['width']=$file_oembed->width; - $oembed['height']=$file_oembed->height; - $oembed['html']=$file_oembed->html; - $oembed['title']=$file_oembed->title; - $oembed['author_name']=$file_oembed->author_name; - $oembed['author_url']=$file_oembed->author_url; - $oembed['url']=$file_oembed->getUrl(); - } elseif (substr($attachment->mimetype, 0, strlen('image/'))==='image/') { - $oembed['type']='photo'; + $oembed['type'] = $file_oembed->type; + $oembed['provider'] = $file_oembed->provider; + $oembed['provider_url'] = $file_oembed->provider_url; + $oembed['width'] = $file_oembed->width; + $oembed['height'] = $file_oembed->height; + $oembed['html'] = $file_oembed->html; + $oembed['title'] = $file_oembed->title; + $oembed['author_name'] = $file_oembed->author_name; + $oembed['author_url'] = $file_oembed->author_url; + $oembed['url'] = $file_oembed->getUrl(); + } elseif (substr($attachment->mimetype, 0, strlen('image/')) === 'image/') { + $oembed['type'] = 'photo'; if ($attachment->filename) { $filepath = File::path($attachment->filename); - $gis = @getimagesize($filepath); + $gis = @getimagesize($filepath); if ($gis) { - $oembed['width'] = $gis[0]; + $oembed['width'] = $gis[0]; $oembed['height'] = $gis[1]; } else { // TODO Either throw an error or find a fallback? } } - $oembed['url']=$attachment->getUrl(); + $oembed['url'] = $attachment->getUrl(); try { - $thumb = $attachment->getThumbnail(); - $oembed['thumbnail_url'] = $thumb->getUrl(); - $oembed['thumbnail_width'] = $thumb->width; + $thumb = $attachment->getThumbnail(); + $oembed['thumbnail_url'] = $thumb->getUrl(); + $oembed['thumbnail_width'] = $thumb->width; $oembed['thumbnail_height'] = $thumb->height; unset($thumb); } catch (UnsupportedMediaException $e) { // No thumbnail data available } } else { - $oembed['type']='link'; - $oembed['url']=common_local_url( + $oembed['type'] = 'link'; + $oembed['url'] = common_local_url( 'attachment', - array('attachment' => $attachment->id) + ['attachment' => $attachment->id] ); } if ($attachment->title) { - $oembed['title']=$attachment->title; + $oembed['title'] = $attachment->title; } break; default: @@ -176,14 +176,14 @@ class OEmbedAction extends Action case 'xml': $this->init_document('xml'); $this->elementStart('oembed'); - foreach (array( - 'version', 'type', 'provider_name', - 'provider_url', 'title', 'author_name', - 'author_url', 'url', 'html', 'width', - 'height', 'cache_age', 'thumbnail_url', - 'thumbnail_width', 'thumbnail_height', - ) as $key) { - if (isset($oembed[$key]) && $oembed[$key]!='') { + foreach ([ + 'version', 'type', 'provider_name', + 'provider_url', 'title', 'author_name', + 'author_url', 'url', 'html', 'width', + 'height', 'cache_age', 'thumbnail_url', + 'thumbnail_width', 'thumbnail_height', + ] as $key) { + if (isset($oembed[$key]) && $oembed[$key] != '') { $this->element($key, null, $oembed[$key]); } } @@ -203,6 +203,7 @@ class OEmbedAction extends Action } } + /** Placeholder */ public function init_document($type) { switch ($type) { @@ -216,7 +217,7 @@ class OEmbedAction extends Action // Check for JSONP callback $callback = $this->arg('callback'); if ($callback) { - print $callback . '('; + echo $callback . '('; } break; default: @@ -226,6 +227,7 @@ class OEmbedAction extends Action } } + /** Placeholder */ public function end_document($type) { switch ($type) { @@ -236,7 +238,7 @@ class OEmbedAction extends Action // Check for JSONP callback $callback = $this->arg('callback'); if ($callback) { - print ')'; + echo ')'; } break; default: @@ -244,7 +246,6 @@ class OEmbedAction extends Action $this->serverError(_('Not a supported data format.'), 501); break; } - return; } /** @@ -252,7 +253,7 @@ class OEmbedAction extends Action * * @param array $args other arguments * - * @return boolean is read only action? + * @return bool is read only action? */ public function isReadOnly($args) { diff --git a/plugins/Media/Controller/AttachmentThumbnail.php b/plugins/Media/Controller/AttachmentThumbnail.php index da8d6857e6..0fe5014e84 100644 --- a/plugins/Media/Controller/AttachmentThumbnail.php +++ b/plugins/Media/Controller/AttachmentThumbnail.php @@ -21,11 +21,12 @@ namespace Plugin\Media\Controller; * * @category Personal * @package GNUsocial + * * @author Evan Prodromou * @copyright 2008-2009 StatusNet, Inc. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ -class Attachment_thumbnailAction extends Attachment_viewAction +class AttachmentThumbnail extends AttachmentView { protected $thumb_w; // max width protected $thumb_h; // max height diff --git a/plugins/Media/Controller/AttachmentView.php b/plugins/Media/Controller/AttachmentView.php index 692b7dd3ed..0d9c12ca99 100644 --- a/plugins/Media/Controller/AttachmentView.php +++ b/plugins/Media/Controller/AttachmentView.php @@ -26,6 +26,7 @@ namespace Plugin\Media\Controller; */ class AttachmentView extends Attachment { + /** Placeholder */ public function showPage(): void { // Disable errors, to not mess with the file contents (suppress errors in case access to this diff --git a/plugins/Media/Util/ImageFile.php b/plugins/Media/Util/ImageFile.php index c00e275f99..ba3ea49a07 100644 --- a/plugins/Media/Util/ImageFile.php +++ b/plugins/Media/Util/ImageFile.php @@ -147,7 +147,7 @@ class ImageFile extends MediaFile * Shortcut method to get an ImageFile from a File * * @param File $file - * @return ImageFile + * * @throws ClientException * @throws FileNotFoundException * @throws NoResultException @@ -156,6 +156,7 @@ class ImageFile extends MediaFile * @throws UseFileAsThumbnailException * * @return ImageFile + * @return ImageFile */ public static function fromFileObject(File $file) { @@ -172,14 +173,15 @@ class ImageFile extends MediaFile throw new UseFileAsThumbnailException($file); } - // And we'll only consider it an image if it has such a media type - if ($media !== 'image') { - throw new UnsupportedMediaException(_m('Unsupported media format.'), $file->getPath()); + // And we'll only consider it an image if it has such a media type + if ($media !== 'image') { + throw new UnsupportedMediaException(_m('Unsupported media format.'), $file->getPath()); + } + + $filepath = $file->getPath(); + + return new self($file->getID(), $filepath, $file->filehash); } - - $filepath = $file->getPath(); - - return new self($file->getID(), $filepath, $file->filehash); } public function getPath() @@ -232,11 +234,11 @@ class ImageFile extends MediaFile * Uses MediaFile's `fromUrl` to do the majority of the work * and ensures the uploaded file is in fact an image. * - * @param string $url Remote image URL - * @param Profile|null $scoped - * @param string|null $name - * @param int|null $file_id same as in this class constructor - * @return ImageFile + * @param string $url Remote image URL + * @param null|Profile $scoped + * @param null|string $name + * @param null|int $file_id same as in this class constructor + * * @throws ClientException * @throws HTTP_Request2_Exception * @throws InvalidFilenameException @@ -246,6 +248,7 @@ class ImageFile extends MediaFile * @throws UseFileAsThumbnailException * * @return ImageFile + * @return ImageFile */ public static function fromUrl(string $url, ?Profile $scoped = null, ?string $name = null, ?int $file_id = null): self { @@ -524,10 +527,10 @@ class ImageFile extends MediaFile $rw = ceil($width * $rh / $height); } } - return [(int)$rw, (int)$rh, - (int)$cx, (int)$cy, - is_null($cw) ? $width : (int)$cw, - is_null($ch) ? $height : (int)$ch]; + return [(int) $rw, (int) $rh, + (int) $cx, (int) $cy, + is_null($cw) ? $width : (int) $cw, + is_null($ch) ? $height : (int) $ch, ]; } /** @@ -570,7 +573,7 @@ class ImageFile extends MediaFile * @param $height * @param $crop * @param false $upscale - * @return File_thumbnail + * * @throws ClientException * @throws FileNotFoundException * @throws FileNotStoredLocallyException @@ -578,6 +581,8 @@ class ImageFile extends MediaFile * @throws ServerException * @throws UnsupportedMediaException * @throws UseFileAsThumbnailException + * + * @return File_thumbnail */ public function getFileThumbnail($width = null, $height = null, $crop = null, $upscale = false) { diff --git a/plugins/Media/Util/MediaFile.php b/plugins/Media/Util/MediaFile.php index e5a1c576b4..cc1377a995 100644 --- a/plugins/Media/Util/MediaFile.php +++ b/plugins/Media/Util/MediaFile.php @@ -95,18 +95,21 @@ class MediaFile * Shortcut method to get a MediaFile from a File * * @param File $file - * @return MediaFile|ImageFile + * * @throws ClientException * @throws FileNotFoundException * @throws NoResultException * @throws ServerException + * + * @return ImageFile|MediaFile */ public static function fromFileObject(File $file) { $filepath = null; try { $filepath = $file->getPath(); - } catch (Exception $e) {} + } catch (Exception $e) { + } return new self($filepath, common_get_mime_media($file->mimetype), $file->filehash, $file->getID()); } @@ -121,10 +124,11 @@ class MediaFile } /** - * @param bool|null $use_local true means require local, null means prefer original, false means use whatever is stored + * @param null|bool $use_local true means require local, null means prefer original, false means use whatever is stored + * * @return string */ - public function getUrl(?bool $use_local=null): ?string + public function getUrl(?bool $use_local = null): ?string { if ($use_local !== false) { if (empty($this->fileurl)) { @@ -147,7 +151,7 @@ class MediaFile return $this->getFile()->getEnclosure(); } - public function delete($useWhere=false) + public function delete($useWhere = false) { if (!is_null($this->fileRecord)) { $this->fileRecord->delete($useWhere); @@ -155,6 +159,9 @@ class MediaFile @unlink($this->filepath); } + /** + * Remove the file from filesystem + */ public function unlink() { $this->filename = null; @@ -228,8 +235,8 @@ class MediaFile $file = new File; $file->filename = $this->filename; - $file->url = $this->fileurl; - $file->urlhash = is_null($file->url) ? null : File::hashurl($file->url); + $file->url = $this->fileurl; + $file->urlhash = is_null($file->url) ? null : File::hashurl($file->url); $file->filehash = $this->filehash; $file->size = filesize($this->filepath); if ($file->size === false) { @@ -248,9 +255,9 @@ class MediaFile // Set file geometrical properties if available try { - $image = ImageFile::fromFileObject($file); - $orig = clone($file); - $file->width = $image->width; + $image = ImageFile::fromFileObject($file); + $orig = clone $file; + $file->width = $image->width; $file->height = $image->height; $file->update($orig); @@ -448,7 +455,7 @@ class MediaFile } $filehash = strtolower(self::getHashOfFile($_FILES[$param]['tmp_name'])); - $fileid = null; + $fileid = null; try { $file = File::getByHash($filehash); // There can be more than one file for the same filehash IF the url are different (due to different metadata). @@ -460,7 +467,7 @@ class MediaFile try { return ImageFile::fromFileObject($file); } catch (UnsupportedMediaException $e) { - return MediaFile::fromFileObject($file); + return self::fromFileObject($file); } } // Assert: If we got to this line, then we only traversed URLs on the while loop above. @@ -469,7 +476,7 @@ class MediaFile $filepath = $file->getPath(); // This function will throw FileNotFoundException if not. // Assert: If we got to this line, then we can use this file and just add redirections. $mimetype = $file->mimetype; - $fileid = $file->getID(); + $fileid = $file->getID(); } else { throw new FileNotFoundException('This isn\'t a path.'); // A bit of dadaist art. // It's natural that a sysadmin prefers to not add redirections to the first remote link of an @@ -530,11 +537,11 @@ class MediaFile * In case the url is an image, this function returns an new ImageFile (which extends MediaFile) * The filename has the following format: bin2hex("{$original_name}.{$ext}")."-{$filehash}" * - * @param string $url Remote media URL - * @param Profile|null $scoped - * @param string|null $name - * @param int|null $file_id same as in this class constructor - * @return ImageFile|MediaFile + * @param string $url Remote media URL + * @param null|Profile $scoped + * @param null|string $name + * @param null|int $file_id same as in this class constructor + * * @throws ClientException * @throws HTTP_Request2_Exception * @throws InvalidFilenameException @@ -544,6 +551,7 @@ class MediaFile * @throws UseFileAsThumbnailException * * @return ImageFile|MediaFile + * @return ImageFile|MediaFile */ public static function fromUrl(string $url, ?Profile $scoped = null, ?string $name = null, ?int $file_id = null) { @@ -557,7 +565,7 @@ class MediaFile 'unnecessarily downloading too large files. URL: %s', $url)); $head = $http->head($url); - $url = $head->getEffectiveUrl(); // to avoid going through redirects again + $url = $head->getEffectiveUrl(); // to avoid going through redirects again if (empty($url)) { throw new ServerException(sprintf('URL after redirects is somehow empty, for URL %s.', $url)); } @@ -565,7 +573,7 @@ class MediaFile $headers = array_change_key_case($headers, CASE_LOWER); if (array_key_exists('content-length', $headers)) { $fileQuota = common_config('attachments', 'file_quota'); - $fileSize = $headers['content-length']; + $fileSize = $headers['content-length']; if ($fileSize > $fileQuota) { // TRANS: Message used to be inserted as %2$s in the text "No file may // TRANS: be larger than %1$d byte and the file you sent was %2$s.". @@ -591,8 +599,7 @@ class MediaFile } else { throw new ServerException(sprintf('Invalid remote media URL headers %s.', $url)); } - unset($head); - unset($headers); + unset($head, $headers); $tempfile = new TemporaryFile('gs-mediafile'); fwrite($tempfile->getResource(), HTTPClient::quickGet($url)); diff --git a/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php b/plugins/StoreRemoteMedia/StoreRemoteMedia.php similarity index 74% rename from plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php rename to plugins/StoreRemoteMedia/StoreRemoteMedia.php index a987024b42..4af322d92a 100644 --- a/plugins/StoreRemoteMedia/StoreRemoteMediaPlugin.php +++ b/plugins/StoreRemoteMedia/StoreRemoteMedia.php @@ -14,10 +14,15 @@ // You should have received a copy of the GNU Affero General Public License // along with GNU social. If not, see . +namespace Plugin\StoreRemoteMedia; + +use App\Core\Module; + /** * The StoreRemoteMedia plugin downloads remotely attached files to local server. * * @package GNUsocial + * * @author Mikael Nordfeldth * @author Stephen Paul Weber * @author Mikael Nordfeldth @@ -26,7 +31,7 @@ * @copyright 2015-2016, 2019-2021 Free Software Foundation, Inc http://www.fsf.org * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later */ -class StoreRemoteMediaPlugin extends Plugin +class StoreRemoteMedia extends Module { const PLUGIN_VERSION = '3.0.0'; @@ -41,11 +46,11 @@ class StoreRemoteMediaPlugin extends Plugin public $append_whitelist = []; // fill this array as domain_whitelist to add more trusted sources public $check_whitelist = false; // security/abuse precaution - public $store_original = false; // Whether to maintain a copy of the original media or only a thumbnail of it - public $thumbnail_width = null; - public $thumbnail_height = null; - public $crop = null; - public $max_size = null; + public $store_original = false; // Whether to maintain a copy of the original media or only a thumbnail of it + public $thumbnail_width; + public $thumbnail_height; + public $crop; + public $max_size; /** * Initialize the StoreRemoteMedia plugin and set up the environment it needs for it. @@ -59,10 +64,10 @@ class StoreRemoteMediaPlugin extends Plugin $this->domain_whitelist = array_merge($this->domain_whitelist, $this->append_whitelist); // Load global configuration if specific not provided - $this->thumbnail_width = $this->thumbnail_width ?? common_config('thumbnail', 'width'); + $this->thumbnail_width = $this->thumbnail_width ?? common_config('thumbnail', 'width'); $this->thumbnail_height = $this->thumbnail_height ?? common_config('thumbnail', 'height'); - $this->max_size = $this->max_size ?? common_config('attachments', 'file_quota'); - $this->crop = $this->crop ?? common_config('thumbnail', 'crop'); + $this->max_size = $this->max_size ?? common_config('attachments', 'file_quota'); + $this->crop = $this->crop ?? common_config('thumbnail', 'crop'); } /** @@ -72,14 +77,16 @@ class StoreRemoteMediaPlugin extends Plugin * @param $file File the file of the created thumbnail * @param &$imgPath null|string = out the path to the created thumbnail (output parameter) * @param $media string = media type (unused) - * @return bool + * * @throws AlreadyFulfilledException * @throws FileNotFoundException * @throws FileNotStoredLocallyException * @throws HTTP_Request2_Exception * @throws ServerException + * + * @return bool */ - public function onCreateFileImageThumbnailSource(File $file, ?string &$imgPath = null, ?string $media=null): bool + public function onCreateFileImageThumbnailSource(File $file, ?string &$imgPath = null, ?string $media = null): bool { // If we are on a private node, we won't do any remote calls (just as a precaution until // we can configure this from config.php for the private nodes) @@ -89,7 +96,7 @@ class StoreRemoteMediaPlugin extends Plugin // If there is a local filename, it is either a local file already or has already been downloaded. if (!$file->isStoredRemotely()) { - common_debug(sprintf('File id==%d isn\'t a non-fetched remote file (%s), so nothing StoreRemoteMedia '. + common_debug(sprintf('File id==%d isn\'t a non-fetched remote file (%s), so nothing StoreRemoteMedia ' . 'should handle.', $file->getID(), _ve($file->filename))); return true; } @@ -106,13 +113,13 @@ class StoreRemoteMediaPlugin extends Plugin if (substr($url, 0, 7) == 'file://') { $filename = substr($url, 7); - $info = getimagesize($filename); + $info = getimagesize($filename); $filename = basename($filename); - $width = $info[0]; - $height = $info[1]; + $width = $info[0]; + $height = $info[1]; } else { $this->checkWhitelist($url); - $head = (new HTTPClient())->head($url); + $head = (new HTTPClient())->head($url); $headers = $head->getHeader(); $headers = array_change_key_case($headers, CASE_LOWER); @@ -120,16 +127,16 @@ class StoreRemoteMediaPlugin extends Plugin $is_image = $this->isRemoteImage($url, $headers); if ($is_image == true) { $file_size = $this->getRemoteFileSize($url, $headers); - if (($file_size!=false) && ($file_size > $this->max_size)) { - common_debug("Went to store remote thumbnail of size " . $file_size . - " but the upload limit is " . $this->max_size . " so we aborted."); + if (($file_size != false) && ($file_size > $this->max_size)) { + common_debug('Went to store remote thumbnail of size ' . $file_size . + ' but the upload limit is ' . $this->max_size . ' so we aborted.'); return false; } } else { return false; } } catch (Exception $err) { - common_debug("Could not determine size of remote image, aborted local storage."); + common_debug('Could not determine size of remote image, aborted local storage.'); throw $err; } @@ -154,7 +161,7 @@ class StoreRemoteMediaPlugin extends Plugin } } catch (UnsupportedMediaException $e) { // Couldn't find anything that looks like an image, nothing to do - common_debug("StoreRemoteMedia was not able to find an image for URL `$url`: " . $e->getMessage()); + common_debug("StoreRemoteMedia was not able to find an image for URL `{$url}`: " . $e->getMessage()); return false; } } @@ -163,33 +170,33 @@ class StoreRemoteMediaPlugin extends Plugin if ($this->store_original) { try { // Update our database for the file record - $orig = clone($file); + $orig = clone $file; $file->filename = $filename; $file->filehash = $filehash; - $file->width = $width; - $file->height = $height; + $file->width = $width; + $file->height = $height; // Throws exception on failure. $file->updateWithKeys($orig); } catch (Exception $err) { - common_log(LOG_ERR, "Went to update a file entry on the database in " . - "StoreRemoteMediaPlugin::storeRemoteThumbnail but encountered error: " . $err); + common_log(LOG_ERR, 'Went to update a file entry on the database in ' . + 'StoreRemoteMediaPlugin::storeRemoteThumbnail but encountered error: ' . $err); throw $err; } } else { try { // Insert a thumbnail record for this file - $data = new stdClass(); - $data->thumbnail_url = $url; - $data->thumbnail_width = $width; + $data = new stdClass(); + $data->thumbnail_url = $url; + $data->thumbnail_width = $width; $data->thumbnail_height = $height; File_thumbnail::saveNew($data, $file->getID()); - $ft = File_thumbnail::byFile($file); - $orig = clone($ft); + $ft = File_thumbnail::byFile($file); + $orig = clone $ft; $ft->filename = $filename; $ft->updateWithKeys($orig); } catch (Exception $err) { - common_log(LOG_ERR, "Went to write a thumbnail entry to the database in " . - "StoreRemoteMediaPlugin::storeRemoteThumbnail but encountered error: " . $err); + common_log(LOG_ERR, 'Went to write a thumbnail entry to the database in ' . + 'StoreRemoteMediaPlugin::storeRemoteThumbnail but encountered error: ' . $err); throw $err; } } @@ -208,24 +215,27 @@ class StoreRemoteMediaPlugin extends Plugin * the content-length variable returned. This isn't 100% foolproof but is * reliable enough for our purposes. * - * @return string|bool the file size if it succeeds, false otherwise. + * @param mixed $url + * @param null|mixed $headers + * + * @return bool|string the file size if it succeeds, false otherwise. */ private function getRemoteFileSize($url, $headers = null) { try { if ($headers === null) { if (!common_valid_http_url($url)) { - common_log(LOG_ERR, "Invalid URL in StoreRemoteMedia::getRemoteFileSize()"); + common_log(LOG_ERR, 'Invalid URL in StoreRemoteMedia::getRemoteFileSize()'); return false; } - $head = (new HTTPClient())->head($url); + $head = (new HTTPClient())->head($url); $headers = $head->getHeader(); $headers = array_change_key_case($headers, CASE_LOWER); } return $headers['content-length'] ?? false; } catch (Exception $err) { - common_log(LOG_ERR, __CLASS__.': getRemoteFileSize on URL : '._ve($url). - ' threw exception: '.$err->getMessage()); + common_log(LOG_ERR, __CLASS__ . ': getRemoteFileSize on URL : ' . _ve($url) . + ' threw exception: ' . $err->getMessage()); return false; } } @@ -234,16 +244,19 @@ class StoreRemoteMediaPlugin extends Plugin * A private helper function that uses a CURL lookup to check the mime type * of a remote URL to see it it's an image. * + * @param mixed $url + * @param null|mixed $headers + * * @return bool true if the remote URL is an image, or false otherwise. */ private function isRemoteImage($url, $headers = null): bool { if (empty($headers)) { if (!common_valid_http_url($url)) { - common_log(LOG_ERR, "Invalid URL in StoreRemoteMedia::isRemoteImage()"); + common_log(LOG_ERR, 'Invalid URL in StoreRemoteMedia::isRemoteImage()'); return false; } - $head = (new HTTPClient())->head($url); + $head = (new HTTPClient())->head($url); $headers = $head->getHeader(); $headers = array_change_key_case($headers, CASE_LOWER); } @@ -256,11 +269,11 @@ class StoreRemoteMediaPlugin extends Plugin * by $this->thumbnail_height * * @param $imgData - The image data to validate. Taken by reference to avoid copying - * @param string|null $url - The url where the image came from, to fetch metadata - * @param array|null $headers - The headers possible previous request to $url - * @param int|null $file_id - The id of the file this image belongs to, used for logging + * @param null|string $url - The url where the image came from, to fetch metadata + * @param null|array $headers - The headers possible previous request to $url + * @param null|int $file_id - The id of the file this image belongs to, used for logging */ - protected function validateAndWriteImage(&$imgData, ?string $url = null, ?array $headers = null, ?int $file_id = null) : array + protected function validateAndWriteImage(&$imgData, ?string $url = null, ?array $headers = null, ?int $file_id = null): array { $info = @getimagesizefromstring($imgData); // array indexes documented on php.net: @@ -271,8 +284,8 @@ class StoreRemoteMediaPlugin extends Plugin throw new UnsupportedMediaException(_m('Image file had impossible geometry (0 width or height)')); } - $width = min($info[0], $this->thumbnail_width); - $height = min($info[1], $this->thumbnail_height); + $width = min($info[0], $this->thumbnail_width); + $height = min($info[1], $this->thumbnail_height); $filehash = hash(File::FILEHASH_ALG, $imgData); try { @@ -281,8 +294,8 @@ class StoreRemoteMediaPlugin extends Plugin } $filename = MediaFile::encodeFilename($original_name ?? _m('Untitled attachment'), $filehash); } catch (Exception $err) { - common_log(LOG_ERR, "Went to write a thumbnail to disk in StoreRemoteMediaPlugin::storeRemoteThumbnail " . - "but encountered error: $err"); + common_log(LOG_ERR, 'Went to write a thumbnail to disk in StoreRemoteMediaPlugin::storeRemoteThumbnail ' . + "but encountered error: {$err}"); throw $err; } @@ -306,21 +319,21 @@ class StoreRemoteMediaPlugin extends Plugin if (!$this->store_original && $this->crop && ($info[0] > $this->thumbnail_width || $info[1] > $this->thumbnail_height)) { try { // Temporary object, not stored in DB - $img = new ImageFile(-1, $fullpath); + $img = new ImageFile(-1, $fullpath); list($width, $height, $x, $y, $w, $h) = $img->scaleToFit($this->thumbnail_width, $this->thumbnail_height, $this->crop); // The boundary box for our resizing $box = [ 'width' => $width, 'height' => $height, - 'x' => $x, 'y' => $y, - 'w' => $w, 'h' => $h, + 'x' => $x, 'y' => $y, + 'w' => $w, 'h' => $h, ]; - $width = $box['width']; + $width = $box['width']; $height = $box['height']; $img->resizeTo($fullpath, $box); } catch (\Intervention\Image\Exception\NotReadableException $e) { - common_log(LOG_ERR, "StoreRemoteMediaPlugin::storeRemoteThumbnail was unable to decode image with Intervention: $e"); + common_log(LOG_ERR, "StoreRemoteMediaPlugin::storeRemoteThumbnail was unable to decode image with Intervention: {$e}"); // No need to interrupt processing } } @@ -331,8 +344,8 @@ class StoreRemoteMediaPlugin extends Plugin } catch (AlreadyFulfilledException $e) { // Carry on } catch (Exception $err) { - common_log(LOG_ERR, "Went to write a thumbnail to disk in StoreRemoteMediaPlugin::storeRemoteThumbnail " . - "but encountered error: $err"); + common_log(LOG_ERR, 'Went to write a thumbnail to disk in StoreRemoteMediaPlugin::storeRemoteThumbnail ' . + "but encountered error: {$err}"); throw $err; } finally { unset($imgData); @@ -342,8 +355,11 @@ class StoreRemoteMediaPlugin extends Plugin } /** - * @return bool false on no check made, provider name on success - * @throws ServerException if check is made but fails + * @param mixed $url + * + * @throws ServerException if check is made but fails + * + * @return bool false on no check made, provider name on success */ protected function checkWhitelist($url) { @@ -353,7 +369,7 @@ class StoreRemoteMediaPlugin extends Plugin $host = parse_url($url, PHP_URL_HOST); foreach ($this->domain_whitelist as $regex => $provider) { - if (preg_match("/$regex/", $host)) { + if (preg_match("/{$regex}/", $host)) { return $provider; // we trust this source, return provider name } } @@ -366,17 +382,17 @@ class StoreRemoteMediaPlugin extends Plugin * Adds this plugin's version information to $versions array * * @param &$versions array inherited from parent + * * @return bool true hook value */ public function onPluginVersion(array &$versions): bool { $versions[] = ['name' => 'StoreRemoteMedia', - 'version' => self::PLUGIN_VERSION, - 'author' => 'Mikael Nordfeldth, Diogo Peralta Cordeiro', - 'homepage' => GNUSOCIAL_ENGINE_URL, - 'description' => - // TRANS: Plugin description. - _m('Plugin for downloading remotely attached files to local server.')]; + 'version' => self::PLUGIN_VERSION, + 'author' => 'Mikael Nordfeldth, Diogo Peralta Cordeiro', + 'homepage' => GNUSOCIAL_ENGINE_URL, + 'description' => // TRANS: Plugin description. + _m('Plugin for downloading remotely attached files to local server.'), ]; return true; } -} \ No newline at end of file +} diff --git a/symfony.lock b/symfony.lock index f75076aa5e..81ba71141a 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,19 +1,189 @@ { + "alchemy/resource-component": { + "version": "0.1.1" + }, + "alchemy/zippy": { + "version": "v0.5.x-dev" + }, + "beberlei/assert": { + "version": "v2.9.9" + }, + "composer/semver": { + "version": "3.2.4" + }, + "composer/xdebug-handler": { + "version": "1.4.6" + }, + "doctrine/annotations": { + "version": "1.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "1.0", + "ref": "a2759dd6123694c8d901d0ec80006e044c2e6457" + }, + "files": [ + "config/routes/annotations.yaml" + ] + }, + "doctrine/cache": { + "version": "1.10.2" + }, + "doctrine/collections": { + "version": "1.6.7" + }, + "doctrine/common": { + "version": "3.1.2" + }, + "doctrine/dbal": { + "version": "2.13.0" + }, "doctrine/deprecations": { "version": "v0.5.3" }, + "doctrine/doctrine-bundle": { + "version": "2.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "2.3", + "ref": "6e0f582596a8f1c865aaa0d3cceb9bf0daf609b4" + }, + "files": [ + "config/packages/doctrine.yaml", + "config/packages/prod/doctrine.yaml", + "config/packages/test/doctrine.yaml", + "src/Entity/.gitignore", + "src/Repository/.gitignore" + ] + }, + "doctrine/doctrine-migrations-bundle": { + "version": "3.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "3.1", + "ref": "ee609429c9ee23e22d6fa5728211768f51ed2818" + }, + "files": [ + "config/packages/doctrine_migrations.yaml", + "migrations/.gitignore" + ] + }, + "doctrine/event-manager": { + "version": "1.1.1" + }, + "doctrine/inflector": { + "version": "2.0.3" + }, + "doctrine/instantiator": { + "version": "1.4.0" + }, + "doctrine/lexer": { + "version": "1.2.1" + }, + "doctrine/migrations": { + "version": "3.1.1" + }, + "doctrine/orm": { + "version": "2.8.4" + }, + "doctrine/persistence": { + "version": "2.1.0" + }, + "doctrine/sql-formatter": { + "version": "1.1.1" + }, + "egulias/email-validator": { + "version": "3.1.1" + }, + "erusev/parsedown": { + "version": "1.7.4" + }, + "friendsofphp/php-cs-fixer": { + "version": "2.17", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "2.17", + "ref": "f0d4b4ddb4e2bb73b227e3987b6ecc53cd8be431" + }, + "files": [ + ".php_cs.dist" + ] + }, + "friendsofphp/proxy-manager-lts": { + "version": "v1.0.3" + }, + "giggsey/libphonenumber-for-php": { + "version": "8.12.21" + }, + "giggsey/locale": { + "version": "1.9" + }, "jchook/phpunit-assert-throws": { "version": "v1.0.3" }, + "laminas/laminas-code": { + "version": "4.1.0" + }, + "laminas/laminas-eventmanager": { + "version": "3.3.1" + }, + "laminas/laminas-zendframework-bridge": { + "version": "1.2.0" + }, + "league/uri-parser": { + "version": "1.4.1" + }, + "lstrojny/functional-php": { + "version": "1.17.0" + }, + "masterminds/html5": { + "version": "2.7.4" + }, + "monolog/monolog": { + "version": "2.2.0" + }, "myclabs/deep-copy": { "version": "1.10.2" }, + "niels-de-blaauw/php-doc-check": { + "version": "v0.2.2" + }, + "nikic/php-parser": { + "version": "v4.10.4" + }, + "odolbeau/phone-number-bundle": { + "version": "3.0", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "master", + "version": "3.0", + "ref": "4388686329b81291918a948cd42891829fb1de71" + } + }, "phar-io/manifest": { "version": "2.0.1" }, "phar-io/version": { "version": "3.1.0" }, + "php-cs-fixer/diff": { + "version": "v1.3.1" + }, + "php-ds/php-ds": { + "version": "v1.3.0" + }, + "phpdocumentor/reflection-common": { + "version": "2.2.0" + }, + "phpdocumentor/reflection-docblock": { + "version": "5.2.2" + }, + "phpdocumentor/type-resolver": { + "version": "1.4.0" + }, "phpspec/prophecy": { "version": "1.13.0" }, @@ -46,6 +216,21 @@ "tests/bootstrap.php" ] }, + "psr/cache": { + "version": "1.0.1" + }, + "psr/container": { + "version": "1.1.1" + }, + "psr/event-dispatcher": { + "version": "1.0.0" + }, + "psr/link": { + "version": "1.1.1" + }, + "psr/log": { + "version": "1.1.3" + }, "sebastian/cli-parser": { "version": "1.0.1" }, @@ -94,7 +279,449 @@ "sebastian/version": { "version": "3.0.2" }, + "sensio/framework-extra-bundle": { + "version": "5.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.2", + "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" + }, + "files": [ + "config/packages/sensio_framework_extra.yaml" + ] + }, + "someonewithpc/memcached-polyfill": { + "version": "1.0.1" + }, + "someonewithpc/redis-polyfill": { + "version": "dev-master" + }, + "symfony/amqp-messenger": { + "version": "v5.2.4" + }, + "symfony/asset": { + "version": "v5.2.4" + }, + "symfony/browser-kit": { + "version": "v5.2.4" + }, + "symfony/cache": { + "version": "v5.2.6" + }, + "symfony/cache-contracts": { + "version": "v2.2.0" + }, + "symfony/config": { + "version": "v5.2.4" + }, + "symfony/console": { + "version": "5.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.1", + "ref": "c6d02bdfba9da13c22157520e32a602dbee8a75c" + }, + "files": [ + "bin/console" + ] + }, + "symfony/css-selector": { + "version": "v5.2.4" + }, + "symfony/debug-bundle": { + "version": "4.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "4.1", + "ref": "f8863cbad2f2e58c4b65fa1eac892ab189971bea" + }, + "files": [ + "config/packages/dev/debug.yaml" + ] + }, + "symfony/debug-pack": { + "version": "v1.0.9" + }, + "symfony/dependency-injection": { + "version": "v5.2.6" + }, + "symfony/deprecation-contracts": { + "version": "v2.2.0" + }, + "symfony/doctrine-bridge": { + "version": "v5.2.6" + }, + "symfony/doctrine-messenger": { + "version": "v5.2.5" + }, + "symfony/dom-crawler": { + "version": "v5.2.4" + }, + "symfony/dotenv": { + "version": "v5.2.4" + }, + "symfony/error-handler": { + "version": "v5.2.6" + }, + "symfony/event-dispatcher": { + "version": "v5.2.4" + }, + "symfony/event-dispatcher-contracts": { + "version": "v2.2.0" + }, + "symfony/expression-language": { + "version": "v5.2.4" + }, + "symfony/filesystem": { + "version": "v5.2.6" + }, + "symfony/finder": { + "version": "v5.2.4" + }, + "symfony/flex": { + "version": "1.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "1.0", + "ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e" + }, + "files": [ + ".env" + ] + }, + "symfony/form": { + "version": "v5.2.6" + }, + "symfony/framework-bundle": { + "version": "5.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.2", + "ref": "6ec87563dcc85cd0c48856dcfbfc29610506d250" + }, + "files": [ + "config/packages/cache.yaml", + "config/packages/framework.yaml", + "config/packages/test/framework.yaml", + "config/preload.php", + "config/routes/dev/framework.yaml", + "config/services.yaml", + "public/index.php", + "src/Controller/.gitignore", + "src/Kernel.php" + ] + }, + "symfony/http-client": { + "version": "v5.2.6" + }, + "symfony/http-client-contracts": { + "version": "v2.3.1" + }, + "symfony/http-foundation": { + "version": "v5.2.4" + }, + "symfony/http-kernel": { + "version": "v5.2.6" + }, + "symfony/intl": { + "version": "v5.2.4" + }, + "symfony/mailer": { + "version": "4.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "4.3", + "ref": "15658c2a0176cda2e7dba66276a2030b52bd81b2" + }, + "files": [ + "config/packages/mailer.yaml" + ] + }, + "symfony/maker-bundle": { + "version": "1.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "1.0", + "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" + } + }, + "symfony/messenger": { + "version": "4.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "4.3", + "ref": "e9a414b113ceadbf4e52abe37bf8f1b443f06ccb" + }, + "files": [ + "config/packages/messenger.yaml" + ] + }, + "symfony/mime": { + "version": "v5.2.6" + }, + "symfony/monolog-bridge": { + "version": "v5.2.5" + }, + "symfony/monolog-bundle": { + "version": "3.7", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "3.7", + "ref": "f4adb4379ee437f91ecb1bd5a41c1de6286b9a04" + }, + "files": [ + "config/packages/dev/monolog.yaml", + "config/packages/prod/deprecations.yaml", + "config/packages/prod/monolog.yaml", + "config/packages/test/monolog.yaml" + ] + }, + "symfony/notifier": { + "version": "5.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.0", + "ref": "c31585e252b32fe0e1f30b1f256af553f4a06eb9" + }, + "files": [ + "config/packages/notifier.yaml" + ] + }, + "symfony/options-resolver": { + "version": "v5.2.4" + }, + "symfony/orm-pack": { + "version": "v2.1.0" + }, + "symfony/phpunit-bridge": { + "version": "5.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.1", + "ref": "bf16921ef8309a81d9f046e9b6369c46bcbd031f" + }, + "files": [ + ".env.test", + "bin/phpunit", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, + "symfony/polyfill-intl-grapheme": { + "version": "v1.22.1" + }, + "symfony/polyfill-intl-icu": { + "version": "v1.22.1" + }, + "symfony/polyfill-intl-idn": { + "version": "v1.22.1" + }, + "symfony/polyfill-intl-normalizer": { + "version": "v1.22.1" + }, + "symfony/polyfill-mbstring": { + "version": "v1.22.1" + }, + "symfony/polyfill-php73": { + "version": "v1.22.1" + }, + "symfony/polyfill-php80": { + "version": "v1.22.1" + }, + "symfony/process": { + "version": "v5.2.4" + }, + "symfony/profiler-pack": { + "version": "v1.0.5" + }, + "symfony/property-access": { + "version": "v5.2.4" + }, + "symfony/property-info": { + "version": "v5.2.4" + }, + "symfony/proxy-manager-bridge": { + "version": "v5.2.4" + }, + "symfony/redis-messenger": { + "version": "v5.2.4" + }, + "symfony/routing": { + "version": "5.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.1", + "ref": "b4f3e7c95e38b606eef467e8a42a8408fc460c43" + }, + "files": [ + "config/packages/prod/routing.yaml", + "config/packages/routing.yaml", + "config/routes.yaml" + ] + }, + "symfony/security-bundle": { + "version": "5.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.1", + "ref": "0a4bae19389d3b9cba1ca0102e3b2bccea724603" + }, + "files": [ + "config/packages/security.yaml" + ] + }, + "symfony/security-core": { + "version": "v5.2.6" + }, + "symfony/security-csrf": { + "version": "v5.2.4" + }, + "symfony/security-guard": { + "version": "v5.2.4" + }, + "symfony/security-http": { + "version": "v5.2.6" + }, + "symfony/serializer": { + "version": "v5.2.4" + }, + "symfony/serializer-pack": { + "version": "v1.0.4" + }, + "symfony/service-contracts": { + "version": "v2.2.0" + }, + "symfony/stopwatch": { + "version": "v5.2.4" + }, + "symfony/string": { + "version": "v5.2.6" + }, + "symfony/test-pack": { + "version": "v1.0.7" + }, + "symfony/translation": { + "version": "3.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "3.3", + "ref": "2ad9d2545bce8ca1a863e50e92141f0b9d87ffcd" + }, + "files": [ + "config/packages/translation.yaml", + "translations/.gitignore" + ] + }, + "symfony/translation-contracts": { + "version": "v2.3.0" + }, + "symfony/twig-bridge": { + "version": "v5.2.6" + }, + "symfony/twig-bundle": { + "version": "5.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "5.0", + "ref": "fab9149bbaa4d5eca054ed93f9e1b66cc500895d" + }, + "files": [ + "config/packages/test/twig.yaml", + "config/packages/twig.yaml", + "templates/base.html.twig" + ] + }, + "symfony/twig-pack": { + "version": "v1.0.1" + }, + "symfony/validator": { + "version": "4.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "4.3", + "ref": "d902da3e4952f18d3bf05aab29512eb61cabd869" + }, + "files": [ + "config/packages/test/validator.yaml", + "config/packages/validator.yaml" + ] + }, + "symfony/var-dumper": { + "version": "v5.2.6" + }, + "symfony/var-exporter": { + "version": "v5.2.4" + }, + "symfony/web-link": { + "version": "v5.2.5" + }, + "symfony/web-profiler-bundle": { + "version": "3.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "3.3", + "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6" + }, + "files": [ + "config/packages/dev/web_profiler.yaml", + "config/packages/test/web_profiler.yaml", + "config/routes/dev/web_profiler.yaml" + ] + }, + "symfony/yaml": { + "version": "v5.2.5" + }, + "symfonycasts/verify-email-bundle": { + "version": "v1.3.0" + }, + "tgalopin/html-sanitizer": { + "version": "1.4.0" + }, + "tgalopin/html-sanitizer-bundle": { + "version": "1.0", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "master", + "version": "1.0", + "ref": "95b935177db9abb65356fe19e57fe5abd908b5b8" + } + }, "theseer/tokenizer": { "version": "1.2.0" + }, + "twig/extra-bundle": { + "version": "v3.3.0" + }, + "twig/markdown-extra": { + "version": "v3.3.0" + }, + "twig/twig": { + "version": "v3.3.0" + }, + "ulrichsg/getopt-php": { + "version": "v3.4.0" + }, + "webmozart/assert": { + "version": "1.10.0" + }, + "wp-cli/php-cli-tools": { + "version": "v0.11.12" } }