From: joshuaruesweg Date: Wed, 10 Mar 2021 13:07:59 +0000 (+0100) Subject: Use own table for unfurl url images X-Git-Tag: 5.4.0_Alpha_1~146^2~16 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=286dbaf82fd2776d91e0120eae2fa44b5d7a48ee;p=GitHub%2FWoltLab%2FWCF.git Use own table for unfurl url images --- diff --git a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_5.4.php b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_5.4.php index 17d7f93153..cc9c1a2d4d 100644 --- a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_5.4.php +++ b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_5.4.php @@ -1,7 +1,7 @@ columns([ + ObjectIdDatabaseTableColumn::create('imageID'), + TextDatabaseTableColumn::create('imageUrl') + ->notNull(), + VarcharDatabaseTableColumn::create('imageHash') + ->notNull() + ->length(40), + NotNullInt10DatabaseTableColumn::create('width'), + NotNullInt10DatabaseTableColumn::create('height'), + VarcharDatabaseTableColumn::create('imageExtension') + ->length(4), + ]) + ->indices([ + DatabaseTablePrimaryIndex::create() + ->columns(['imageID']), + DatabaseTableIndex::create('imageHash') + ->type(DatabaseTableIndex::UNIQUE_TYPE) + ->columns(['imageHash']), + ]), + DatabaseTable::create('wcf1_unfurl_url') ->columns([ ObjectIdDatabaseTableColumn::create('urlID'), @@ -232,14 +253,8 @@ return [ NotNullVarchar255DatabaseTableColumn::create('title'), TextDatabaseTableColumn::create('description') ->notNull(), - TextDatabaseTableColumn::create('imageUrl') - ->notNull() - ->defaultValue(''), - NotNullVarchar255DatabaseTableColumn::create('imageType') - ->defaultValue('NOIMAGE'), - VarcharDatabaseTableColumn::create('imageHash') - ->notNull() - ->length(45), + IntDatabaseTableColumn::create('imageID') + ->length(10), NotNullVarchar255DatabaseTableColumn::create('status') ->defaultValue('PENDING'), NotNullInt10DatabaseTableColumn::create('lastFetch') @@ -251,5 +266,12 @@ return [ DatabaseTableIndex::create('urlHash') ->type(DatabaseTableIndex::UNIQUE_TYPE) ->columns(['urlHash']), + ]) + ->foreignKeys([ + DatabaseTableForeignKey::create() + ->columns(['imageID']) + ->referencedTable('wcf1_unfurl_url_image') + ->referencedColumns(['imageID']) + ->onDelete('SET NULL'), ]), ]; diff --git a/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrl.class.php b/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrl.class.php index aea01b6fef..4756f0017e 100644 --- a/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrl.class.php +++ b/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrl.class.php @@ -25,16 +25,19 @@ use wcf\util\Url; * @property-read string $description * @property-read string $imageHash * @property-read string $imageUrl - * @property-read string $imageType + * @property-read string $imageExtension + * @property-read int $width + * @property-read int $height * @property-read int $lastFetch + * @property-read int $imageID */ class UnfurlUrl extends DatabaseObject { - public const IMAGE_SQUARED = "SQUARED"; + private const IMAGE_SQUARED = "SQUARED"; - public const IMAGE_COVER = "COVER"; + private const IMAGE_COVER = "COVER"; - public const IMAGE_NO_IMAGE = "NOIMAGE"; + private const IMAGE_NO_IMAGE = "NOIMAGE"; public const STATUS_PENDING = "PENDING"; @@ -42,10 +45,36 @@ class UnfurlUrl extends DatabaseObject public const STATUS_REJECTED = "REJECTED"; + public const IMAGE_DIR = "images/unfurlUrl/"; + + /** + * @inheritDoc + */ + public function __construct($id, $row = null, ?DatabaseObject $object = null) + { + if ($id !== null) { + $sql = "SELECT unfurl_url.*, unfurl_url_image.* + FROM wcf" . WCF_N . "_unfurl_url unfurl_url + LEFT JOIN wcf" . WCF_N . "_unfurl_url_image unfurl_url_image + ON unfurl_url_image.imageID = unfurl_url.imageID + WHERE unfurl_url.urlID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute([$id]); + $row = $statement->fetchArray(); + + // enforce data type 'array' + if ($row === false) { + $row = []; + } + } elseif ($object !== null) { + $row = $object->data; + } + + $this->handleData($row); + } + /** * Renders the unfurl url card and returns the template. - * - * @return string */ public function render(): string { @@ -56,8 +85,6 @@ class UnfurlUrl extends DatabaseObject /** * Returns the hostname of the url. - * - * @return string */ public function getHost(): string { @@ -74,7 +101,10 @@ class UnfurlUrl extends DatabaseObject public function getImageUrl(): ?string { if (!empty($this->imageHash)) { - return WCF::getPath() . 'images/unfurlUrl/' . \substr($this->imageHash, 0, 2) . '/' . $this->imageHash; + $imageFolder = self::IMAGE_DIR . \substr($this->imageHash, 0, 2) . "/"; + $imageName = $this->imageHash . '.' . $this->imageExtension; + + return WCF::getPath() . $imageFolder . $imageName; } elseif (!empty($this->imageUrl)) { if (MODULE_IMAGE_PROXY) { $key = CryptoUtil::createSignedString($this->imageUrl); @@ -92,12 +122,25 @@ class UnfurlUrl extends DatabaseObject public function hasCoverImage(): bool { - return $this->imageType == self::IMAGE_COVER && !empty($this->getImageUrl()); + return $this->getImageType() == self::IMAGE_COVER && !empty($this->getImageUrl()); } public function hasSquaredImage(): bool { - return $this->imageType == self::IMAGE_SQUARED && !empty($this->getImageUrl()); + return $this->getImageType() == self::IMAGE_SQUARED && !empty($this->getImageUrl()); + } + + private function getImageType(): string + { + if (!$this->imageID) { + return self::IMAGE_NO_IMAGE; + } + + if ($this->width == $this->height) { + return self::IMAGE_SQUARED; + } + + return self::IMAGE_COVER; } /** @@ -111,8 +154,10 @@ class UnfurlUrl extends DatabaseObject throw new \InvalidArgumentException("Given URL is not a valid URL."); } - $sql = "SELECT unfurl_url.* + $sql = "SELECT unfurl_url.*, unfurl_url_image.* FROM wcf" . WCF_N . "_unfurl_url unfurl_url + LEFT JOIN wcf" . WCF_N . "_unfurl_url_image unfurl_url_image + ON unfurl_url_image.imageID = unfurl_url.imageID WHERE unfurl_url.urlHash = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([\sha1($url)]); diff --git a/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlAction.class.php b/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlAction.class.php index 9f808c3007..bb0169bc08 100644 --- a/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlAction.class.php +++ b/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlAction.class.php @@ -5,6 +5,7 @@ namespace wcf\data\unfurl\url; use wcf\data\AbstractDatabaseObjectAction; use wcf\system\background\BackgroundQueueHandler; use wcf\system\background\job\UnfurlUrlBackgroundJob; +use wcf\system\WCF; /** * Contains all dbo actions for unfurl url objects. @@ -25,6 +26,10 @@ class UnfurlUrlAction extends AbstractDatabaseObjectAction */ public function create() { + if (isset($this->parameters['imageData']) && !empty($this->parameters['imageData'])) { + $this->parameters['data']['imageID'] = $this->saveImageData($this->parameters['imageData']); + } + /** @var UnfurlUrl $object */ $object = parent::create(); @@ -37,19 +42,53 @@ class UnfurlUrlAction extends AbstractDatabaseObjectAction return $object; } + /** + * @inheritDoc + */ + public function update() + { + if (isset($this->parameters['imageData']) && !empty($this->parameters['imageData'])) { + $this->parameters['data']['imageID'] = $this->saveImageData($this->parameters['imageData']); + } + + parent::update(); + } + + private function saveImageData(array $imageData): int + { + $keys = $values = ''; + $statementParameters = []; + foreach ($imageData as $key => $value) { + if (!empty($keys)) { + $keys .= ','; + $values .= ','; + } + + $keys .= $key; + $values .= '?'; + $statementParameters[] = $value; + } + + // save object + $sql = "INSERT INTO wcf" . WCF_N . "_unfurl_url_image + (" . $keys . ") + VALUES (" . $values . ")"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($statementParameters); + + return WCF::getDB()->getInsertID("wcf" . WCF_N . "_unfurl_url_image", "imageID"); + } + /** * Returns the unfurl url object to a given url. - * - * @return UnfurlUrl */ - public function findOrCreate() + public function findOrCreate(): UnfurlUrl { $object = UnfurlUrl::getByUrl($this->parameters['data']['url']); if (!$object) { $returnValues = (new self([], 'create', [ 'data' => [ - 'imageUrl' => '', 'url' => $this->parameters['data']['url'], 'urlHash' => \sha1($this->parameters['data']['url']), ], diff --git a/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlList.class.php b/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlList.class.php index d2e7b9901a..ce7e382351 100644 --- a/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlList.class.php +++ b/wcfsetup/install/files/lib/data/unfurl/url/UnfurlUrlList.class.php @@ -20,4 +20,19 @@ use wcf\data\DatabaseObjectList; */ class UnfurlUrlList extends DatabaseObjectList { + /** + * @inheritDoc + */ + public function __construct() + { + parent::__construct(); + + if (!empty($this->sqlSelects)) { + $this->sqlSelects .= ','; + } + $this->sqlSelects .= "unfurl_url_image.*"; + $this->sqlJoins .= " + LEFT JOIN wcf" . WCF_N . "_unfurl_url_image unfurl_url_image + ON unfurl_url_image.imageID = unfurl_url.imageID"; + } } diff --git a/wcfsetup/install/files/lib/system/background/job/UnfurlUrlBackgroundJob.class.php b/wcfsetup/install/files/lib/system/background/job/UnfurlUrlBackgroundJob.class.php index c888b89d5c..2e4e968586 100644 --- a/wcfsetup/install/files/lib/system/background/job/UnfurlUrlBackgroundJob.class.php +++ b/wcfsetup/install/files/lib/system/background/job/UnfurlUrlBackgroundJob.class.php @@ -10,8 +10,10 @@ use wcf\system\message\unfurl\exception\DownloadFailed; use wcf\system\message\unfurl\exception\ParsingFailed; use wcf\system\message\unfurl\exception\UrlInaccessible; use wcf\system\message\unfurl\UnfurlResponse; +use wcf\system\WCF; use wcf\util\FileUtil; use wcf\util\StringUtil; +use wcf\util\Url; /** * Represents a background job to get information for an url. @@ -79,42 +81,22 @@ final class UnfurlUrlBackgroundJob extends AbstractBackgroundJob $description = StringUtil::truncate($unfurlResponse->getDescription()); } - if ($unfurlResponse->getImageUrl()) { - try { - $image = $this->downloadImage($unfurlResponse->getImage()); - $imageData = \getimagesizefromstring($image); - if ($imageData !== false) { - $imageType = $this->validateImage($imageData); - if (!(MODULE_IMAGE_PROXY || IMAGE_ALLOW_EXTERNAL_SOURCE)) { - $imageHash = $this->saveImage($imageData, $image); - } else { - $imageHash = ""; - } - } else { - $imageType = UnfurlUrl::IMAGE_NO_IMAGE; - } + $imageData = []; + $imageID = null; + if ($unfurlResponse->getImageUrl() && Url::is($unfurlResponse->getImageUrl())) { + $imageID = self::getImageIdByUrl($unfurlResponse->getImageUrl()); - if ($imageType === UnfurlUrl::IMAGE_NO_IMAGE) { - $imageUrl = $imageHash = ""; - } else { - $imageUrl = $unfurlResponse->getImageUrl(); - } - } catch (UrlInaccessible | DownloadFailed $e) { - $imageType = UnfurlUrl::IMAGE_NO_IMAGE; - $imageUrl = $imageHash = ""; + if ($imageID === null) { + $imageData = $this->getImageData($unfurlResponse); } - } else { - $imageType = UnfurlUrl::IMAGE_NO_IMAGE; - $imageUrl = $imageHash = ""; } $this->save( UnfurlUrl::STATUS_SUCCESSFUL, $title, $description, - $imageType, - $imageUrl, - $imageHash + $imageID, + $imageData ); } catch (UrlInaccessible | ParsingFailed $e) { if (\ENABLE_DEBUG_MODE) { @@ -125,6 +107,54 @@ final class UnfurlUrlBackgroundJob extends AbstractBackgroundJob } } + private function getImageData(UnfurlResponse $unfurlResponse): array + { + $imageSaveData = []; + + if (empty($unfurlResponse->getImageUrl()) || !Url::is($unfurlResponse->getImageUrl())) { + throw new BadMethodCallException("Invalid image given."); + } + + try { + $imageResponse = $unfurlResponse->getImage(); + $image = $this->downloadImage($imageResponse); + $imageData = \getimagesizefromstring($image); + + if ($imageData !== false) { + if ($this->validateImage($imageData)) { + $imageSaveData['imageUrl'] = $unfurlResponse->getImageUrl(); + $imageSaveData['width'] = $imageData[0]; + $imageSaveData['height'] = $imageData[1]; + if (!(MODULE_IMAGE_PROXY || IMAGE_ALLOW_EXTERNAL_SOURCE)) { + $imageSaveData['imageHash'] = $this->saveImage($imageData, $image); + $imageSaveData['imageExtension'] = $this->getImageExtension($imageData); + } + } + } + } catch (UrlInaccessible | DownloadFailed $e) { + $imageSaveData = []; + } + + return $imageSaveData; + } + + private static function getImageIdByUrl(string $url): ?int + { + $sql = "SELECT imageID + FROM wcf" . WCF_N . "_unfurl_url_image + WHERE imageUrl = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute([$url]); + + $imageID = $statement->fetchSingleColumn(); + + if ($imageID === false) { + return null; + } + + return $imageID; + } + private function downloadImage(Response $imageResponse): string { $image = ""; @@ -143,61 +173,65 @@ final class UnfurlUrlBackgroundJob extends AbstractBackgroundJob return $image; } - private function validateImage(array $imageData): string + private function validateImage(array $imageData): bool { $isSquared = $imageData[0] === $imageData[1]; if ( (!$isSquared && ($imageData[0] < 300 && $imageData[1] < 150)) || \min($imageData[0], $imageData[1]) < 50 ) { - return UnfurlUrl::IMAGE_NO_IMAGE; - } else { - if ($isSquared) { - return UnfurlUrl::IMAGE_SQUARED; - } else { - return UnfurlUrl::IMAGE_COVER; - } + return false; + } + + if (!$this->getImageExtension($imageData)) { + return false; } + + return true; } private function saveImage(array $imageData, string $image): string { - switch ($imageData[2]) { - case \IMAGETYPE_PNG: - $extension = 'png'; - break; - case \IMAGETYPE_GIF: - $extension = 'gif'; - break; - case \IMAGETYPE_JPEG: - $extension = 'jpg'; - break; - - default: - throw new DownloadFailed(); - } - $imageHash = \sha1($image); $path = WCF_DIR . 'images/unfurlUrl/' . \substr($imageHash, 0, 2); FileUtil::makePath($path); + $extension = $this->getImageExtension($imageData); + $fileLocation = $path . '/' . $imageHash . '.' . $extension; \file_put_contents($fileLocation, $image); @\touch($fileLocation); - return $imageHash . '.' . $extension; + return $imageHash; + } + + private function getImageExtension(array $imageData): ?string + { + switch ($imageData[2]) { + case \IMAGETYPE_PNG: + return 'png'; + break; + case \IMAGETYPE_GIF: + return 'gif'; + break; + case \IMAGETYPE_JPEG: + return 'jpg'; + break; + + default: + return null; + } } private function save( string $status, string $title = "", string $description = "", - string $imageType = UnfurlUrl::IMAGE_NO_IMAGE, - string $imageUrl = "", - string $imageHash = "" + ?int $imageID = null, + array $imageData = [] ): void { switch ($status) { case UnfurlUrl::STATUS_PENDING: @@ -209,14 +243,8 @@ final class UnfurlUrlBackgroundJob extends AbstractBackgroundJob throw new BadMethodCallException("Invalid status '{$status}' given."); } - switch ($imageType) { - case UnfurlUrl::IMAGE_COVER: - case UnfurlUrl::IMAGE_NO_IMAGE: - case UnfurlUrl::IMAGE_SQUARED: - break; - - default: - throw new BadMethodCallException("Invalid imageType '{$imageType}' given."); + if ($imageID !== null && !empty($imageData)) { + throw new BadMethodCallException("You cannot pass an imageID and imageData at the same time."); } $urlAction = new UnfurlUrlAction([$this->urlID], 'update', [ @@ -224,11 +252,10 @@ final class UnfurlUrlBackgroundJob extends AbstractBackgroundJob 'status' => $status, 'title' => $title, 'description' => $description, - 'imageType' => $imageType, - 'imageUrl' => $imageUrl, - 'imageHash' => $imageHash, + 'imageID' => $imageID, 'lastFetch' => TIME_NOW, ], + 'imageData' => $imageData, ]); $urlAction->executeAction(); } diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index 5b68c62cde..bf04c662c2 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -1450,14 +1450,26 @@ DROP TABLE IF EXISTS wcf1_unfurl_url; CREATE TABLE wcf1_unfurl_url ( urlID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, url TEXT NOT NULL, - urlHash VARCHAR(40) NOT NULL UNIQUE KEY (urlHash), + urlHash VARCHAR(40) NOT NULL, title VARCHAR(255) NOT NULL DEFAULT '', description TEXT, - imageUrl TEXT NOT NULL, - imageType VARCHAR(255) NOT NULL DEFAULT 'NOIMAGE', - imageHash VARCHAR(45) NOT NULL DEFAULT '', + imageID INT(10), status VARCHAR(255) NOT NULL DEFAULT 'PENDING', - lastFetch INT(10) NOT NULL DEFAULT 0 + lastFetch INT(10) NOT NULL DEFAULT 0, + + UNIQUE KEY urlHash (urlHash) +); + +DROP TABLE IF EXISTS wcf1_unfurl_url_image; +CREATE TABLE wcf1_unfurl_url_image ( + imageID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + imageUrl TEXT NOT NULL, + imageHash VARCHAR(40) DEFAULT NULL, + width INT(10) NOT NULL, + height INT(10) NOT NULL, + imageExtension VARCHAR(4) DEFAULT NULL, + + UNIQUE KEY imageHash (imageHash) ); DROP TABLE IF EXISTS wcf1_user; @@ -2181,6 +2193,8 @@ ALTER TABLE wcf1_tracked_visit ADD FOREIGN KEY (userID) REFERENCES wcf1_user (us ALTER TABLE wcf1_tracked_visit_type ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE; ALTER TABLE wcf1_tracked_visit_type ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE; +ALTER TABLE wcf1_unfurl_url ADD FOREIGN KEY (imageID) REFERENCES wcf1_unfurl_url_image (imageID) ON DELETE SET NULL; + ALTER TABLE wcf1_user ADD FOREIGN KEY (avatarID) REFERENCES wcf1_user_avatar (avatarID) ON DELETE SET NULL; ALTER TABLE wcf1_user ADD FOREIGN KEY (rankID) REFERENCES wcf1_user_rank (rankID) ON DELETE SET NULL; ALTER TABLE wcf1_user ADD FOREIGN KEY (userOnlineGroupID) REFERENCES wcf1_user_group (groupID) ON DELETE SET NULL;