Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework processing of og:images in rich embeds #6170

Open
wants to merge 11 commits into
base: 6.2
Choose a base branch
from
5 changes: 5 additions & 0 deletions com.woltlab.wcf/objectType.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,11 @@
<definitionname>com.woltlab.wcf.file</definitionname>
<classname>wcf\system\file\processor\UserCoverPhotoFileProcessor</classname>
</type>
<type>
<name>com.woltlab.wcf.unfurl</name>
<definitionname>com.woltlab.wcf.file</definitionname>
<classname>wcf\system\file\processor\UnfurlUrlImageFileProcessor</classname>
</type>
<!-- deprecated -->
<type>
<name>com.woltlab.wcf.page.controller</name>
Expand Down
8 changes: 8 additions & 0 deletions com.woltlab.wcf/option.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,15 @@ XING</selectoptions>
<option name="module_url_unfurling">
<categoryname>message.general.unfurl</categoryname>
<optiontype>boolean</optiontype>
<enableoptions>url_unfurling_load_images</enableoptions>
<defaultvalue>1</defaultvalue>
<showorder>1</showorder>
</option>
<option name="url_unfurling_no_images">
<categoryname>message.general.unfurl</categoryname>
<optiontype>boolean</optiontype>
<defaultvalue>0</defaultvalue>
<showorder>2</showorder>
</option>
<!-- /message.general.unfurl -->
<!-- message.general.formatting -->
Expand Down
8 changes: 4 additions & 4 deletions com.woltlab.wcf/templates/shared_unfurlUrl.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
{else}
<div class="unfurlUrlCardContainer">
<div class="unfurlUrlCard{*
*}{if $object->hasCoverImage()} unfurlUrlCardCoverImage{/if}{*
*}{if $object->hasSquaredImage()} unfurlUrlCardSquaredImage{/if}{*
*}{if !URL_UNFURLING_NO_IMAGES && $object->hasCoverImage()} unfurlUrlCardCoverImage{/if}{*
*}{if !URL_UNFURLING_NO_IMAGES && $object->hasSquaredImage()} unfurlUrlCardSquaredImage{/if}{*
*}">
{if !$object->getImageUrl()|empty}
{if !URL_UNFURLING_NO_IMAGES && !$object->getImageUrl()|empty}
<img src="{$object->getImageUrl()}" height="{$object->height}" width="{$object->width}" class="unfurlUrlImage" alt="" loading="lazy">
{/if}
<div class="unfurlUrlInformation">
Expand All @@ -20,4 +20,4 @@
{/if}
{else}
<a {anchorAttributes url=$object->url isUgc=$enableUgc}>{$object->url}</a>
{/if}
{/if}
13 changes: 13 additions & 0 deletions wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,17 @@
->referencedColumns(['fileID'])
->onDelete('SET NULL'),
]),
PartialDatabaseTable::create('wcf1_unfurl_url_image')
->columns([
IntDatabaseTableColumn::create('fileID')
->length(10)
->defaultValue(null),
])
->foreignKeys([
DatabaseTableForeignKey::create()
->columns(['fileID'])
->referencedTable('wcf1_file')
->referencedColumns(['fileID'])
->onDelete('SET NULL'),
])
];
1 change: 0 additions & 1 deletion wcfsetup/install/files/images/unfurlUrl/.htaccess

This file was deleted.

1 change: 1 addition & 0 deletions wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ static function (\wcf\event\worker\RebuildWorkerCollecting $event) {
$event->register(\wcf\system\worker\CommentResponseRebuildDataWorker::class, 121);
$event->register(\wcf\system\worker\AttachmentRebuildDataWorker::class, 450);
$event->register(\wcf\system\worker\MediaRebuildDataWorker::class, 450);
$event->register(\wcf\system\worker\UnfurlUrlRebuildDataWorker::class, 450);
$event->register(\wcf\system\worker\FileRebuildDataWorker::class, 475);
$event->register(\wcf\system\worker\SitemapRebuildWorker::class, 500);
$event->register(\wcf\system\worker\StatDailyRebuildDataWorker::class, 800);
Expand Down
14 changes: 10 additions & 4 deletions wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrl.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use wcf\action\ImageProxyAction;
use wcf\data\DatabaseObject;
use wcf\system\cache\runtime\FileRuntimeCache;
use wcf\system\request\LinkHandler;
use wcf\system\WCF;
use wcf\util\CryptoUtil;
Expand Down Expand Up @@ -32,6 +33,7 @@
* @property-read int $imageID
* @property-read int $isStored
* @property-read string $status
* @property-read int|null $fileID
*/
class UnfurlUrl extends DatabaseObject
{
Expand All @@ -47,7 +49,12 @@ class UnfurlUrl extends DatabaseObject

public const STATUS_REJECTED = "REJECTED";

/**
* @deprecated 6.2
*/
public const IMAGE_DIR = "images/unfurlUrl/";
public const THUMBNAIL_WIDTH = 800;
public const THUMBNAIL_HEIGHT = 400;

/**
* @inheritDoc
Expand Down Expand Up @@ -103,11 +110,10 @@ public function getHost(): string
*/
public function getImageUrl(): ?string
{
if ($this->isStored) {
$imageFolder = self::IMAGE_DIR . \substr($this->imageUrlHash, 0, 2) . "/";
$imageName = $this->imageUrlHash . '.' . $this->imageExtension;
if ($this->isStored && $this->fileID) {
$file = FileRuntimeCache::getInstance()->getObject($this->fileID);

return WCF::getPath() . $imageFolder . $imageName;
return $file?->getFullSizeImageSource();
} elseif (!empty($this->imageUrl)) {
if (MODULE_IMAGE_PROXY) {
$key = CryptoUtil::createSignedString($this->imageUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
namespace wcf\data\unfurl\url;

use wcf\data\DatabaseObjectEditor;
use wcf\data\file\File;
use wcf\data\file\FileEditor;
use wcf\system\exception\SystemException;
use wcf\system\image\adapter\exception\ImageNotProcessable;
use wcf\system\image\adapter\exception\ImageNotReadable;
use wcf\system\image\ImageHandler;
use wcf\util\FileUtil;

use function wcf\functions\exception\logThrowable;

/**
* Provide functions to edit an unfurl url.
Expand All @@ -21,4 +30,50 @@ class UnfurlUrlEditor extends DatabaseObjectEditor
* @inheritDoc
*/
public static $baseClass = UnfurlUrl::class;

/**
* Creates a webp thumbnail for the given file.
*/
public static function createWebpThumbnail(string $file, string $originalFile): ?File
{
$imageData = \getimagesize($file);

$imageAdapter = ImageHandler::getInstance()->getAdapter();
if (!$imageAdapter->checkMemoryLimit($imageData[0], $imageData[1], $imageData['mime'])) {
return null;
}
$webpFile = FileUtil::getTemporaryFilename(extension: 'webp');

try {
$imageAdapter->loadFile($file);
$thumbnail = $imageAdapter->createThumbnail(UnfurlUrl::THUMBNAIL_WIDTH, UnfurlUrl::THUMBNAIL_HEIGHT);
$imageAdapter->saveImageAs($thumbnail, $webpFile, 'webp', 80);

// Clean up the thumbnail
$thumbnail = null;

return FileEditor::createFromExistingFile(
$webpFile,
\pathinfo($originalFile, \PATHINFO_BASENAME) . ".webp",
'com.woltlab.wcf.unfurl'
);
} catch (SystemException | ImageNotReadable $e) {
return null;
} catch (ImageNotProcessable $e) {
logThrowable($e);

return null;
} catch (\Throwable $e) {
logThrowable($e);
// Ignore any errors trying to save the file unless in debug mode.
if (\ENABLE_DEBUG_MODE) {
throw $e;
}

return null;
} finally {
// Clean up temporary files
@\unlink($webpFile);
}
}
}
16 changes: 16 additions & 0 deletions wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlList.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace wcf\data\unfurl\url;

use wcf\data\DatabaseObjectList;
use wcf\system\cache\runtime\FileRuntimeCache;

/**
* Represents a list of unfurled urls.
Expand Down Expand Up @@ -35,4 +36,19 @@ public function __construct()
LEFT JOIN wcf1_unfurl_url_image unfurl_url_image
ON unfurl_url_image.imageID = unfurl_url.imageID";
}

#[\Override]
public function readObjects()
{
parent::readObjects();

$fileIDs = [];
foreach ($this->objects as $object) {
if ($object->fileID !== null) {
$fileIDs[] = $object->fileID;
}
}

FileRuntimeCache::getInstance()->cacheObjectIDs($fileIDs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
use BadMethodCallException;
use LogicException;
use Psr\Http\Message\ResponseInterface;
use wcf\data\file\File;
use wcf\data\unfurl\url\UnfurlUrl;
use wcf\data\unfurl\url\UnfurlUrlAction;
use wcf\data\unfurl\url\UnfurlUrlEditor;
use wcf\system\message\unfurl\exception\DownloadFailed;
use wcf\system\message\unfurl\exception\ParsingFailed;
use wcf\system\message\unfurl\exception\UrlInaccessible;
Expand Down Expand Up @@ -83,7 +85,7 @@ public function perform()
$imageData = [];
$imageID = null;
$imageUrl = $unfurlResponse->getImageUrl();
if ($imageUrl) {
if (!URL_UNFURLING_NO_IMAGES && $imageUrl) {
if (
\strpos($imageUrl, '\\') === false
&& \strpos($imageUrl, "'") === false
Expand Down Expand Up @@ -144,21 +146,20 @@ private function getImageData(UnfurlResponse $unfurlResponse): array
if (!$this->validateImage($imageData)) {
return [];
}
$file = $this->createFile(
$imageData,
\pathinfo($unfurlResponse->getImageUrl(), PATHINFO_FILENAME),
$image
);

$imageSaveData = [
return [
'imageUrl' => $unfurlResponse->getImageUrl(),
'imageUrlHash' => \sha1($unfurlResponse->getImageUrl()),
'width' => $imageData[0],
'height' => $imageData[1],
'fileID' => $file?->fileID,
'isStored' => $file !== null ? 1 : 0,
'width' => $file?->width ?? $imageData[0],
'height' => $file?->height ?? $imageData[1],
];

if (!(MODULE_IMAGE_PROXY || IMAGE_ALLOW_EXTERNAL_SOURCE)) {
$this->saveImage($imageData, $image, $imageSaveData['imageUrlHash']);
$imageSaveData['imageExtension'] = $this->getImageExtension($imageData);
$imageSaveData['isStored'] = 1;
}

return $imageSaveData;
} catch (UrlInaccessible | DownloadFailed $e) {
return [];
}
Expand Down Expand Up @@ -218,19 +219,25 @@ private function validateImage(array $imageData): bool
return true;
}

private function saveImage(array $imageData, string $image, string $imageHash): string
private function createFile(array $imageData, string $originalFile, string $image): ?File
{
$path = WCF_DIR . UnfurlUrl::IMAGE_DIR . \substr($imageHash, 0, 2) . '/';

FileUtil::makePath($path);

$extension = $this->getImageExtension($imageData);

$fileLocation = $path . $imageHash . '.' . $extension;

\file_put_contents($fileLocation, $image);

return $imageHash;
$tmp = FileUtil::getTemporaryFilename(extension: $extension);
\file_put_contents($tmp, $image);

$file = UnfurlUrlEditor::createWebpThumbnail(
$tmp,
\sprintf(
"%s.%s",
$originalFile,
$extension
)
);

// Clean up temporary files
@\unlink($tmp);

return $file;
}

private function getImageExtension(array $imageData): ?string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace wcf\system\file\processor;

use wcf\data\file\File;
use wcf\system\database\util\PreparedStatementConditionBuilder;
use wcf\system\WCF;

/**
* File processor for unfurl url images.
*
* @author Olaf Braun
* @copyright 2001-2025 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.2
*/
final class UnfurlUrlImageFileProcessor extends AbstractFileProcessor
{

#[\Override]
public function acceptUpload(string $filename, int $fileSize, array $context): FileProcessorPreflightResult
{
return FileProcessorPreflightResult::InvalidContext;
}

#[\Override]
public function canAdopt(File $file, array $context): bool
{
return false;
}

#[\Override]
public function adopt(File $file, array $context): void
{
// do nothing
}

#[\Override]
public function canDelete(File $file): bool
{
return false;
}

#[\Override]
public function canDownload(File $file): bool
{
return true;
}

#[\Override]
public function delete(array $fileIDs, array $thumbnailIDs): void
{
$conditionBuilder = new PreparedStatementConditionBuilder();
$conditionBuilder->add('fileID IN (?)', [$fileIDs]);

$sql = "UPDATE wcf1_unfurl_url_image
SET isStored = ?
" . $conditionBuilder;
$statement = WCF::getDB()->prepare($sql);
$statement->execute([0, ...$conditionBuilder->getParameters()]);
}

#[\Override]
public function getObjectTypeName(): string
{
return 'com.woltlab.wcf.unfurl';
}
}
Loading
Loading