This repository has been archived on 2025-02-26. You can view files and clone it, but cannot push or open issues or pull requests.
szurubooru/src/Models/Entities/PostEntity.php
2014-04-29 21:35:29 +02:00

490 lines
12 KiB
PHP

<?php
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class PostEntity extends AbstractEntity
{
public $type;
public $name;
public $origName;
public $fileHash;
public $fileSize;
public $mimeType;
public $safety;
public $hidden;
public $uploadDate;
public $imageWidth;
public $imageHeight;
public $uploaderId;
public $source;
public $commentCount;
public $favCount;
public $score;
public function getUploader()
{
if ($this->hasCache('uploader'))
return $this->getCache('uploader');
$uploader = UserModel::findById($this->uploaderId, false);
$this->setCache('uploader', $uploader);
return $uploader;
}
public function setUploader($user)
{
$this->uploaderId = $user->id;
$this->setCache('uploader', $user);
}
public function getComments()
{
if ($this->hasCache('comments'))
return $this->getCache('comments');
$comments = CommentModel::findAllByPostId($this->id);
$this->setCache('comments', $comments);
return $comments;
}
public function getFavorites()
{
if ($this->hasCache('favoritee'))
return $this->getCache('favoritee');
$stmt = new Sql\SelectStatement();
$stmt->setColumn('user.*');
$stmt->setTable('user');
$stmt->addInnerJoin('favoritee', new Sql\EqualsFunctor('favoritee.user_id', 'user.id'));
$stmt->setCriterion(new Sql\EqualsFunctor('favoritee.post_id', new Sql\Binding($this->id)));
$rows = Database::fetchAll($stmt);
$favorites = UserModel::convertRows($rows);
$this->setCache('favoritee', $favorites);
return $favorites;
}
public function getRelations()
{
if ($this->hasCache('relations'))
return $this->getCache('relations');
$stmt = new Sql\SelectStatement();
$stmt->setColumn('post.*');
$stmt->setTable('post');
$binding = new Sql\Binding($this->id);
$stmt->addInnerJoin('crossref', (new Sql\DisjunctionFunctor)
->add(
(new Sql\ConjunctionFunctor)
->add(new Sql\EqualsFunctor('post.id', 'crossref.post2_id'))
->add(new Sql\EqualsFunctor('crossref.post_id', $binding)))
->add(
(new Sql\ConjunctionFunctor)
->add(new Sql\EqualsFunctor('post.id', 'crossref.post_id'))
->add(new Sql\EqualsFunctor('crossref.post2_id', $binding))));
$rows = Database::fetchAll($stmt);
$posts = PostModel::convertRows($rows);
$this->setCache('relations', $posts);
return $posts;
}
public function setRelations(array $relations)
{
foreach ($relations as $relatedPost)
if (!$relatedPost->id)
throw new Exception('All related posts must be saved');
$uniqueRelations = [];
foreach ($relations as $relatedPost)
$uniqueRelations[$relatedPost->id] = $relatedPost;
$relations = array_values($uniqueRelations);
$this->setCache('relations', $relations);
}
public function setRelationsFromText($relationsText)
{
$config = getConfig();
$relatedIds = array_filter(preg_split('/\D/', $relationsText));
$relatedPosts = [];
foreach ($relatedIds as $relatedId)
{
if ($relatedId == $this->id)
continue;
if (count($relatedPosts) > $config->browsing->maxRelatedPosts)
throw new SimpleException('Too many related posts (maximum: %d)', $config->browsing->maxRelatedPosts);
$relatedPosts []= PostModel::findById($relatedId);
}
$this->setRelations($relatedPosts);
}
public function getTags()
{
if ($this->hasCache('tags'))
return $this->getCache('tags');
$tags = TagModel::findAllByPostId($this->id);
$this->setCache('tags', $tags);
return $tags;
}
public function setTags(array $tags)
{
foreach ($tags as $tag)
if (!$tag->id)
throw new Exception('All tags must be saved');
$uniqueTags = [];
foreach ($tags as $tag)
$uniqueTags[$tag->id] = $tag;
$tags = array_values($uniqueTags);
$this->setCache('tags', $tags);
}
public function setTagsFromText($tagsText)
{
$tagNames = TagModel::validateTags($tagsText);
$tags = [];
foreach ($tagNames as $tagName)
{
$tag = TagModel::findByName($tagName, false);
if (!$tag)
{
$tag = TagModel::spawn();
$tag->name = $tagName;
TagModel::save($tag);
}
$tags []= $tag;
}
$this->setTags($tags);
}
public function isTaggedWith($tagName)
{
$tagName = trim(strtolower($tagName));
foreach ($this->getTags() as $tag)
if (trim(strtolower($tag->name)) == $tagName)
return true;
return false;
}
public function setHidden($hidden)
{
$this->hidden = boolval($hidden);
}
public function setSafety($safety)
{
$this->safety = PostModel::validateSafety($safety);
}
public function setSource($source)
{
$this->source = PostModel::validateSource($source);
}
public function getThumbCustomPath($width = null, $height = null)
{
return PostModel::getThumbCustomPath($this->name, $width, $height);
}
public function getThumbDefaultPath($width = null, $height = null)
{
return PostModel::getThumbDefaultPath($this->name, $width, $height);
}
public function getFullPath()
{
return PostModel::getFullPath($this->name);
}
public function hasCustomThumb($width = null, $height = null)
{
$thumbPath = $this->getThumbCustomPath($width, $height);
return file_exists($thumbPath);
}
public function setCustomThumbnailFromPath($srcPath)
{
$config = getConfig();
$mimeType = mime_content_type($srcPath);
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
throw new SimpleException('Invalid thumbnail type "%s"', $mimeType);
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
if ($imageWidth != $config->browsing->thumbWidth)
throw new SimpleException('Invalid thumbnail width (should be %d)', $config->browsing->thumbWidth);
if ($imageHeight != $config->browsing->thumbHeight)
throw new SimpleException('Invalid thumbnail height (should be %d)', $config->browsing->thumbHeight);
$dstPath = $this->getThumbCustomPath();
if (is_uploaded_file($srcPath))
move_uploaded_file($srcPath, $dstPath);
else
rename($srcPath, $dstPath);
}
public function makeThumb($width = null, $height = null)
{
list ($width, $height) = PostModel::validateThumbSize($width, $height);
$dstPath = $this->getThumbDefaultPath($width, $height);
$srcPath = $this->getFullPath();
if ($this->type == PostType::Youtube)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
$contents = file_get_contents('http://img.youtube.com/vi/' . $this->fileHash . '/mqdefault.jpg');
file_put_contents($tmpPath, $contents);
if (file_exists($tmpPath))
$srcImage = imagecreatefromjpeg($tmpPath);
}
else switch ($this->mimeType)
{
case 'image/jpeg':
$srcImage = imagecreatefromjpeg($srcPath);
break;
case 'image/png':
$srcImage = imagecreatefrompng($srcPath);
break;
case 'image/gif':
$srcImage = imagecreatefromgif($srcPath);
break;
case 'application/x-shockwave-flash':
$srcImage = null;
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
$cmd = sprintf(
'dump-gnash --screenshot last --screenshot-file "%s" -1 -r1 --max-advances 15 "%s"',
$tmpPath,
$srcPath);
exec($cmd);
if (file_exists($tmpPath))
$srcImage = imagecreatefrompng($tmpPath);
if (!$srcImage)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
exec('swfrender ' . $srcPath . ' -o ' . $tmpPath);
if (file_exists($tmpPath))
$srcImage = imagecreatefrompng($tmpPath);
}
break;
case 'video/mp4':
case 'video/webm':
case 'video/ogg':
case 'application/ogg':
case 'video/x-flv':
case 'video/3gpp':
$srcImage = null;
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
$cmd = sprintf(
'ffmpegthumbnailer -i"%s" -o"%s" -s0 -t"12%"',
$srcPath,
$tmpPath);
exec($cmd);
if (file_exists($tmpPath))
$srcImage = imagecreatefrompng($tmpPath);
if (!$srcImage)
{
exec($cmd);
$cmd = sprintf(
'ffmpeg -i "%s" -vframes 1 "%s"',
$srcPath,
$tmpPath);
if (file_exists($tmpPath))
$srcImage = imagecreatefrompng($tmpPath);
}
break;
default:
break;
}
if (isset($tmpPath) and file_exists($tmpPath))
unlink($tmpPath);
if (!isset($srcImage))
return false;
$config = getConfig();
switch ($config->browsing->thumbStyle)
{
case 'outside':
$dstImage = ThumbnailHelper::cropOutside($srcImage, $width, $height);
break;
case 'inside':
$dstImage = ThumbnailHelper::cropInside($srcImage, $width, $height);
break;
default:
throw new SimpleException('Unknown thumbnail crop style');
}
imagejpeg($dstImage, $dstPath);
imagedestroy($srcImage);
imagedestroy($dstImage);
return true;
}
public function setContentFromPath($srcPath)
{
$this->fileSize = filesize($srcPath);
$this->fileHash = md5_file($srcPath);
if ($this->fileSize == 0)
throw new SimpleException('Specified file is empty');
$this->mimeType = mime_content_type($srcPath);
switch ($this->mimeType)
{
case 'image/gif':
case 'image/png':
case 'image/jpeg':
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
$this->type = PostType::Image;
$this->imageWidth = $imageWidth;
$this->imageHeight = $imageHeight;
break;
case 'application/x-shockwave-flash':
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
$this->type = PostType::Flash;
$this->imageWidth = $imageWidth;
$this->imageHeight = $imageHeight;
break;
case 'video/webm':
case 'video/mp4':
case 'video/ogg':
case 'application/ogg':
case 'video/x-flv':
case 'video/3gpp':
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
$this->type = PostType::Video;
$this->imageWidth = $imageWidth;
$this->imageHeight = $imageHeight;
break;
default:
throw new SimpleException('Invalid file type "%s"', $this->mimeType);
}
$duplicatedPost = PostModel::findByHash($this->fileHash, false);
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
throw new SimpleException('Duplicate upload: %s', TextHelper::reprPost($duplicatedPost));
$dstPath = $this->getFullPath();
if (is_uploaded_file($srcPath))
move_uploaded_file($srcPath, $dstPath);
else
rename($srcPath, $dstPath);
$thumbPath = $this->getThumbDefaultPath();
if (file_exists($thumbPath))
unlink($thumbPath);
}
public function setContentFromUrl($srcUrl)
{
if (!preg_match('/^https?:\/\//', $srcUrl))
throw new SimpleException('Invalid URL "%s"', $srcUrl);
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $srcUrl, $matches))
{
$youtubeId = $matches[1];
$this->type = PostType::Youtube;
$this->mimeType = null;
$this->fileSize = null;
$this->fileHash = $youtubeId;
$this->imageWidth = null;
$this->imageHeight = null;
$thumbPath = $this->getThumbDefaultPath();
if (file_exists($thumbPath))
unlink($thumbPath);
$duplicatedPost = PostModel::findByHash($youtubeId, false);
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
throw new SimpleException('Duplicate upload: %s' . TextHelper::reprPost($duplicatedPost));
return;
}
$srcPath = tempnam(sys_get_temp_dir(), 'upload') . '.dat';
//warning: low level sh*t ahead
//download the URL $srcUrl into $srcPath
$maxBytes = TextHelper::stripBytesUnits(ini_get('upload_max_filesize'));
set_time_limit(0);
$urlFP = fopen($srcUrl, 'rb');
if (!$urlFP)
throw new SimpleException('Cannot open URL for reading');
$srcFP = fopen($srcPath, 'w+b');
if (!$srcFP)
{
fclose($urlFP);
throw new SimpleException('Cannot open file for writing');
}
try
{
while (!feof($urlFP))
{
$buffer = fread($urlFP, 4 * 1024);
if (fwrite($srcFP, $buffer) === false)
throw new SimpleException('Cannot write into file');
fflush($srcFP);
if (ftell($srcFP) > $maxBytes)
throw new SimpleException('File is too big (maximum size: %s)', TextHelper::useBytesUnits($maxBytes));
}
}
finally
{
fclose($urlFP);
fclose($srcFP);
}
try
{
$this->setContentFromPath($srcPath);
}
finally
{
if (file_exists($srcPath))
unlink($srcPath);
}
}
public function getEditToken()
{
$x = [];
foreach ($this->getTags() as $tag)
$x []= TextHelper::reprTag($tag->name);
foreach ($this->getRelations() as $relatedPost)
$x []= TextHelper::reprPost($relatedPost);
$x []= $this->safety;
$x []= $this->source;
$x []= $this->fileHash;
natcasesort($x);
$x = join(' ', $x);
return md5($x);
}
}