Models: enhanced entities filtering

This commit is contained in:
Marcin Kurczewski 2013-10-28 11:19:15 +01:00
parent 49b91b7f55
commit 9e6716021a
18 changed files with 622 additions and 494 deletions

View file

@ -161,7 +161,7 @@ $(function()
if (term != '')
$.get(searchInput.attr('data-autocomplete-url') + '?json', {filter: term}, function(data)
{
response($.map(data.tags, function(tag) { return { label: tag, value: tag }; }));
response($.map(data.tags, function(tag) { return { label: tag.name, value: tag.name }; }));
});
},
focus: function()
@ -191,13 +191,18 @@ function getTagItOptions()
function(request, response)
{
var term = request.term.toLowerCase();
var results = $.grep(this.options.availableTags, function(a)
var tags = $.map(this.options.availableTags, function(a)
{
return a.name;
});
var results = $.grep(tags, function(a)
{
if (term.length < 3)
return a.toLowerCase().indexOf(term) == 0;
else
return a.toLowerCase().indexOf(term) != -1;
});
results = results.slice(0, 15);
if (!this.options.allowDuplicates)
results = this._subtractArray(results, this.assignedTags());
response(results);

View file

@ -21,7 +21,8 @@ class Bootstrap
'core.js',
];
$this->context->layoutName = isset($_GET['json'])
$this->context->json = isset($_GET['json']);
$this->context->layoutName = $this->context->json
? 'layout-json'
: 'layout-normal';
$this->context->transport = new StdClass;

View file

@ -12,35 +12,19 @@ class CommentController
$this->context->stylesheets []= 'comment-list.css';
$this->context->stylesheets []= 'comment-small.css';
$this->context->stylesheets []= 'paginator.css';
$this->context->subTitle = 'comments';
if ($this->context->user->hasEnabledEndlessScrolling())
$this->context->scripts []= 'paginator-endless.js';
$page = intval($page);
$commentsPerPage = intval($this->config->comments->commentsPerPage);
$this->context->subTitle = 'comments';
PrivilegesHelper::confirmWithException(Privilege::ListComments);
$buildDbQuery = function($dbQuery)
{
$dbQuery->from('comment');
$dbQuery->orderBy('comment_date')->desc();
};
$countDbQuery = R::$f->begin();
$countDbQuery->select('COUNT(1)')->as('count');
$buildDbQuery($countDbQuery);
$commentCount = intval($countDbQuery->get('row')['count']);
$commentCount = Model_Comment::getEntityCount(null);
$pageCount = ceil($commentCount / $commentsPerPage);
$page = max(1, min($pageCount, $page));
$comments = Model_Comment::getEntities(null, $commentsPerPage, $page);
$searchDbQuery = R::$f->begin();
$searchDbQuery->select('comment.*');
$buildDbQuery($searchDbQuery);
$searchDbQuery->limit('?')->put($commentsPerPage);
$searchDbQuery->offset('?')->put(($page - 1) * $commentsPerPage);
$comments = $searchDbQuery->get();
$comments = R::convertToBeans('comment', $comments);
R::preload($comments, ['commenter' => 'user', 'post', 'post.uploader' => 'user']);
$this->context->postGroups = true;
$this->context->transport->paginator = new StdClass;

View file

@ -82,79 +82,11 @@ class PostController
$this->context->transport->searchQuery = $query;
PrivilegesHelper::confirmWithException(Privilege::ListPosts);
$buildDbQuery = function($dbQuery, $query)
{
$dbQuery
->addSql(', ')
->open()
->select('COUNT(1)')
->from('comment')
->where('comment.post_id = post.id')
->close()
->as('comment_count');
$dbQuery
->addSql(', ')
->open()
->select('COUNT(1)')
->from('favoritee')
->where('favoritee.post_id = post.id')
->close()
->as('fav_count');
$dbQuery
->addSql(', ')
->open()
->select('COUNT(1)')
->from('post_tag')
->where('post_tag.post_id = post.id')
->close()
->as('tag_count');
$dbQuery->from('post');
/* safety */
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
{
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
$this->context->user->hasEnabledSafety($safety);
});
$dbQuery->where('safety')->in('(' . R::genSlots($allowedSafety) . ')');
foreach ($allowedSafety as $s)
$dbQuery->put($s);
/* hidden */
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
$dbQuery->andNot('hidden');
/* query tokens */
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
if (count($tokens) > $this->config->browsing->maxSearchTokens)
throw new SimpleException('Too many search tokens (maximum: ' . $this->config->browsing->maxSearchTokens . ')');
/* tokens */
$this->decorateSearchQuery($dbQuery, $tokens);
};
$countDbQuery = R::$f->begin();
$countDbQuery->select('COUNT(1)')->as('count');
$buildDbQuery($countDbQuery, $query);
$postCount = intval($countDbQuery->get('row')['count']);
$postCount = Model_Post::getEntityCount($query);
$pageCount = ceil($postCount / $postsPerPage);
$page = max(1, min($pageCount, $page));
$posts = Model_Post::getEntities($query, $postsPerPage, $page);
$searchDbQuery = R::$f->begin();
$searchDbQuery->select('post.*');
$buildDbQuery($searchDbQuery, $query);
$searchDbQuery->limit('?')->put($postsPerPage);
$searchDbQuery->offset('?')->put(($page - 1) * $postsPerPage);
$posts = $searchDbQuery->get();
$posts = R::convertToBeans('post', $posts);
R::preload($posts, ['uploader' => 'user']);
$this->context->transport->paginator = new StdClass;
$this->context->transport->paginator->page = $page;
$this->context->transport->paginator->pageCount = $pageCount;
@ -614,20 +546,6 @@ class PostController
if ($fav->user->id == $this->context->user->id)
$favorite = true;
$dbQuery = R::$f->begin();
$dbQuery->select('tag.name, COUNT(1) AS count');
$dbQuery->from('tag');
$dbQuery->innerJoin('post_tag');
$dbQuery->on('tag.id = post_tag.tag_id');
$dbQuery->where('tag.id')->in('(' . R::genSlots($post->sharedTag) . ')');
foreach ($post->sharedTag as $tag)
$dbQuery->put($tag->id);
$dbQuery->groupBy('tag.id');
$rows = $dbQuery->get();
$this->context->transport->tagDistribution = [];
foreach ($rows as $row)
$this->context->transport->tagDistribution[$row['name']] = $row['count'];
$this->context->stylesheets []= 'post-view.css';
$this->context->stylesheets []= 'comment-small.css';
$this->context->scripts []= 'post-view.js';
@ -780,249 +698,4 @@ class PostController
$this->context->transport->fileHash = 'post' . $post->file_hash;
$this->context->transport->filePath = $path;
}
private function decorateSearchQuery($dbQuery, $tokens)
{
$orderColumn = 'post.id';
$orderDir = 1;
$randomReset = true;
foreach ($tokens as $token)
{
if ($token{0} == '-')
{
$andFunc = 'andNot';
$token = substr($token, 1);
$neg = true;
}
else
{
$andFunc = 'and';
$neg = false;
}
$pos = strpos($token, ':');
if ($pos === false)
{
$val = $token;
$dbQuery
->$andFunc()
->exists()
->open()
->select('1')
->from('post_tag')
->innerJoin('tag')
->on('post_tag.tag_id = tag.id')
->where('post_id = post.id')
->and('LOWER(tag.name) = LOWER(?)')->put($val)
->close();
continue;
}
$key = substr($token, 0, $pos);
$val = substr($token, $pos + 1);
switch ($key)
{
case 'id':
$ids = preg_split('/[;,]/', $val);
$ids = array_map('intval', $ids);
$dbQuery->$andFunc('id')->in('(' . R::genSlots($ids) . ')');
foreach ($ids as $id)
$dbQuery->put($id);
break;
case 'idmin':
case 'idmax':
$operator = $key == 'idmin' ? '>=' : '<=';
$dbQuery->$andFunc('id ' . $operator . ' ?')->put(intval($val));
break;
case 'favmin':
case 'favmax':
$operator = $key == 'favmin' ? '>=' : '<=';
$dbQuery->$andFunc('fav_count ' . $operator . ' ?')->put(intval($val));
break;
case 'tagmin':
case 'tagmax':
$operator = $key == 'tagmin' ? '>=' : '<=';
$dbQuery->$andFunc('tag_count ' . $operator . ' ?')->put(intval($val));
break;
case 'commentmin':
case 'commentmax':
$operator = $key == 'commentmin' ? '>=' : '<=';
$dbQuery->$andFunc('comment_count ' . $operator . ' ?')->put(intval($val));
break;
case 'type':
switch ($val)
{
case 'swf':
$type = PostType::Flash;
break;
case 'img':
$type = PostType::Image;
break;
case 'yt':
case 'youtube':
$type = PostType::Youtube;
break;
default:
throw new SimpleException('Unknown type "' . $val . '"');
}
$dbQuery
->$andFunc('type = ?')
->put($type);
break;
case 'date':
case 'datemin':
case 'datemax':
list ($year, $month, $day) = explode('-', $val . '-0-0');
$yearMin = $yearMax = intval($year);
$monthMin = $monthMax = intval($month);
$monthMin = $monthMin ?: 1;
$monthMax = $monthMax ?: 12;
$dayMin = $dayMax = intval($day);
$dayMin = $dayMin ?: 1;
$dayMax = $dayMax ?: intval(date('t', mktime(0, 0, 0, $monthMax, 1, $year)));
$timeMin = mktime(0, 0, 0, $monthMin, $dayMin, $yearMin);
$timeMax = mktime(0, 0, -1, $monthMax, $dayMax+1, $yearMax);
if ($key == 'date')
{
$dbQuery
->$andFunc('upload_date >= ?')
->and('upload_date <= ?')
->put($timeMin)
->put($timeMax);
}
elseif ($key == 'datemin')
{
$dbQuery
->$andFunc('upload_date >= ?')
->put($timeMin);
}
elseif ($key == 'datemax')
{
$dbQuery
->$andFunc('upload_date <= ?')
->put($timeMax);
}
else
{
throw new Exception('Invalid key');
}
break;
case 'fav':
case 'favs':
case 'favoritee':
case 'favoriter':
$dbQuery
->$andFunc()
->exists()
->open()
->select('1')
->from('favoritee')
->innerJoin('user')
->on('favoritee.user_id = user.id')
->where('post_id = post.id')
->and('user.name = ?')->put($val)
->close();
break;
case 'submit':
case 'upload':
case 'uploader':
case 'uploaded':
$dbQuery
->$andFunc('uploader_id = ')
->open()
->select('user.id')
->from('user')
->where('name = ?')->put($val)
->close();
break;
case 'order':
if (substr($val, -4) == 'desc')
{
$orderDir = 1;
$val = rtrim(substr($val, 0, -4), ',');
}
elseif (substr($val, -3) == 'asc')
{
$orderDir = -1;
$val = rtrim(substr($val, 0, -3), ',');
}
if ($val{0} == '-')
{
$orderDir *= -1;
$val = substr($val, 1);
}
if ($neg)
{
$orderDir *= -1;
}
switch ($val)
{
case 'id':
$orderColumn = 'post.id';
break;
case 'date':
$orderColumn = 'post.upload_date';
break;
case 'comment':
case 'comments':
case 'commentcount':
$orderColumn = 'comment_count';
break;
case 'fav':
case 'favs':
case 'favcount':
$orderColumn = 'fav_count';
break;
case 'tag':
case 'tags':
case 'tagcount':
$orderColumn = 'tag_count';
break;
case 'random':
//seeding works like this: if you visit anything
//that triggers order other than random, the seed
//is going to reset. however, it stays the same as
//long as you keep visiting pages with order:random
//specified.
$randomReset = false;
if (!isset($_SESSION['browsing-seed']))
$_SESSION['browsing-seed'] = mt_rand();
$seed = $_SESSION['browsing-seed'];
$orderColumn = 'SUBSTR(id * ' . $seed .', LENGTH(id) + 2)';
break;
default:
throw new SimpleException('Unknown key "' . $val . '"');
}
break;
default:
throw new SimpleException('Unknown key "' . $key . '"');
}
}
if ($randomReset)
unset($_SESSION['browsing-seed']);
$dbQuery->orderBy($orderColumn);
if ($orderDir == 1)
$dbQuery->desc();
else
$dbQuery->asc();
}
}

View file

@ -12,35 +12,13 @@ class TagController
PrivilegesHelper::confirmWithException(Privilege::ListTags);
$suppliedFilter = InputHelper::get('filter');
$dbQuery = R::$f->begin();
$dbQuery->select('tag.*, COUNT(1) AS count');
$dbQuery->from('tag');
$dbQuery->innerJoin('post_tag');
$dbQuery->on('tag.id = post_tag.tag_id');
if ($suppliedFilter !== null)
{
if (strlen($suppliedFilter) >= 3)
$suppliedFilter = '%' . $suppliedFilter;
$suppliedFilter .= '%';
$dbQuery->where('LOWER(tag.name) LIKE LOWER(?)')->put($suppliedFilter);
}
$dbQuery->groupBy('tag.id');
$dbQuery->orderBy('LOWER(tag.name)')->asc();
if ($suppliedFilter !== null)
$dbQuery->limit(15);
$rows = $dbQuery->get();
$tags = R::convertToBeans('tag', $rows);
$tags = [];
$tagDistribution = [];
foreach ($rows as $row)
{
$tags []= strval($row['name']);
$tagDistribution[$row['name']] = intval($row['count']);
}
$tags = Model_Tag::getEntities($suppliedFilter, null, null);
$this->context->transport->tags = $tags;
$this->context->transport->tagDistribution = $tagDistribution;
if ($this->context->json)
$this->context->transport->tags = array_values(array_map(function($tag) {
return ['name' => $tag->name, 'count' => $tag->getPostCount()];
}, $this->context->transport->tags));
}
/**

View file

@ -56,58 +56,21 @@ class UserController
if ($this->context->user->hasEnabledEndlessScrolling())
$this->context->scripts []= 'paginator-endless.js';
$page = intval($page);
$usersPerPage = intval($this->config->browsing->usersPerPage);
$this->context->subTitle = 'users';
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
if ($sortStyle == '' or $sortStyle == 'alpha')
$sortStyle = 'alpha,asc';
if ($sortStyle == 'date')
$sortStyle = 'date,asc';
$buildDbQuery = function($dbQuery, $sortStyle)
{
$dbQuery->from('user');
$page = intval($page);
$usersPerPage = intval($this->config->browsing->usersPerPage);
$this->context->subTitle = 'users';
PrivilegesHelper::confirmWithException(Privilege::ListUsers);
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');
}
};
$countDbQuery = R::$f->begin();
$countDbQuery->select('COUNT(1)')->as('count');
$buildDbQuery($countDbQuery, $sortStyle);
$userCount = intval($countDbQuery->get('row')['count']);
$userCount = Model_User::getEntityCount($sortStyle);
$pageCount = ceil($userCount / $usersPerPage);
$page = max(1, min($pageCount, $page));
$users = Model_User::getEntities($sortStyle, $usersPerPage, $page);
$searchDbQuery = R::$f->begin();
$searchDbQuery->select('user.*');
$buildDbQuery($searchDbQuery, $sortStyle);
$searchDbQuery->limit('?')->put($usersPerPage);
$searchDbQuery->offset('?')->put(($page - 1) * $usersPerPage);
$users = $searchDbQuery->get();
$users = R::convertToBeans('user', $users);
$this->context->sortStyle = $sortStyle;
$this->context->transport->paginator = new StdClass;
$this->context->transport->paginator->page = $page;
@ -369,67 +332,19 @@ class UserController
$this->context->scripts []= 'paginator-endless.js';
$this->context->subTitle = $name;
$buildDbQuery = function($dbQuery, $user, $tab)
{
$dbQuery->from('post');
$query = '';
if ($tab == 'uploads')
$query = 'submit:' . $user->name;
elseif ($tab == 'favs')
$query = 'fav:' . $user->name;
else
throw new SimpleException('Wrong tab');
/* safety */
$allowedSafety = array_filter(PostSafety::getAll(), function($safety)
{
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
$this->context->user->hasEnabledSafety($safety);
});
$dbQuery->where('safety IN (' . R::genSlots($allowedSafety) . ')');
foreach ($allowedSafety as $s)
$dbQuery->put($s);
/* hidden */
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
$dbQuery->andNot('hidden');
/* tab */
switch ($tab)
{
case 'uploads':
$dbQuery
->and('uploader_id = ?')
->put($user->id);
break;
case 'favs':
$dbQuery
->and()
->exists()
->open()
->select('1')
->from('favoritee')
->where('post_id = post.id')
->and('favoritee.user_id = ?')
->put($user->id)
->close();
break;
}
};
$countDbQuery = R::$f->begin()->select('COUNT(*)')->as('count');
$buildDbQuery($countDbQuery, $user, $tab);
$postCount = intval($countDbQuery->get('row')['count']);
$postCount = Model_Post::getEntityCount($query);
$pageCount = ceil($postCount / $postsPerPage);
$page = max(1, min($pageCount, $page));
$posts = Model_Post::getEntities($query, $postsPerPage, $page);
$searchDbQuery = R::$f->begin()->select('*');
$buildDbQuery($searchDbQuery, $user, $tab);
$searchDbQuery->orderBy('id DESC')
->limit('?')
->put($postsPerPage)
->offset('?')
->put(($page - 1) * $postsPerPage);
$posts = $searchDbQuery->get();
$posts = R::convertToBeans('post', $posts);
R::preload($posts, ['uploader' => 'user']);
$this->context->transport->user = $user;
$this->context->transport->tab = $tab;
$this->context->transport->paginator = new StdClass;

View file

@ -0,0 +1,53 @@
<?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;
}
public static function getEntities($query, $perPage = null, $page = 1)
{
$table = static::getTableName();
$rows = self::getEntitiesRows($query, $perPage, $page);
$entities = R::convertToBeans($table, $rows);
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);
return intval($dbQuery->get('row')['count']);
}
}

View file

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

View file

@ -1,9 +1,19 @@
<?php
class Model_Comment extends RedBean_SimpleModel
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('comment', 'id = ?', [$key]);
$comment = R::findOne(self::getTableName(), 'id = ?', [$key]);
if (!$comment)
{
if ($throw)

View file

@ -0,0 +1,9 @@
<?php
class Model_Comment_QueryBuilder implements AbstractQueryBuilder
{
public static function build($dbQuery, $query)
{
$dbQuery->from('comment');
$dbQuery->orderBy('id')->desc();
}
}

View file

@ -1,11 +1,11 @@
<?php
class Model_Post extends RedBean_SimpleModel
class Model_Post extends AbstractModel
{
public static function locate($key, $disallowNumeric = false, $throw = true)
{
if (is_numeric($key) and !$disallowNumeric)
{
$post = R::findOne('post', 'id = ?', [$key]);
$post = R::findOne(self::getTableName(), 'id = ?', [$key]);
if (!$post)
{
if ($throw)
@ -15,7 +15,7 @@ class Model_Post extends RedBean_SimpleModel
}
else
{
$post = R::findOne('post', 'name = ?', [$key]);
$post = R::findOne(self::getTableName(), 'name = ?', [$key]);
if (!$post)
{
if ($throw)
@ -46,4 +46,14 @@ class Model_Post extends RedBean_SimpleModel
return $source;
}
public static function getTableName()
{
return 'post';
}
public static function getQueryBuilder()
{
return 'Model_Post_QueryBuilder';
}
}

View file

@ -0,0 +1,386 @@
<?php
class Model_Post_QueryBuilder implements AbstractQueryBuilder
{
protected static function attachTableCount($dbQuery, $tableName, $shortName)
{
$dbQuery
->addSql(', ')
->open()
->select('COUNT(1)')
->from($tableName)
->where($tableName . '.post_id = post.id')
->close()
->as($shortName . '_count');
}
protected static function attachCommentCount($dbQuery)
{
self::attachTableCount($dbQuery, 'comment', 'comment');
}
protected static function attachFavCount($dbQuery)
{
self::attachTableCount($dbQuery, 'favoritee', 'fav');
}
protected static function attachTagCount($dbQuery)
{
self::attachTableCount($dbQuery, 'post_tag', 'tag');
}
protected static function filterUserSafety($dbQuery)
{
$context = \Chibi\Registry::getContext();
$allowedSafety = array_filter(PostSafety::getAll(), function($safety) use ($context)
{
return PrivilegesHelper::confirm(Privilege::ListPosts, PostSafety::toString($safety)) and
$context->user->hasEnabledSafety($safety);
});
$dbQuery->addSql('safety')->in('(' . R::genSlots($allowedSafety) . ')');
foreach ($allowedSafety as $s)
$dbQuery->put($s);
}
protected static function filterUserHidden($dbQuery)
{
if (!PrivilegesHelper::confirm(Privilege::ListPosts, 'hidden'))
$dbQuery->not()->addSql('hidden');
else
$dbQuery->addSql('1');
}
protected static function filterChain($dbQuery)
{
if (isset($dbQuery->__chained))
$dbQuery->and();
else
$dbQuery->where();
$dbQuery->__chained = true;
}
protected static function filterNegate($dbQuery)
{
$dbQuery->not();
}
protected static function filterTag($dbQuery, $val)
{
$dbQuery
->exists()
->open()
->select('1')
->from('post_tag')
->innerJoin('tag')
->on('post_tag.tag_id = tag.id')
->where('post_id = post.id')
->and('LOWER(tag.name) = LOWER(?)')->put($val)
->close();
}
protected static function filterTokenId($dbQuery, $val)
{
$ids = preg_split('/[;,]/', $val);
$ids = array_map('intval', $ids);
$dbQuery->addSql('id')->in('(' . R::genSlots($ids) . ')');
foreach ($ids as $id)
$dbQuery->put($id);
}
protected static function filterTokenIdMin($dbQuery, $val)
{
$dbQuery->addSql('id >= ?')->put(intval($val));
}
protected static function filterTokenIdMax($dbQuery, $val)
{
$dbQuery->addSql('id <= ?')->put(intval($val));
}
protected static function filterTokenTagMin($dbQuery, $val)
{
$dbQuery->addSql('tag_count >= ?')->put(intval($val));
}
protected static function filterTokenTagMax($dbQuery, $val)
{
$dbQuery->addSql('tag_count <= ?')->put(intval($val));
}
protected static function filterTokenFavMin($dbQuery, $val)
{
$dbQuery->addSql('fav_count >= ?')->put(intval($val));
}
protected static function filterTokenFavMax($dbQuery, $val)
{
$dbQuery->addSql('fav_count <= ?')->put(intval($val));
}
protected static function filterTokenCommentMin($dbQuery, $val)
{
$dbQuery->addSql('comment_count >= ?')->put(intval($val));
}
protected static function filterTokenCommentMax($dbQuery, $val)
{
$dbQuery->addSql('comment_count <= ?')->put(intval($val));
}
protected static function filterTokenType($dbQuery, $val)
{
switch ($val)
{
case 'swf':
$type = PostType::Flash;
break;
case 'img':
$type = PostType::Image;
break;
case 'yt':
case 'youtube':
$type = PostType::Youtube;
break;
default:
throw new SimpleException('Unknown type "' . $val . '"');
}
$dbQuery->addSql('type = ?')->put($type);
}
protected static function __filterTokenDateParser($val)
{
list ($year, $month, $day) = explode('-', $val . '-0-0');
$yearMin = $yearMax = intval($year);
$monthMin = $monthMax = intval($month);
$monthMin = $monthMin ?: 1;
$monthMax = $monthMax ?: 12;
$dayMin = $dayMax = intval($day);
$dayMin = $dayMin ?: 1;
$dayMax = $dayMax ?: intval(date('t', mktime(0, 0, 0, $monthMax, 1, $year)));
$timeMin = mktime(0, 0, 0, $monthMin, $dayMin, $yearMin);
$timeMax = mktime(0, 0, -1, $monthMax, $dayMax+1, $yearMax);
return [$timeMin, $timeMax];
}
protected static function filterTokenDate($dbQuery, $val)
{
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
$dbQuery
->addSql('upload_date >= ?')->and('upload_date <= ?')
->put($timeMin)
->put($timeMax);
}
protected static function filterTokenDateMin($dbQuery, $val)
{
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
$dbQuery->addSql('upload_date >= ?')->put($timeMin);
}
protected static function filterTokenDateMax($dbQuery, $val)
{
list ($timeMin, $timeMax) = self::__filterTokenDateParser($val);
$dbQuery->addSql('upload_date <= ?')->put($timeMax);
}
protected static function filterTokenFav($dbQuery, $val)
{
$dbQuery
->exists()
->open()
->select('1')
->from('favoritee')
->innerJoin('user')
->on('favoritee.user_id = user.id')
->where('post_id = post.id')
->and('user.name = ?')->put($val)
->close();
}
protected static function filterTokenFavs($dbQuery, $val)
{
return self::filterTokenFav($dbQuery, $val);
}
protected static function filterTokenFavitee($dbQuery, $val)
{
return self::filterTokenFav($dbQuery, $val);
}
protected static function filterTokenFaviter($dbQuery, $val)
{
return self::filterTokenFav($dbQuery, $val);
}
protected static function filterTokenSubmit($dbQuery, $val)
{
$dbQuery
->addSql('uploader_id = ')
->open()
->select('user.id')
->from('user')
->where('name = ?')->put($val)
->close();
}
protected static function filterTokenUploader($dbQuery, $val)
{
return self::filterTokenSubmit($dbQuery, $val);
}
protected static function filterTokenUpload($dbQuery, $val)
{
return self::filterTokenSubmit($dbQuery, $val);
}
protected static function filterTokenUploaded($dbQuery, $val)
{
return self::filterTokenSubmit($dbQuery, $val);
}
protected static function order($dbQuery, $val)
{
$randomReset = true;
$orderDir = 1;
if (substr($val, -4) == 'desc')
{
$orderDir = 1;
$val = rtrim(substr($val, 0, -4), ',');
}
elseif (substr($val, -3) == 'asc')
{
$orderDir = -1;
$val = rtrim(substr($val, 0, -3), ',');
}
if ($val{0} == '-')
{
$orderDir *= -1;
$val = substr($val, 1);
}
switch ($val)
{
case 'id':
$orderColumn = 'post.id';
break;
case 'date':
$orderColumn = 'post.upload_date';
break;
case 'comment':
case 'comments':
case 'commentcount':
$orderColumn = 'comment_count';
break;
case 'fav':
case 'favs':
case 'favcount':
$orderColumn = 'fav_count';
break;
case 'tag':
case 'tags':
case 'tagcount':
$orderColumn = 'tag_count';
break;
case 'random':
//seeding works like this: if you visit anything
//that triggers order other than random, the seed
//is going to reset. however, it stays the same as
//long as you keep visiting pages with order:random
//specified.
$randomReset = false;
if (!isset($_SESSION['browsing-seed']))
$_SESSION['browsing-seed'] = mt_rand();
$seed = $_SESSION['browsing-seed'];
$orderColumn = 'SUBSTR(id * ' . $seed .', LENGTH(id) + 2)';
break;
default:
throw new SimpleException('Unknown key "' . $val . '"');
}
if ($randomReset)
unset($_SESSION['browsing-seed']);
$dbQuery->orderBy($orderColumn);
if ($orderDir == 1)
$dbQuery->desc();
else
$dbQuery->asc();
}
public static function build($dbQuery, $query)
{
$config = \Chibi\Registry::getConfig();
$context = \Chibi\Registry::getContext();
self::attachCommentCount($dbQuery);
self::attachFavCount($dbQuery);
self::attachTagCount($dbQuery);
$dbQuery->from('post');
self::filterChain($dbQuery);
self::filterUserSafety($dbQuery);
self::filterChain($dbQuery);
self::filterUserHidden($dbQuery);
/* query tokens */
$tokens = array_filter(array_unique(explode(' ', $query)), function($x) { return $x != ''; });
if (count($tokens) > $config->browsing->maxSearchTokens)
throw new SimpleException('Too many search tokens (maximum: ' . $config->browsing->maxSearchTokens . ')');
$orderToken = 'id';
foreach ($tokens as $token)
{
if ($token{0} == '-')
{
$token = substr($token, 1);
$neg = true;
}
else
{
$neg = false;
}
$pos = strpos($token, ':');
if ($pos === false)
{
self::filterChain($dbQuery);
if ($neg)
self::filterNegate($dbQuery);
self::filterTag($dbQuery, $token);
continue;
}
$key = substr($token, 0, $pos);
$val = substr($token, $pos + 1);
$methodName = 'filterToken' . TextHelper::kebabCaseToCamelCase($key);
if (method_exists(__CLASS__, $methodName))
{
self::filterChain($dbQuery);
if ($neg)
self::filterNegate($dbQuery);
self::$methodName($dbQuery, $val);
}
elseif ($key == 'order')
{
if ($neg)
$orderToken = $val;
else
$orderToken = '-' . $val;
}
else
{
throw new SimpleException('Unknown key "' . $key . '"');
}
}
self::order($dbQuery, $orderToken);
}
}

View file

@ -1,5 +1,5 @@
<?php
class Model_Tag extends RedBean_SimpleModel
class Model_Tag extends AbstractModel
{
public static function locate($key, $throw = true)
{
@ -50,6 +50,14 @@ class Model_Tag extends RedBean_SimpleModel
return $tag;
}
public function getPostCount()
{
if ($this->bean->getMeta('post_count'))
return $this->bean->getMeta('post_count');
return $this->bean->countShared('post');
}
public static function validateTags($tags)
{
$tags = trim($tags);
@ -65,4 +73,31 @@ class Model_Tag extends RedBean_SimpleModel
return $tags;
}
public static function getTableName()
{
return 'tag';
}
public static function getQueryBuilder()
{
return 'Model_Tag_Querybuilder';
}
public static function getEntities($query, $perPage = null, $page = 1)
{
$table = static::getTableName();
$rows = self::getEntitiesRows($query, $perPage, $page);
$entities = R::convertToBeans($table, $rows);
$rowMap = [];
foreach ($rows as &$row)
$rowMap[$row['id']] = $row;
unset ($row);
foreach ($entities as $entity)
$entity->setMeta('post_count', $rowMap[$entity->id]['count']);
return $entities;
}
}

View file

@ -0,0 +1,24 @@
<?php
class model_Tag_QueryBuilder implements AbstractQueryBuilder
{
public static function build($dbQuery, $query)
{
$limitQuery = false;
$dbQuery->addSql(', COUNT(post_tag.post_id)')->as('count');
$dbQuery->from('tag');
$dbQuery->innerJoin('post_tag');
$dbQuery->on('tag.id = post_tag.tag_id');
if ($query !== null)
{
$limitQuery = true;
if (strlen($query) >= 3)
$query = '%' . $query;
$query .= '%';
$dbQuery->where('LOWER(tag.name) LIKE LOWER(?)')->put($query);
}
$dbQuery->groupBy('tag.id');
$dbQuery->orderBy('LOWER(tag.name)')->asc();
if ($limitQuery)
$dbQuery->limit(15);
}
}

View file

@ -1,9 +1,9 @@
<?php
class Model_User extends RedBean_SimpleModel
class Model_User extends AbstractModel
{
public static function locate($key, $throw = true)
{
$user = R::findOne('user', 'name = ?', [$key]);
$user = R::findOne(self::getTableName(), 'name = ?', [$key]);
if (!$user)
{
if ($throw)
@ -88,7 +88,7 @@ class Model_User extends RedBean_SimpleModel
{
$userName = trim($userName);
$dbUser = R::findOne('user', 'name = ?', [$userName]);
$dbUser = R::findOne(self::getTableName(), 'name = ?', [$userName]);
if ($dbUser !== null)
{
if (!$dbUser->email_confirmed and \Chibi\Registry::getConfig()->registration->needEmailForRegistering)
@ -159,4 +159,13 @@ class Model_User extends RedBean_SimpleModel
return sha1($salt1 . $salt2 . $pass);
}
public static function getTableName()
{
return 'user';
}
public static function getQueryBuilder()
{
return 'Model_User_QueryBuilder';
}
}

View file

@ -0,0 +1,31 @@
<?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');
}
}
}

View file

@ -34,7 +34,7 @@
<?php echo $tag->name ?>
</a>
<span class="count">
<?php echo TextHelper::useDecimalUnits($this->context->transport->tagDistribution[$tag->name]) ?>
<?php echo TextHelper::useDecimalUnits($tag->getPostCount()) ?>
</span>
</li>
<?php endforeach ?>

View file

@ -1,10 +1,10 @@
<?php $max = max($this->context->transport->tagDistribution) ?>
<?php $max = max([0]+array_map(function($x) { return $x->getPostCount(); }, $this->context->transport->tags)); ?>
<div class="tags">
<ul>
<?php foreach ($this->context->transport->tagDistribution as $tagName => $count): ?>
<li class="tag" title="<?php echo $tagName ?> (<?php echo $count ?>)">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tagName]) ?>" style="opacity: <?php printf('%.02f', 0.25 + 0.75 * log($count) / log($max)) ?>">
<?php echo $tagName . ' (' . $count . ')' ?>
<?php foreach ($this->context->transport->tags as $tag): ?>
<li class="tag" title="<?php echo $tag->name ?> (<?php echo $tag->getPostCount() ?>)">
<a href="<?php echo \Chibi\UrlHelper::route('post', 'list', ['query' => $tag->name]) ?>" style="opacity: <?php printf('%.02f', 0.25 + 0.75 * log($tag->getPostCount()) / max(1, log(max(1, $max)))) ?>">
<?php echo $tag->name . ' (' . $tag->getPostCount() . ')' ?>
</a>
</li>
<?php endforeach ?>