<?php
/**
- * Makes non-critical database adjustments (i.e. everything that is not related
+ * Makes non-critical database adjustments (i.e. everything that is not related
* to sessions).
*
* @author Tim Duesterhus
DefaultFalseBooleanDatabaseTableColumn::create('invertPermissions'),
]),
+ DatabaseTable::create('wcf1_unfurl_url_image')
+ ->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'),
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')
DatabaseTableIndex::create('urlHash')
->type(DatabaseTableIndex::UNIQUE_TYPE)
->columns(['urlHash']),
+ ])
+ ->foreignKeys([
+ DatabaseTableForeignKey::create()
+ ->columns(['imageID'])
+ ->referencedTable('wcf1_unfurl_url_image')
+ ->referencedColumns(['imageID'])
+ ->onDelete('SET NULL'),
]),
];
* @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";
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
{
/**
* Returns the hostname of the url.
- *
- * @return string
*/
public function getHost(): string
{
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);
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;
}
/**
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)]);
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.
*/
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();
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']),
],
*/
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";
+ }
}
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.
$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) {
}
}
+ 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 = "";
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:
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', [
'status' => $status,
'title' => $title,
'description' => $description,
- 'imageType' => $imageType,
- 'imageUrl' => $imageUrl,
- 'imageHash' => $imageHash,
+ 'imageID' => $imageID,
'lastFetch' => TIME_NOW,
],
+ 'imageData' => $imageData,
]);
$urlAction->executeAction();
}
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;
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;