2013-10-15 20:22:52 +02:00
|
|
|
<?php
|
2013-10-28 11:19:15 +01:00
|
|
|
class Model_Post extends AbstractModel
|
2013-10-15 20:22:52 +02:00
|
|
|
{
|
2013-11-22 21:20:56 +01:00
|
|
|
protected static $config;
|
|
|
|
|
|
|
|
public static function initModel()
|
|
|
|
{
|
|
|
|
self::$config = \Chibi\Registry::getConfig();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getTableName()
|
|
|
|
{
|
|
|
|
return 'post';
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getQueryBuilder()
|
|
|
|
{
|
|
|
|
return 'Model_Post_QueryBuilder';
|
|
|
|
}
|
|
|
|
|
|
|
|
public function preload()
|
|
|
|
{
|
|
|
|
R::preload($this->bean, ['uploader' => 'user','favoritee' => 'user']);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-10-21 14:32:47 +02:00
|
|
|
public static function locate($key, $disallowNumeric = false, $throw = true)
|
2013-10-17 22:57:32 +02:00
|
|
|
{
|
|
|
|
if (is_numeric($key) and !$disallowNumeric)
|
|
|
|
{
|
2013-10-28 11:19:15 +01:00
|
|
|
$post = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
2013-10-17 22:57:32 +02:00
|
|
|
if (!$post)
|
2013-10-21 14:32:47 +02:00
|
|
|
{
|
|
|
|
if ($throw)
|
|
|
|
throw new SimpleException('Invalid post ID "' . $key . '"');
|
|
|
|
return null;
|
|
|
|
}
|
2013-10-17 22:57:32 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-10-28 11:19:15 +01:00
|
|
|
$post = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
2013-10-17 22:57:32 +02:00
|
|
|
if (!$post)
|
2013-10-21 14:32:47 +02:00
|
|
|
{
|
|
|
|
if ($throw)
|
|
|
|
throw new SimpleException('Invalid post name "' . $key . '"');
|
|
|
|
return null;
|
|
|
|
}
|
2013-10-17 22:57:32 +02:00
|
|
|
}
|
|
|
|
return $post;
|
|
|
|
}
|
|
|
|
|
2013-11-22 21:20:56 +01:00
|
|
|
public static function create()
|
|
|
|
{
|
|
|
|
$post = R::dispense(self::getTableName());
|
|
|
|
$post->hidden = false;
|
|
|
|
$post->upload_date = time();
|
|
|
|
do
|
|
|
|
{
|
|
|
|
$post->name = md5(mt_rand() . uniqid());
|
|
|
|
}
|
|
|
|
while (file_exists(self::getFullPath($post->name)));
|
|
|
|
return $post;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function remove($post)
|
|
|
|
{
|
|
|
|
//remove stuff from auxiliary tables
|
|
|
|
R::trashAll(R::find('postscore', 'post_id = ?', [$post->id]));
|
|
|
|
R::trashAll(R::find('crossref', 'post_id = ? OR post2_id = ?', [$post->id, $post->id]));
|
|
|
|
foreach ($post->ownComment as $comment)
|
|
|
|
{
|
|
|
|
$comment->post = null;
|
|
|
|
R::store($comment);
|
|
|
|
}
|
|
|
|
$post->ownFavoritee = [];
|
|
|
|
$post->sharedTag = [];
|
|
|
|
R::store($post);
|
|
|
|
R::trash($post);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function save($post)
|
|
|
|
{
|
|
|
|
R::store($post);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-10-15 20:22:52 +02:00
|
|
|
public static function validateSafety($safety)
|
|
|
|
{
|
|
|
|
$safety = intval($safety);
|
|
|
|
|
|
|
|
if (!in_array($safety, PostSafety::getAll()))
|
|
|
|
throw new SimpleException('Invalid safety type "' . $safety . '"');
|
|
|
|
|
|
|
|
return $safety;
|
|
|
|
}
|
|
|
|
|
2013-10-19 20:58:51 +02:00
|
|
|
public static function validateSource($source)
|
|
|
|
{
|
|
|
|
$source = trim($source);
|
|
|
|
|
2013-11-01 10:37:35 +01:00
|
|
|
$maxLength = 200;
|
2013-10-19 20:58:51 +02:00
|
|
|
if (strlen($source) > $maxLength)
|
|
|
|
throw new SimpleException('Source must have at most ' . $maxLength . ' characters');
|
|
|
|
|
|
|
|
return $source;
|
|
|
|
}
|
2013-10-28 11:19:15 +01:00
|
|
|
|
2013-11-22 21:20:56 +01:00
|
|
|
private static function validateThumbSize($width, $height)
|
2013-10-28 11:19:15 +01:00
|
|
|
{
|
2013-11-22 21:20:56 +01:00
|
|
|
$width = $width === null ? self::$config->browsing->thumbWidth : $width;
|
|
|
|
$height = $height === null ? self::$config->browsing->thumbHeight : $height;
|
|
|
|
$width = min(1000, max(1, $width));
|
|
|
|
$height = min(1000, max(1, $height));
|
|
|
|
return [$width, $height];
|
2013-10-28 11:19:15 +01:00
|
|
|
}
|
|
|
|
|
2013-11-22 21:20:56 +01:00
|
|
|
public static function getAllPostCount()
|
2013-10-28 11:19:15 +01:00
|
|
|
{
|
2013-11-22 21:20:56 +01:00
|
|
|
return R::$f
|
|
|
|
->begin()
|
|
|
|
->select('count(1)')
|
|
|
|
->as('count')
|
|
|
|
->from(self::getTableName())
|
|
|
|
->get('row')['count'];
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function getThumbPathTokenized($text, $name, $width = null, $height = null)
|
|
|
|
{
|
|
|
|
list ($width, $height) = self::validateThumbSize($width, $height);
|
|
|
|
|
|
|
|
return TextHelper::replaceTokens($text, [
|
|
|
|
'fullpath' => self::$config->main->thumbsPath . DS . $name,
|
|
|
|
'width' => $width,
|
|
|
|
'height' => $height]);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getThumbCustomPath($name, $width = null, $height = null)
|
|
|
|
{
|
|
|
|
return self::getThumbPathTokenized('{fullpath}.custom', $name, $width, $height);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getThumbDefaultPath($name, $width = null, $height = null)
|
|
|
|
{
|
|
|
|
return self::getThumbPathTokenized('{fullpath}-{width}x{height}.default', $name, $width, $height);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getFullPath($name)
|
|
|
|
{
|
|
|
|
return self::$config->main->filesPath . DS . $name;
|
2013-10-28 11:19:15 +01:00
|
|
|
}
|
2013-11-21 21:06:18 +01:00
|
|
|
|
|
|
|
public function isTaggedWith($tagName)
|
|
|
|
{
|
|
|
|
$tagName = trim(strtolower($tagName));
|
|
|
|
foreach ($this->sharedTag as $tag)
|
|
|
|
if (trim(strtolower($tag->name)) == $tagName)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
2013-11-22 21:20:56 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setHidden($hidden)
|
|
|
|
{
|
|
|
|
$this->hidden = boolval($hidden);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setSafety($safety)
|
|
|
|
{
|
|
|
|
$this->safety = self::validateSafety($safety);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setSource($source)
|
|
|
|
{
|
|
|
|
$this->source = self::validateSource($source);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setTagsFromText($tagsText)
|
|
|
|
{
|
|
|
|
$tagNames = Model_Tag::validateTags($tagsText);
|
|
|
|
$dbTags = Model_Tag::insertOrUpdate($tagNames);
|
|
|
|
|
|
|
|
$this->sharedTag = $dbTags;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setRelationsFromText($relationsText)
|
|
|
|
{
|
|
|
|
$relatedIds = array_filter(preg_split('/\D/', $relationsText));
|
|
|
|
|
|
|
|
$relatedPosts = [];
|
|
|
|
foreach ($relatedIds as $relatedId)
|
|
|
|
{
|
|
|
|
if ($relatedId == $this->id)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (count($relatedPosts) > self::$config->browsing->maxRelatedPosts)
|
|
|
|
throw new SimpleException('Too many related posts (maximum: ' . self::$config->browsing->maxRelatedPosts . ')');
|
|
|
|
|
|
|
|
$relatedPosts []= self::locate($relatedId);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->bean->via('crossref')->sharedPost = $relatedPosts;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setCustomThumbnailFromPath($srcPath)
|
|
|
|
{
|
|
|
|
$mimeType = mime_content_type($srcPath);
|
|
|
|
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
|
|
|
|
throw new SimpleException('Invalid thumbnail type "' . $mimeType . '"');
|
|
|
|
|
|
|
|
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
|
|
|
|
if ($imageWidth != self::$config->browsing->thumbWidth)
|
|
|
|
throw new SimpleException('Invalid thumbnail width (should be ' . self::$config->browsing->thumbWidth . ')');
|
|
|
|
if ($imageWidth != self::$config->browsing->thumbHeight)
|
|
|
|
throw new SimpleException('Invalid thumbnail width (should be ' . self::$config->browsing->thumbHeight . ')');
|
|
|
|
|
|
|
|
$dstPath = self::getThumbCustomPath($this->name);
|
|
|
|
|
|
|
|
if (is_uploaded_file($srcPath))
|
|
|
|
move_uploaded_file($srcPath, $dstPath);
|
|
|
|
else
|
|
|
|
rename($srcPath, $dstPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setContentFromPath($srcPath)
|
|
|
|
{
|
2013-11-23 10:39:56 +01:00
|
|
|
$this->file_size = filesize($srcPath);
|
|
|
|
$this->file_hash = md5_file($srcPath);
|
|
|
|
|
|
|
|
if ($this->file_size == 0)
|
|
|
|
throw new SimpleException('Specified file is empty');
|
|
|
|
|
2013-11-22 21:20:56 +01:00
|
|
|
$this->mime_type = mime_content_type($srcPath);
|
|
|
|
switch ($this->mime_type)
|
|
|
|
{
|
|
|
|
case 'image/gif':
|
|
|
|
case 'image/png':
|
|
|
|
case 'image/jpeg':
|
|
|
|
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
|
|
|
|
$this->type = PostType::Image;
|
|
|
|
$this->image_width = $imageWidth;
|
|
|
|
$this->image_height = $imageHeight;
|
|
|
|
break;
|
|
|
|
case 'application/x-shockwave-flash':
|
|
|
|
list ($imageWidth, $imageHeight) = getimagesize($srcPath);
|
|
|
|
$this->type = PostType::Flash;
|
|
|
|
$this->image_width = $imageWidth;
|
|
|
|
$this->image_height = $imageHeight;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new SimpleException('Invalid file type "' . $this->mime_type . '"');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->orig_name = basename($srcPath);
|
|
|
|
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$this->file_hash]);
|
2013-11-23 17:27:56 +01:00
|
|
|
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
2013-11-22 21:20:56 +01:00
|
|
|
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
|
|
|
|
|
|
|
$dstPath = $this->getFullPath($this->name);
|
|
|
|
|
|
|
|
if (is_uploaded_file($srcPath))
|
|
|
|
move_uploaded_file($srcPath, $dstPath);
|
|
|
|
else
|
|
|
|
rename($srcPath, $dstPath);
|
2013-11-23 17:27:56 +01:00
|
|
|
|
|
|
|
$thumbPath = self::getThumbDefaultPath($this->name);
|
|
|
|
if (file_exists($thumbPath))
|
|
|
|
unlink($thumbPath);
|
2013-11-22 21:20:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function setContentFromUrl($srcUrl)
|
|
|
|
{
|
|
|
|
$this->orig_name = $srcUrl;
|
|
|
|
|
|
|
|
if (!preg_match('/^https?:\/\//', $srcUrl))
|
|
|
|
throw new SimpleException('Invalid URL "' . $srcUrl . '"');
|
|
|
|
|
|
|
|
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $srcUrl, $matches))
|
|
|
|
{
|
|
|
|
$origName = $matches[1];
|
|
|
|
$this->orig_name = $origName;
|
|
|
|
$this->type = PostType::Youtube;
|
|
|
|
$this->mime_type = null;
|
|
|
|
$this->file_size = null;
|
|
|
|
$this->file_hash = null;
|
|
|
|
$this->image_width = null;
|
|
|
|
$this->image_height = null;
|
|
|
|
|
2013-11-23 17:27:56 +01:00
|
|
|
$thumbPath = self::getThumbDefaultPath($this->name);
|
|
|
|
if (file_exists($thumbPath))
|
|
|
|
unlink($thumbPath);
|
|
|
|
|
2013-11-22 21:20:56 +01:00
|
|
|
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
|
2013-11-23 17:27:56 +01:00
|
|
|
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
2013-11-22 21:20:56 +01:00
|
|
|
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
|
|
|
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 allowed size: ' . TextHelper::useBytesUnits($maxBytes) . ')');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
fclose($urlFP);
|
|
|
|
fclose($srcFP);
|
|
|
|
}
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
$this->setContentFromPath($srcPath);
|
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
if (file_exists($srcPath))
|
|
|
|
unlink($srcPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function makeThumb($width = null, $height = null)
|
|
|
|
{
|
|
|
|
list ($width, $height) = self::validateThumbSize($width, $height);
|
|
|
|
$dstPath = self::getThumbDefaultPath($this->name, $width, $height);
|
|
|
|
$srcPath = self::getFullPath($this->name);
|
|
|
|
|
|
|
|
if ($this->type == PostType::Youtube)
|
|
|
|
{
|
|
|
|
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
|
|
|
|
$contents = file_get_contents('http://img.youtube.com/vi/' . $this->orig_name . '/mqdefault.jpg');
|
|
|
|
file_put_contents($tmpPath, $contents);
|
|
|
|
if (file_exists($tmpPath))
|
|
|
|
$srcImage = imagecreatefromjpeg($tmpPath);
|
|
|
|
}
|
|
|
|
else switch ($this->mime_type)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
exec('which dump-gnash', $tmp, $exitCode);
|
|
|
|
if ($exitCode == 0)
|
|
|
|
{
|
|
|
|
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
|
|
|
exec('dump-gnash --screenshot last --screenshot-file ' . $tmpPath . ' -1 -r1 --max-advances 15 ' . $srcPath);
|
|
|
|
if (file_exists($tmpPath))
|
|
|
|
$srcImage = imagecreatefrompng($tmpPath);
|
|
|
|
}
|
|
|
|
if (!$srcImage)
|
|
|
|
{
|
|
|
|
exec('which swfrender', $tmp, $exitCode);
|
|
|
|
if ($exitCode == 0)
|
|
|
|
{
|
|
|
|
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.png';
|
|
|
|
exec('swfrender ' . $srcPath . ' -o ' . $tmpPath);
|
|
|
|
if (file_exists($tmpPath))
|
|
|
|
$srcImage = imagecreatefrompng($tmpPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($tmpPath))
|
|
|
|
unlink($tmpPath);
|
|
|
|
|
|
|
|
if (!isset($srcImage))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (self::$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;
|
|
|
|
}
|
2013-10-15 20:22:52 +02:00
|
|
|
}
|
2013-11-22 21:20:56 +01:00
|
|
|
|
|
|
|
Model_Post::initModel();
|