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:
Marcin Kurczewski 2013-12-18 15:10:53 +01:00
parent 8c0c5269c4
commit 5607cfc353
63 changed files with 2603 additions and 1592 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -402,8 +402,11 @@ button:hover {
}
pre.debug {
margin-left: 1em;
text-align: left;
color: black;
white-space: normal;
text-indent: -1em;
}
.spoiler:before,

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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) . ' &ndash; ' . TextHelper::reprTags($post->sharedTag);
$this->context->subTitle = 'showing ' . TextHelper::reprPost($post) . ' &ndash; ' . 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);

View file

@ -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]));
}
}

View file

@ -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
View 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;
}
}
}
}

View file

@ -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

View file

@ -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');
}

View file

@ -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())]);
}
}

View 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);
}
}
}

View file

@ -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);
}
}

View file

@ -1,5 +0,0 @@
<?php
interface AbstractQueryBuilder
{
public static function build($dbQuery, $query);
}

101
src/Models/CommentModel.php Normal file
View 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;
}
}

View 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]);
}
}

View 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;
}
}

View 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);
}
}
}

View 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'];
}
}

View 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;
}
}

View 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
View file

@ -0,0 +1,5 @@
<?php
interface IModel
{
static function getTableName();
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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');
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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
View 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();

View 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;
});
}
}

View 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'];
}
}

View 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();
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View 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
View 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
View 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
View 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
View 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);
}
}

View 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;

View 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;

View file

@ -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">

View file

@ -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

View file

@ -16,7 +16,7 @@
<div class="left">
Tags:&nbsp;
<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>

View file

@ -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>

View file

@ -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&hellip;" 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&hellip;" 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,&hellip;" 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,&hellip;" 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&hellip;"/></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">

View file

@ -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 ?>

View file

@ -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)): ?>

View file

@ -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 ?>

View file

@ -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 ?>">

View file

@ -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 ?>

View file

@ -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 []=
[

View file

@ -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();

View file

@ -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
{