Models rewrite; removed RedBeanPHP; misc changes
Pages load 1.5-2x faster Exception trace in JSON is now represented as an array Fixed pagination of default favorites page in user pages Fixed thumbnail size validation for non-square thumbnails
This commit is contained in:
parent
8c0c5269c4
commit
5607cfc353
63 changed files with 2603 additions and 1592 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -4,6 +4,3 @@
|
|||
[submodule "php-markdown"]
|
||||
path = lib/php-markdown
|
||||
url = https://github.com/michelf/php-markdown.git
|
||||
[submodule "redbean"]
|
||||
path = lib/redbean
|
||||
url = https://github.com/gabordemooij/redbean.git
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 95cf7d231b60acdeee050041d44503df1b288b74
|
|
@ -402,8 +402,11 @@ button:hover {
|
|||
}
|
||||
|
||||
pre.debug {
|
||||
margin-left: 1em;
|
||||
text-align: left;
|
||||
color: black;
|
||||
white-space: normal;
|
||||
text-indent: -1em;
|
||||
}
|
||||
|
||||
.spoiler:before,
|
||||
|
|
|
@ -58,7 +58,7 @@ class Bootstrap
|
|||
{
|
||||
StatusHelper::failure(rtrim($e->getMessage(), '.') . '.');
|
||||
$this->context->transport->exception = $e;
|
||||
$this->context->transport->queries = array_map(function($x) { return preg_replace('/\s+/', ' ', $x); }, queryLogger()->getLogs());
|
||||
$this->context->transport->queries = Database::getLogs();
|
||||
$this->context->viewName = 'error-exception';
|
||||
(new \Chibi\View())->renderFile($this->context->layoutName);
|
||||
}
|
||||
|
|
|
@ -17,15 +17,15 @@ class AuthController
|
|||
$config = \Chibi\Registry::getConfig();
|
||||
$context = \Chibi\Registry::getContext();
|
||||
|
||||
$dbUser = Model_User::locate($name, false);
|
||||
$dbUser = UserModel::findByNameOrEmail($name, false);
|
||||
if ($dbUser === null)
|
||||
throw new SimpleException('Invalid username');
|
||||
|
||||
$passwordHash = Model_User::hashPassword($password, $dbUser->pass_salt);
|
||||
if ($passwordHash != $dbUser->pass_hash)
|
||||
$passwordHash = UserModel::hashPassword($password, $dbUser->passSalt);
|
||||
if ($passwordHash != $dbUser->passHash)
|
||||
throw new SimpleException('Invalid password');
|
||||
|
||||
if (!$dbUser->staff_confirmed and $config->registration->staffActivation)
|
||||
if (!$dbUser->staffConfirmed and $config->registration->staffActivation)
|
||||
throw new SimpleException('Staff hasn\'t confirmed your registration yet');
|
||||
|
||||
if ($dbUser->banned)
|
||||
|
@ -105,20 +105,20 @@ class AuthController
|
|||
{
|
||||
if (!empty($context->user) and $context->user->id)
|
||||
{
|
||||
$dbUser = R::findOne('user', 'id = ?', [$context->user->id]);
|
||||
$dbUser = UserModel::findById($context->user->id);
|
||||
$_SESSION['user'] = serialize($dbUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
$dummy = R::dispense('user');
|
||||
$dummy->name = Model_User::getAnonymousName();
|
||||
$dummy->access_rank = AccessRank::Anonymous;
|
||||
$dummy->anonymous = true;
|
||||
$dummy = UserModel::spawn();
|
||||
$dummy->name = UserModel::getAnonymousName();
|
||||
$dummy->accessRank = AccessRank::Anonymous;
|
||||
$_SESSION['user'] = serialize($dummy);
|
||||
}
|
||||
}
|
||||
|
||||
$context->user = unserialize($_SESSION['user']);
|
||||
$context->loggedIn = $context->user->anonymous ? false : true;
|
||||
$context->loggedIn = $context->user->accessRank != AccessRank::Anonymous;
|
||||
if (!$context->loggedIn)
|
||||
{
|
||||
try
|
||||
|
@ -135,7 +135,7 @@ class AuthController
|
|||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
if ($context->user !== null)
|
||||
$_SESSION['user'] = serialize($context->user);
|
||||
self::doLogOut();
|
||||
self::doLogIn();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,9 +21,13 @@ class CommentController
|
|||
PrivilegesHelper::confirmWithException(Privilege::ListComments);
|
||||
|
||||
$page = max(1, $page);
|
||||
list ($comments, $commentCount) = Model_Comment::getEntitiesWithCount(null, $commentsPerPage, $page);
|
||||
$comments = CommentSearchService::getEntities(null, $commentsPerPage, $page);
|
||||
$commentCount = CommentSearchService::getEntityCount(null, $commentsPerPage, $page);
|
||||
$pageCount = ceil($commentCount / $commentsPerPage);
|
||||
R::preload($comments, ['commenter' => 'user', 'post', 'post.uploader' => 'user', 'post.sharedTag']);
|
||||
CommentModel::preloadCommenters($comments);
|
||||
CommentModel::preloadPosts($comments);
|
||||
$posts = array_map(function($comment) { return $comment->getPost(); }, $comments);
|
||||
PostModel::preloadTags($posts);
|
||||
|
||||
$this->context->postGroups = true;
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
|
@ -47,22 +51,24 @@ class CommentController
|
|||
if ($this->config->registration->needEmailForCommenting)
|
||||
PrivilegesHelper::confirmEmail($this->context->user);
|
||||
|
||||
$post = Model_Post::locate($postId);
|
||||
$post = PostModel::findById($postId);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$text = InputHelper::get('text');
|
||||
$text = Model_Comment::validateText($text);
|
||||
$text = CommentModel::validateText($text);
|
||||
|
||||
$comment = Model_Comment::create();
|
||||
$comment->post = $post;
|
||||
$comment = CommentModel::spawn();
|
||||
$comment->setPost($post);
|
||||
if ($this->context->loggedIn)
|
||||
$comment->commenter = $this->context->user;
|
||||
$comment->comment_date = time();
|
||||
$comment->setCommenter($this->context->user);
|
||||
else
|
||||
$comment->setCommenter(null);
|
||||
$comment->commentDate = time();
|
||||
$comment->text = $text;
|
||||
if (InputHelper::get('sender') != 'preview')
|
||||
{
|
||||
Model_Comment::save($comment);
|
||||
CommentModel::save($comment);
|
||||
LogHelper::log('{user} commented on {post}', ['post' => TextHelper::reprPost($post->id)]);
|
||||
}
|
||||
$this->context->transport->textPreview = $comment->getText();
|
||||
|
@ -78,13 +84,12 @@ class CommentController
|
|||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$comment = Model_Comment::locate($id);
|
||||
R::preload($comment, ['commenter' => 'user']);
|
||||
$comment = CommentModel::findById($id);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($comment->commenter));
|
||||
Model_Comment::remove($comment);
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($comment->getCommenter()));
|
||||
CommentModel::remove($comment);
|
||||
|
||||
LogHelper::log('{user} removed comment from {post}', ['post' => TextHelper::reprPost($comment->post)]);
|
||||
LogHelper::log('{user} removed comment from {post}', ['post' => TextHelper::reprPost($comment->getPost())]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,14 +9,14 @@ class IndexController
|
|||
{
|
||||
$this->context->subTitle = 'home';
|
||||
$this->context->stylesheets []= 'index-index.css';
|
||||
$this->context->transport->postCount = Model_Post::getAllPostCount();
|
||||
$this->context->transport->postCount = PostModel::getCount();
|
||||
|
||||
$featuredPost = $this->getFeaturedPost();
|
||||
if ($featuredPost)
|
||||
{
|
||||
$this->context->featuredPost = $featuredPost;
|
||||
$this->context->featuredPostDate = Model_Property::get(Model_Property::FeaturedPostDate);
|
||||
$this->context->featuredPostUser = Model_User::locate(Model_Property::get(Model_Property::FeaturedPostUserName), false);
|
||||
$this->context->featuredPostDate = PropertyModel::get(PropertyModel::FeaturedPostDate);
|
||||
$this->context->featuredPostUser = UserModel::findByNameOrEmail(PropertyModel::get(PropertyModel::FeaturedPostUserName), false);
|
||||
$this->context->pageThumb = \Chibi\UrlHelper::route('post', 'thumb', ['name' => $featuredPost->name]);
|
||||
}
|
||||
}
|
||||
|
@ -42,15 +42,15 @@ class IndexController
|
|||
{
|
||||
$featuredPostRotationTime = $this->config->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$featuredPostId = Model_Property::get(Model_Property::FeaturedPostId);
|
||||
$featuredPostDate = Model_Property::get(Model_Property::FeaturedPostDate);
|
||||
$featuredPostId = PropertyModel::get(PropertyModel::FeaturedPostId);
|
||||
$featuredPostDate = PropertyModel::get(PropertyModel::FeaturedPostDate);
|
||||
|
||||
//check if too old
|
||||
if (!$featuredPostId or $featuredPostDate + $featuredPostRotationTime < time())
|
||||
return $this->featureNewPost();
|
||||
|
||||
//check if post was deleted
|
||||
$featuredPost = Model_Post::locate($featuredPostId, false, false);
|
||||
$featuredPost = PostModel::findById($featuredPostId, false);
|
||||
if (!$featuredPost)
|
||||
return $this->featureNewPost();
|
||||
|
||||
|
@ -59,20 +59,20 @@ class IndexController
|
|||
|
||||
private function featureNewPost()
|
||||
{
|
||||
$featuredPostId = R::$f->begin()
|
||||
$query = (new SqlQuery)
|
||||
->select('id')
|
||||
->from('post')
|
||||
->where('type = ?')->put(PostType::Image)
|
||||
->and('safety = ?')->put(PostSafety::Safe)
|
||||
->orderBy($this->config->main->dbDriver == 'sqlite' ? 'random()' : 'rand()')
|
||||
->desc()
|
||||
->get('row')['id'];
|
||||
->desc();
|
||||
$featuredPostId = Database::fetchOne($query)['id'];
|
||||
if (!$featuredPostId)
|
||||
return null;
|
||||
|
||||
Model_Property::set(Model_Property::FeaturedPostId, $featuredPostId);
|
||||
Model_Property::set(Model_Property::FeaturedPostDate, time());
|
||||
Model_Property::set(Model_Property::FeaturedPostUserName, null);
|
||||
return Model_Post::locate($featuredPostId);
|
||||
PropertyModel::set(PropertyModel::FeaturedPostId, $featuredPostId);
|
||||
PropertyModel::set(PropertyModel::FeaturedPostDate, time());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, null);
|
||||
return PostModel::findById($featuredPostId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ class PostController
|
|||
private static function serializePost($post)
|
||||
{
|
||||
$x = [];
|
||||
foreach ($post->sharedTag as $tag)
|
||||
foreach ($post->getTags() as $tag)
|
||||
$x []= TextHelper::reprTag($tag->name);
|
||||
foreach ($post->via('crossref')->sharedPost as $relatedPost)
|
||||
foreach ($post->getRelations() as $relatedPost)
|
||||
$x []= TextHelper::reprPost($relatedPost);
|
||||
$x []= $post->safety;
|
||||
$x []= $post->source;
|
||||
$x []= $post->file_hash;
|
||||
$x []= $post->fileHash;
|
||||
natcasesort($x);
|
||||
$x = join(' ', $x);
|
||||
return md5($x);
|
||||
|
@ -104,10 +104,11 @@ class PostController
|
|||
}
|
||||
|
||||
$page = max(1, $page);
|
||||
list($posts, $postCount) = Model_Post::getEntitiesWithCount($query, $postsPerPage, $page);
|
||||
$posts = PostSearchService::getEntities($query, $postsPerPage, $page);
|
||||
$postCount = PostSearchService::getEntityCount($query, $postsPerPage, $page);
|
||||
$pageCount = ceil($postCount / $postsPerPage);
|
||||
$page = min($pageCount, $page);
|
||||
Model_Post::attachTags($posts);
|
||||
PostModel::preloadTags($posts);
|
||||
|
||||
$this->context->transport->paginator = new StdClass;
|
||||
$this->context->transport->paginator->page = $page;
|
||||
|
@ -126,33 +127,40 @@ class PostController
|
|||
*/
|
||||
public function toggleTagAction($id, $tag, $enable)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
$tagName = $tag;
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
$this->context->transport->post = $post;
|
||||
|
||||
$tagRow = Model_Tag::locate($tag, false);
|
||||
if ($tagRow !== null)
|
||||
$tag = $tagRow->name;
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::MassTag);
|
||||
$tags = array_map(function($x) { return $x->name; }, $post->sharedTag);
|
||||
|
||||
if (!$enable and in_array($tag, $tags))
|
||||
$tags = $post->getTags();
|
||||
|
||||
if (!$enable)
|
||||
{
|
||||
$tags = array_diff($tags, [$tag]);
|
||||
foreach ($tags as $i => $tag)
|
||||
if ($tag->name == $tagName)
|
||||
unset($tags[$i]);
|
||||
LogHelper::log('{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
elseif ($enable)
|
||||
{
|
||||
$tags += [$tag];
|
||||
$tag = TagModel::findByName($tagName, false);
|
||||
if ($tag === null)
|
||||
{
|
||||
$tag = TagModel::spawn();
|
||||
$tag->name = $tagName;
|
||||
TagModel::save($tag);
|
||||
}
|
||||
|
||||
$tags []= $tag;
|
||||
LogHelper::log('{user} tagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
}
|
||||
|
||||
$dbTags = Model_Tag::insertOrUpdate($tags);
|
||||
$post->sharedTag = $dbTags;
|
||||
$post->setTags($tags);
|
||||
|
||||
Model_Post::save($post);
|
||||
PostModel::save($post);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
@ -197,18 +205,18 @@ class PostController
|
|||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
R::transaction(function()
|
||||
Database::transaction(function()
|
||||
{
|
||||
$post = Model_Post::create();
|
||||
$post = PostModel::spawn();
|
||||
LogHelper::bufferChanges();
|
||||
|
||||
//basic stuff
|
||||
$anonymous = InputHelper::get('anonymous');
|
||||
if ($this->context->loggedIn and !$anonymous)
|
||||
$post->uploader = $this->context->user;
|
||||
$post->setUploader($this->context->user);
|
||||
|
||||
//store the post to get the ID in the logs
|
||||
Model_Post::save($post);
|
||||
PostModel::forgeId($post);
|
||||
|
||||
//do the edits
|
||||
$this->doEdit($post, true);
|
||||
|
@ -227,13 +235,13 @@ class PostController
|
|||
$fmt .= ' added {post} (tags: {tags}, safety: {safety}, source: {source})';
|
||||
LogHelper::log($fmt, [
|
||||
'post' => TextHelper::reprPost($post),
|
||||
'tags' => join(', ', array_map(['TextHelper', 'reprTag'], $post->sharedTag)),
|
||||
'tags' => TextHelper::reprTags($post->getTags()),
|
||||
'safety' => PostSafety::toString($post->safety),
|
||||
'source' => $post->source]);
|
||||
|
||||
//finish
|
||||
LogHelper::flush();
|
||||
Model_Post::save($post);
|
||||
PostModel::save($post);
|
||||
});
|
||||
|
||||
StatusHelper::success();
|
||||
|
@ -247,7 +255,7 @@ class PostController
|
|||
*/
|
||||
public function editAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
$this->context->transport->post = $post;
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
|
@ -260,8 +268,8 @@ class PostController
|
|||
$this->doEdit($post, false);
|
||||
LogHelper::flush();
|
||||
|
||||
Model_Post::save($post);
|
||||
Model_Tag::removeUnused();
|
||||
PostModel::save($post);
|
||||
TagModel::removeUnused();
|
||||
|
||||
StatusHelper::success();
|
||||
}
|
||||
|
@ -274,7 +282,7 @@ class PostController
|
|||
*/
|
||||
public function flagAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagPost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
|
@ -299,15 +307,13 @@ class PostController
|
|||
*/
|
||||
public function hideAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$post->setHidden(true);
|
||||
Model_Post::save($post);
|
||||
PostModel::save($post);
|
||||
|
||||
LogHelper::log('{user} hidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
|
@ -321,15 +327,13 @@ class PostController
|
|||
*/
|
||||
public function unhideAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$post->setHidden(false);
|
||||
Model_Post::save($post);
|
||||
PostModel::save($post);
|
||||
|
||||
LogHelper::log('{user} unhidden {post}', ['post' => TextHelper::reprPost($post)]);
|
||||
StatusHelper::success();
|
||||
|
@ -343,14 +347,12 @@ class PostController
|
|||
*/
|
||||
public function deleteAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
Model_Post::remove($post);
|
||||
PostModel::remove($post);
|
||||
|
||||
LogHelper::log('{user} deleted {post}', ['post' => TextHelper::reprPost($id)]);
|
||||
StatusHelper::success();
|
||||
|
@ -365,7 +367,7 @@ class PostController
|
|||
*/
|
||||
public function addFavoriteAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
|
@ -373,8 +375,7 @@ class PostController
|
|||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
|
||||
$this->context->user->addToFavorites($post);
|
||||
Model_User::save($this->context->user);
|
||||
UserModel::addToUserFavorites($this->context->user, $post);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
@ -385,7 +386,7 @@ class PostController
|
|||
*/
|
||||
public function remFavoriteAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FavoritePost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
|
@ -393,8 +394,7 @@ class PostController
|
|||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
|
||||
$this->context->user->remFromFavorites($post);
|
||||
Model_User::save($this->context->user);
|
||||
UserModel::removeFromUserFavorites($this->context->user, $post);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
@ -407,7 +407,7 @@ class PostController
|
|||
*/
|
||||
public function scoreAction($id, $score)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ScorePost);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
|
@ -415,8 +415,7 @@ class PostController
|
|||
if (!$this->context->loggedIn)
|
||||
throw new SimpleException('Not logged in');
|
||||
|
||||
$this->context->user->score($post, $score);
|
||||
Model_User::save($this->context->user);
|
||||
UserModel::updateUserScore($this->context->user, $post, $score);
|
||||
StatusHelper::success();
|
||||
}
|
||||
}
|
||||
|
@ -428,11 +427,11 @@ class PostController
|
|||
*/
|
||||
public function featureAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FeaturePost);
|
||||
Model_Property::set(Model_Property::FeaturedPostId, $post->id);
|
||||
Model_Property::set(Model_Property::FeaturedPostDate, time());
|
||||
Model_Property::set(Model_Property::FeaturedPostUserName, $this->context->user->name);
|
||||
PropertyModel::set(PropertyModel::FeaturedPostId, $post->id);
|
||||
PropertyModel::set(PropertyModel::FeaturedPostDate, time());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, $this->context->user->name);
|
||||
StatusHelper::success();
|
||||
LogHelper::log('{user} featured {post} on main page', ['post' => TextHelper::reprPost($post)]);
|
||||
}
|
||||
|
@ -445,35 +444,31 @@ class PostController
|
|||
*/
|
||||
public function viewAction($id)
|
||||
{
|
||||
$post = Model_Post::locate($id);
|
||||
R::preload($post, [
|
||||
'tag',
|
||||
'uploader' => 'user',
|
||||
'ownComment.commenter' => 'user']);
|
||||
R::preload($this->context->user, ['ownFavoritee']);
|
||||
$post = PostModel::findByIdOrName($id);
|
||||
CommentModel::preloadCommenters($post->getComments());
|
||||
|
||||
if ($post->hidden)
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost, 'hidden');
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewPost, PostSafety::toString($post->safety));
|
||||
|
||||
Model_Post_QueryBuilder::enableTokenLimit(false);
|
||||
PostSearchService::enableTokenLimit(false);
|
||||
try
|
||||
{
|
||||
$this->context->transport->lastSearchQuery = InputHelper::get('last-search-query');
|
||||
$prevPostQuery = $this->context->transport->lastSearchQuery . ' prev:' . $id;
|
||||
$nextPostQuery = $this->context->transport->lastSearchQuery . ' next:' . $id;
|
||||
$prevPost = current(Model_Post::getEntities($prevPostQuery, 1, 1));
|
||||
$nextPost = current(Model_Post::getEntities($nextPostQuery, 1, 1));
|
||||
$prevPost = current(PostSearchService::getEntities($prevPostQuery, 1, 1));
|
||||
$nextPost = current(PostSearchService::getEntities($nextPostQuery, 1, 1));
|
||||
}
|
||||
#search for some reason was invalid, e.g. tag was deleted in the meantime
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->context->transport->lastSearchQuery = '';
|
||||
$prevPost = current(Model_Post::getEntities('prev:' . $id, 1, 1));
|
||||
$nextPost = current(Model_Post::getEntities('next:' . $id, 1, 1));
|
||||
$prevPost = current(PostModel::getEntities('prev:' . $id, 1, 1));
|
||||
$nextPost = current(PostModel::getEntities('next:' . $id, 1, 1));
|
||||
}
|
||||
Model_Post_QueryBuilder::enableTokenLimit(true);
|
||||
PostSearchService::enableTokenLimit(true);
|
||||
|
||||
$favorite = $this->context->user->hasFavorited($post);
|
||||
$score = $this->context->user->getScore($post);
|
||||
|
@ -483,13 +478,13 @@ class PostController
|
|||
$this->context->stylesheets []= 'post-view.css';
|
||||
$this->context->stylesheets []= 'comment-small.css';
|
||||
$this->context->scripts []= 'post-view.js';
|
||||
$this->context->subTitle = 'showing ' . TextHelper::reprPost($post) . ' – ' . TextHelper::reprTags($post->sharedTag);
|
||||
$this->context->subTitle = 'showing ' . TextHelper::reprPost($post) . ' – ' . TextHelper::reprTags($post->getTags());
|
||||
$this->context->favorite = $favorite;
|
||||
$this->context->score = $score;
|
||||
$this->context->flagged = $flagged;
|
||||
$this->context->transport->post = $post;
|
||||
$this->context->transport->prevPostId = $prevPost ? $prevPost['id'] : null;
|
||||
$this->context->transport->nextPostId = $nextPost ? $nextPost['id'] : null;
|
||||
$this->context->transport->prevPostId = $prevPost ? $prevPost->id : null;
|
||||
$this->context->transport->nextPostId = $nextPost ? $nextPost->id : null;
|
||||
$this->context->transport->editToken = self::serializePost($post);
|
||||
}
|
||||
|
||||
|
@ -501,13 +496,13 @@ class PostController
|
|||
*/
|
||||
public function thumbAction($name, $width = null, $height = null)
|
||||
{
|
||||
$path = Model_Post::getThumbCustomPath($name, $width, $height);
|
||||
$path = PostModel::getThumbCustomPath($name, $width, $height);
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$path = Model_Post::getThumbDefaultPath($name, $width, $height);
|
||||
$path = PostModel::getThumbDefaultPath($name, $width, $height);
|
||||
if (!file_exists($path))
|
||||
{
|
||||
$post = Model_Post::locate($name);
|
||||
$post = PostModel::findByIdOrName($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ListPosts, PostSafety::toString($post->safety));
|
||||
$post->makeThumb($width, $height);
|
||||
|
@ -534,8 +529,7 @@ class PostController
|
|||
*/
|
||||
public function retrieveAction($name)
|
||||
{
|
||||
$this->context->layoutName = 'layout-file';
|
||||
$post = Model_Post::locate($name, true);
|
||||
$post = PostModel::findByName($name, true);
|
||||
|
||||
PrivilegesHelper::confirmWithException(Privilege::RetrievePost);
|
||||
PrivilegesHelper::confirmWithException(Privilege::RetrievePost, PostSafety::toString($post->safety));
|
||||
|
@ -546,22 +540,23 @@ class PostController
|
|||
if (!is_readable($path))
|
||||
throw new SimpleException('Post file is not readable');
|
||||
|
||||
$ext = substr($post->orig_name, strrpos($post->orig_name, '.') + 1);
|
||||
if (strpos($post->orig_name, '.') === false)
|
||||
$ext = '.dat';
|
||||
$ext = substr($post->origName, strrpos($post->origName, '.') + 1);
|
||||
if (strpos($post->origName, '.') === false)
|
||||
$ext = 'dat';
|
||||
$fn = sprintf('%s_%s_%s.%s',
|
||||
$this->config->main->title,
|
||||
$post->id,
|
||||
join(',', array_map(function($tag) { return $tag->name; }, $post->sharedTag)),
|
||||
join(',', array_map(function($tag) { return $tag->name; }, $post->getTags())),
|
||||
$ext);
|
||||
$fn = preg_replace('/[[:^print:]]/', '', $fn);
|
||||
|
||||
$ttl = 60 * 60 * 24 * 14;
|
||||
|
||||
$this->context->layoutName = 'layout-file';
|
||||
$this->context->transport->cacheDaysToLive = 14;
|
||||
$this->context->transport->customFileName = $fn;
|
||||
$this->context->transport->mimeType = $post->mimeType;
|
||||
$this->context->transport->fileHash = 'post' . $post->file_hash;
|
||||
$this->context->transport->fileHash = 'post' . $post->fileHash;
|
||||
$this->context->transport->filePath = $path;
|
||||
}
|
||||
|
||||
|
@ -569,14 +564,11 @@ class PostController
|
|||
|
||||
private function doEdit($post, $isNew)
|
||||
{
|
||||
if (!$isNew)
|
||||
R::preload($post, ['uploader' => 'user']);
|
||||
|
||||
/* file contents */
|
||||
if (!empty($_FILES['file']['name']))
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$suppliedFile = $_FILES['file'];
|
||||
self::handleUploadErrors($suppliedFile);
|
||||
|
@ -590,7 +582,7 @@ class PostController
|
|||
elseif (InputHelper::get('url'))
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$url = InputHelper::get('url');
|
||||
$post->setContentFromUrl($url);
|
||||
|
@ -604,7 +596,7 @@ class PostController
|
|||
if ($suppliedSafety !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$oldSafety = $post->safety;
|
||||
$post->setSafety($suppliedSafety);
|
||||
|
@ -619,11 +611,11 @@ class PostController
|
|||
if ($suppliedTags !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$oldTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
|
||||
$oldTags = array_map(function($tag) { return $tag->name; }, $post->getTags());
|
||||
$post->setTagsFromText($suppliedTags);
|
||||
$newTags = array_map(function($tag) { return $tag->name; }, $post->sharedTag);
|
||||
$newTags = array_map(function($tag) { return $tag->name; }, $post->getTags());
|
||||
|
||||
foreach (array_diff($oldTags, $newTags) as $tag)
|
||||
LogHelper::log('{user} untagged {post} with {tag}', ['post' => TextHelper::reprPost($post), 'tag' => TextHelper::reprTag($tag)]);
|
||||
|
@ -637,7 +629,7 @@ class PostController
|
|||
if ($suppliedSource !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$oldSource = $post->source;
|
||||
$post->setSource($suppliedSource);
|
||||
|
@ -652,11 +644,11 @@ class PostController
|
|||
if ($suppliedRelations !== null)
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$oldRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
|
||||
$oldRelatedIds = array_map(function($post) { return $post->id; }, $post->getRelations());
|
||||
$post->setRelationsFromText($suppliedRelations);
|
||||
$newRelatedIds = array_map(function($post) { return $post->id; }, $post->via('crossref')->sharedPost);
|
||||
$newRelatedIds = array_map(function($post) { return $post->id; }, $post->getRelations());
|
||||
|
||||
foreach (array_diff($oldRelatedIds, $newRelatedIds) as $post2id)
|
||||
LogHelper::log('{user} removed relation between {post} and {post2}', ['post' => TextHelper::reprPost($post), 'post2' => TextHelper::reprPost($post2id)]);
|
||||
|
@ -669,7 +661,7 @@ class PostController
|
|||
if (!empty($_FILES['thumb']['name']))
|
||||
{
|
||||
if (!$isNew)
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->uploader));
|
||||
PrivilegesHelper::confirmWithException(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($post->getUploader()));
|
||||
|
||||
$suppliedFile = $_FILES['thumb'];
|
||||
self::handleUploadErrors($suppliedFile);
|
||||
|
|
|
@ -15,7 +15,7 @@ class TagController
|
|||
PrivilegesHelper::confirmWithException(Privilege::ListTags);
|
||||
$suppliedFilter = $filter ?: InputHelper::get('filter') ?: 'order:alpha,asc';
|
||||
|
||||
$tags = Model_Tag::getEntitiesRows($suppliedFilter, null, null);
|
||||
$tags = TagSearchService::getEntitiesRows($suppliedFilter, null, null);
|
||||
$this->context->filter = $suppliedFilter;
|
||||
$this->context->transport->tags = $tags;
|
||||
|
||||
|
@ -39,30 +39,15 @@ class TagController
|
|||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
Model_Tag::removeUnused();
|
||||
TagModel::removeUnused();
|
||||
|
||||
$suppliedSourceTag = InputHelper::get('source-tag');
|
||||
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
$suppliedSourceTag = TagModel::validateTag($suppliedSourceTag);
|
||||
|
||||
$suppliedTargetTag = InputHelper::get('target-tag');
|
||||
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
|
||||
$targetTag = Model_Tag::locate($suppliedTargetTag);
|
||||
$suppliedTargetTag = TagModel::validateTag($suppliedTargetTag);
|
||||
|
||||
if ($sourceTag->id == $targetTag->id)
|
||||
throw new SimpleException('Source and target tag are the same');
|
||||
|
||||
R::preload($sourceTag, 'post');
|
||||
|
||||
foreach ($sourceTag->sharedPost as $post)
|
||||
{
|
||||
foreach ($post->sharedTag as $key => $postTag)
|
||||
if ($postTag->id == $sourceTag->id)
|
||||
unset($post->sharedTag[$key]);
|
||||
$post->sharedTag []= $targetTag;
|
||||
Model_Post::save($post);
|
||||
}
|
||||
Model_Tag::remove($sourceTag);
|
||||
TagModel::merge($suppliedSourceTag, $suppliedTargetTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
LogHelper::log('{user} merged {source} with {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
|
@ -82,21 +67,15 @@ class TagController
|
|||
PrivilegesHelper::confirmWithException(Privilege::MergeTags);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
Model_Tag::removeUnused();
|
||||
TagModel::removeUnused();
|
||||
|
||||
$suppliedSourceTag = InputHelper::get('source-tag');
|
||||
$suppliedSourceTag = Model_Tag::validateTag($suppliedSourceTag);
|
||||
$sourceTag = Model_Tag::locate($suppliedSourceTag);
|
||||
$suppliedSourceTag = TagModel::validateTag($suppliedSourceTag);
|
||||
|
||||
$suppliedTargetTag = InputHelper::get('target-tag');
|
||||
$suppliedTargetTag = Model_Tag::validateTag($suppliedTargetTag);
|
||||
$targetTag = Model_Tag::locate($suppliedTargetTag, false);
|
||||
$suppliedTargetTag = TagModel::validateTag($suppliedTargetTag);
|
||||
|
||||
if ($targetTag and $targetTag->id != $sourceTag->id)
|
||||
throw new SimpleException('Target tag already exists');
|
||||
|
||||
$sourceTag->name = $suppliedTargetTag;
|
||||
Model_Tag::save($sourceTag);
|
||||
TagModel::rename($suppliedSourceTag, $suppliedTargetTag);
|
||||
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('tag', 'list'));
|
||||
LogHelper::log('{user} renamed {source} to {target}', ['source' => TextHelper::reprTag($suppliedSourceTag), 'target' => TextHelper::reprTag($suppliedTargetTag)]);
|
||||
|
@ -121,7 +100,7 @@ class TagController
|
|||
$suppliedQuery = ' ';
|
||||
$suppliedTag = InputHelper::get('tag');
|
||||
if (!empty($suppliedTag))
|
||||
$suppliedTag = Model_Tag::validateTag($suppliedTag);
|
||||
$suppliedTag = TagModel::validateTag($suppliedTag);
|
||||
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('post', 'list', ['source' => 'mass-tag', 'query' => $suppliedQuery, 'additionalInfo' => $suppliedTag]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,25 +22,20 @@ class UserController
|
|||
$linkActionName)
|
||||
{
|
||||
//prepare unique user token
|
||||
do
|
||||
{
|
||||
$tokenText = md5(mt_rand() . uniqid());
|
||||
}
|
||||
while (R::findOne('usertoken', 'token = ?', [$tokenText]) !== null);
|
||||
$token = R::dispense('usertoken');
|
||||
$token->user = $user;
|
||||
$token->token = $tokenText;
|
||||
$token = TokenModel::spawn();
|
||||
$token->setUser($user);
|
||||
$token->token = TokenModel::forgeUnusedToken();
|
||||
$token->used = false;
|
||||
$token->expires = null;
|
||||
R::store($token);
|
||||
TokenModel::save($token);
|
||||
|
||||
\Chibi\Registry::getContext()->mailSent = true;
|
||||
$tokens = [];
|
||||
$tokens['host'] = $_SERVER['HTTP_HOST'];
|
||||
$tokens['token'] = $tokenText;
|
||||
$tokens['token'] = $token->token; //gosh this code looks so silly
|
||||
$tokens['nl'] = PHP_EOL;
|
||||
if ($linkActionName !== null)
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', $linkActionName, ['token' => $tokenText]);
|
||||
$tokens['link'] = \Chibi\UrlHelper::route('user', $linkActionName, ['token' => $token->token]);
|
||||
|
||||
$body = wordwrap(TextHelper::replaceTokens($body, $tokens), 70);
|
||||
$subject = TextHelper::replaceTokens($subject, $tokens);
|
||||
|
@ -73,8 +68,8 @@ class UserController
|
|||
$regConfig = \Chibi\Registry::getConfig()->registration;
|
||||
if (!$regConfig->confirmationEmailEnabled)
|
||||
{
|
||||
$user->email_confirmed = $user->email_unconfirmed;
|
||||
$user->email_unconfirmed = null;
|
||||
$user->emailConfirmed = $user->emailUnconfirmed;
|
||||
$user->emailUnconfirmed = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -84,7 +79,7 @@ class UserController
|
|||
$regConfig->confirmationEmailSubject,
|
||||
$regConfig->confirmationEmailSenderName,
|
||||
$regConfig->confirmationEmailSenderEmail,
|
||||
$user->email_unconfirmed,
|
||||
$user->emailUnconfirmed,
|
||||
'activation');
|
||||
}
|
||||
|
||||
|
@ -98,7 +93,7 @@ class UserController
|
|||
$regConfig->passwordResetEmailSubject,
|
||||
$regConfig->passwordResetEmailSenderName,
|
||||
$regConfig->passwordResetEmailSenderEmail,
|
||||
$user->email_confirmed,
|
||||
$user->emailConfirmed,
|
||||
'password-reset');
|
||||
}
|
||||
|
||||
|
@ -130,7 +125,8 @@ class UserController
|
|||
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
|
||||
|
||||
$page = max(1, $page);
|
||||
list ($users, $userCount) = Model_User::getEntitiesWithCount($sortStyle, $usersPerPage, $page);
|
||||
$users = UserSearchService::getEntities($sortStyle, $usersPerPage, $page);
|
||||
$userCount = UserSearchService::getEntityCount($sortStyle, $usersPerPage, $page);
|
||||
$pageCount = ceil($userCount / $usersPerPage);
|
||||
|
||||
$this->context->sortStyle = $sortStyle;
|
||||
|
@ -151,7 +147,7 @@ class UserController
|
|||
*/
|
||||
public function flagAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::FlagUser);
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
|
@ -177,13 +173,13 @@ class UserController
|
|||
*/
|
||||
public function banAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = true;
|
||||
Model_User::save($user);
|
||||
UserModel::save($user);
|
||||
|
||||
LogHelper::log('{user} banned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
|
@ -198,13 +194,13 @@ class UserController
|
|||
*/
|
||||
public function unbanAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::BanUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->banned = false;
|
||||
Model_User::save($user);
|
||||
UserModel::save($user);
|
||||
|
||||
LogHelper::log('{user} unbanned {subject}', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
|
@ -219,12 +215,12 @@ class UserController
|
|||
*/
|
||||
public function acceptRegistrationAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::AcceptUserRegistration);
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$user->staff_confirmed = true;
|
||||
Model_User::save($user);
|
||||
$user->staffConfirmed = true;
|
||||
UserModel::save($user);
|
||||
LogHelper::log('{user} confirmed {subject}\'s account', ['subject' => TextHelper::reprUser($user)]);
|
||||
StatusHelper::success();
|
||||
}
|
||||
|
@ -238,7 +234,7 @@ class UserController
|
|||
*/
|
||||
public function deleteAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
PrivilegesHelper::confirmWithException(Privilege::DeleteUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
|
@ -252,13 +248,13 @@ class UserController
|
|||
$name = $user->name;
|
||||
if ($this->context->user->id == $user->id)
|
||||
{
|
||||
$suppliedPasswordHash = Model_User::hashPassword($suppliedCurrentPassword, $user->pass_salt);
|
||||
if ($suppliedPasswordHash != $user->pass_hash)
|
||||
$suppliedPasswordHash = UserModel::hashPassword($suppliedCurrentPassword, $user->passSalt);
|
||||
if ($suppliedPasswordHash != $user->passHash)
|
||||
throw new SimpleException('Must supply valid password');
|
||||
}
|
||||
|
||||
$oldId = $user->id;
|
||||
Model_User::remove($user);
|
||||
UserModel::remove($user);
|
||||
if ($oldId == $this->context->user->id)
|
||||
AuthController::doLogOut();
|
||||
|
||||
|
@ -276,7 +272,7 @@ class UserController
|
|||
*/
|
||||
public function settingsAction($name)
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserSettings, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
|
@ -295,7 +291,8 @@ class UserController
|
|||
$user->enablePostTagTitles(InputHelper::get('post-tag-titles'));
|
||||
$user->enableHidingDislikedPosts(InputHelper::get('hide-disliked-posts'));
|
||||
|
||||
Model_User::save($user);
|
||||
if ($user->accessRank != AccessRank::Anonymous)
|
||||
UserModel::save($user);
|
||||
if ($user->id == $this->context->user->id)
|
||||
$this->context->user = $user;
|
||||
AuthController::doReLog();
|
||||
|
@ -313,7 +310,7 @@ class UserController
|
|||
{
|
||||
try
|
||||
{
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
PrivilegesHelper::confirmWithException(Privilege::ViewUser, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
|
||||
$this->loadUserView($user);
|
||||
|
@ -325,7 +322,7 @@ class UserController
|
|||
$this->context->suppliedPassword2 = $suppliedPassword2 = InputHelper::get('password2');
|
||||
$this->context->suppliedEmail = $suppliedEmail = InputHelper::get('email');
|
||||
$this->context->suppliedAccessRank = $suppliedAccessRank = InputHelper::get('access-rank');
|
||||
$currentPasswordHash = $user->pass_hash;
|
||||
$currentPasswordHash = $user->passHash;
|
||||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
|
@ -335,7 +332,7 @@ class UserController
|
|||
if ($suppliedName != '' and $suppliedName != $user->name)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserName, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$suppliedName = Model_User::validateUserName($suppliedName);
|
||||
$suppliedName = UserModel::validateUserName($suppliedName);
|
||||
$oldName = $user->name;
|
||||
$user->name = $suppliedName;
|
||||
LogHelper::log('{user} renamed {old} to {new}', ['old' => TextHelper::reprUser($oldName), 'new' => TextHelper::reprUser($suppliedName)]);
|
||||
|
@ -346,45 +343,45 @@ class UserController
|
|||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserPassword, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
if ($suppliedPassword1 != $suppliedPassword2)
|
||||
throw new SimpleException('Specified passwords must be the same');
|
||||
$suppliedPassword = Model_User::validatePassword($suppliedPassword1);
|
||||
$user->pass_hash = Model_User::hashPassword($suppliedPassword, $user->pass_salt);
|
||||
$suppliedPassword = UserModel::validatePassword($suppliedPassword1);
|
||||
$user->passHash = UserModel::hashPassword($suppliedPassword, $user->passSalt);
|
||||
LogHelper::log('{user} changed {subject}\'s password', ['subject' => TextHelper::reprUser($user)]);
|
||||
}
|
||||
|
||||
if ($suppliedEmail != '' and $suppliedEmail != $user->email_confirmed)
|
||||
if ($suppliedEmail != '' and $suppliedEmail != $user->emailConfirmed)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserEmail, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$suppliedEmail = Model_User::validateEmail($suppliedEmail);
|
||||
$suppliedEmail = UserModel::validateEmail($suppliedEmail);
|
||||
if ($this->context->user->id == $user->id)
|
||||
{
|
||||
$user->email_unconfirmed = $suppliedEmail;
|
||||
if (!empty($user->email_unconfirmed))
|
||||
$user->emailUnconfirmed = $suppliedEmail;
|
||||
if (!empty($user->emailUnconfirmed))
|
||||
$confirmMail = true;
|
||||
LogHelper::log('{user} changed e-mail to {mail}', ['mail' => $suppliedEmail]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$user->email_unconfirmed = null;
|
||||
$user->email_confirmed = $suppliedEmail;
|
||||
$user->emailUnconfirmed = null;
|
||||
$user->emailConfirmed = $suppliedEmail;
|
||||
LogHelper::log('{user} changed {subject}\'s e-mail to {mail}', ['subject' => TextHelper::reprUser($user), 'mail' => $suppliedEmail]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($suppliedAccessRank != '' and $suppliedAccessRank != $user->access_rank)
|
||||
if ($suppliedAccessRank != '' and $suppliedAccessRank != $user->accessRank)
|
||||
{
|
||||
PrivilegesHelper::confirmWithException(Privilege::ChangeUserAccessRank, PrivilegesHelper::getIdentitySubPrivilege($user));
|
||||
$suppliedAccessRank = Model_User::validateAccessRank($suppliedAccessRank);
|
||||
$user->access_rank = $suppliedAccessRank;
|
||||
$suppliedAccessRank = UserModel::validateAccessRank($suppliedAccessRank);
|
||||
$user->accessRank = $suppliedAccessRank;
|
||||
LogHelper::log('{user} changed {subject}\'s access rank to {rank}', ['subject' => TextHelper::reprUser($user), 'rank' => AccessRank::toString($suppliedAccessRank)]);
|
||||
}
|
||||
|
||||
if ($this->context->user->id == $user->id)
|
||||
{
|
||||
$suppliedPasswordHash = Model_User::hashPassword($suppliedCurrentPassword, $user->pass_salt);
|
||||
$suppliedPasswordHash = UserModel::hashPassword($suppliedCurrentPassword, $user->passSalt);
|
||||
if ($suppliedPasswordHash != $currentPasswordHash)
|
||||
throw new SimpleException('Must supply valid current password');
|
||||
}
|
||||
Model_User::save($user);
|
||||
UserModel::save($user);
|
||||
|
||||
if ($confirmMail)
|
||||
self::sendEmailChangeConfirmation($user);
|
||||
|
@ -398,7 +395,7 @@ class UserController
|
|||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->context->transport->user = Model_User::locate($name);
|
||||
$this->context->transport->user = UserModel::findByNameOrEmail($name);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
@ -406,16 +403,16 @@ class UserController
|
|||
|
||||
|
||||
/**
|
||||
* @route /user/{name}
|
||||
* @route /user/{name}/{tab}
|
||||
* @route /user/{name}/{tab}/{page}
|
||||
* @validate name [^\/]+
|
||||
* @validate tab favs|uploads
|
||||
* @validate page \d*
|
||||
*/
|
||||
public function viewAction($name, $tab, $page)
|
||||
public function viewAction($name, $tab = 'favs', $page)
|
||||
{
|
||||
$postsPerPage = intval($this->config->browsing->postsPerPage);
|
||||
$user = Model_User::locate($name);
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
if ($tab === null)
|
||||
$tab = 'favs';
|
||||
if ($page === null)
|
||||
|
@ -439,9 +436,10 @@ class UserController
|
|||
throw new SimpleException('Wrong tab');
|
||||
|
||||
$page = max(1, $page);
|
||||
list ($posts, $postCount) = Model_Post::getEntitiesWithCount($query, $postsPerPage, $page);
|
||||
$posts = PostSearchService::getEntities($query, $postsPerPage, $page);
|
||||
$postCount = PostSearchService::getEntityCount($query, $postsPerPage, $page);
|
||||
$pageCount = ceil($postCount / $postsPerPage);
|
||||
Model_Post::attachTags($posts);
|
||||
PostModel::preloadTags($posts);
|
||||
|
||||
$this->context->transport->tab = $tab;
|
||||
$this->context->transport->lastSearchQuery = $query;
|
||||
|
@ -468,9 +466,9 @@ class UserController
|
|||
$this->context->user->enableSafety($safety,
|
||||
!$this->context->user->hasEnabledSafety($safety));
|
||||
|
||||
if ($this->context->user->accessRank != AccessRank::Anonymous)
|
||||
UserModel::save($this->context->user);
|
||||
AuthController::doReLog();
|
||||
if (!$this->context->user->anonymous)
|
||||
Model_User::save($this->context->user);
|
||||
|
||||
StatusHelper::success();
|
||||
}
|
||||
|
@ -504,42 +502,42 @@ class UserController
|
|||
|
||||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$suppliedName = Model_User::validateUserName($suppliedName);
|
||||
$suppliedName = UserModel::validateUserName($suppliedName);
|
||||
|
||||
if ($suppliedPassword1 != $suppliedPassword2)
|
||||
throw new SimpleException('Specified passwords must be the same');
|
||||
$suppliedPassword = Model_User::validatePassword($suppliedPassword1);
|
||||
$suppliedPassword = UserModel::validatePassword($suppliedPassword1);
|
||||
|
||||
$suppliedEmail = Model_User::validateEmail($suppliedEmail);
|
||||
$suppliedEmail = UserModel::validateEmail($suppliedEmail);
|
||||
if (empty($suppliedEmail) and $this->config->registration->needEmailForRegistering)
|
||||
throw new SimpleException('E-mail address is required - you will be sent confirmation e-mail.');
|
||||
|
||||
//register the user
|
||||
$dbUser = Model_User::create();
|
||||
$dbUser = UserModel::spawn();
|
||||
$dbUser->name = $suppliedName;
|
||||
$dbUser->pass_hash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
|
||||
$dbUser->email_unconfirmed = $suppliedEmail;
|
||||
$dbUser->passHash = UserModel::hashPassword($suppliedPassword, $dbUser->passSalt);
|
||||
$dbUser->emailUnconfirmed = $suppliedEmail;
|
||||
|
||||
$dbUser->join_date = time();
|
||||
if (R::findOne('user') === null)
|
||||
$dbUser->joinDate = time();
|
||||
if (UserModel::getCount() == 0)
|
||||
{
|
||||
//very first user
|
||||
$dbUser->access_rank = AccessRank::Admin;
|
||||
$dbUser->staff_confirmed = true;
|
||||
$dbUser->email_unconfirmed = null;
|
||||
$dbUser->email_confirmed = $suppliedEmail;
|
||||
$dbUser->accessRank = AccessRank::Admin;
|
||||
$dbUser->staffConfirmed = true;
|
||||
$dbUser->emailUnconfirmed = null;
|
||||
$dbUser->emailConfirmed = $suppliedEmail;
|
||||
}
|
||||
else
|
||||
{
|
||||
$dbUser->access_rank = AccessRank::Registered;
|
||||
$dbUser->staff_confirmed = false;
|
||||
$dbUser->staff_confirmed = null;
|
||||
$dbUser->accessRank = AccessRank::Registered;
|
||||
$dbUser->staffConfirmed = false;
|
||||
$dbUser->staffConfirmed = null;
|
||||
}
|
||||
|
||||
//save the user to db if everything went okay
|
||||
Model_User::save($dbUser);
|
||||
UserModel::save($dbUser);
|
||||
|
||||
if (!empty($dbUser->email_unconfirmed))
|
||||
if (!empty($dbUser->emailUnconfirmed))
|
||||
self::sendEmailChangeConfirmation($dbUser);
|
||||
|
||||
$message = 'Congratulations, your account was created.';
|
||||
|
@ -573,14 +571,15 @@ class UserController
|
|||
$this->context->subTitle = 'account activation';
|
||||
$this->context->viewName = 'message';
|
||||
|
||||
$dbToken = Model_Token::locate($token);
|
||||
$dbToken = TokenModel::findByToken($token);
|
||||
TokenModel::checkValidity($dbToken);
|
||||
|
||||
$dbUser = $dbToken->user;
|
||||
$dbUser->email_confirmed = $dbUser->email_unconfirmed;
|
||||
$dbUser->email_unconfirmed = null;
|
||||
$dbUser = $dbToken->getUser();
|
||||
$dbUser->emailConfirmed = $dbUser->emailUnconfirmed;
|
||||
$dbUser->emailUnconfirmed = null;
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
Model_User::save($dbUser);
|
||||
TokenModel::save($dbToken);
|
||||
UserModel::save($dbUser);
|
||||
|
||||
LogHelper::log('{subject} just activated account', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
$message = 'Activation completed successfully.';
|
||||
|
@ -605,7 +604,8 @@ class UserController
|
|||
$this->context->subTitle = 'password reset';
|
||||
$this->context->viewName = 'message';
|
||||
|
||||
$dbToken = Model_Token::locate($token);
|
||||
$dbToken = TokenModel::findByToken($token);
|
||||
TokenModel::checkValidity($dbToken);
|
||||
|
||||
$alphabet = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9'));
|
||||
$randomPassword = join('', array_map(function($x) use ($alphabet)
|
||||
|
@ -613,11 +613,11 @@ class UserController
|
|||
return $alphabet[$x];
|
||||
}, array_rand($alphabet, 8)));
|
||||
|
||||
$dbUser = $dbToken->user;
|
||||
$dbUser->pass_hash = Model_User::hashPassword($randomPassword, $dbUser->pass_salt);
|
||||
$dbUser = $dbToken->getUser();
|
||||
$dbUser->passHash = UserModel::hashPassword($randomPassword, $dbUser->passSalt);
|
||||
$dbToken->used = true;
|
||||
R::store($dbToken);
|
||||
Model_User::save($dbUser);
|
||||
TokenModel::save($dbToken);
|
||||
UserModel::save($dbUser);
|
||||
|
||||
LogHelper::log('{subject} just reset password', ['subject' => TextHelper::reprUser($dbUser)]);
|
||||
$message = 'Password reset successful. Your new password is **' . $randomPassword . '**.';
|
||||
|
@ -642,8 +642,8 @@ class UserController
|
|||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = InputHelper::get('name');
|
||||
$user = Model_User::locate($name);
|
||||
if (empty($user->email_confirmed))
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
if (empty($user->emailConfirmed))
|
||||
throw new SimpleException('This user has no e-mail confirmed; password reset cannot proceed');
|
||||
|
||||
self::sendPasswordResetConfirmation($user);
|
||||
|
@ -663,10 +663,10 @@ class UserController
|
|||
if (InputHelper::get('submit'))
|
||||
{
|
||||
$name = InputHelper::get('name');
|
||||
$user = Model_User::locate($name);
|
||||
if (empty($user->email_unconfirmed))
|
||||
$user = UserModel::findByNameOrEmail($name);
|
||||
if (empty($user->emailUnconfirmed))
|
||||
{
|
||||
if (!empty($user->email_confirmed))
|
||||
if (!empty($user->emailConfirmed))
|
||||
throw new SimpleException('E-mail was already confirmed; activation skipped');
|
||||
else
|
||||
throw new SimpleException('This user has no e-mail specified; activation cannot proceed');
|
||||
|
|
110
src/Database.php
Normal file
110
src/Database.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
class Database
|
||||
{
|
||||
protected static $pdo = null;
|
||||
protected static $queries = [];
|
||||
|
||||
public static function connect($driver, $location, $user, $pass)
|
||||
{
|
||||
if (self::connected())
|
||||
throw new Exception('Database is already connected');
|
||||
|
||||
$dsn = $driver . ':' . $location;
|
||||
try
|
||||
{
|
||||
self::$pdo = new PDO($dsn, $user, $pass);
|
||||
self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
self::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
self::$pdo = null;
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public static function makeStatement(SqlQuery $sqlQuery)
|
||||
{
|
||||
try
|
||||
{
|
||||
$stmt = self::$pdo->prepare($sqlQuery->getSql());
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw new Exception('Problem with ' . $sqlQuery->getSql() . ' (' . $e->getMessage() . ')');
|
||||
}
|
||||
foreach ($sqlQuery->getBindings() as $key => $value)
|
||||
$stmt->bindValue(is_numeric($key) ? $key + 1 : $key, $value);
|
||||
return $stmt;
|
||||
}
|
||||
|
||||
public static function disconnect()
|
||||
{
|
||||
self::$pdo = null;
|
||||
}
|
||||
|
||||
public static function connected()
|
||||
{
|
||||
return self::$pdo !== null;
|
||||
}
|
||||
|
||||
public static function query(SqlQuery $sqlQuery)
|
||||
{
|
||||
if (!self::connected())
|
||||
throw new Exception('Database is not connected');
|
||||
$statement = self::makeStatement($sqlQuery);
|
||||
$statement->execute();
|
||||
self::$queries []= $sqlQuery;
|
||||
return $statement;
|
||||
}
|
||||
|
||||
public static function fetchOne(SqlQuery $sqlQuery)
|
||||
{
|
||||
$statement = self::query($sqlQuery);
|
||||
return $statement->fetch();
|
||||
}
|
||||
|
||||
public static function fetchAll(SqlQuery $sqlQuery)
|
||||
{
|
||||
$statement = self::query($sqlQuery);
|
||||
return $statement->fetchAll();
|
||||
}
|
||||
|
||||
public static function getLogs()
|
||||
{
|
||||
return self::$queries;
|
||||
}
|
||||
|
||||
public static function inTransaction()
|
||||
{
|
||||
return self::$pdo->inTransaction();
|
||||
}
|
||||
|
||||
public static function lastInsertId()
|
||||
{
|
||||
return self::$pdo->lastInsertId();
|
||||
}
|
||||
|
||||
public static function transaction($func)
|
||||
{
|
||||
if (self::inTransaction())
|
||||
{
|
||||
return $func();
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
self::$pdo->beginTransaction();
|
||||
$ret = $func();
|
||||
self::$pdo->commit();
|
||||
return $ret;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
self::$pdo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ class LogEvent
|
|||
$this->ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
|
||||
|
||||
$context = \Chibi\Registry::getContext();
|
||||
$tokens['anon'] = Model_User::getAnonymousName();
|
||||
$tokens['anon'] = UserModel::getAnonymousName();
|
||||
if ($context->loggedIn and isset($context->user))
|
||||
$tokens['user'] = TextHelper::reprUser($context->user->name);
|
||||
else
|
||||
|
|
|
@ -42,7 +42,7 @@ class PrivilegesHelper
|
|||
}
|
||||
}
|
||||
|
||||
return intval($user->access_rank) >= $minAccessRank;
|
||||
return intval($user->accessRank) >= $minAccessRank;
|
||||
}
|
||||
|
||||
public static function confirmWithException($privilege, $subPrivilege = null)
|
||||
|
@ -63,7 +63,7 @@ class PrivilegesHelper
|
|||
|
||||
public static function confirmEmail($user)
|
||||
{
|
||||
if (!$user->email_confirmed)
|
||||
if (!$user->emailConfirmed)
|
||||
throw new SimpleException('Need e-mail address confirmation to continue');
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,18 @@ class TextHelper
|
|||
return $text;
|
||||
}
|
||||
|
||||
//todo: convert to enum and make one method
|
||||
public static function snakeCaseToCamelCase($string, $lower = false)
|
||||
{
|
||||
$string = preg_split('/_/', $string);
|
||||
$string = array_map('trim', $string);
|
||||
$string = array_map('ucfirst', $string);
|
||||
$string = join('', $string);
|
||||
if ($lower)
|
||||
$string = lcfirst($string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function kebabCaseToCamelCase($string)
|
||||
{
|
||||
$string = preg_split('/-/', $string);
|
||||
|
@ -152,13 +164,9 @@ class TextHelper
|
|||
|
||||
foreach ($obj as $key => $val)
|
||||
{
|
||||
if ($val instanceof RedBean_OODBBean)
|
||||
if ($val instanceof Exception)
|
||||
{
|
||||
$set($key, R::exportAll($val));
|
||||
}
|
||||
elseif ($val instanceof Exception)
|
||||
{
|
||||
$set($key, ['message' => $val->getMessage(), 'trace' => $val->getTraceAsString()]);
|
||||
$set($key, ['message' => $val->getMessage(), 'trace' => explode("\n", $val->getTraceAsString())]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
136
src/Models/AbstractCrudModel.php
Normal file
136
src/Models/AbstractCrudModel.php
Normal file
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
abstract class AbstractCrudModel implements IModel
|
||||
{
|
||||
public static function spawn()
|
||||
{
|
||||
$entityClassName = static::getEntityClassName();
|
||||
return new $entityClassName();
|
||||
}
|
||||
|
||||
public static function remove($entities)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static function save($entity)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function findById($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from(static::getTableName())
|
||||
->where('id = ?')->put($key);
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid ' . static::getTableName() . ' ID "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function findByIds(array $ids)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from(static::getTableName())
|
||||
->where('id')->in()->genSlots($ids)->put($ids);
|
||||
|
||||
$rows = Database::fetchAll($query);
|
||||
if ($rows)
|
||||
return self::convertRows($rows);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function getCount()
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query->select('count(1)')->as('count')->from(static::getTableName());
|
||||
return Database::fetchOne($query)['count'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static function getEntityClassName()
|
||||
{
|
||||
$modelClassName = get_called_class();
|
||||
$entityClassName = str_replace('Model', 'Entity', $modelClassName);
|
||||
return $entityClassName;
|
||||
}
|
||||
|
||||
public static function convertRow($row)
|
||||
{
|
||||
$entity = self::spawn();
|
||||
foreach ($row as $key => $val)
|
||||
{
|
||||
$key = TextHelper::snakeCaseToCamelCase($key, true);
|
||||
$entity->$key = $val;
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public static function convertRows(array $rows)
|
||||
{
|
||||
foreach ($rows as $i => $row)
|
||||
$rows[$i] = self::convertRow($row);
|
||||
return $rows;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function forgeId($entity)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
if (!Database::inTransaction())
|
||||
throw new Exception('Can be run only within transaction');
|
||||
if (!$entity->id)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
$query = (new SqlQuery);
|
||||
if ($config->main->dbDriver == 'sqlite')
|
||||
$query->insertInto($table)->defaultValues();
|
||||
else
|
||||
$query->insertInto($table)->values()->open()->close();
|
||||
Database::query($query);
|
||||
$entity->id = Database::lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
public static function preloadOneToMany($entities,
|
||||
$foreignEntityLocalSelector,
|
||||
$foreignEntityForeignSelector,
|
||||
$foreignEntityProcessor,
|
||||
$foreignEntitySetter)
|
||||
{
|
||||
if (empty($entities))
|
||||
return;
|
||||
|
||||
$foreignIds = [];
|
||||
$entityMap = [];
|
||||
foreach ($entities as $entity)
|
||||
{
|
||||
$foreignId = $foreignEntityLocalSelector($entity);
|
||||
if (!isset($entityMap[$foreignId]))
|
||||
$entityMap[$foreignId] = [];
|
||||
$entityMap[$foreignId] []= $entity;
|
||||
$foreignIds []= $foreignId;
|
||||
}
|
||||
|
||||
$foreignEntities = $foreignEntityProcessor($foreignIds);
|
||||
|
||||
foreach ($foreignEntities as $foreignEntity)
|
||||
{
|
||||
$key = $foreignEntityForeignSelector($foreignEntity);
|
||||
foreach ($entityMap[$key] as $entity)
|
||||
$foreignEntitySetter($entity, $foreignEntity);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
<?php
|
||||
abstract class AbstractModel extends RedBean_SimpleModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
throw new SimpleException('Not implemented.');
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
throw new SimpleException('Not implemented.');
|
||||
}
|
||||
|
||||
public static function getEntitiesRows($query, $perPage = null, $page = 1)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$dbQuery = R::$f->getNew()->begin();
|
||||
$dbQuery->select($table . '.*');
|
||||
$builder = static::getQueryBuilder();
|
||||
if ($builder)
|
||||
$builder::build($dbQuery, $query);
|
||||
else
|
||||
$dbQuery->from($table);
|
||||
if ($perPage !== null)
|
||||
{
|
||||
$dbQuery->limit('?')->put($perPage);
|
||||
$dbQuery->offset('?')->put(($page - 1) * $perPage);
|
||||
}
|
||||
|
||||
$rows = $dbQuery->get();
|
||||
return $rows;
|
||||
}
|
||||
|
||||
protected static function convertRows($rows, $table, $fast = false)
|
||||
{
|
||||
if (empty($rows))
|
||||
return [];
|
||||
|
||||
if (!$fast)
|
||||
return R::convertToBeans($table, $rows);
|
||||
|
||||
$entities = R::dispense($table, count($rows));
|
||||
if (count($rows) == 1)
|
||||
$entities = [$entities];
|
||||
$entity = reset($entities);
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$entity->import($row);
|
||||
$entity = next($entities);
|
||||
}
|
||||
reset($entities);
|
||||
return $entities;
|
||||
}
|
||||
|
||||
public static function getEntities($query, $perPage = null, $page = 1, $fast = false)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$rows = self::getEntitiesRows($query, $perPage, $page);
|
||||
$entities = self::convertRows($rows, $table, $fast);
|
||||
return $entities;
|
||||
}
|
||||
|
||||
public static function getEntityCount($query)
|
||||
{
|
||||
$table = static::getTableName();
|
||||
$dbQuery = R::$f->getNew()->begin();
|
||||
$dbQuery->select('COUNT(1)')->as('count');
|
||||
$builder = static::getQueryBuilder();
|
||||
if ($builder)
|
||||
$builder::build($dbQuery, $query);
|
||||
else
|
||||
$dbQuery->from($table);
|
||||
$ret = intval($dbQuery->get('row')['count']);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function getEntitiesWithCount($query, $perPage = null, $page = 1)
|
||||
{
|
||||
return
|
||||
[
|
||||
self::getEntities($query, $perPage, $page, true),
|
||||
self::getEntityCount($query)
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
<?php
|
||||
interface AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query);
|
||||
}
|
101
src/Models/CommentModel.php
Normal file
101
src/Models/CommentModel.php
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
class CommentModel extends AbstractCrudModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
public static function spawn()
|
||||
{
|
||||
$comment = new CommentEntity;
|
||||
$comment->commentDate = time();
|
||||
return $comment;
|
||||
}
|
||||
|
||||
public static function save($comment)
|
||||
{
|
||||
Database::transaction(function() use ($comment)
|
||||
{
|
||||
self::forgeId($comment);
|
||||
|
||||
$bindings = [
|
||||
'text' => $comment->text,
|
||||
'post_id' => $comment->postId,
|
||||
'comment_date' => $comment->commentDate,
|
||||
'commenter_id' => $comment->commenterId];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('comment')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($comment->id);
|
||||
|
||||
Database::query($query);
|
||||
});
|
||||
}
|
||||
|
||||
public static function remove($comment)
|
||||
{
|
||||
Database::transaction(function() use ($comment)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('comment')
|
||||
->where('id = ?')->put($comment->id);
|
||||
Database::query($query);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function findAllByPostId($key)
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query
|
||||
->select('comment.*')
|
||||
->from('comment')
|
||||
->where('post_id = ?')
|
||||
->put($key);
|
||||
|
||||
$rows = Database::fetchAll($query);
|
||||
if ($rows)
|
||||
return self::convertRows($rows);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function preloadCommenters($comments)
|
||||
{
|
||||
self::preloadOneToMany($comments,
|
||||
function($comment) { return $comment->commenterId; },
|
||||
function($user) { return $user->id; },
|
||||
function($userIds) { return UserModel::findByIds($userIds); },
|
||||
function($comment, $user) { return $comment->setCache('commenter', $user); });
|
||||
}
|
||||
|
||||
public static function preloadPosts($comments)
|
||||
{
|
||||
self::preloadOneToMany($comments,
|
||||
function($comment) { return $comment->postId; },
|
||||
function($post) { return $post->id; },
|
||||
function($postIds) { return PostModel::findByIds($postIds); },
|
||||
function($comment, $post) { $comment->setCache('post', $post); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateText($text)
|
||||
{
|
||||
$text = trim($text);
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
if (strlen($text) < $config->comments->minLength)
|
||||
throw new SimpleException(sprintf('Comment must have at least %d characters', $config->comments->minLength));
|
||||
|
||||
if (strlen($text) > $config->comments->maxLength)
|
||||
throw new SimpleException(sprintf('Comment must have at most %d characters', $config->comments->maxLength));
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
23
src/Models/Entities/AbstractEntity.php
Normal file
23
src/Models/Entities/AbstractEntity.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
class AbstractEntity
|
||||
{
|
||||
public $id;
|
||||
protected $__cache;
|
||||
|
||||
public function setCache($key, $value)
|
||||
{
|
||||
$this->__cache[$key] = $value;
|
||||
}
|
||||
|
||||
public function getCache($key)
|
||||
{
|
||||
return isset($this->__cache[$key])
|
||||
? $this->__cache[$key]
|
||||
: null;
|
||||
}
|
||||
|
||||
public function hasCache($key)
|
||||
{
|
||||
return isset($this->__cache[$key]);
|
||||
}
|
||||
}
|
43
src/Models/Entities/CommentEntity.php
Normal file
43
src/Models/Entities/CommentEntity.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
class CommentEntity extends AbstractEntity
|
||||
{
|
||||
public $text;
|
||||
public $postId;
|
||||
public $commentDate;
|
||||
public $commenterId;
|
||||
|
||||
public function getText()
|
||||
{
|
||||
return TextHelper::parseMarkdown($this->text);
|
||||
}
|
||||
|
||||
public function setPost($post)
|
||||
{
|
||||
$this->setCache('post', $post);
|
||||
$this->postId = $post->id;
|
||||
}
|
||||
|
||||
public function setCommenter($user)
|
||||
{
|
||||
$this->setCache('commenter', $user);
|
||||
$this->commenterId = $user ? $user->id : null;
|
||||
}
|
||||
|
||||
public function getPost()
|
||||
{
|
||||
if ($this->hasCache('post'))
|
||||
return $this->getCache('post');
|
||||
$post = PostModel::findById($this->postId);
|
||||
$this->setCache('post', $post);
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getCommenter()
|
||||
{
|
||||
if ($this->hasCache('commenter'))
|
||||
return $this->getCache('commenter');
|
||||
$user = UserModel::findById($this->commenterId, false);
|
||||
$this->setCache('commenter', $user);
|
||||
return $user;
|
||||
}
|
||||
}
|
423
src/Models/Entities/PostEntity.php
Normal file
423
src/Models/Entities/PostEntity.php
Normal file
|
@ -0,0 +1,423 @@
|
|||
<?php
|
||||
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 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');
|
||||
$query = (new SqlQuery)
|
||||
->select('user.*')
|
||||
->from('user')
|
||||
->innerJoin('favoritee')->on('favoritee.user_id = user.id')
|
||||
->where('favoritee.post_id = ?')->put($this->id);
|
||||
$rows = Database::fetchAll($query);
|
||||
$favorites = UserModel::convertRows($rows);
|
||||
$this->setCache('favoritee', $favorites);
|
||||
return $favorites;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getRelations()
|
||||
{
|
||||
if ($this->hasCache('relations'))
|
||||
return $this->getCache('relations');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->select('post.*')
|
||||
->from('post')
|
||||
->innerJoin('crossref')
|
||||
->on()->open()->raw('post.id = crossref.post2_id')->and('crossref.post_id = :id')->close()
|
||||
->or()->open()->raw('post.id = crossref.post_id')->and('crossref.post2_id = :id')->close()
|
||||
->put(['id' => $this->id]);
|
||||
$rows = Database::fetchAll($query);
|
||||
$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 = \Chibi\Registry::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: ' . $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 = \Chibi\Registry::getConfig();
|
||||
|
||||
$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 != $config->browsing->thumbWidth)
|
||||
throw new SimpleException('Invalid thumbnail width (should be ' . $config->browsing->thumbWidth . ')');
|
||||
if ($imageHeight != $config->browsing->thumbHeight)
|
||||
throw new SimpleException('Invalid thumbnail height (should be ' . $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->origName . '/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;
|
||||
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;
|
||||
|
||||
$config = \Chibi\Registry::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;
|
||||
default:
|
||||
throw new SimpleException('Invalid file type "' . $this->mimeType . '"');
|
||||
}
|
||||
|
||||
$this->origName = basename($srcPath);
|
||||
$duplicatedPost = PostModel::findByHash($this->fileHash, false);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
throw new SimpleException('Duplicate upload: @' . $duplicatedPost->id);
|
||||
|
||||
$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)
|
||||
{
|
||||
$this->origName = $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->origName = $origName;
|
||||
$this->type = PostType::Youtube;
|
||||
$this->mimeType = null;
|
||||
$this->fileSize = null;
|
||||
$this->fileHash = $origName;
|
||||
$this->imageWidth = null;
|
||||
$this->imageHeight = null;
|
||||
|
||||
$thumbPath = $this->getThumbDefaultPath();
|
||||
if (file_exists($thumbPath))
|
||||
unlink($thumbPath);
|
||||
|
||||
$duplicatedPost = PostModel::findByHash($origName, false);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
14
src/Models/Entities/TagEntity.php
Normal file
14
src/Models/Entities/TagEntity.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
class TagEntity extends AbstractEntity
|
||||
{
|
||||
public $name;
|
||||
|
||||
public function getPostCount()
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('count(*)')->as('count')
|
||||
->from('post_tag')
|
||||
->where('tag_id = ?')->put($this->id);
|
||||
return Database::fetchOne($query)['count'];
|
||||
}
|
||||
}
|
18
src/Models/Entities/TokenEntity.php
Normal file
18
src/Models/Entities/TokenEntity.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
class TokenEntity extends AbstractEntity
|
||||
{
|
||||
public $userId;
|
||||
public $token;
|
||||
public $used;
|
||||
public $expires;
|
||||
|
||||
public function getUser()
|
||||
{
|
||||
return UserModel::findById($this->userId);
|
||||
}
|
||||
|
||||
public function setUser($user)
|
||||
{
|
||||
$this->userId = $user ? $user->id : null;
|
||||
}
|
||||
}
|
151
src/Models/Entities/UserEntity.php
Normal file
151
src/Models/Entities/UserEntity.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
class UserEntity extends AbstractEntity
|
||||
{
|
||||
public $name;
|
||||
public $passSalt;
|
||||
public $passHash;
|
||||
public $staffConfirmed;
|
||||
public $emailUnconfirmed;
|
||||
public $emailConfirmed;
|
||||
public $joinDate;
|
||||
public $accessRank;
|
||||
public $settings;
|
||||
public $banned;
|
||||
|
||||
public function getAvatarUrl($size = 32)
|
||||
{
|
||||
$subject = !empty($this->emailConfirmed)
|
||||
? $this->emailConfirmed
|
||||
: $this->passSalt . $this->name;
|
||||
$hash = md5(strtolower(trim($subject)));
|
||||
$url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getSetting($key)
|
||||
{
|
||||
$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(UserModel::SETTING_SAFETY);
|
||||
if (!$all)
|
||||
return $safety == PostSafety::Safe;
|
||||
return $all & PostSafety::toFlag($safety);
|
||||
}
|
||||
|
||||
public function enableSafety($safety, $enabled)
|
||||
{
|
||||
$all = $this->getSetting(UserModel::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(UserModel::SETTING_SAFETY, $new);
|
||||
}
|
||||
|
||||
public function hasEnabledHidingDislikedPosts()
|
||||
{
|
||||
$ret = $this->getSetting(UserModel::SETTING_HIDE_DISLIKED_POSTS);
|
||||
if ($ret === null)
|
||||
$ret = !\Chibi\Registry::getConfig()->browsing->showDislikedPostsDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enableHidingDislikedPosts($enabled)
|
||||
{
|
||||
$this->setSetting(UserModel::SETTING_HIDE_DISLIKED_POSTS, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
public function hasEnabledPostTagTitles()
|
||||
{
|
||||
$ret = $this->getSetting(UserModel::SETTING_POST_TAG_TITLES);
|
||||
if ($ret === null)
|
||||
$ret = \Chibi\Registry::getConfig()->browsing->showPostTagTitlesDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enablePostTagTitles($enabled)
|
||||
{
|
||||
$this->setSetting(UserModel::SETTING_POST_TAG_TITLES, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
public function hasEnabledEndlessScrolling()
|
||||
{
|
||||
$ret = $this->getSetting(UserModel::SETTING_ENDLESS_SCROLLING);
|
||||
if ($ret === null)
|
||||
$ret = \Chibi\Registry::getConfig()->browsing->endlessScrollingDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enableEndlessScrolling($enabled)
|
||||
{
|
||||
$this->setSetting(UserModel::SETTING_ENDLESS_SCROLLING, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
public function hasFavorited($post)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('count(1)')->as('count')
|
||||
->from('favoritee')
|
||||
->where('user_id = ?')->put($this->id)
|
||||
->and('post_id = ?')->put($post->id);
|
||||
return Database::fetchOne($query)['count'] == 1;
|
||||
}
|
||||
|
||||
public function getScore($post)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('score')
|
||||
->from('post_score')
|
||||
->where('user_id = ?')->put($this->id)
|
||||
->and('post_id = ?')->put($post->id);
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return intval($row['score']);
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getFavoriteCount()
|
||||
{
|
||||
$sqlQuery = (new SqlQuery)
|
||||
->select('count(1)')->as('count')
|
||||
->from('favoritee')
|
||||
->where('user_id = ?')->put($this->id);
|
||||
return Database::fetchOne($sqlQuery)['count'];
|
||||
}
|
||||
|
||||
public function getCommentCount()
|
||||
{
|
||||
$sqlQuery = (new SqlQuery)
|
||||
->select('count(1)')->as('count')
|
||||
->from('comment')
|
||||
->where('commenter_id = ?')->put($this->id);
|
||||
return Database::fetchOne($sqlQuery)['count'];
|
||||
}
|
||||
}
|
5
src/Models/IModel.php
Normal file
5
src/Models/IModel.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
interface IModel
|
||||
{
|
||||
static function getTableName();
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
<?php
|
||||
class Model_Comment extends AbstractModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
public static function getQueryBuilder()
|
||||
{
|
||||
return 'Model_Comment_QueryBuilder';
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
$comment = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
||||
if (!$comment)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid comment ID "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
return $comment;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateText($text)
|
||||
{
|
||||
$text = trim($text);
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
if (strlen($text) < $config->comments->minLength)
|
||||
throw new SimpleException(sprintf('Comment must have at least %d characters', $config->comments->minLength));
|
||||
|
||||
if (strlen($text) > $config->comments->maxLength)
|
||||
throw new SimpleException(sprintf('Comment must have at most %d characters', $config->comments->maxLength));
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function getText()
|
||||
{
|
||||
return TextHelper::parseMarkdown($this->text);
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<?php
|
||||
class Model_Comment_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$dbQuery
|
||||
->from('comment')
|
||||
->where('post_id')
|
||||
->is()->not('NULL')
|
||||
->orderBy('id')
|
||||
->desc();
|
||||
}
|
||||
}
|
|
@ -1,460 +0,0 @@
|
|||
<?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 static function locate($key, $disallowNumeric = false, $throw = true)
|
||||
{
|
||||
if (is_numeric($key) and !$disallowNumeric)
|
||||
{
|
||||
$post = R::findOne(self::getTableName(), 'id = ?', [$key]);
|
||||
if (!$post)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid post ID "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$post = R::findOne(self::getTableName(), 'name = ?', [$key]);
|
||||
if (!$post)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid post name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
if (!in_array($safety, PostSafety::getAll()))
|
||||
throw new SimpleException('Invalid safety type "' . $safety . '"');
|
||||
|
||||
return $safety;
|
||||
}
|
||||
|
||||
public static function validateSource($source)
|
||||
{
|
||||
$source = trim($source);
|
||||
|
||||
$maxLength = 200;
|
||||
if (strlen($source) > $maxLength)
|
||||
throw new SimpleException('Source must have at most ' . $maxLength . ' characters');
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
private static function validateThumbSize($width, $height)
|
||||
{
|
||||
$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 getAllPostCount()
|
||||
{
|
||||
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::absolutePath(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 TextHelper::absolutePath(self::$config->main->filesPath . DS . $name);
|
||||
}
|
||||
|
||||
public static function attachTags($posts)
|
||||
{
|
||||
//slow!!!
|
||||
//R::preload($posts, 'sharedTag|tag');
|
||||
$ids = array_map(function($x) { return $x->id; }, $posts);
|
||||
$sql = 'SELECT post_tag.post_id, tag.* FROM tag INNER JOIN post_tag ON post_tag.tag_id = tag.id WHERE post_id IN (' . R::genSlots($ids) . ')';
|
||||
$rows = R::getAll($sql, $ids);
|
||||
$postMap = array_fill_keys($ids, []);
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$postMap[$row['post_id']] []= $row;
|
||||
}
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
$tagRows = $postMap[$post->id];
|
||||
$tags = self::convertRows($tagRows, 'tag', true);
|
||||
$post->setProperty('sharedTag', $tags, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function isTaggedWith($tagName)
|
||||
{
|
||||
$tagName = trim(strtolower($tagName));
|
||||
foreach ($this->sharedTag as $tag)
|
||||
if (trim(strtolower($tag->name)) == $tagName)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasCustomThumb($width = null, $height = null)
|
||||
{
|
||||
$thumbPath = self::getThumbCustomPath($this->name, $width, $height);
|
||||
return file_exists($thumbPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
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->file_size = filesize($srcPath);
|
||||
$this->file_hash = md5_file($srcPath);
|
||||
|
||||
if ($this->file_size == 0)
|
||||
throw new SimpleException('Specified file is empty');
|
||||
|
||||
$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]);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
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);
|
||||
|
||||
$thumbPath = self::getThumbDefaultPath($this->name);
|
||||
if (file_exists($thumbPath))
|
||||
unlink($thumbPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
|
||||
$thumbPath = self::getThumbDefaultPath($this->name);
|
||||
if (file_exists($thumbPath))
|
||||
unlink($thumbPath);
|
||||
|
||||
$duplicatedPost = R::findOne('post', 'orig_name = ?', [$origName]);
|
||||
if ($duplicatedPost !== null and (!$this->id or $this->id != $duplicatedPost->id))
|
||||
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();
|
|
@ -1,38 +0,0 @@
|
|||
<?php
|
||||
class Model_Property extends RedBean_SimpleModel
|
||||
{
|
||||
const FeaturedPostId = 0;
|
||||
const FeaturedPostUserName = 1;
|
||||
const FeaturedPostDate = 2;
|
||||
const DbVersion = 3;
|
||||
|
||||
static $allProperties = null;
|
||||
|
||||
public static function get($propertyId)
|
||||
{
|
||||
if (self::$allProperties === null)
|
||||
{
|
||||
self::$allProperties = [];
|
||||
foreach (R::find('property') as $prop)
|
||||
{
|
||||
self::$allProperties[$prop->prop_id] = $prop->value;
|
||||
}
|
||||
}
|
||||
return isset(self::$allProperties[$propertyId])
|
||||
? self::$allProperties[$propertyId]
|
||||
: null;
|
||||
}
|
||||
|
||||
public static function set($propertyId, $value)
|
||||
{
|
||||
$row = R::findOne('property', 'prop_id = ?', [$propertyId]);
|
||||
if (!$row)
|
||||
{
|
||||
$row = R::dispense('property');
|
||||
$row->prop_id = $propertyId;
|
||||
}
|
||||
$row->value = $value;
|
||||
self::$allProperties[$propertyId] = $value;
|
||||
R::store($row);
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
<?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]);
|
||||
if (!$tag)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid tag name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function removeUnused()
|
||||
{
|
||||
$dbQuery = R::$f
|
||||
->begin()
|
||||
->select('id, name')
|
||||
->from(self::getTableName())
|
||||
->where()
|
||||
->not()->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.tag_id = tag.id')
|
||||
->close();
|
||||
$rows = $dbQuery->get();
|
||||
$entities = R::convertToBeans(self::getTableName(), $rows);
|
||||
R::trashAll($entities);
|
||||
}
|
||||
|
||||
public static function insertOrUpdate($tags)
|
||||
{
|
||||
$dbTags = [];
|
||||
foreach ($tags as $tag)
|
||||
{
|
||||
$dbTag = self::locate($tag, false);
|
||||
if (!$dbTag)
|
||||
{
|
||||
$dbTag = R::dispense(self::getTableName());
|
||||
$dbTag->name = $tag;
|
||||
R::store($dbTag);
|
||||
}
|
||||
$dbTags []= $dbTag;
|
||||
}
|
||||
return $dbTags;
|
||||
}
|
||||
|
||||
public static function validateTag($tag)
|
||||
{
|
||||
$tag = trim($tag);
|
||||
|
||||
$minLength = 1;
|
||||
$maxLength = 64;
|
||||
if (strlen($tag) < $minLength)
|
||||
throw new SimpleException('Tag must have at least ' . $minLength . ' characters');
|
||||
if (strlen($tag) > $maxLength)
|
||||
throw new SimpleException('Tag must have at most ' . $maxLength . ' characters');
|
||||
|
||||
if (!preg_match('/^[()\[\]a-zA-Z0-9_.-]+$/i', $tag))
|
||||
throw new SimpleException('Invalid tag "' . $tag . '"');
|
||||
|
||||
if (preg_match('/^\.\.?$/', $tag))
|
||||
throw new SimpleException('Invalid tag "' . $tag . '"');
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public static function validateTags($tags)
|
||||
{
|
||||
$tags = trim($tags);
|
||||
$tags = preg_split('/[,;\s]+/', $tags);
|
||||
$tags = array_filter($tags, function($x) { return $x != ''; });
|
||||
$tags = array_unique($tags);
|
||||
|
||||
foreach ($tags as $key => $tag)
|
||||
$tags[$key] = self::validateTag($tag);
|
||||
|
||||
if (empty($tags))
|
||||
throw new SimpleException('No tags set');
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
public function getPostCount()
|
||||
{
|
||||
if ($this->bean->getMeta('post_count'))
|
||||
return $this->bean->getMeta('post_count');
|
||||
return $this->bean->countShared('post');
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
class Model_Token extends AbstractModel
|
||||
{
|
||||
public static function locate($key, $throw = true)
|
||||
{
|
||||
if (empty($key))
|
||||
throw new SimpleException('Invalid security token');
|
||||
|
||||
$token = R::findOne('usertoken', 'token = ?', [$key]);
|
||||
if ($token === null)
|
||||
{
|
||||
if ($throw)
|
||||
throw new SimpleException('No user with security token');
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($token->used)
|
||||
throw new SimpleException('This token was already used');
|
||||
|
||||
if ($token->expires !== null and time() > $token->expires)
|
||||
throw new SimpleException('This token has expired');
|
||||
|
||||
return $token;
|
||||
}
|
||||
}
|
|
@ -1,300 +0,0 @@
|
|||
<?php
|
||||
class Model_User extends AbstractModel
|
||||
{
|
||||
const SETTING_SAFETY = 1;
|
||||
const SETTING_ENDLESS_SCROLLING = 2;
|
||||
const SETTING_POST_TAG_TITLES = 3;
|
||||
const SETTING_HIDE_DISLIKED_POSTS = 4;
|
||||
|
||||
|
||||
|
||||
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]);
|
||||
if ($user)
|
||||
return $user;
|
||||
|
||||
$user = R::findOne(self::getTableName(), 'LOWER(email_confirmed) = LOWER(?)', [trim($key)]);
|
||||
if ($user)
|
||||
return $user;
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function create()
|
||||
{
|
||||
$user = R::dispense(self::getTableName());
|
||||
$user->pass_salt = md5(mt_rand() . uniqid());
|
||||
return $user;
|
||||
}
|
||||
|
||||
public static function remove($user)
|
||||
{
|
||||
//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 static function save($user)
|
||||
{
|
||||
R::store($user);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function getAnonymousName()
|
||||
{
|
||||
return '[Anonymous user]';
|
||||
}
|
||||
|
||||
public static function validateUserName($userName)
|
||||
{
|
||||
$userName = trim($userName);
|
||||
|
||||
$dbUser = R::findOne(self::getTableName(), 'LOWER(name) = LOWER(?)', [$userName]);
|
||||
if ($dbUser !== null)
|
||||
{
|
||||
if (!$dbUser->email_confirmed and \Chibi\Registry::getConfig()->registration->needEmailForRegistering)
|
||||
throw new SimpleException('User with this name is already registered and awaits e-mail confirmation');
|
||||
|
||||
if (!$dbUser->staff_confirmed and \Chibi\Registry::getConfig()->registration->staffActivation)
|
||||
throw new SimpleException('User with this name is already registered and awaits staff confirmation');
|
||||
|
||||
throw new SimpleException('User with this name is already registered');
|
||||
}
|
||||
|
||||
$userNameMinLength = intval(\Chibi\Registry::getConfig()->registration->userNameMinLength);
|
||||
$userNameMaxLength = intval(\Chibi\Registry::getConfig()->registration->userNameMaxLength);
|
||||
$userNameRegex = \Chibi\Registry::getConfig()->registration->userNameRegex;
|
||||
|
||||
if (strlen($userName) < $userNameMinLength)
|
||||
throw new SimpleException(sprintf('User name must have at least %d characters', $userNameMinLength));
|
||||
|
||||
if (strlen($userName) > $userNameMaxLength)
|
||||
throw new SimpleException(sprintf('User name must have at most %d characters', $userNameMaxLength));
|
||||
|
||||
if (!preg_match($userNameRegex, $userName))
|
||||
throw new SimpleException('User name contains invalid characters');
|
||||
|
||||
return $userName;
|
||||
}
|
||||
|
||||
public static function validatePassword($password)
|
||||
{
|
||||
$passMinLength = intval(\Chibi\Registry::getConfig()->registration->passMinLength);
|
||||
$passRegex = \Chibi\Registry::getConfig()->registration->passRegex;
|
||||
|
||||
if (strlen($password) < $passMinLength)
|
||||
throw new SimpleException(sprintf('Password must have at least %d characters', $passMinLength));
|
||||
|
||||
if (!preg_match($passRegex, $password))
|
||||
throw new SimpleException('Password contains invalid characters');
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
public static function validateEmail($email)
|
||||
{
|
||||
$email = trim($email);
|
||||
|
||||
if (!empty($email) and !TextHelper::isValidEmail($email))
|
||||
throw new SimpleException('E-mail address appears to be invalid');
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
public static function validateAccessRank($accessRank)
|
||||
{
|
||||
$accessRank = intval($accessRank);
|
||||
|
||||
if (!in_array($accessRank, AccessRank::getAll()))
|
||||
throw new SimpleException('Invalid access rank type "' . $accessRank . '"');
|
||||
|
||||
if ($accessRank == AccessRank::Nobody)
|
||||
throw new SimpleException('Cannot set special accesss rank "' . $accessRank . '"');
|
||||
|
||||
return $accessRank;
|
||||
}
|
||||
|
||||
public static function hashPassword($pass, $salt2)
|
||||
{
|
||||
$salt1 = \Chibi\Registry::getConfig()->main->salt;
|
||||
return sha1($salt1 . $salt2 . $pass);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getAvatarUrl($size = 32)
|
||||
{
|
||||
$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 function getSetting($key)
|
||||
{
|
||||
$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 hasEnabledHidingDislikedPosts()
|
||||
{
|
||||
$ret = $this->getSetting(self::SETTING_HIDE_DISLIKED_POSTS);
|
||||
if ($ret === null)
|
||||
$ret = !\Chibi\Registry::getConfig()->browsing->showDislikedPostsDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enableHidingDislikedPosts($enabled)
|
||||
{
|
||||
$this->setSetting(self::SETTING_HIDE_DISLIKED_POSTS, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
public function hasEnabledPostTagTitles()
|
||||
{
|
||||
$ret = $this->getSetting(self::SETTING_POST_TAG_TITLES);
|
||||
if ($ret === null)
|
||||
$ret = \Chibi\Registry::getConfig()->browsing->showPostTagTitlesDefault;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function enablePostTagTitles($enabled)
|
||||
{
|
||||
$this->setSetting(self::SETTING_POST_TAG_TITLES, $enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
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->via('favoritee')->sharedPost as $favPost)
|
||||
if ($favPost->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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
<?php
|
||||
class Model_User_QueryBuilder implements AbstractQueryBuilder
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
{
|
||||
$sortStyle = $query;
|
||||
$dbQuery->from('user');
|
||||
|
||||
switch ($sortStyle)
|
||||
{
|
||||
case 'alpha,asc':
|
||||
$dbQuery->orderBy('name')->asc();
|
||||
break;
|
||||
case 'alpha,desc':
|
||||
$dbQuery->orderBy('name')->desc();
|
||||
break;
|
||||
case 'date,asc':
|
||||
$dbQuery->orderBy('join_date')->asc();
|
||||
break;
|
||||
case 'date,desc':
|
||||
$dbQuery->orderBy('join_date')->desc();
|
||||
break;
|
||||
case 'pending':
|
||||
$dbQuery->where('staff_confirmed IS NULL');
|
||||
$dbQuery->or('staff_confirmed = 0');
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown sort style');
|
||||
}
|
||||
}
|
||||
}
|
281
src/Models/PostModel.php
Normal file
281
src/Models/PostModel.php
Normal file
|
@ -0,0 +1,281 @@
|
|||
<?php
|
||||
class PostModel extends AbstractCrudModel
|
||||
{
|
||||
protected static $config;
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'post';
|
||||
}
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$config = \Chibi\Registry::getConfig();
|
||||
}
|
||||
|
||||
public static function spawn()
|
||||
{
|
||||
$post = new PostEntity;
|
||||
$post->hidden = false;
|
||||
$post->uploadDate = time();
|
||||
do
|
||||
{
|
||||
$post->name = md5(mt_rand() . uniqid());
|
||||
}
|
||||
while (file_exists($post->getFullPath()));
|
||||
return $post;
|
||||
}
|
||||
|
||||
public static function save($post)
|
||||
{
|
||||
Database::transaction(function() use ($post)
|
||||
{
|
||||
self::forgeId($post);
|
||||
|
||||
$bindings = [
|
||||
'type' => $post->type,
|
||||
'name' => $post->name,
|
||||
'orig_name' => $post->origName,
|
||||
'file_hash' => $post->fileHash,
|
||||
'file_size' => $post->fileSize,
|
||||
'mime_type' => $post->mimeType,
|
||||
'safety' => $post->safety,
|
||||
'hidden' => $post->hidden,
|
||||
'upload_date' => $post->uploadDate,
|
||||
'image_width' => $post->imageWidth,
|
||||
'image_height' => $post->imageHeight,
|
||||
'uploader_id' => $post->uploaderId,
|
||||
'source' => $post->source,
|
||||
];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('post')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($post->id);
|
||||
Database::query($query);
|
||||
|
||||
//tags
|
||||
$tags = $post->getTags();
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('post_tag')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
Database::query($query);
|
||||
|
||||
foreach ($tags as $postTag)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->insertInto('post_tag')
|
||||
->surround('post_id, tag_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$post->id, $postTag->id]);
|
||||
Database::query($query);
|
||||
}
|
||||
|
||||
//relations
|
||||
$relations = $post->getRelations();
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('crossref')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->or('post2_id = ?')->put($post->id);
|
||||
Database::query($query);
|
||||
|
||||
foreach ($relations as $relatedPost)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->insertInto('crossref')
|
||||
->surround('post_id, post2_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$post->id, $relatedPost->id]);
|
||||
Database::query($query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function remove($post)
|
||||
{
|
||||
Database::transaction(function() use ($post)
|
||||
{
|
||||
$queries = [];
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post_score')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post_tag')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('crossref')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->or('post2_id = ?')->put($post->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('favoritee')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->update('comment')
|
||||
->set('post_id = NULL')
|
||||
->where('post_id = ?')->put($post->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post')
|
||||
->where('id = ?')->put($post->id);
|
||||
|
||||
foreach ($queries as $query)
|
||||
Database::query($query);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public static function findByName($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('post')
|
||||
->where('name = ?')->put($key);
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid post name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function findByIdOrName($key, $throw = true)
|
||||
{
|
||||
if (is_numeric($key))
|
||||
$post = self::findById($key, $throw);
|
||||
else
|
||||
$post = self::findByName($key, $throw);
|
||||
return $post;
|
||||
}
|
||||
|
||||
public static function findByHash($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('post')
|
||||
->where('file_hash = ?')->put($key);
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid post hash "' . $hash . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function preloadTags($posts)
|
||||
{
|
||||
if (empty($posts))
|
||||
return;
|
||||
|
||||
$postMap = [];
|
||||
$tagsMap = [];
|
||||
foreach ($posts as $post)
|
||||
{
|
||||
$postId = $post->id;
|
||||
$postMap[$postId] = $post;
|
||||
$tagsMap[$postId] = [];
|
||||
}
|
||||
$postIds = array_keys($postMap);
|
||||
|
||||
$sqlQuery = (new SqlQuery)
|
||||
->select('tag.*, post_id')
|
||||
->from('tag')
|
||||
->innerJoin('post_tag')->on('post_tag.tag_id = tag.id')
|
||||
->where('post_id')->in()->genSlots($postIds)->put($postIds);
|
||||
$rows = Database::fetchAll($sqlQuery);
|
||||
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
if (isset($tags[$row['id']]))
|
||||
continue;
|
||||
unset($row['post_id']);
|
||||
$tag = TagModel::convertRow($row);
|
||||
$tags[$row['id']] = $tag;
|
||||
}
|
||||
|
||||
foreach ($rows as $row)
|
||||
{
|
||||
$postId = $row['post_id'];
|
||||
$tagsMap[$postId] []= $tags[$row['id']];
|
||||
}
|
||||
|
||||
foreach ($tagsMap as $postId => $tags)
|
||||
{
|
||||
$postMap[$postId]->setCache('tags', $tags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateSafety($safety)
|
||||
{
|
||||
$safety = intval($safety);
|
||||
|
||||
if (!in_array($safety, PostSafety::getAll()))
|
||||
throw new SimpleException('Invalid safety type "' . $safety . '"');
|
||||
|
||||
return $safety;
|
||||
}
|
||||
|
||||
public static function validateSource($source)
|
||||
{
|
||||
$source = trim($source);
|
||||
|
||||
$maxLength = 200;
|
||||
if (strlen($source) > $maxLength)
|
||||
throw new SimpleException('Source must have at most ' . $maxLength . ' characters');
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
public static function validateThumbSize($width, $height)
|
||||
{
|
||||
$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];
|
||||
}
|
||||
|
||||
private static function getThumbPathTokenized($text, $name, $width = null, $height = null)
|
||||
{
|
||||
list ($width, $height) = self::validateThumbSize($width, $height);
|
||||
|
||||
return TextHelper::absolutePath(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 TextHelper::absolutePath(self::$config->main->filesPath . DS . $name);
|
||||
}
|
||||
}
|
||||
|
||||
PostModel::init();
|
73
src/Models/PropertyModel.php
Normal file
73
src/Models/PropertyModel.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
class PropertyModel implements IModel
|
||||
{
|
||||
const FeaturedPostId = 0;
|
||||
const FeaturedPostUserName = 1;
|
||||
const FeaturedPostDate = 2;
|
||||
const DbVersion = 3;
|
||||
|
||||
static $allProperties = null;
|
||||
static $loaded = false;
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'property';
|
||||
}
|
||||
|
||||
public static function loadIfNecessary()
|
||||
{
|
||||
if (!self::$loaded)
|
||||
{
|
||||
self::$loaded = true;
|
||||
self::$allProperties = [];
|
||||
$query = (new SqlQuery())->select('*')->from('property');
|
||||
foreach (Database::fetchAll($query) as $row)
|
||||
self::$allProperties[$row['prop_id']] = $row['value'];
|
||||
}
|
||||
}
|
||||
|
||||
public static function get($propertyId)
|
||||
{
|
||||
self::loadIfNecessary();
|
||||
return isset(self::$allProperties[$propertyId])
|
||||
? self::$allProperties[$propertyId]
|
||||
: null;
|
||||
}
|
||||
|
||||
public static function set($propertyId, $value)
|
||||
{
|
||||
self::loadIfNecessary();
|
||||
Database::transaction(function() use ($propertyId, $value)
|
||||
{
|
||||
$row = Database::query((new SqlQuery)
|
||||
->select('id')
|
||||
->from('property')
|
||||
->where('prop_id = ?')
|
||||
->put($propertyId));
|
||||
|
||||
$query = (new SqlQuery);
|
||||
|
||||
if ($row)
|
||||
{
|
||||
$query
|
||||
->update('property')
|
||||
->set('value = ?')
|
||||
->put($value)
|
||||
->where('prop_id = ?')
|
||||
->put($propertyId);
|
||||
}
|
||||
else
|
||||
{
|
||||
$query
|
||||
->insertInto('property')
|
||||
->open()->raw('prop_id, value_id')->close()
|
||||
->open()->raw('?, ?')->close()
|
||||
->put([$propertyId, $value]);
|
||||
}
|
||||
|
||||
Database::query($query);
|
||||
|
||||
self::$allProperties[$propertyId] = $value;
|
||||
});
|
||||
}
|
||||
}
|
56
src/Models/SearchServices/AbstractQueryBuilder.php
Normal file
56
src/Models/SearchServices/AbstractQueryBuilder.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
abstract class AbstractSearchService
|
||||
{
|
||||
protected static function getModelClassName()
|
||||
{
|
||||
$searchServiceClassName = get_called_class();
|
||||
$modelClassName = str_replace('SearchService', 'Model', $searchServiceClassName);
|
||||
return $modelClassName;
|
||||
}
|
||||
|
||||
protected static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected static function decoratePager(SqlQuery $sqlQuery, $perPage, $page)
|
||||
{
|
||||
if ($perPage === null)
|
||||
return;
|
||||
$sqlQuery->limit('?')->put($perPage);
|
||||
$sqlQuery->offset('?')->put(($page - 1) * $perPage);
|
||||
}
|
||||
|
||||
static function getEntitiesRows($searchQuery, $perPage = null, $page = 1)
|
||||
{
|
||||
$modelClassName = self::getModelClassName();
|
||||
$table = $modelClassName::getTableName();
|
||||
|
||||
$sqlQuery = new SqlQuery();
|
||||
$sqlQuery->select($table . '.*');
|
||||
static::decorate($sqlQuery, $searchQuery);
|
||||
self::decoratePager($sqlQuery, $perPage, $page);
|
||||
|
||||
$rows = Database::fetchAll($sqlQuery);
|
||||
return $rows;
|
||||
}
|
||||
|
||||
static function getEntities($searchQuery, $perPage = null, $page = 1)
|
||||
{
|
||||
$modelClassName = self::getModelClassName();
|
||||
$rows = static::getEntitiesRows($searchQuery, $perPage, $page);
|
||||
return $modelClassName::convertRows($rows);
|
||||
}
|
||||
|
||||
static function getEntityCount($searchQuery)
|
||||
{
|
||||
$modelClassName = self::getModelClassName();
|
||||
$table = $modelClassName::getTableName();
|
||||
|
||||
$sqlQuery = new SqlQuery();
|
||||
$sqlQuery->select('count(1)')->as('count');
|
||||
static::decorate($sqlQuery, $searchQuery);
|
||||
|
||||
return Database::fetchOne($sqlQuery)['count'];
|
||||
}
|
||||
}
|
13
src/Models/SearchServices/CommentSearchService.php
Normal file
13
src/Models/SearchServices/CommentSearchService.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
class CommentSearchService extends AbstractSearchService
|
||||
{
|
||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
||||
{
|
||||
$sqlQuery
|
||||
->from('comment')
|
||||
->where('post_id')
|
||||
->is()->not('NULL')
|
||||
->orderBy('id')
|
||||
->desc();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
||||
class PostSearchService extends AbstractSearchService
|
||||
{
|
||||
private static $enableTokenLimit = true;
|
||||
|
||||
|
@ -8,40 +8,40 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
self::$enableTokenLimit = $enable;
|
||||
}
|
||||
|
||||
protected static function filterUserSafety($dbQuery)
|
||||
protected static function filterUserSafety(SqlQuery $sqlQuery)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
$dbQuery->addSql('safety')->in('(' . R::genSlots($allowedSafety) . ')');
|
||||
$sqlQuery->raw('safety')->in()->genSlots($allowedSafety);
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
$sqlQuery->put($s);
|
||||
}
|
||||
|
||||
protected static function filterUserHidden($dbQuery)
|
||||
protected static function filterUserHidden(SqlQuery $sqlQuery)
|
||||
{
|
||||
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
|
||||
$dbQuery->not()->addSql('hidden');
|
||||
$sqlQuery->not('hidden');
|
||||
else
|
||||
$dbQuery->addSql('1');
|
||||
$sqlQuery->raw('1');
|
||||
}
|
||||
|
||||
protected static function filterChain($dbQuery)
|
||||
protected static function filterChain(SqlQuery $sqlQuery)
|
||||
{
|
||||
if (isset($dbQuery->__chained))
|
||||
$dbQuery->and();
|
||||
if (isset($sqlQuery->__chained))
|
||||
$sqlQuery->and();
|
||||
else
|
||||
$dbQuery->where();
|
||||
$dbQuery->__chained = true;
|
||||
$sqlQuery->where();
|
||||
$sqlQuery->__chained = true;
|
||||
}
|
||||
|
||||
protected static function filterNegate($dbQuery)
|
||||
protected static function filterNegate(SqlQuery $sqlQuery)
|
||||
{
|
||||
$dbQuery->not();
|
||||
$sqlQuery->not();
|
||||
}
|
||||
|
||||
protected static function filterTag($dbQuery, $val)
|
||||
protected static function filterTag($sqlQuery, $val)
|
||||
{
|
||||
$tag = Model_Tag::locate($val);
|
||||
$dbQuery
|
||||
$tag = TagModel::findByName($val);
|
||||
$sqlQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
|
@ -51,66 +51,64 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenId($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenId($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$ids = preg_split('/[;,]/', $val);
|
||||
$ids = array_map('intval', $ids);
|
||||
$dbQuery->addSql('id')->in('(' . R::genSlots($ids) . ')');
|
||||
foreach ($ids as $id)
|
||||
$dbQuery->put($id);
|
||||
$sqlQuery->raw('id')->in()->genSlots($ids)->put($ids);
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMin($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenIdMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id >= ?')->put(intval($val));
|
||||
$sqlQuery->raw('id >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenIdMax($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenIdMax($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('id <= ?')->put(intval($val));
|
||||
$sqlQuery->raw('id <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMin($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenScoreMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score >= ?')->put(intval($val));
|
||||
$sqlQuery->raw('score >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenScoreMax($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenScoreMax($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('score <= ?')->put(intval($val));
|
||||
$sqlQuery->raw('score <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMin($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenTagMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count >= ?')->put(intval($val));
|
||||
$sqlQuery->raw('tag_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenTagMax($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenTagMax($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('tag_count <= ?')->put(intval($val));
|
||||
$sqlQuery->raw('tag_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMin($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenFavMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count >= ?')->put(intval($val));
|
||||
$sqlQuery->raw('fav_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenFavMax($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenFavMax($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('fav_count <= ?')->put(intval($val));
|
||||
$sqlQuery->raw('fav_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMin($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenCommentMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count >= ?')->put(intval($val));
|
||||
$sqlQuery->raw('comment_count >= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenCommentMax($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenCommentMax($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$dbQuery->addSql('comment_count <= ?')->put(intval($val));
|
||||
$sqlQuery->raw('comment_count <= ?')->put(intval($val));
|
||||
}
|
||||
|
||||
protected static function filterTokenSpecial($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenSpecial($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$context = \Chibi\Registry::getContext();
|
||||
|
||||
|
@ -118,11 +116,11 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
{
|
||||
case 'liked':
|
||||
case 'likes':
|
||||
$dbQuery
|
||||
$sqlQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('postscore')
|
||||
->from('post_score')
|
||||
->where('post_id = post.id')
|
||||
->and('score > 0')
|
||||
->and('user_id = ?')->put($context->user->id)
|
||||
|
@ -131,11 +129,11 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
|
||||
case 'disliked':
|
||||
case 'dislikes':
|
||||
$dbQuery
|
||||
$sqlQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('postscore')
|
||||
->from('post_score')
|
||||
->where('post_id = post.id')
|
||||
->and('score < 0')
|
||||
->and('user_id = ?')->put($context->user->id)
|
||||
|
@ -147,7 +145,7 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
}
|
||||
}
|
||||
|
||||
protected static function filterTokenType($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenType($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
switch (strtolower($val))
|
||||
{
|
||||
|
@ -164,7 +162,7 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
default:
|
||||
throw new SimpleException('Unknown type "' . $val . '"');
|
||||
}
|
||||
$dbQuery->addSql('type = ?')->put($type);
|
||||
$sqlQuery->raw('type = ?')->put($type);
|
||||
}
|
||||
|
||||
protected static function __filterTokenDateParser($val)
|
||||
|
@ -182,30 +180,30 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
return [$timeMin, $timeMax];
|
||||
}
|
||||
|
||||
protected static function filterTokenDate($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenDate($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery
|
||||
->addSql('upload_date >= ?')->put($timeMin)
|
||||
$sqlQuery
|
||||
->raw('upload_date >= ?')->put($timeMin)
|
||||
->and('upload_date <= ?')->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMin($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenDateMin($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date >= ?')->put($timeMin);
|
||||
$sqlQuery->raw('upload_date >= ?')->put($timeMin);
|
||||
}
|
||||
|
||||
protected static function filterTokenDateMax($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenDateMax($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
|
||||
$dbQuery->addSql('upload_date <= ?')->put($timeMax);
|
||||
$sqlQuery->raw('upload_date <= ?')->put($timeMax);
|
||||
}
|
||||
|
||||
protected static function filterTokenFav($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenFav($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$user = Model_User::locate($val);
|
||||
$dbQuery
|
||||
$user = UserModel::findByNameOrEmail($val);
|
||||
$sqlQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
|
@ -215,15 +213,15 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenFavs($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenFavs($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
return self::filterTokenFav($searchContext, $dbQuery, $val);
|
||||
return self::filterTokenFav($searchContext, $sqlQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenComment($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenComment($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$user = Model_User::locate($val);
|
||||
$dbQuery
|
||||
$user = UserModel::findByNameOrEmail($val);
|
||||
$sqlQuery
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
|
@ -233,51 +231,51 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
->close();
|
||||
}
|
||||
|
||||
protected static function filterTokenCommenter($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenCommenter($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
return self::filterTokenComment($searchContext, $dbQuery, $val);
|
||||
return self::filterTokenComment($searchContext, $sqlQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenSubmit($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenSubmit($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$user = Model_User::locate($val);
|
||||
$dbQuery->addSql('uploader_id = ?')->put($user->id);
|
||||
$user = UserModel::findByNameOrEmail($val);
|
||||
$sqlQuery->raw('uploader_id = ?')->put($user->id);
|
||||
}
|
||||
|
||||
protected static function filterTokenUploader($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenUploader($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($searchContext, $dbQuery, $val);
|
||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUpload($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenUpload($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($searchContext, $dbQuery, $val);
|
||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenUploaded($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenUploaded($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
return self::filterTokenSubmit($searchContext, $dbQuery, $val);
|
||||
return self::filterTokenSubmit($searchContext, $sqlQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenPrev($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenPrev($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
self::__filterTokenPrevNext($searchContext, $dbQuery, $val);
|
||||
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
|
||||
}
|
||||
|
||||
protected static function filterTokenNext($searchContext, $dbQuery, $val)
|
||||
protected static function filterTokenNext($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$searchContext->orderDir *= -1;
|
||||
self::__filterTokenPrevNext($searchContext, $dbQuery, $val);
|
||||
self::__filterTokenPrevNext($searchContext, $sqlQuery, $val);
|
||||
}
|
||||
|
||||
protected static function __filterTokenPrevNext($searchContext, $dbQuery, $val)
|
||||
protected static function __filterTokenPrevNext($searchContext, SqlQuery $sqlQuery, $val)
|
||||
{
|
||||
$op1 = $searchContext->orderDir == 1 ? '<' : '>';
|
||||
$op2 = $searchContext->orderDir != 1 ? '<' : '>';
|
||||
$dbQuery
|
||||
$sqlQuery
|
||||
->open()
|
||||
->open()
|
||||
->addSql($searchContext->orderColumn . ' ' . $op1 . ' ')
|
||||
->raw($searchContext->orderColumn . ' ' . $op1 . ' ')
|
||||
->open()
|
||||
->select($searchContext->orderColumn)
|
||||
->from('post p2')
|
||||
|
@ -287,7 +285,7 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
->close()
|
||||
->or()
|
||||
->open()
|
||||
->addSql($searchContext->orderColumn . ' = ')
|
||||
->raw($searchContext->orderColumn . ' = ')
|
||||
->open()
|
||||
->select($searchContext->orderColumn)
|
||||
->from('post p2')
|
||||
|
@ -405,19 +403,19 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
return $unparsedTokens;
|
||||
}
|
||||
|
||||
public static function build($dbQuery, $query)
|
||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
||||
{
|
||||
$config = \Chibi\Registry::getConfig();
|
||||
|
||||
$dbQuery->from('post');
|
||||
$sqlQuery->from('post');
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserSafety($dbQuery);
|
||||
self::filterChain($dbQuery);
|
||||
self::filterUserHidden($dbQuery);
|
||||
self::filterChain($sqlQuery);
|
||||
self::filterUserSafety($sqlQuery);
|
||||
self::filterChain($sqlQuery);
|
||||
self::filterUserHidden($sqlQuery);
|
||||
|
||||
/* query tokens */
|
||||
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
|
||||
$tokens = array_filter(array_unique(explode(' ', $searchQuery)), function($x) { return $x != ''; });
|
||||
if (self::$enableTokenLimit and count($tokens) > $config->browsing->maxSearchTokens)
|
||||
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
|
||||
|
||||
|
@ -428,7 +426,7 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
$searchContext->orderColumn = 'id';
|
||||
$searchContext->orderDir = 1;
|
||||
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $dbQuery, &$orderToken)
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery, &$orderToken)
|
||||
{
|
||||
if ($key != 'order')
|
||||
return false;
|
||||
|
@ -443,47 +441,47 @@ class Model_Post_QueryBuilder implements AbstractQueryBuilder
|
|||
});
|
||||
|
||||
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $dbQuery)
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
|
||||
{
|
||||
if ($key !== null)
|
||||
return false;
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
self::filterChain($sqlQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::filterTag($dbQuery, $val);
|
||||
self::filterNegate($sqlQuery);
|
||||
self::filterTag($sqlQuery, $val);
|
||||
return true;
|
||||
});
|
||||
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $dbQuery)
|
||||
$tokens = self::iterateTokens($tokens, function($neg, $key, $val) use ($searchContext, $sqlQuery)
|
||||
{
|
||||
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
|
||||
if (!method_exists(__CLASS__, $methodName))
|
||||
return false;
|
||||
|
||||
self::filterChain($dbQuery);
|
||||
self::filterChain($sqlQuery);
|
||||
if ($neg)
|
||||
self::filterNegate($dbQuery);
|
||||
self::$methodName($searchContext, $dbQuery, $val);
|
||||
self::filterNegate($sqlQuery);
|
||||
self::$methodName($searchContext, $sqlQuery, $val);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!empty($tokens))
|
||||
throw new SimpleException('Unknown search token "' . array_shift($tokens) . '"');
|
||||
|
||||
$dbQuery->orderBy($searchContext->orderColumn);
|
||||
$sqlQuery->orderBy($searchContext->orderColumn);
|
||||
if ($searchContext->orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
$sqlQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
$sqlQuery->asc();
|
||||
|
||||
if ($searchContext->orderColumn != 'id')
|
||||
{
|
||||
$dbQuery->addSql(', id');
|
||||
$sqlQuery->raw(', id');
|
||||
if ($searchContext->orderDir == 1)
|
||||
$dbQuery->desc();
|
||||
$sqlQuery->desc();
|
||||
else
|
||||
$dbQuery->asc();
|
||||
$sqlQuery->asc();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,27 @@
|
|||
<?php
|
||||
class model_Tag_QueryBuilder implements AbstractQueryBuilder
|
||||
class TagSearchService extends AbstractSearchService
|
||||
{
|
||||
public static function build($dbQuery, $query)
|
||||
public static function decorate(SqlQuery $sqlQuery, $searchQuery)
|
||||
{
|
||||
$allowedSafety = PrivilegesHelper::getAllowedSafety();
|
||||
$limitQuery = false;
|
||||
$dbQuery
|
||||
->addSql(', COUNT(post_tag.post_id)')
|
||||
$sqlQuery
|
||||
->raw(', COUNT(post_tag.post_id)')
|
||||
->as('post_count')
|
||||
->from('tag')
|
||||
->innerJoin('post_tag')
|
||||
->on('tag.id = post_tag.tag_id')
|
||||
->innerJoin('post')
|
||||
->on('post.id = post_tag.post_id')
|
||||
->where('safety IN (' . R::genSlots($allowedSafety) . ')');
|
||||
->where('safety')->in()->genSlots($allowedSafety);
|
||||
foreach ($allowedSafety as $s)
|
||||
$dbQuery->put($s);
|
||||
$sqlQuery->put($s);
|
||||
|
||||
$orderToken = null;
|
||||
|
||||
if ($query !== null)
|
||||
if ($searchQuery !== null)
|
||||
{
|
||||
$tokens = preg_split('/\s+/', $query);
|
||||
$tokens = preg_split('/\s+/', $searchQuery);
|
||||
foreach ($tokens as $token)
|
||||
{
|
||||
if (strpos($token, ':') !== false)
|
||||
|
@ -39,7 +39,7 @@ class model_Tag_QueryBuilder implements AbstractQueryBuilder
|
|||
if (strlen($token) >= 3)
|
||||
$token = '%' . $token;
|
||||
$token .= '%';
|
||||
$dbQuery
|
||||
$sqlQuery
|
||||
->and('LOWER(tag.name)')
|
||||
->like('LOWER(?)')
|
||||
->put($token);
|
||||
|
@ -47,16 +47,16 @@ class model_Tag_QueryBuilder implements AbstractQueryBuilder
|
|||
}
|
||||
}
|
||||
|
||||
$dbQuery->groupBy('tag.id');
|
||||
$sqlQuery->groupBy('tag.id');
|
||||
if ($orderToken)
|
||||
self::order($dbQuery,$orderToken);
|
||||
self::order($sqlQuery,$orderToken);
|
||||
|
||||
|
||||
if ($limitQuery)
|
||||
$dbQuery->limit(15);
|
||||
$sqlQuery->limit(15);
|
||||
}
|
||||
|
||||
private static function order($dbQuery, $value)
|
||||
private static function order(SqlQuery $sqlQuery, $value)
|
||||
{
|
||||
if (strpos($value, ',') !== false)
|
||||
{
|
||||
|
@ -71,17 +71,17 @@ class model_Tag_QueryBuilder implements AbstractQueryBuilder
|
|||
switch ($orderColumn)
|
||||
{
|
||||
case 'popularity':
|
||||
$dbQuery->orderBy('post_count');
|
||||
$sqlQuery->orderBy('post_count');
|
||||
break;
|
||||
|
||||
case 'alpha':
|
||||
$dbQuery->orderBy('name');
|
||||
$sqlQuery->orderBy('name');
|
||||
break;
|
||||
}
|
||||
|
||||
if ($orderDir == 'asc')
|
||||
$dbQuery->asc();
|
||||
$sqlQuery->asc();
|
||||
else
|
||||
$dbQuery->desc();
|
||||
$sqlQuery->desc();
|
||||
}
|
||||
}
|
31
src/Models/SearchServices/UserSearchService.php
Normal file
31
src/Models/SearchServices/UserSearchService.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
class UserSearchService extends AbstractSearchService
|
||||
{
|
||||
protected static function decorate(SQLQuery $sqlQuery, $searchQuery)
|
||||
{
|
||||
$sqlQuery->from('user');
|
||||
|
||||
$sortStyle = $searchQuery;
|
||||
switch ($sortStyle)
|
||||
{
|
||||
case 'alpha,asc':
|
||||
$sqlQuery->orderBy('name')->asc();
|
||||
break;
|
||||
case 'alpha,desc':
|
||||
$sqlQuery->orderBy('name')->desc();
|
||||
break;
|
||||
case 'date,asc':
|
||||
$sqlQuery->orderBy('join_date')->asc();
|
||||
break;
|
||||
case 'date,desc':
|
||||
$sqlQuery->orderBy('join_date')->desc();
|
||||
break;
|
||||
case 'pending':
|
||||
$sqlQuery->where('staff_confirmed IS NULL');
|
||||
$sqlQuery->or('staff_confirmed = 0');
|
||||
break;
|
||||
default:
|
||||
throw new SimpleException('Unknown sort style "' . $sortStyle . '"');
|
||||
}
|
||||
}
|
||||
}
|
186
src/Models/TagModel.php
Normal file
186
src/Models/TagModel.php
Normal file
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
class TagModel extends AbstractCrudModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'tag';
|
||||
}
|
||||
|
||||
public static function save($tag)
|
||||
{
|
||||
Database::transaction(function() use ($tag)
|
||||
{
|
||||
self::forgeId($tag, 'tag');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('tag')
|
||||
->set('name = ?')->put($tag->name)
|
||||
->where('id = ?')->put($tag->id);
|
||||
|
||||
Database::query($query);
|
||||
});
|
||||
return $tag->id;
|
||||
}
|
||||
|
||||
public static function remove($tag)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('post_tag')
|
||||
->where('tag_id = ?')->put($tag->id);
|
||||
Database::query($query);
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('tag')
|
||||
->where('id = ?')->put($tag->id);
|
||||
Database::query($query);
|
||||
}
|
||||
|
||||
public static function rename($sourceName, $targetName)
|
||||
{
|
||||
Database::transaction(function() use ($sourceName, $targetName)
|
||||
{
|
||||
$sourceTag = TagModel::findByName($sourceName);
|
||||
$targetTag = TagModel::findByName($targetName, false);
|
||||
|
||||
if ($targetTag and $targetTag->id != $sourceTag->id)
|
||||
throw new SimpleException('Target tag already exists');
|
||||
|
||||
$sourceTag->name = $targetName;
|
||||
self::save($sourceTag);
|
||||
});
|
||||
}
|
||||
|
||||
public static function merge($sourceName, $targetName)
|
||||
{
|
||||
Database::transaction(function() use ($sourceName, $targetName)
|
||||
{
|
||||
$sourceTag = TagModel::findByName($sourceName);
|
||||
$targetTag = TagModel::findByName($targetName);
|
||||
|
||||
if ($sourceTag->id == $targetTag->id)
|
||||
throw new SimpleException('Source and target tag are the same');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->select('post.id')
|
||||
->from('post')
|
||||
->where()
|
||||
->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.post_id = post.id')
|
||||
->and('post_tag.tag_id = ?')->put($sourceTag->id)
|
||||
->close()
|
||||
->and()
|
||||
->not()->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.post_id = post.id')
|
||||
->and('post_tag.tag_id = ?')->put($targetTag->id)
|
||||
->close();
|
||||
$rows = Database::fetchAll($query);
|
||||
$postIds = array_map(function($row) { return $row['id']; }, $rows);
|
||||
|
||||
self::remove($sourceTag);
|
||||
|
||||
foreach ($postIds as $postId)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->insertInto('post_tag')
|
||||
->surround('post_id, tag_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$postId, $targetTag->id]);
|
||||
Database::query($query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function findAllByPostId($key)
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query
|
||||
->select('tag.*')
|
||||
->from('tag')
|
||||
->innerJoin('post_tag')
|
||||
->on('post_tag.tag_id = tag.id')
|
||||
->where('post_tag.post_id = ?')
|
||||
->put($key);
|
||||
|
||||
$rows = Database::fetchAll($query);
|
||||
if ($rows)
|
||||
return self::convertRows($rows);
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function findByName($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('tag')
|
||||
->where('LOWER(name) = LOWER(?)')->put($key);
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid tag name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function removeUnused()
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('tag')
|
||||
->where()
|
||||
->not()->exists()
|
||||
->open()
|
||||
->select('1')
|
||||
->from('post_tag')
|
||||
->where('post_tag.tag_id = tag.id')
|
||||
->close();
|
||||
Database::query($query);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateTag($tag)
|
||||
{
|
||||
$tag = trim($tag);
|
||||
|
||||
$minLength = 1;
|
||||
$maxLength = 64;
|
||||
if (strlen($tag) < $minLength)
|
||||
throw new SimpleException('Tag must have at least ' . $minLength . ' characters');
|
||||
if (strlen($tag) > $maxLength)
|
||||
throw new SimpleException('Tag must have at most ' . $maxLength . ' characters');
|
||||
|
||||
if (!preg_match('/^[()\[\]a-zA-Z0-9_.-]+$/i', $tag))
|
||||
throw new SimpleException('Invalid tag "' . $tag . '"');
|
||||
|
||||
if (preg_match('/^\.\.?$/', $tag))
|
||||
throw new SimpleException('Invalid tag "' . $tag . '"');
|
||||
|
||||
return $tag;
|
||||
}
|
||||
|
||||
public static function validateTags($tags)
|
||||
{
|
||||
$tags = trim($tags);
|
||||
$tags = preg_split('/[,;\s]+/', $tags);
|
||||
$tags = array_filter($tags, function($x) { return $x != ''; });
|
||||
$tags = array_unique($tags);
|
||||
|
||||
foreach ($tags as $key => $tag)
|
||||
$tags[$key] = self::validateTag($tag);
|
||||
|
||||
if (empty($tags))
|
||||
throw new SimpleException('No tags set');
|
||||
|
||||
return $tags;
|
||||
}
|
||||
}
|
81
src/Models/TokenModel.php
Normal file
81
src/Models/TokenModel.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
class TokenModel extends AbstractCrudModel
|
||||
implements IModel
|
||||
{
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'user_token';
|
||||
}
|
||||
|
||||
public static function save($token)
|
||||
{
|
||||
Database::transaction(function() use ($token)
|
||||
{
|
||||
self::forgeId($token);
|
||||
|
||||
$bindings = [
|
||||
'user_id' => $token->userId,
|
||||
'token' => $token->token,
|
||||
'used' => $token->used,
|
||||
'expires' => $token->expires,
|
||||
];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('user_token')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($token->id);
|
||||
Database::query($query);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function findByToken($key, $throw = true)
|
||||
{
|
||||
if (empty($key))
|
||||
throw new SimpleException('Invalid security token');
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('user_token')
|
||||
->where('token = ?')->put($key);
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('No user with such security token');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function checkValidity($token)
|
||||
{
|
||||
if (empty($token))
|
||||
throw new SimpleException('Invalid security token');
|
||||
|
||||
if ($token->used)
|
||||
throw new SimpleException('This token was already used');
|
||||
|
||||
if ($token->expires !== null and time() > $token->expires)
|
||||
throw new SimpleException('This token has expired');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function forgeUnusedToken()
|
||||
{
|
||||
$tokenText = '';
|
||||
while (true)
|
||||
{
|
||||
$tokenText = md5(mt_rand() . uniqid());
|
||||
$token = self::findByToken($tokenText, false);
|
||||
if (!$token)
|
||||
return $tokenText;
|
||||
}
|
||||
}
|
||||
}
|
253
src/Models/UserModel.php
Normal file
253
src/Models/UserModel.php
Normal file
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
class UserModel extends AbstractCrudModel
|
||||
{
|
||||
const SETTING_SAFETY = 1;
|
||||
const SETTING_ENDLESS_SCROLLING = 2;
|
||||
const SETTING_POST_TAG_TITLES = 3;
|
||||
const SETTING_HIDE_DISLIKED_POSTS = 4;
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
return 'user';
|
||||
}
|
||||
|
||||
public static function spawn()
|
||||
{
|
||||
$user = new UserEntity();
|
||||
$user->passSalt = md5(mt_rand() . uniqid());
|
||||
return $user;
|
||||
}
|
||||
|
||||
public static function save($user)
|
||||
{
|
||||
if ($user->accessRank == AccessRank::Anonymous)
|
||||
throw new Exception('Trying to save anonymous user into database');
|
||||
Database::transaction(function() use ($user)
|
||||
{
|
||||
self::forgeId($user);
|
||||
|
||||
$bindings = [
|
||||
'name' => $user->name,
|
||||
'pass_salt' => $user->passSalt,
|
||||
'pass_hash' => $user->passHash,
|
||||
'staff_confirmed' => $user->staffConfirmed,
|
||||
'email_unconfirmed' => $user->emailUnconfirmed,
|
||||
'email_confirmed' => $user->emailConfirmed,
|
||||
'join_date' => $user->joinDate,
|
||||
'access_rank' => $user->accessRank,
|
||||
'settings' => $user->settings,
|
||||
'banned' => $user->banned
|
||||
];
|
||||
|
||||
$query = (new SqlQuery)
|
||||
->update('user')
|
||||
->set(join(', ', array_map(function($key) { return $key . ' = ?'; }, array_keys($bindings))))
|
||||
->put(array_values($bindings))
|
||||
->where('id = ?')->put($user->id);
|
||||
Database::query($query);
|
||||
});
|
||||
}
|
||||
|
||||
public static function remove($user)
|
||||
{
|
||||
Database::transaction(function() use ($user)
|
||||
{
|
||||
$queries = [];
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('post_score')
|
||||
->where('user_id = ?')->put($user->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->update('comment')
|
||||
->set('commenter_id = NULL')
|
||||
->where('commenter_id = ?')->put($user->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->update('post')
|
||||
->set('uploader_id = NULL')
|
||||
->where('uploader_id = ?')->put($user->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('favoritee')
|
||||
->where('user_id = ?')->put($user->id);
|
||||
|
||||
$queries []= (new SqlQuery)
|
||||
->deleteFrom('user')
|
||||
->where('id = ?')->put($user->id);
|
||||
|
||||
foreach ($queries as $query)
|
||||
Database::query($query);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function findByName($key, $throw = true)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->select('*')
|
||||
->from('user')
|
||||
->where('LOWER(name) = LOWER(?)')->put(trim($key));
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function findByNameOrEmail($key, $throw = true)
|
||||
{
|
||||
$query = new SqlQuery();
|
||||
$query->select('*')
|
||||
->from('user')
|
||||
->where('LOWER(name) = LOWER(:user)')
|
||||
->or('LOWER(email_confirmed) = LOWER(:user)')
|
||||
->put(['user' => trim($key)]);
|
||||
|
||||
$row = Database::fetchOne($query);
|
||||
if ($row)
|
||||
return self::convertRow($row);
|
||||
|
||||
if ($throw)
|
||||
throw new SimpleException('Invalid user name "' . $key . '"');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function updateUserScore($user, $post, $score)
|
||||
{
|
||||
Database::transaction(function() use ($user, $post, $score)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('post_score')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->and('user_id = ?')->put($user->id);
|
||||
Database::query($query);
|
||||
$score = intval($score);
|
||||
if ($score != 0)
|
||||
{
|
||||
$query = (new SqlQuery);
|
||||
$query->insertInto('post_score')
|
||||
->surround('post_id, user_id, score')
|
||||
->values()->surround('?, ?, ?')
|
||||
->put([$post->id, $user->id, $score]);
|
||||
Database::query($query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static function addToUserFavorites($user, $post)
|
||||
{
|
||||
Database::transaction(function() use ($user, $post)
|
||||
{
|
||||
self::removeFromUserFavorites($user, $post);
|
||||
$query = (new SqlQuery);
|
||||
$query->insertInto('favoritee')
|
||||
->surround('post_id, user_id')
|
||||
->values()->surround('?, ?')
|
||||
->put([$post->id, $user->id]);
|
||||
Database::query($query);
|
||||
});
|
||||
}
|
||||
|
||||
public static function removeFromUserFavorites($user, $post)
|
||||
{
|
||||
Database::transaction(function() use ($user, $post)
|
||||
{
|
||||
$query = (new SqlQuery)
|
||||
->deleteFrom('favoritee')
|
||||
->where('post_id = ?')->put($post->id)
|
||||
->and('user_id = ?')->put($user->id);
|
||||
Database::query($query);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateUserName($userName)
|
||||
{
|
||||
$userName = trim($userName);
|
||||
|
||||
$dbUser = self::findByName($userName, false);
|
||||
if ($dbUser !== null)
|
||||
{
|
||||
if (!$dbUser->emailConfirmed and \Chibi\Registry::getConfig()->registration->needEmailForRegistering)
|
||||
throw new SimpleException('User with this name is already registered and awaits e-mail confirmation');
|
||||
|
||||
if (!$dbUser->staffConfirmed and \Chibi\Registry::getConfig()->registration->staffActivation)
|
||||
throw new SimpleException('User with this name is already registered and awaits staff confirmation');
|
||||
|
||||
throw new SimpleException('User with this name is already registered');
|
||||
}
|
||||
|
||||
$userNameMinLength = intval(\Chibi\Registry::getConfig()->registration->userNameMinLength);
|
||||
$userNameMaxLength = intval(\Chibi\Registry::getConfig()->registration->userNameMaxLength);
|
||||
$userNameRegex = \Chibi\Registry::getConfig()->registration->userNameRegex;
|
||||
|
||||
if (strlen($userName) < $userNameMinLength)
|
||||
throw new SimpleException(sprintf('User name must have at least %d characters', $userNameMinLength));
|
||||
|
||||
if (strlen($userName) > $userNameMaxLength)
|
||||
throw new SimpleException(sprintf('User name must have at most %d characters', $userNameMaxLength));
|
||||
|
||||
if (!preg_match($userNameRegex, $userName))
|
||||
throw new SimpleException('User name contains invalid characters');
|
||||
|
||||
return $userName;
|
||||
}
|
||||
|
||||
public static function validatePassword($password)
|
||||
{
|
||||
$passMinLength = intval(\Chibi\Registry::getConfig()->registration->passMinLength);
|
||||
$passRegex = \Chibi\Registry::getConfig()->registration->passRegex;
|
||||
|
||||
if (strlen($password) < $passMinLength)
|
||||
throw new SimpleException(sprintf('Password must have at least %d characters', $passMinLength));
|
||||
|
||||
if (!preg_match($passRegex, $password))
|
||||
throw new SimpleException('Password contains invalid characters');
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
public static function validateEmail($email)
|
||||
{
|
||||
$email = trim($email);
|
||||
|
||||
if (!empty($email) and !TextHelper::isValidEmail($email))
|
||||
throw new SimpleException('E-mail address appears to be invalid');
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
public static function validateAccessRank($accessRank)
|
||||
{
|
||||
$accessRank = intval($accessRank);
|
||||
|
||||
if (!in_array($accessRank, AccessRank::getAll()))
|
||||
throw new SimpleException('Invalid access rank type "' . $accessRank . '"');
|
||||
|
||||
if ($accessRank == AccessRank::Nobody)
|
||||
throw new SimpleException('Cannot set special accesss rank "' . $accessRank . '"');
|
||||
|
||||
return $accessRank;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function getAnonymousName()
|
||||
{
|
||||
return '[Anonymous user]';
|
||||
}
|
||||
|
||||
public static function hashPassword($pass, $salt2)
|
||||
{
|
||||
$salt1 = \Chibi\Registry::getConfig()->main->salt;
|
||||
return sha1($salt1 . $salt2 . $pass);
|
||||
}
|
||||
}
|
99
src/SqlQuery.php
Normal file
99
src/SqlQuery.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
class SqlQuery
|
||||
{
|
||||
protected $sql;
|
||||
protected $bindings;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->sql = '';
|
||||
$this->bindings = [];
|
||||
}
|
||||
|
||||
public function __call($name, array $arguments)
|
||||
{
|
||||
$name = TextHelper::camelCaseToKebabCase($name);
|
||||
$name = str_replace('-', ' ', $name);
|
||||
$this->sql .= $name . ' ';
|
||||
|
||||
if (!empty($arguments))
|
||||
{
|
||||
$arg = array_shift($arguments);
|
||||
assert(empty($arguments));
|
||||
|
||||
if (is_object($arg))
|
||||
{
|
||||
throw new Exception('Not implemented');
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->sql .= $arg . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function put($arg)
|
||||
{
|
||||
if (is_array($arg))
|
||||
{
|
||||
foreach ($arg as $key => $val)
|
||||
{
|
||||
if (is_numeric($key))
|
||||
$this->bindings []= $val;
|
||||
else
|
||||
$this->bindings[$key] = $val;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->bindings []= $arg;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function raw($raw)
|
||||
{
|
||||
$this->sql .= $raw . ' ';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function open()
|
||||
{
|
||||
$this->sql .= '(';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->sql .= ') ';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function surround($raw)
|
||||
{
|
||||
$this->sql .= '(' . $raw . ') ';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function genSlots($bindings)
|
||||
{
|
||||
if (empty($bindings))
|
||||
return $this;
|
||||
$this->sql .= '(';
|
||||
$this->sql .= join(',', array_fill(0, count($bindings), '?'));
|
||||
$this->sql .= ') ';
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBindings()
|
||||
{
|
||||
return $this->bindings;
|
||||
}
|
||||
|
||||
public function getSql()
|
||||
{
|
||||
return trim($this->sql);
|
||||
}
|
||||
}
|
31
src/Upgrades/mysql/Upgrade9.sql
Normal file
31
src/Upgrades/mysql/Upgrade9.sql
Normal file
|
@ -0,0 +1,31 @@
|
|||
UPDATE post SET file_hash = orig_name WHERE type = 3;
|
||||
|
||||
CREATE TRIGGER post_tag_update AFTER UPDATE ON post_tag FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET tag_count = tag_count + 1 WHERE post.id = new.post_id;
|
||||
UPDATE post SET tag_count = tag_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER favoritee_update AFTER UPDATE ON favoritee FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET fav_count = fav_count + 1 WHERE post.id = new.post_id;
|
||||
UPDATE post SET fav_count = fav_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER comment_update AFTER UPDATE ON comment FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET comment_count = comment_count + 1 WHERE post.id = new.post_id;
|
||||
UPDATE post SET comment_count = comment_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
ALTER TABLE usertoken RENAME TO user_token;
|
||||
|
||||
DROP TRIGGER post_score_update;
|
||||
ALTER TABLE postscore RENAME TO post_score;
|
||||
|
||||
CREATE TRIGGER post_score_update AFTER UPDATE ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score + new.score WHERE post.id = new.post_id;
|
||||
UPDATE post SET score = post.score - old.score WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
31
src/Upgrades/sqlite/Upgrade9.sql
Normal file
31
src/Upgrades/sqlite/Upgrade9.sql
Normal file
|
@ -0,0 +1,31 @@
|
|||
UPDATE post SET file_hash = orig_name WHERE type = 3;
|
||||
|
||||
CREATE TRIGGER post_tag_update AFTER UPDATE ON post_tag FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET tag_count = tag_count + 1 WHERE post.id = new.post_id;
|
||||
UPDATE post SET tag_count = tag_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER favoritee_update AFTER UPDATE ON favoritee FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET fav_count = fav_count + 1 WHERE post.id = new.post_id;
|
||||
UPDATE post SET fav_count = fav_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER comment_update AFTER UPDATE ON comment FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET comment_count = comment_count + 1 WHERE post.id = new.post_id;
|
||||
UPDATE post SET comment_count = comment_count - 1 WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
||||
ALTER TABLE usertoken RENAME TO user_token;
|
||||
|
||||
DROP TRIGGER post_score_update;
|
||||
ALTER TABLE postscore RENAME TO post_score;
|
||||
|
||||
CREATE TRIGGER post_score_update AFTER UPDATE ON post_score FOR EACH ROW
|
||||
BEGIN
|
||||
UPDATE post SET score = post.score + new.score WHERE post.id = new.post_id;
|
||||
UPDATE post SET score = post.score - old.score WHERE post.id = old.post_id;
|
||||
END;
|
||||
|
|
@ -9,12 +9,12 @@
|
|||
$currentGroup = null;
|
||||
foreach ($this->context->transport->comments as $comment)
|
||||
{
|
||||
if ($comment->post_id != $currentGroupPostId)
|
||||
if ($comment->postId != $currentGroupPostId)
|
||||
{
|
||||
unset($currentGroup);
|
||||
$currentGroup = [];
|
||||
$currentGroupPostId = $comment->post_id;
|
||||
$posts[$comment->post_id] = $comment->post;
|
||||
$currentGroupPostId = $comment->postId;
|
||||
$posts[$comment->postId] = $comment->getPost();
|
||||
$groups[] = &$currentGroup;
|
||||
}
|
||||
$currentGroup []= $comment;
|
||||
|
@ -23,7 +23,7 @@
|
|||
<?php foreach ($groups as $group): ?>
|
||||
<div class="comment-group">
|
||||
<div class="post-wrapper">
|
||||
<?php $this->context->post = $posts[reset($group)->post_id] ?>
|
||||
<?php $this->context->post = $posts[reset($group)->postId] ?>
|
||||
<?php echo $this->renderFile('post-small') ?>
|
||||
</div>
|
||||
<div class="comments">
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
<div class="comment">
|
||||
<div class="avatar">
|
||||
<?php if ($this->context->comment->commenter): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->comment->commenter->name]) ?>">
|
||||
<img src="<?php echo htmlspecialchars($this->context->comment->commenter->getAvatarUrl(40)) ?>" alt="<?php echo $this->context->comment->commenter->name ?>"/>
|
||||
<?php $commenter = $this->context->comment->getCommenter() ?>
|
||||
<?php if ($commenter): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $commenter->name]) ?>">
|
||||
<img src="<?php echo htmlspecialchars($commenter->getAvatarUrl(40)) ?>" alt="<?php echo $commenter->name ?>"/>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="<?php echo Model_User::getAnonymousName() ?>">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="<?php echo UserModel::getAnonymousName() ?>">
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
<div class="header">
|
||||
<span class="nickname">
|
||||
<?php if ($this->context->comment->commenter): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->comment->commenter->name]) ?>">
|
||||
<?php echo $this->context->comment->commenter->name ?>
|
||||
<?php if ($commenter): ?>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $commenter->name]) ?>">
|
||||
<?php echo $commenter->name ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?php echo Model_User::getAnonymousName() ?>
|
||||
<?php echo UserModel::getAnonymousName() ?>
|
||||
<?php endif ?>
|
||||
</span>
|
||||
|
||||
<span class="date">
|
||||
<?php echo date('Y-m-d H:i', $this->context->comment->comment_date) ?>
|
||||
<?php echo date('Y-m-d H:i', $this->context->comment->commentDate) ?>
|
||||
</span>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($this->context->comment->commenter))): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::DeleteComment, PrivilegesHelper::getIdentitySubPrivilege($commenter))): ?>
|
||||
<span class="delete">
|
||||
<a class="simple-action confirmable" href="<?php echo \Chibi\UrlHelper::route('comment', 'delete', ['id' => $this->context->comment->id]) ?>" data-confirm-text="Are you sure you want to delete this comment?">
|
||||
delete
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<div class="left">
|
||||
Tags:
|
||||
<ul class="tags">
|
||||
<?php $tags = $this->context->featuredPost->sharedTag ?>
|
||||
<?php $tags = $this->context->featuredPost->getTags() ?>
|
||||
<?php uasort($tags, function($a, $b) { return strnatcasecmp($a->name, $b->name); }) ?>
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<li>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<footer>
|
||||
<div class="main-wrapper">
|
||||
<span>Load: <?php echo sprintf('%.05f', microtime(true) - $this->context->startTime) ?>s</span>
|
||||
<span>Queries: <?php echo count(queryLogger()->getLogs()) ?></span>
|
||||
<span>Queries: <?php echo count(Database::getLogs()) ?></span>
|
||||
<span><a href="<?php echo SZURU_LINK ?>">szurubooru v<?php echo SZURU_VERSION ?></a></span>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ListLogs)): ?>
|
||||
<span><a href="<?php echo \Chibi\UrlHelper::route('log', 'list') ?>">Logs</a></span>
|
||||
|
@ -51,7 +51,15 @@
|
|||
<?php if ($this->config->misc->debugQueries): ?>
|
||||
<hr>
|
||||
<div class="main-wrapper">
|
||||
<pre class="debug"><?php echo join('<br>', array_map(function($x) { return preg_replace('/\s+/', ' ', $x); }, queryLogger()->getLogs())) ?></pre>
|
||||
<pre class="debug">
|
||||
<?php foreach (Database::getLogs() as $query)
|
||||
{
|
||||
$bindings = [];
|
||||
foreach ($query->getBindings() as $k => $v)
|
||||
$bindings []= $k . '=' . $v;
|
||||
printf('<p>%s [%s]</p>', htmlspecialchars($query->getSql()), join(', ', $bindings));
|
||||
} ?>
|
||||
</pre>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</footer>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<form action="<?php echo \Chibi\UrlHelper::route('post', 'edit', ['id' => $this->context->transport->post->id]) ?>" method="post" enctype="multipart/form-data" class="edit-post aligned unit">
|
||||
<h1>edit post</h1>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSafety, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="safety">
|
||||
<label class="left">Safety:</label>
|
||||
<?php foreach (PostSafety::getAll() as $safety): ?>
|
||||
|
@ -12,29 +12,29 @@
|
|||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostTags, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="tags">
|
||||
<label class="left" for="tags">Tags:</label>
|
||||
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags…" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->sharedTag)) ?>"/></div>
|
||||
<div class="input-wrapper"><input type="text" name="tags" id="tags" placeholder="enter some tags…" value="<?php echo join(',', array_map(function($tag) { return $tag->name; }, $this->context->transport->post->getTags())) ?>"/></div>
|
||||
</div>
|
||||
<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))): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostSource, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="source">
|
||||
<label class="left" for="source">Source:</label>
|
||||
<div class="input-wrapper"><input type="text" name="source" id="source" value="<?php echo $this->context->transport->post->source ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostRelations, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="relations">Relations:</label>
|
||||
<div class="input-wrapper"><input type="text" name="relations" id="relations" placeholder="id1,id2,…" value="<?php echo join(',', array_map(function($post) { return $post->id; }, $this->context->transport->post->via('crossref')->sharedPost)) ?>"/></div>
|
||||
<div class="input-wrapper"><input type="text" name="relations" id="relations" placeholder="id1,id2,…" value="<?php echo join(',', array_map(function($post) { return $post->id; }, $this->context->transport->post->getRelations())) ?>"/></div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostFile, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="url">
|
||||
<label class="left" for="url">File:</label>
|
||||
<div class="input-wrapper"><input type="text" name="url" id="url" placeholder="Some url…"/></div>
|
||||
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader))): ?>
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::EditPostThumb, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader()))): ?>
|
||||
<div class="thumb">
|
||||
<label class="left" for="thumb">Thumb:</label>
|
||||
<div class="input-wrapper">
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
|
||||
<?php elseif ($post->type == PostType::Flash): ?>
|
||||
|
||||
<iframe width="<?php echo $post->image_width ?>" height="<?php echo $post->image_height ?>" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $post->name]) ?>"> </iframe>
|
||||
<iframe width="<?php echo $post->imageWidth ?>" height="<?php echo $post->imageHeight ?>" src="<?php echo \Chibi\UrlHelper::route('post', 'retrieve', ['name' => $post->name]) ?>"> </iframe>
|
||||
|
||||
<?php elseif ($post->type == PostType::Youtube): ?>
|
||||
|
||||
<iframe style="width: 800px; height: 600px; border: 0;" src="//www.youtube.com/embed/<?php echo $post->orig_name ?>" allowfullscreen></iframe>
|
||||
<iframe style="width: 800px; height: 600px; border: 0;" src="//www.youtube.com/embed/<?php echo $post->origName ?>" allowfullscreen></iframe>
|
||||
|
||||
<?php endif ?>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<?php endif ?>
|
||||
|
||||
<?php if ($this->context->user->hasEnabledPostTagTitles()): ?>
|
||||
<a title="<?php echo TextHelper::reprTags($this->context->post->sharedTag) ?>" class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
<a title="<?php echo TextHelper::reprTags($this->context->post->getTags()) ?>" class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
<?php else: ?>
|
||||
<a class="link" href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $this->context->post->id]) ?>">
|
||||
<?php endif ?>
|
||||
|
@ -24,8 +24,8 @@
|
|||
$x =
|
||||
[
|
||||
'score' => $this->context->post->score,
|
||||
'comments' => $this->context->post->comment_count,
|
||||
'favs' => $this->context->post->fav_count,
|
||||
'comments' => $this->context->post->commentCount,
|
||||
'favs' => $this->context->post->favCount,
|
||||
];
|
||||
?>
|
||||
<?php if (!empty($x)): ?>
|
||||
|
|
|
@ -33,9 +33,9 @@
|
|||
</nav>
|
||||
|
||||
<div class="unit tags">
|
||||
<h1>tags (<?php echo count($this->context->transport->post->sharedTag) ?>)</h1>
|
||||
<h1>tags (<?php echo count($this->context->transport->post->getTags()) ?>)</h1>
|
||||
<ul>
|
||||
<?php $tags = $this->context->transport->post->sharedTag ?>
|
||||
<?php $tags = $this->context->transport->post->getTags() ?>
|
||||
<?php uasort($tags, function($a, $b) { return strnatcasecmp($a->name, $b->name); }) ?>
|
||||
<?php foreach ($tags as $tag): ?>
|
||||
<li title="<?php echo $tag->name ?>">
|
||||
|
@ -55,17 +55,18 @@
|
|||
|
||||
<div class="key-value uploader">
|
||||
<span class="key">Uploader:</span>
|
||||
<?php if ($this->context->transport->post->uploader): ?>
|
||||
<span class="value" title="<?php echo $val = $this->context->transport->post->uploader->name ?>">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $this->context->transport->post->uploader->name]) ?>">
|
||||
<img src="<?php echo htmlentities($this->context->transport->post->uploader->getAvatarUrl(16)) ?>" alt="<?php echo $this->context->transport->post->uploader->name ?>"/>
|
||||
<?php $uploader = $this->context->transport->post->getUploader() ?>
|
||||
<?php if ($uploader): ?>
|
||||
<span class="value" title="<?php echo $val = $uploader->name ?>">
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $uploader->name]) ?>">
|
||||
<img src="<?php echo htmlentities($uploader->getAvatarUrl(16)) ?>" alt="<?php echo $uploader->name ?>"/>
|
||||
<?php echo $val ?>
|
||||
</a>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="value" title="<?php echo Model_User::getAnonymousName() ?>">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="<?php echo Model_User::getAnonymousName() ?>"/>
|
||||
<?php echo Model_User::getAnonymousName() ?>
|
||||
<span class="value" title="<?php echo UserModel::getAnonymousName() ?>">
|
||||
<img src="<?php echo \Chibi\UrlHelper::absoluteUrl('/media/img/pixel.gif') ?>" alt="<?php echo UserModel::getAnonymousName() ?>"/>
|
||||
<?php echo UserModel::getAnonymousName() ?>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
@ -109,17 +110,17 @@
|
|||
|
||||
<div class="key-value date">
|
||||
<span class="key">Date:</span>
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d H:i', $this->context->transport->post->upload_date) ?>">
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d H:i', $this->context->transport->post->uploadDate) ?>">
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if ($this->context->transport->post->image_width > 0): ?>
|
||||
<?php if ($this->context->transport->post->imageWidth > 0): ?>
|
||||
<div class="key-value dim">
|
||||
<span class="key">Dimensions:</span>
|
||||
<span class="value" title="<?php echo $val = sprintf('%dx%d',
|
||||
$this->context->transport->post->image_width,
|
||||
$this->context->transport->post->image_height) ?>">
|
||||
$this->context->transport->post->imageWidth,
|
||||
$this->context->transport->post->imageHeight) ?>">
|
||||
<?php echo $val ?>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -146,18 +147,18 @@
|
|||
<?php echo isset($mimes[$mime]) ? $mimes[$mime] : 'unknown' ?>
|
||||
</span>
|
||||
<span class="size">
|
||||
<?php echo TextHelper::useBytesUnits($this->context->transport->post->file_size) ?>
|
||||
<?php echo TextHelper::useBytesUnits($this->context->transport->post->fileSize) ?>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<?php if (count($this->context->transport->post->ownFavoritee) > 0): ?>
|
||||
<?php if (count($this->context->transport->post->getFavorites()) > 0): ?>
|
||||
<div class="unit favorites">
|
||||
<h1>favorites (<?php echo count($this->context->transport->post->ownFavoritee) ?>)</h1>
|
||||
<h1>favorites (<?php echo count($this->context->transport->post->getFavorites()) ?>)</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->via('favoritee')->sharedUser as $user): ?>
|
||||
<?php foreach ($this->context->transport->post->getFavorites() as $user): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('user', 'view', ['name' => $user->name]) ?>" title="<?php echo $user->name ?>">
|
||||
<img src="<?php echo htmlspecialchars($user->getAvatarUrl()) ?>" alt="<?php echo $user->name ?>">
|
||||
|
@ -168,11 +169,11 @@
|
|||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (count($this->context->transport->post->via('crossref')->sharedPost)): ?>
|
||||
<?php if (count($this->context->transport->post->getRelations())): ?>
|
||||
<div class="relations unit">
|
||||
<h1>related</h1>
|
||||
<ul>
|
||||
<?php foreach ($this->context->transport->post->via('crossref')->sharedPost as $relatedPost): ?>
|
||||
<?php foreach ($this->context->transport->post->getRelations() as $relatedPost): ?>
|
||||
<li>
|
||||
<a href="<?php echo \Chibi\UrlHelper::route('post', 'view', ['id' => $relatedPost->id]) ?>">
|
||||
@<?php echo $relatedPost->id ?>
|
||||
|
@ -193,7 +194,7 @@
|
|||
$editPostPrivileges = array_fill_keys($editPostPrivileges, false);
|
||||
foreach (array_keys($editPostPrivileges) as $privilege)
|
||||
{
|
||||
if (PrivilegesHelper::confirm($privilege, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader)))
|
||||
if (PrivilegesHelper::confirm($privilege, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||
$editPostPrivileges[$privilege] = true;
|
||||
}
|
||||
$canEditAnything = count(array_filter($editPostPrivileges)) > 0;
|
||||
|
@ -231,7 +232,7 @@
|
|||
];
|
||||
}
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader)))
|
||||
if (PrivilegesHelper::confirm(Privilege::HidePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||
{
|
||||
if ($this->context->transport->post->hidden)
|
||||
{
|
||||
|
@ -288,7 +289,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->uploader)))
|
||||
if (PrivilegesHelper::confirm(Privilege::DeletePost, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->post->getUploader())))
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
|
@ -315,11 +316,11 @@
|
|||
<?php endif ?>
|
||||
|
||||
<div class="comments-wrapper">
|
||||
<?php if (!empty($this->context->transport->post->ownComment)): ?>
|
||||
<?php if (!empty($this->context->transport->post->getComments())): ?>
|
||||
<div class="comments unit">
|
||||
<h1>comments (<?php echo $this->context->transport->post->countOwn('comment') ?>)</h1>
|
||||
<h1>comments (<?php echo count($this->context->transport->post->getComments()) ?>)</h1>
|
||||
<div class="comments">
|
||||
<?php foreach ($this->context->transport->post->ownComment as $comment): ?>
|
||||
<?php foreach ($this->context->transport->post->getComments() as $comment): ?>
|
||||
<?php $this->context->comment = $comment ?>
|
||||
<?php echo $this->renderFile('comment-small') ?>
|
||||
<?php endforeach ?>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<div class="input-wrapper"><select name="access-rank" id="access-rank">
|
||||
<?php foreach (AccessRank::getAll() as $rank): ?>
|
||||
<?php if ($rank == AccessRank::Nobody) continue ?>
|
||||
<?php if (($this->context->suppliedAccessRank != '' and $rank == $this->context->suppliedAccessRank) or ($this->context->suppliedAccessRank == '' and $rank == $this->context->transport->user->access_rank)): ?>
|
||||
<?php if (($this->context->suppliedAccessRank != '' and $rank == $this->context->suppliedAccessRank) or ($this->context->suppliedAccessRank == '' and $rank == $this->context->transport->user->accessRank)): ?>
|
||||
<option value="<?php echo $rank ?>" selected="selected">
|
||||
<?php else: ?>
|
||||
<option value="<?php echo $rank ?>">
|
||||
|
|
|
@ -41,9 +41,9 @@
|
|||
</a>
|
||||
</h1>
|
||||
|
||||
<div class="date-registered">Date registered: <?php echo date('Y-m-d H:i', $user->join_date) ?></div>
|
||||
<div class="fav-count">Favorite count: <?php echo $user->countOwn('favoritee') ?></div>
|
||||
<div class="post-count">Post count: <?php echo $user->alias('uploader')->countOwn('post') ?></div>
|
||||
<div class="date-registered">Date registered: <?php echo date('Y-m-d H:i', $user->joinDate) ?></div>
|
||||
<div class="fav-count">Favorite count: <?php echo $user->getFavoriteCount() ?></div>
|
||||
<div class="post-count">Post count: <?php echo $user->getCommentCount() ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
|
|
|
@ -11,18 +11,18 @@
|
|||
|
||||
<div class="key-value join-date">
|
||||
<span class="key">Joined:</span>
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d', $this->context->transport->user->join_date) ?>"><?php echo $val ?></span>
|
||||
<span class="value" title="<?php echo $val = date('Y-m-d', $this->context->transport->user->joinDate) ?>"><?php echo $val ?></span>
|
||||
</div>
|
||||
|
||||
<div class="key-value access-rank">
|
||||
<span class="key">Access rank:</span>
|
||||
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(AccessRank::toString($this->context->transport->user->access_rank)) ?>"><?php echo $val ?></span>
|
||||
<span class="value" title="<?php echo $val = TextHelper::camelCaseToHumanCase(AccessRank::toString($this->context->transport->user->accessRank)) ?>"><?php echo $val ?></span>
|
||||
</div>
|
||||
|
||||
<?php if (PrivilegesHelper::confirm(Privilege::ViewUserEmail, PrivilegesHelper::getIdentitySubPrivilege($this->context->transport->user))): ?>
|
||||
<div class="key-value email">
|
||||
<span class="key">E-mail:</span>
|
||||
<span class="value" title="<?php echo $val = ($this->context->transport->user->email_unconfirmed ? '(unconfirmed) ' . $this->context->transport->user->email_unconfirmed : $this->context->transport->user->email_confirmed ?: 'none specified') ?>"><?php echo $val ?></span>
|
||||
<span class="value" title="<?php echo $val = ($this->context->transport->user->emailUnconfirmed ? '(unconfirmed) ' . $this->context->transport->user->emailUnconfirmed : $this->context->transport->user->emailConfirmed ?: 'none specified') ?>"><?php echo $val ?></span>
|
||||
<br>(only you and staff can see this)
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
@ -113,7 +113,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (PrivilegesHelper::confirm(Privilege::AcceptUserRegistration) and !$this->context->transport->user->staff_confirmed and $this->config->registration->staffActivation)
|
||||
if (PrivilegesHelper::confirm(Privilege::AcceptUserRegistration) and !$this->context->transport->user->staffConfirmed and $this->config->registration->staffActivation)
|
||||
{
|
||||
$options []=
|
||||
[
|
||||
|
|
14
src/core.php
14
src/core.php
|
@ -12,7 +12,6 @@ ini_set('memory_limit', '128M');
|
|||
|
||||
//basic include calls, autoloader init
|
||||
require_once $rootDir . 'lib' . DS . 'php-markdown' . DS . 'Michelf' . DS . 'Markdown.php';
|
||||
require_once $rootDir . 'lib' . DS . 'redbean' . DS . 'RedBean' . DS . 'redbean.inc.php';
|
||||
require_once $rootDir . 'lib' . DS . 'chibi-core' . DS . 'Facade.php';
|
||||
\Chibi\AutoLoader::init(__DIR__);
|
||||
|
||||
|
@ -40,20 +39,9 @@ $context = \Chibi\Registry::getContext();
|
|||
$context->startTime = $startTime;
|
||||
$context->rootDir = $rootDir;
|
||||
|
||||
//load database
|
||||
R::setup($config->main->dbDriver . ':' . TextHelper::absolutePath($config->main->dbLocation), $config->main->dbUser, $config->main->dbPass);
|
||||
R::freeze(true);
|
||||
Database::connect($config->main->dbDriver, TextHelper::absolutePath($config->main->dbLocation), $config->main->dbUser, $config->main->dbPass);
|
||||
|
||||
//wire models
|
||||
foreach (\Chibi\AutoLoader::getAllIncludablePaths() as $path)
|
||||
if (preg_match('/Model/', $path))
|
||||
\Chibi\AutoLoader::safeInclude($path);
|
||||
|
||||
function queryLogger()
|
||||
{
|
||||
static $queryLogger = null;
|
||||
if ($queryLogger === null)
|
||||
$queryLogger = RedBean_Plugin_QueryLogger::getInstanceAndAttach(R::getDatabaseAdapter());
|
||||
return $queryLogger;
|
||||
}
|
||||
queryLogger();
|
||||
|
|
|
@ -4,7 +4,7 @@ $config = \Chibi\Registry::getConfig();
|
|||
|
||||
function getDbVersion()
|
||||
{
|
||||
$dbVersion = Model_Property::get(Model_Property::DbVersion);
|
||||
$dbVersion = PropertyModel::get(PropertyModel::DbVersion);
|
||||
if (strpos($dbVersion, '.') !== false)
|
||||
{
|
||||
list ($dbVersionMajor, $dbVersionMinor) = explode('.', $dbVersion);
|
||||
|
@ -50,7 +50,7 @@ foreach ($upgrades as $upgradePath)
|
|||
{
|
||||
try
|
||||
{
|
||||
R::exec($query);
|
||||
Database::query((new SqlQuery)->raw($query));
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
|
@ -58,10 +58,10 @@ foreach ($upgrades as $upgradePath)
|
|||
echo $query . PHP_EOL;
|
||||
die;
|
||||
}
|
||||
Model_Property::set(Model_Property::DbVersion, $upgradeVersionMajor . '.' . $upgradeVersionMinor);
|
||||
PropertyModel::set(PropertyModel::DbVersion, $upgradeVersionMajor . '.' . $upgradeVersionMinor);
|
||||
}
|
||||
}
|
||||
Model_Property::set(Model_Property::DbVersion, $upgradeVersionMajor);
|
||||
PropertyModel::set(PropertyModel::DbVersion, $upgradeVersionMajor);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue