Refactor of controllers and models

- Most of model-related code moved from controllers to model classes, much
  fewer calls to R::whatever() in controllers
- Post editing and uploading shares the same code, thus making implementing
  stuff easier in the future
- Added support for default bean wiring, no more calls to R::preload() all over
  the place
- More robust concurrent post editing detection
This commit is contained in:
Marcin Kurczewski 2013-11-22 21:20:56 +01:00
parent 0a5169a7d6
commit d8997edc57
14 changed files with 785 additions and 527 deletions

View file

@ -90,6 +90,8 @@ editPostThumb=moderator
editPostSource=moderator
editPostRelations.own=registered
editPostRelations.all=moderator
editPostFile.all=moderator
editPostFile.own=moderator
hidePost.own=moderator
hidePost.all=moderator
deletePost.own=moderator

View file

@ -25,7 +25,6 @@ class CommentController
$page = max(1, min($pageCount, $page));
$comments = Model_Comment::getEntities(null, $commentsPerPage, $page);
R::preload($comments, ['commenter' => 'user', 'post', 'post.uploader' => 'user']);
$this->context->postGroups = true;
$this->context->transport->paginator = new StdClass;
$this->context->transport->paginator->page = $page;
@ -55,7 +54,7 @@ class CommentController
$text = InputHelper::get('text');
$text = Model_Comment::validateText($text);
$comment = R::dispense('comment');
$comment = Model_Comment::create();
$comment->post = $post;
if ($this->context->loggedIn)
$comment->commenter = $this->context->user;
@ -63,7 +62,7 @@ class CommentController
$comment->text = $text;
if (InputHelper::get('sender') != 'preview')
{
R::store($comment);
Model_Comment::save($comment);
LogHelper::logEvent('comment-add', '{user} commented on {post}', ['post' => TextHelper::reprPost($post->id)]);
}
$this->context->transport->textPreview = $comment->getText();
@ -80,10 +79,10 @@ class CommentController
public function deleteAction($id)
{
$comment = Model_Comment::locate($id);
R::preload($comment, ['commenter' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($comment->commenter));
Model_Comment::remove($comment);
LogHelper::logEvent('comment-del', '{user} removed comment from {post}', ['post' => TextHelper::reprPost($comment->post)]);
R::trash($comment);
StatusHelper::success();
}
}

View file

@ -9,7 +9,7 @@ class IndexController
{
$this->context->subTitle = 'home';
$this->context->stylesheets []= 'index-index.css';
$this->context->transport->postCount = R::$f->begin()->select('count(1)')->as('count')->from('post')->get('row')['count'];
$this->context->transport->postCount = Model_Post::getAllPostCount();
$featuredPostRotationTime = $this->config->misc->featuredPostMaxDays * 24 * 3600;

View file

@ -8,13 +8,18 @@ class PostController
$callback();
}
private static function serializeTags($post)
private static function serializePost($post)
{
$x = [];
foreach ($post->sharedTag as $tag)
$x []= $tag->name;
$x []= TextHelper::reprTag($tag->name);
foreach ($post->via('crossref')->sharedPost as $relatedPost)
$x []= TextHelper::reprPost($relatedPost);
$x []= $post->safety;
$x []= $post->source;
$x []= $post->file_hash;
natcasesort($x);
$x = join('', $x);
$x = join(' ', $x);
return md5($x);
}
@ -120,7 +125,6 @@ class PostController
public function toggleTagAction($id, $tag)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
$this->context->transport->post = $post;
$tagRow = Model_Tag::locate($tag, false);
@ -146,7 +150,7 @@ class PostController
$dbTags = Model_Tag::insertOrUpdate($tags);
$post->sharedTag = $dbTags;
R::store($post);
Model_Post::save($post);
StatusHelper::success();
}
}
@ -192,173 +196,38 @@ class PostController
if (InputHelper::get('submit'))
{
/* file contents */
if (isset($_FILES['file']))
R::transaction(function()
{
$suppliedFile = $_FILES['file'];
self::handleUploadErrors($suppliedFile);
$origName = basename($suppliedFile['name']);
$sourcePath = $suppliedFile['tmp_name'];
}
elseif (InputHelper::get('url'))
{
$url = InputHelper::get('url');
$origName = $url;
if (!preg_match('/^https?:\/\//', $url))
throw new SimpleException('Invalid URL "' . $url . '"');
$post = Model_Post::create();
if (preg_match('/youtube.com\/watch.*?=([a-zA-Z0-9_-]+)/', $url, $matches))
{
$origName = $matches[1];
$postType = PostType::Youtube;
$sourcePath = null;
}
else
{
$sourcePath = tempnam(sys_get_temp_dir(), 'upload') . '.dat';
//warning: low level sh*t ahead
//download the URL $url into $sourcePath
$maxBytes = TextHelper::stripBytesUnits(ini_get('upload_max_filesize'));
set_time_limit(0);
$urlFP = fopen($url, 'rb');
if (!$urlFP)
throw new SimpleException('Cannot open URL for reading');
$sourceFP = fopen($sourcePath, 'w+b');
if (!$sourceFP)
{
fclose($urlFP);
throw new SimpleException('Cannot open file for writing');
}
try
{
while (!feof($urlFP))
{
$buffer = fread($urlFP, 4 * 1024);
if (fwrite($sourceFP, $buffer) === false)
throw new SimpleException('Cannot write into file');
fflush($sourceFP);
if (ftell($sourceFP) > $maxBytes)
throw new SimpleException('File is too big (maximum allowed size: ' . TextHelper::useBytesUnits($maxBytes) . ')');
}
}
finally
{
fclose($urlFP);
fclose($sourceFP);
}
}
}
/* file details */
$mimeType = null;
if ($sourcePath)
{
if (function_exists('mime_content_type'))
$mimeType = mime_content_type($sourcePath);
else
$mimeType = $suppliedFile['type'];
}
$imageWidth = null;
$imageHeight = null;
switch ($mimeType)
{
case 'image/gif':
case 'image/png':
case 'image/jpeg':
$postType = PostType::Image;
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
break;
case 'application/x-shockwave-flash':
$postType = PostType::Flash;
list ($imageWidth, $imageHeight) = getimagesize($sourcePath);
break;
default:
if (!isset($postType))
throw new SimpleException('Invalid file type "' . $mimeType . '"');
}
if ($sourcePath)
{
$fileSize = filesize($sourcePath);
$fileHash = md5_file($sourcePath);
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$fileHash]);
if ($duplicatedPost !== null)
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
}
else
{
$fileSize = 0;
$fileHash = null;
if ($postType == PostType::Youtube)
{
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
if ($duplicatedPost !== null)
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
}
}
do
{
$name = md5(mt_rand() . uniqid());
$path = $this->config->main->filesPath . DS . $name;
}
while (file_exists($path));
/* safety */
$suppliedSafety = InputHelper::get('safety');
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
/* tags */
$suppliedTags = InputHelper::get('tags');
$suppliedTags = Model_Tag::validateTags($suppliedTags);
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
/* source */
$suppliedSource = InputHelper::get('source');
$suppliedSource = Model_Post::validateSource($suppliedSource);
/* anonymous */
//basic stuff
$anonymous = InputHelper::get('anonymous');
/* db storage */
$dbPost = R::dispense('post');
$dbPost->type = $postType;
$dbPost->name = $name;
$dbPost->orig_name = $origName;
$dbPost->file_hash = $fileHash;
$dbPost->file_size = $fileSize;
$dbPost->mime_type = $mimeType;
$dbPost->safety = $suppliedSafety;
$dbPost->source = $suppliedSource;
$dbPost->hidden = false;
$dbPost->upload_date = time();
$dbPost->image_width = $imageWidth;
$dbPost->image_height = $imageHeight;
if ($this->context->loggedIn and !$anonymous)
$dbPost->uploader = $this->context->user;
$dbPost->ownFavoritee = [];
$dbPost->sharedTag = $dbTags;
$post->uploader = $this->context->user;
if ($sourcePath)
{
if (is_uploaded_file($sourcePath))
move_uploaded_file($sourcePath, $path);
else
rename($sourcePath, $path);
}
R::store($dbPost);
//store the post to get the ID in the logs
Model_Post::save($post);
//log
LogHelper::bufferChanges();
$fmt = ($anonymous and !$this->config->misc->logAnonymousUploads)
? 'someone'
: '{user}';
$fmt .= ' added {post} tagged with {tags} marked as {safety}';
LogHelper::logEvent('post-new', $fmt, [
'post' => TextHelper::reprPost($dbPost),
'tags' => join(', ', array_map(['TextHelper', 'reprTag'], $dbTags)),
'safety' => PostSafety::toString($dbPost->safety)]);
$fmt .= ' added {post}';
LogHelper::logEvent('post-new', $fmt, ['post' => TextHelper::reprPost($post)]);
//after logging basic info, do the editing stuff
$this->doEdit($post, true);
//this basically means that user didn't specify file nor url
if (empty($post->type))
throw new SimpleException('No post type detected; upload faled');
LogHelper::flush();
//finish
Model_Post::save($post);
});
StatusHelper::success();
}
@ -372,111 +241,21 @@ class PostController
public function editAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
$this->context->transport->post = $post;
if (InputHelper::get('submit'))
{
$editToken = InputHelper::get('edit-token');
if ($editToken != self::serializePost($post))
throw new SimpleException('This post was already edited by someone else in the meantime');
LogHelper::bufferChanges();
$this->doEdit($post, false);
LogHelper::flush();
/* safety */
$suppliedSafety = InputHelper::get('safety');
if ($suppliedSafety !== null and $suppliedSafety != $post->safety)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSafety = Model_Post::validateSafety($suppliedSafety);
$post->safety = $suppliedSafety;
$edited = true;
LogHelper::logEvent('post-edit', '{user} changed safety for {post} to {safety}', ['post' => TextHelper::reprPost($post), 'safety' => PostSafety::toString($post->safety)]);
}
/* tags */
$suppliedTags = InputHelper::get('tags');
if ($suppliedTags !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$currentToken = self::serializeTags($post);
if (InputHelper::get('tags-token') != $currentToken)
throw new SimpleException('Someone else has changed the tags in the meantime');
$suppliedTags = Model_Tag::validateTags($suppliedTags);
$dbTags = Model_Tag::insertOrUpdate($suppliedTags);
$oldTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
$post->sharedTag = $dbTags;
$edited = true;
foreach (array_diff($oldTags, $suppliedTags) as $tag)
LogHelper::logEvent('post-tag-del', '{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
foreach (array_diff($suppliedTags, $oldTags) as $tag)
LogHelper::logEvent('post-tag-add', '{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
}
/* thumbnail */
if (!empty($_FILES['thumb']['name']))
{
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedFile = $_FILES['thumb'];
self::handleUploadErrors($suppliedFile);
$mimeType = mime_content_type($suppliedFile['tmp_name']);
if (!in_array($mimeType, ['image/gif', 'image/png', 'image/jpeg']))
throw new SimpleException('Invalid thumbnail type "' . $mimeType . '"');
list ($imageWidth, $imageHeight) = getimagesize($suppliedFile['tmp_name']);
if ($imageWidth != $this->config->browsing->thumbWidth)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbWidth . ')');
if ($imageWidth != $this->config->browsing->thumbHeight)
throw new SimpleException('Invalid thumbnail width (should be ' . $this->config->browsing->thumbHeight . ')');
$path = $this->config->main->thumbsPath . DS . $post->name . '.custom';
move_uploaded_file($suppliedFile['tmp_name'], $path);
LogHelper::logEvent('post-edit', '{user} added custom thumb for {post}', ['post' => TextHelper::reprPost($post)]);
}
/* source */
$suppliedSource = InputHelper::get('source');
if ($suppliedSource !== null and $suppliedSource != $post->source)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedSource = Model_Post::validateSource($suppliedSource);
$post->source = $suppliedSource;
$edited = true;
LogHelper::logEvent('post-edit', '{user} changed source for {post} to {source}', ['post' => TextHelper::reprPost($post), 'source' => $post->source]);
}
/* relations */
$suppliedRelations = InputHelper::get('relations');
if ($suppliedRelations !== null)
{
PrivilegesHelper::confirmWithException(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$relatedIds = array_filter(preg_split('/\D/', $suppliedRelations));
$relatedPosts = [];
foreach ($relatedIds as $relatedId)
{
if ($relatedId == $post->id)
continue;
if (count($relatedPosts) > $this->config->browsing->maxRelatedPosts)
throw new SimpleException('Too many related posts (maximum: ' . $this->config->browsing->maxRelatedPosts . ')');
$relatedPosts []= Model_Post::locate($relatedId);
}
$oldRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
$post->via('crossref')->sharedPost = $relatedPosts;
foreach (array_diff($oldRelatedIds, $relatedIds) as $post2id)
LogHelper::logEvent('post-relation-del', '{user} removed relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
foreach (array_diff($relatedIds, $oldRelatedIds) as $post2id)
LogHelper::logEvent('post-relation-add', '{user} added relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
}
R::store($post);
Model_Post::save($post);
Model_Tag::removeUnused();
LogHelper::flush();
StatusHelper::success();
}
}
@ -514,13 +293,12 @@ class PostController
public function hideAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
if (InputHelper::get('submit'))
{
$post->hidden = true;
R::store($post);
$post->setHidden(true);
Model_Post::save($post);
LogHelper::logEvent('post-hide', '{user} hidden {post}', ['post' => TextHelper::reprPost($post)]);
StatusHelper::success();
@ -535,13 +313,12 @@ class PostController
public function unhideAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
if (InputHelper::get('submit'))
{
$post->hidden = false;
R::store($post);
$post->setHidden(false);
Model_Post::save($post);
LogHelper::logEvent('post-unhide', '{user} unhidden {post}', ['post' => TextHelper::reprPost($post)]);
StatusHelper::success();
@ -556,23 +333,11 @@ class PostController
public function deleteAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['uploader' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
if (InputHelper::get('submit'))
{
//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);
Model_Post::remove($post);
LogHelper::logEvent('post-delete', '{user} deleted {post}', ['post' => TextHelper::reprPost($id)]);
StatusHelper::success();
@ -588,7 +353,6 @@ class PostController
public function addFavoriteAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['favoritee' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
if (InputHelper::get('submit'))
@ -596,12 +360,8 @@ class PostController
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
foreach ($post->via('favoritee')->sharedUser as $fav)
if ($fav->id == $this->context->user->id)
throw new SimpleException('Already in favorites');
$post->link('favoritee')->user = $this->context->user;
R::store($post);
$this->context->user->addToFavorites($post);
Model_User::save($this->context->user);
StatusHelper::success();
}
}
@ -613,7 +373,6 @@ class PostController
public function remFavoriteAction($id)
{
$post = Model_Post::locate($id);
R::preload($post, ['favoritee' => 'user']);
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
if (InputHelper::get('submit'))
@ -621,16 +380,8 @@ class PostController
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
$finalKey = null;
foreach ($post->ownFavoritee as $key => $fav)
if ($fav->user->id == $this->context->user->id)
$finalKey = $key;
if ($finalKey === null)
throw new SimpleException('Not in favorites');
unset ($post->ownFavoritee[$finalKey]);
R::store($post);
$this->context->user->remFromFavorites($post);
Model_User::save($this->context->user);
StatusHelper::success();
}
}
@ -651,15 +402,8 @@ class PostController
if (!$this->context->loggedIn)
throw new SimpleException('Not logged in');
$p = R::findOne('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->context->user->id]);
if (!$p)
{
$p = R::dispense('postscore');
$p->post = $post;
$p->user = $this->context->user;
}
$p->score = $score;
R::store($p);
$this->context->user->score($post, $score);
Model_User::save($this->context->user);
StatusHelper::success();
}
}
@ -690,7 +434,6 @@ class PostController
{
$post = Model_Post::locate($id);
R::preload($post, [
'uploader' => 'user',
'tag',
'comment',
'ownComment.commenter' => 'user']);
@ -728,19 +471,8 @@ class PostController
$buildNextPostQuery($nextPostQuery, $id, true);
$nextPost = $nextPostQuery->get('row');
$favorite = false;
$score = null;
if ($this->context->loggedIn)
{
foreach ($post->ownFavoritee as $fav)
if ($fav->user->id == $this->context->user->id)
$favorite = true;
$s = R::findOne('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->context->user->id]);
if ($s)
$score = intval($s->score);
}
$favorite = $this->context->user->hasFavorited($post);
$score = $this->context->user->getScore($post);
$flagged = in_array(TextHelper::reprPost($post), SessionHelper::get('flagged', []));
$this->context->pageThumb = \Chibi\UrlHelper::route('post', 'thumb', ['name' => $post->name]);
@ -754,7 +486,7 @@ class PostController
$this->context->transport->post = $post;
$this->context->transport->prevPostId = $prevPost ? $prevPost['id'] : null;
$this->context->transport->nextPostId = $nextPost ? $nextPost['id'] : null;
$this->context->transport->tagsToken = self::serializeTags($post);
$this->context->transport->editToken = self::serializePost($post);
}
@ -765,98 +497,25 @@ class PostController
*/
public function thumbAction($name, $width = null, $height = null)
{
$dstWidth = $width === null ? $this->config->browsing->thumbWidth : $width;
$dstHeight = $height === null ? $this->config->browsing->thumbHeight : $height;
$dstWidth = min(1000, max(1, $dstWidth));
$dstHeight = min(1000, max(1, $dstHeight));
$this->context->layoutName = 'layout-file';
$path = $this->config->main->thumbsPath . DS . $name . '.custom';
$path = Model_Post::getThumbCustomPath($name, $width, $height);
if (!file_exists($path))
$path = $this->config->main->thumbsPath . DS . $name . '-' . $dstWidth . 'x' . $dstHeight . '.default';
{
$path = Model_Post::getThumbDefaultPath($name, $width, $height);
if (!file_exists($path))
{
$post = Model_Post::locate($name);
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
PrivilegesHelper::confirmWithException(Privilege::ListPosts, PostSafety::toString($post->safety));
$srcPath = $this->config->main->filesPath . DS . $post->name;
if ($post->type == PostType::Youtube)
{
$tmpPath = tempnam(sys_get_temp_dir(), 'thumb') . '.jpg';
$contents = file_get_contents('http://img.youtube.com/vi/' . $post->orig_name . '/mqdefault.jpg');
file_put_contents($tmpPath, $contents);
if (file_exists($tmpPath))
$srcImage = imagecreatefromjpeg($tmpPath);
}
else switch ($post->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($srcImage))
{
switch ($this->config->browsing->thumbStyle)
{
case 'outside':
$dstImage = ThumbnailHelper::cropOutside($srcImage, $dstWidth, $dstHeight);
break;
case 'inside':
$dstImage = ThumbnailHelper::cropInside($srcImage, $dstWidth, $dstHeight);
break;
default:
throw new SimpleException('Unknown thumbnail crop style');
}
imagejpeg($dstImage, $path);
imagedestroy($srcImage);
imagedestroy($dstImage);
}
else
{
$post->makeThumb($width, $height);
if (!file_exists($path))
$path = $this->config->main->mediaPath . DS . 'img' . DS . 'thumb.jpg';
}
if (isset($tmpPath))
unlink($tmpPath);
}
if (!is_readable($path))
throw new SimpleException('Thumbnail file is not readable');
$this->context->layoutName = 'layout-file';
$this->context->transport->cacheDaysToLive = 30;
$this->context->transport->mimeType = 'image/jpeg';
$this->context->transport->fileHash = 'thumb' . md5($name . filemtime($path));
@ -873,7 +532,6 @@ class PostController
{
$this->context->layoutName = 'layout-file';
$post = Model_Post::locate($name, true);
R::preload($post, ['tag']);
PrivilegesHelper::confirmWithException(Privilege::RetrievePost);
PrivilegesHelper::confirmWithException(Privilege::RetrievePost, PostSafety::toString($post->safety));
@ -902,4 +560,117 @@ class PostController
$this->context->transport->fileHash = 'post' . $post->file_hash;
$this->context->transport->filePath = $path;
}
private function doEdit($post, $isNew)
{
/* file contents */
if (isset($_FILES['file']))
{
if (!$isNew)
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedFile = $_FILES['file'];
self::handleUploadErrors($suppliedFile);
$srcPath = $suppliedFile['tmp_name'];
$post->setContentFromPath($srcPath);
if (!$isNew)
LogHelper::logEvent('post-edit', '{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
}
elseif (InputHelper::get('url'))
{
if (!$isNew)
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$url = InputHelper::get('url');
$post->setContentFromUrl($url);
if (!$isNew)
LogHelper::logEvent('post-edit', '{user} changed contents of {post}', ['post' => TextHelper::reprPost($post)]);
}
/* safety */
$suppliedSafety = InputHelper::get('safety');
if ($suppliedSafety !== null)
{
if (!$isNew)
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$oldSafety = $post->safety;
$post->setSafety($suppliedSafety);
$newSafety = $post->safety;
if ($oldSafety != $newSafety)
LogHelper::logEvent('post-edit', '{user} changed safety for {post} to {safety}', ['post' => TextHelper::reprPost($post), 'safety' => PostSafety::toString($post->safety)]);
}
/* tags */
$suppliedTags = InputHelper::get('tags');
if ($suppliedTags !== null)
{
if (!$isNew)
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$oldTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
$post->setTagsFromText($suppliedTags);
$newTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
foreach (array_diff($oldTags, $newTags) as $tag)
LogHelper::logEvent('post-tag-del', '{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
foreach (array_diff($newTags, $oldTags) as $tag)
LogHelper::logEvent('post-tag-add', '{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
}
/* source */
$suppliedSource = InputHelper::get('source');
if ($suppliedSource !== null)
{
if (!$isNew)
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$oldSource = $post->source;
$post->setSource($suppliedSource);
$newSource = $post->source;
if ($oldSource != $newSource)
LogHelper::logEvent('post-edit', '{user} changed source for {post} to {source}', ['post' => TextHelper::reprPost($post), 'source' => $post->source]);
}
/* relations */
$suppliedRelations = InputHelper::get('relations');
if ($suppliedRelations !== null)
{
if (!$isNew)
PrivilegesHelper::confirmWithException(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$oldRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
$post->setRelationsFromText($suppliedRelations);
$newRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
foreach (array_diff($oldRelatedIds, $newRelatedIds) as $post2id)
LogHelper::logEvent('post-relation-del', '{user} removed relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
foreach (array_diff($newRelatedIds, $oldRelatedIds) as $post2id)
LogHelper::logEvent('post-relation-add', '{user} added relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
}
/* thumbnail */
if (!empty($_FILES['thumb']['name']))
{
if (!$isNew)
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
$suppliedFile = $_FILES['thumb'];
self::handleUploadErrors($suppliedFile);
$srcPath = $suppliedFile['tmp_name'];
$post->setCustomThumbnailFromPath($srcPath);
LogHelper::logEvent('post-edit', '{user} changed thumb for {post}', ['post' => TextHelper::reprPost($post)]);
}
}
}

View file

@ -41,6 +41,8 @@ class TagController
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
if (InputHelper::get('submit'))
{
Model_Tag::removeUnused();
$suppliedSourceTag = InputHelper::get('source-tag');
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
$sourceTag = Model_Tag::locate($suppliedSourceTag);
@ -60,9 +62,9 @@ class TagController
if ($postTag->id == $sourceTag->id)
unset($post->sharedTag[$key]);
$post->sharedTag []= $targetTag;
R::store($post);
Model_Post::save($post);
}
R::trash($sourceTag);
Model_Tag::remove($sourceTag);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
LogHelper::logEvent('tag-merge', '{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
@ -83,6 +85,8 @@ class TagController
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
if (InputHelper::get('submit'))
{
Model_Tag::removeUnused();
$suppliedSourceTag = InputHelper::get('source-tag');
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
$sourceTag = Model_Tag::locate($suppliedSourceTag);
@ -95,7 +99,7 @@ class TagController
throw new SimpleException('Target tag already exists');
$sourceTag->name = $suppliedTargetTag;
R::store($sourceTag);
Model_Tag::save($sourceTag);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
LogHelper::logEvent('tag-rename', '{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);

View file

@ -184,7 +184,7 @@ class UserController
if (InputHelper::get('submit'))
{
$user->banned = true;
R::store($user);
Model_User::save($user);
LogHelper::logEvent('ban', '{user} banned {subject}', ['subject' => TextHelper::reprUser($user)]);
StatusHelper::success();
@ -205,7 +205,7 @@ class UserController
if (InputHelper::get('submit'))
{
$user->banned = false;
R::store($user);
Model_User::save($user);
LogHelper::logEvent('unban', '{user} unbanned {subject}', ['subject' => TextHelper::reprUser($user)]);
StatusHelper::success();
@ -225,7 +225,7 @@ class UserController
if (InputHelper::get('submit'))
{
$user->staff_confirmed = true;
R::store($user);
Model_User::save($user);
LogHelper::logEvent('reg-accept', '{user} confirmed account for {subject}', ['subject' => TextHelper::reprUser($user)]);
StatusHelper::success();
}
@ -257,22 +257,11 @@ class UserController
if ($suppliedPasswordHash != $user->pass_hash)
throw new SimpleException('Must supply valid password');
}
R::trashAll(R::find('postscore', 'user_id = ?', [$user->id]));
foreach ($user->alias('commenter')->ownComment as $comment)
{
$comment->commenter = null;
R::store($comment);
}
foreach ($user->alias('uploader')->ownPost as $post)
{
$post->uploader = null;
R::store($post);
}
$user->ownFavoritee = [];
if ($user->id == $this->context->user->id)
$oldId = $user->id;
Model_User::remove($user);
if ($oldId == $this->context->user->id)
AuthController::doLogOut();
R::store($user);
R::trash($user);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
LogHelper::logEvent('user-del', '{user} removed account for {subject}', ['subject' => TextHelper::reprUser($name)]);
@ -305,7 +294,7 @@ class UserController
$user->enableEndlessScrolling(InputHelper::get('endless-scrolling'));
R::store($user);
Model_User::save($user);
if ($user->id == $this->context->user->id)
$this->context->user = $user;
AuthController::doReLog();
@ -394,7 +383,7 @@ class UserController
if ($suppliedPasswordHash != $currentPasswordHash)
throw new SimpleException('Must supply valid current password');
}
R::store($user);
Model_User::save($user);
if ($confirmMail)
self::sendEmailChangeConfirmation($user);
@ -478,7 +467,7 @@ class UserController
AuthController::doReLog();
if (!$this->context->user->anonymous)
R::store($this->context->user);
Model_User::save($this->context->user);
StatusHelper::success();
}
@ -523,9 +512,8 @@ class UserController
throw new SimpleException('E-mail address is required - you will be sent confirmation e-mail.');
//register the user
$dbUser = R::dispense('user');
$dbUser = Model_User::create();
$dbUser->name = $suppliedName;
$dbUser->pass_salt = md5(mt_rand() . uniqid());
$dbUser->pass_hash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
$dbUser->email_unconfirmed = $suppliedEmail;
@ -546,7 +534,7 @@ class UserController
}
//save the user to db if everything went okay
R::store($dbUser);
Model_User::save($dbUser);
if (!empty($dbUser->email_unconfirmed))
self::sendEmailChangeConfirmation($dbUser);
@ -589,7 +577,7 @@ class UserController
$dbUser->email_unconfirmed = null;
$dbToken->used = true;
R::store($dbToken);
R::store($dbUser);
Model_User::save($dbUser);
LogHelper::logEvent('user-activation', '{subject} just activated account', ['subject' => TextHelper::reprUser($dbUser)]);
$message = 'Activation completed successfully.';
@ -626,7 +614,7 @@ class UserController
$dbUser->pass_hash = Model_User::hashPassword($randomPassword, $dbUser->pass_salt);
$dbToken->used = true;
R::store($dbToken);
R::store($dbUser);
Model_User::save($dbUser);
LogHelper::logEvent('user-pass-reset', '{subject} just reset password', ['subject' => TextHelper::reprUser($dbUser)]);
$message = 'Password reset successful. Your new password is **' . $randomPassword . '**.';

View file

@ -50,4 +50,34 @@ abstract class AbstractModel extends RedBean_SimpleModel
$dbQuery->from($table);
return intval($dbQuery->get('row')['count']);
}
public static function create()
{
return R::dispense(static::getTableName());
}
public static function remove($entity)
{
R::trash($entity);
}
public static function save($entity)
{
R::store($entity);
}
/* FUSE methods for RedBeanPHP, preventing some aliasing errors */
public function open()
{
$this->preload();
}
public function after_update()
{
$this->preload();
}
public function preload()
{
}
}

View file

@ -11,6 +11,13 @@ class Model_Comment extends AbstractModel
return 'Model_Comment_QueryBuilder';
}
public function preload()
{
R::preload($this->bean, ['commenter' => 'user', 'post', 'post.uploader' => 'user']);
}
public static function locate($key, $throw = true)
{
$comment = R::findOne(self::getTableName(), 'id = ?', [$key]);
@ -23,6 +30,8 @@ class Model_Comment extends AbstractModel
return $comment;
}
public static function validateText($text)
{
$text = trim($text);

View file

@ -1,6 +1,30 @@
<?php
class Model_Post extends AbstractModel
{
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']);
}
public static function locate($key, $disallowNumeric = false, $throw = true)
{
if (is_numeric($key) and !$disallowNumeric)
@ -26,6 +50,42 @@ class Model_Post extends AbstractModel
return $post;
}
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);
}
public static function validateSafety($safety)
{
$safety = intval($safety);
@ -47,14 +107,48 @@ class Model_Post extends AbstractModel
return $source;
}
public static function getTableName()
private static function validateThumbSize($width, $height)
{
return 'post';
$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];
}
public static function getQueryBuilder()
public static function getAllPostCount()
{
return 'Model_Post_QueryBuilder';
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;
}
public function isTaggedWith($tagName)
@ -65,4 +159,269 @@ class Model_Post extends AbstractModel
return true;
return false;
}
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)
{
$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);
$this->file_size = filesize($srcPath);
$this->file_hash = md5_file($srcPath);
$duplicatedPost = R::findOne('post', 'file_hash = ?', [$this->file_hash]);
if ($duplicatedPost !== null)
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);
}
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;
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
if ($duplicatedPost !== null)
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;
}
}
Model_Post::initModel();

View file

@ -1,6 +1,18 @@
<?php
class Model_Tag extends AbstractModel
{
public static function getTableName()
{
return 'tag';
}
public static function getQueryBuilder()
{
return 'Model_Tag_Querybuilder';
}
public static function locate($key, $throw = true)
{
$tag = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$key]);
@ -13,6 +25,8 @@ class Model_Tag extends AbstractModel
return $tag;
}
public static function removeUnused()
{
$dbQuery = R::$f
@ -68,14 +82,6 @@ class Model_Tag extends AbstractModel
return $tag;
}
public function getPostCount()
{
if ($this->bean->getMeta('post_count'))
return $this->bean->getMeta('post_count');
return $this->bean->countShared('post');
}
public static function validateTags($tags)
{
$tags = trim($tags);
@ -92,13 +98,10 @@ class Model_Tag extends AbstractModel
return $tags;
}
public static function getTableName()
public function getPostCount()
{
return 'tag';
}
public static function getQueryBuilder()
{
return 'Model_Tag_Querybuilder';
if ($this->bean->getMeta('post_count'))
return $this->bean->getMeta('post_count');
return $this->bean->countShared('post');
}
}

View file

@ -1,6 +1,23 @@
<?php
class Model_User extends AbstractModel
{
const SETTING_SAFETY = 1;
const SETTING_ENDLESS_SCROLLING = 2;
public static function getTableName()
{
return 'user';
}
public static function getQueryBuilder()
{
return 'Model_User_QueryBuilder';
}
public static function locate($key, $throw = true)
{
$user = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$key]);
@ -17,82 +34,44 @@ class Model_User extends AbstractModel
return null;
}
public function getAvatarUrl($size = 32)
public static function create()
{
$subject = !empty($this->email_confirmed)
? $this->email_confirmed
: $this->pass_salt . $this->name;
$hash = md5(strtolower(trim($subject)));
$url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
return $url;
$user = R::dispense(self::getTableName());
$user->pass_salt = md5(mt_rand() . uniqid());
return $user;
}
public function getSetting($key)
public static function remove($user)
{
$settings = json_decode($this->settings, true);
return isset($settings[$key])
? $settings[$key]
: null;
//remove stuff from auxiliary tables
R::trashAll(R::find('postscore', 'user_id = ?', [$user->id]));
foreach ($user->alias('commenter')->ownComment as $comment)
{
$comment->commenter = null;
R::store($comment);
}
foreach ($user->alias('uploader')->ownPost as $post)
{
$post->uploader = null;
R::store($post);
}
$user->ownFavoritee = [];
R::store($user);
R::trash($user);
}
public function setSetting($key, $value)
public static function save($user)
{
$settings = json_decode($this->settings, true);
$settings[$key] = $value;
$settings = json_encode($settings);
if (strlen($settings) > 200)
throw new SimpleException('Too much data');
$this->settings = $settings;
R::store($user);
}
const SETTING_SAFETY = 1;
const SETTING_ENDLESS_SCROLLING = 2;
public function hasEnabledSafety($safety)
public static function getAnonymousName()
{
$all = $this->getSetting(self::SETTING_SAFETY);
if (!$all)
return $safety == PostSafety::Safe;
return $all & PostSafety::toFlag($safety);
return '[Anonymous user]';
}
public function enableSafety($safety, $enabled)
{
$all = $this->getSetting(self::SETTING_SAFETY);
if (!$all)
$all = PostSafety::toFlag(PostSafety::Safe);
$new = $all;
if (!$enabled)
{
$new &= ~PostSafety::toFlag($safety);
if (!$new)
$new = PostSafety::toFlag(PostSafety::Safe);
}
else
{
$new |= PostSafety::toFlag($safety);
}
$this->setSetting(self::SETTING_SAFETY, $new);
}
public function hasEnabledEndlessScrolling()
{
$ret = $this->getSetting(self::SETTING_ENDLESS_SCROLLING);
if ($ret === null)
$ret = \Chibi\Registry::getConfig()->browsing->endlessScrollingDefault;
return $ret;
}
public function enableEndlessScrolling($enabled)
{
$this->setSetting(self::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0);
}
public static function validateUserName($userName)
{
$userName = trim($userName);
@ -168,13 +147,126 @@ class Model_User extends AbstractModel
return sha1($salt1 . $salt2 . $pass);
}
public static function getTableName()
public function getAvatarUrl($size = 32)
{
return 'user';
$subject = !empty($this->email_confirmed)
? $this->email_confirmed
: $this->pass_salt . $this->name;
$hash = md5(strtolower(trim($subject)));
$url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
return $url;
}
public static function getQueryBuilder()
public function getSetting($key)
{
return 'Model_User_QueryBuilder';
$settings = json_decode($this->settings, true);
return isset($settings[$key])
? $settings[$key]
: null;
}
public function setSetting($key, $value)
{
$settings = json_decode($this->settings, true);
$settings[$key] = $value;
$settings = json_encode($settings);
if (strlen($settings) > 200)
throw new SimpleException('Too much data');
$this->settings = $settings;
}
public function hasEnabledSafety($safety)
{
$all = $this->getSetting(self::SETTING_SAFETY);
if (!$all)
return $safety == PostSafety::Safe;
return $all & PostSafety::toFlag($safety);
}
public function enableSafety($safety, $enabled)
{
$all = $this->getSetting(self::SETTING_SAFETY);
if (!$all)
$all = PostSafety::toFlag(PostSafety::Safe);
$new = $all;
if (!$enabled)
{
$new &= ~PostSafety::toFlag($safety);
if (!$new)
$new = PostSafety::toFlag(PostSafety::Safe);
}
else
{
$new |= PostSafety::toFlag($safety);
}
$this->setSetting(self::SETTING_SAFETY, $new);
}
public function hasEnabledEndlessScrolling()
{
$ret = $this->getSetting(self::SETTING_ENDLESS_SCROLLING);
if ($ret === null)
$ret = \Chibi\Registry::getConfig()->browsing->endlessScrollingDefault;
return $ret;
}
public function enableEndlessScrolling($enabled)
{
$this->setSetting(self::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0);
}
public function hasFavorited($post)
{
foreach ($this->bean->ownFavoritee as $fav)
if ($fav->post->id == $post->id)
return true;
return false;
}
public function getScore($post)
{
$s = R::findOne('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->id]);
if ($s)
return intval($s->score);
return null;
}
public function addToFavorites($post)
{
R::preload($this->bean, ['favoritee' => 'post']);
foreach ($this->bean->ownFavoritee as $fav)
if ($fav->post_id == $post->id)
throw new SimpleException('Already in favorites');
$this->bean->link('favoritee')->post = $post;
}
public function remFromFavorites($post)
{
$finalKey = null;
foreach ($this->bean->ownFavoritee as $key => $fav)
if ($fav->post_id == $post->id)
$finalKey = $key;
if ($finalKey === null)
throw new SimpleException('Not in favorites');
unset($this->bean->ownFavoritee[$finalKey]);
}
public function score($post, $score)
{
R::trashAll(R::find('postscore', 'post_id = ? AND user_id = ?', [$post->id, $this->id]));
$score = intval($score);
if ($score != 0)
{
$p = $this->bean->link('postscore');
$p->post = $post;
$p->score = $score;
}
}
}

View file

@ -11,6 +11,7 @@ class Privilege extends Enum
const EditPostThumb = 8;
const EditPostSource = 26;
const EditPostRelations = 30;
const EditPostFile = 36;
const HidePost = 9;
const DeletePost = 10;
const FeaturePost = 25;

View file

@ -17,7 +17,7 @@
<label class="left" for="tags">Tags:</label>
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags&hellip;" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->sharedTag)) ?>"/></div>
</div>
<input type="hidden" name="tags-token" id="tags-token" value="<?php echo $this->context->transport->tagsToken ?>"/>
<input type="hidden" name="edit-token" id="edit-token" value="<?php echo $this->context->transport->editToken ?>"/>
<?php endif ?>
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>

View file

@ -11,7 +11,7 @@ setlocale(LC_CTYPE, 'en_US.UTF-8');
ini_set('memory_limit', '128M');
//extension sanity checks
$requiredExtensions = ['pdo', 'pdo_sqlite', 'gd', 'openssl'];
$requiredExtensions = ['pdo', 'pdo_sqlite', 'gd', 'openssl', 'fileinfo'];
foreach ($requiredExtensions as $ext)
if (!extension_loaded($ext))
die('PHP extension "' . $ext . '" must be enabled to continue.' . PHP_EOL);