Fixed automatic featuring post
- Fixed main page view - Code moved from StaticPagesController to PostModel - Code split into semantically meaningful methods - Allowed anonymous featuring through API - Added protection against automatic featuring of hidden post
This commit is contained in:
parent
6b40d6be7e
commit
8aa499a0b9
9 changed files with 262 additions and 61 deletions
|
@ -1,16 +1,22 @@
|
|||
<?php
|
||||
class FeaturePostJob extends AbstractPostJob
|
||||
{
|
||||
const ANONYMOUS = 'anonymous';
|
||||
|
||||
public function execute()
|
||||
{
|
||||
$post = $this->post;
|
||||
|
||||
PropertyModel::set(PropertyModel::FeaturedPostId, $post->getId());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostDate, time());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, Auth::getCurrentUser()->getName());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUnixTime, time());
|
||||
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUserName,
|
||||
($this->hasArgument(self::ANONYMOUS) and $this->getArgument(self::ANONYMOUS))
|
||||
? null
|
||||
: Auth::getCurrentUser()->getName());
|
||||
|
||||
Logger::log('{user} featured {post} on main page', [
|
||||
'user' => TextHelper::reprPost(Auth::getCurrentUser()),
|
||||
'user' => TextHelper::reprPost(PropertyModel::get(PropertyModel::FeaturedPostUserName)),
|
||||
'post' => TextHelper::reprPost($post)]);
|
||||
|
||||
return $post;
|
||||
|
|
|
@ -7,14 +7,14 @@ class StaticPagesController
|
|||
$context->transport->postCount = PostModel::getCount();
|
||||
$context->viewName = 'static-main';
|
||||
|
||||
$featuredPost = $this->getFeaturedPost();
|
||||
PostModel::featureRandomPostIfNecessary();
|
||||
$featuredPost = PostModel::getFeaturedPost();
|
||||
if ($featuredPost)
|
||||
{
|
||||
$context->featuredPost = $featuredPost;
|
||||
$context->featuredPostDate = PropertyModel::get(PropertyModel::FeaturedPostDate);
|
||||
$context->featuredPostUser = UserModel::getByNameOrEmail(
|
||||
PropertyModel::get(PropertyModel::FeaturedPostUserName),
|
||||
false);
|
||||
$context->featuredPostUnixTime = PropertyModel::get(PropertyModel::FeaturedPostUnixTime);
|
||||
$context->featuredPostUser = UserModel::tryGetByNameOrEmail(
|
||||
PropertyModel::get(PropertyModel::FeaturedPostUserName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,24 +34,4 @@ class StaticPagesController
|
|||
$context->path = TextHelper::absolutePath($config->help->paths[$tab]);
|
||||
$context->tab = $tab;
|
||||
}
|
||||
|
||||
private function getFeaturedPost()
|
||||
{
|
||||
$config = getConfig();
|
||||
$featuredPostRotationTime = $config->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$featuredPostId = PropertyModel::get(PropertyModel::FeaturedPostId);
|
||||
$featuredPostDate = PropertyModel::get(PropertyModel::FeaturedPostDate);
|
||||
|
||||
//check if too old
|
||||
if (!$featuredPostId or $featuredPostDate + $featuredPostRotationTime < time())
|
||||
return PropertyModel::featureNewPost();
|
||||
|
||||
//check if post was deleted
|
||||
$featuredPost = PostModel::tryGetById($featuredPostId);
|
||||
if (!$featuredPost)
|
||||
return PropertyModel::featureNewPost();
|
||||
|
||||
return $featuredPost;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -288,4 +288,59 @@ final class PostModel extends AbstractCrudModel
|
|||
{
|
||||
return TextHelper::absolutePath(getConfig()->main->filesPath . DS . $name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function getFeaturedPost()
|
||||
{
|
||||
$featuredPostId = PropertyModel::get(PropertyModel::FeaturedPostId);
|
||||
if (!$featuredPostId)
|
||||
return null;
|
||||
return PostModel::tryGetById($featuredPostId);
|
||||
}
|
||||
|
||||
public static function featureRandomPostIfNecessary()
|
||||
{
|
||||
$config = getConfig();
|
||||
$featuredPostRotationTime = $config->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$featuredPostId = PropertyModel::get(PropertyModel::FeaturedPostId);
|
||||
$featuredPostUnixTime = PropertyModel::get(PropertyModel::FeaturedPostUnixTime);
|
||||
|
||||
//check if too old
|
||||
if (!$featuredPostId or $featuredPostUnixTime + $featuredPostRotationTime < time())
|
||||
{
|
||||
self::featureRandomPost();
|
||||
return true;
|
||||
}
|
||||
|
||||
//check if post was deleted
|
||||
$featuredPost = PostModel::tryGetById($featuredPostId);
|
||||
if (!$featuredPost)
|
||||
{
|
||||
self::featureRandomPost();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function featureRandomPost()
|
||||
{
|
||||
$stmt = (new Sql\SelectStatement)
|
||||
->setColumn('id')
|
||||
->setTable('post')
|
||||
->setCriterion((new Sql\ConjunctionFunctor)
|
||||
->add(new Sql\NegationFunctor(new Sql\StringExpression('hidden')))
|
||||
->add(new Sql\EqualsFunctor('type', new Sql\Binding(PostType::Image)))
|
||||
->add(new Sql\EqualsFunctor('safety', new Sql\Binding(PostSafety::Safe))))
|
||||
->setOrderBy(new Sql\RandomFunctor(), Sql\SelectStatement::ORDER_DESC);
|
||||
$featuredPostId = Database::fetchOne($stmt)['id'];
|
||||
if (!$featuredPostId)
|
||||
return null;
|
||||
|
||||
PropertyModel::set(PropertyModel::FeaturedPostId, $featuredPostId);
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUnixTime, time());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,17 @@ final class PropertyModel implements IModel
|
|||
{
|
||||
const FeaturedPostId = 0;
|
||||
const FeaturedPostUserName = 1;
|
||||
const FeaturedPostDate = 2;
|
||||
const FeaturedPostUnixTime = 2;
|
||||
const DbVersion = 3;
|
||||
|
||||
static $allProperties = null;
|
||||
static $loaded = false;
|
||||
static $allProperties;
|
||||
static $loaded;
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$allProperties = null;
|
||||
self::$loaded = false;
|
||||
}
|
||||
|
||||
public static function getTableName()
|
||||
{
|
||||
|
@ -19,16 +25,16 @@ final class PropertyModel implements IModel
|
|||
|
||||
public static function loadIfNecessary()
|
||||
{
|
||||
if (!self::$loaded)
|
||||
{
|
||||
self::$loaded = true;
|
||||
self::$allProperties = [];
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt ->setColumn('*');
|
||||
$stmt ->setTable('property');
|
||||
foreach (Database::fetchAll($stmt) as $row)
|
||||
self::$allProperties[$row['prop_id']] = $row['value'];
|
||||
}
|
||||
if (self::$loaded)
|
||||
return;
|
||||
|
||||
self::$loaded = true;
|
||||
self::$allProperties = [];
|
||||
$stmt = new Sql\SelectStatement();
|
||||
$stmt ->setColumn('*');
|
||||
$stmt ->setTable('property');
|
||||
foreach (Database::fetchAll($stmt) as $row)
|
||||
self::$allProperties[$row['prop_id']] = $row['value'];
|
||||
}
|
||||
|
||||
public static function get($propertyId)
|
||||
|
@ -68,23 +74,4 @@ final class PropertyModel implements IModel
|
|||
self::$allProperties[$propertyId] = $value;
|
||||
});
|
||||
}
|
||||
|
||||
public static function featureNewPost()
|
||||
{
|
||||
$stmt = (new Sql\SelectStatement)
|
||||
->setColumn('id')
|
||||
->setTable('post')
|
||||
->setCriterion((new Sql\ConjunctionFunctor)
|
||||
->add(new Sql\EqualsFunctor('type', new Sql\Binding(PostType::Image)))
|
||||
->add(new Sql\EqualsFunctor('safety', new Sql\Binding(PostSafety::Safe))))
|
||||
->setOrderBy(new Sql\RandomFunctor(), Sql\SelectStatement::ORDER_DESC);
|
||||
$featuredPostId = Database::fetchOne($stmt)['id'];
|
||||
if (!$featuredPostId)
|
||||
return null;
|
||||
|
||||
self::set(self::FeaturedPostId, $featuredPostId);
|
||||
self::set(self::FeaturedPostDate, time());
|
||||
self::set(self::FeaturedPostUserName, null);
|
||||
return PostModel::getById($featuredPostId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ Assets::addStylesheet('static-main.css');
|
|||
</a>,
|
||||
<?php endif ?>
|
||||
|
||||
<?php $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)) ?>
|
||||
<?php $x = round((time() - $this->context->featuredPostUnixTime) / (24 * 3600.)) ?>
|
||||
<?php if ($x == 0): ?>
|
||||
today
|
||||
<?php elseif ($x == 1):?>
|
||||
|
|
|
@ -84,6 +84,7 @@ function prepareEnvironment($testEnvironment)
|
|||
Access::init();
|
||||
Logger::init();
|
||||
Mailer::init();
|
||||
PropertyModel::init();
|
||||
|
||||
\Chibi\Database::connect(
|
||||
$config->main->dbDriver,
|
||||
|
|
49
tests/JobTests/FeaturePostJobTest.php
Normal file
49
tests/JobTests/FeaturePostJobTest.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
class FeaturePostJobTest extends AbstractTest
|
||||
{
|
||||
public function testFeaturing()
|
||||
{
|
||||
$this->grantAccess('featurePost');
|
||||
|
||||
$user = $this->mockUser();
|
||||
$this->login($user);
|
||||
$post1 = $this->mockPost($user);
|
||||
$post2 = $this->mockPost($user);
|
||||
|
||||
$this->assert->doesNotThrow(function() use ($post2)
|
||||
{
|
||||
Api::run(
|
||||
new FeaturePostJob(),
|
||||
[
|
||||
FeaturePostJob::POST_ID => $post2->getId()
|
||||
]);
|
||||
});
|
||||
|
||||
$this->assert->areEqual($post2->getId(), PropertyModel::get(PropertyModel::FeaturedPostId));
|
||||
$this->assert->areEqual($user->getName(), PropertyModel::get(PropertyModel::FeaturedPostUserName));
|
||||
$this->assert->isNotNull(PropertyModel::get(PropertyModel::FeaturedPostUnixTime));
|
||||
}
|
||||
|
||||
public function testAnonymousFeaturing()
|
||||
{
|
||||
$this->grantAccess('featurePost');
|
||||
|
||||
$user = $this->mockUser();
|
||||
$this->login($user);
|
||||
$post1 = $this->mockPost($user);
|
||||
$post2 = $this->mockPost($user);
|
||||
|
||||
$this->assert->doesNotThrow(function() use ($post2)
|
||||
{
|
||||
Api::run(
|
||||
new FeaturePostJob(),
|
||||
[
|
||||
FeaturePostJob::POST_ID => $post2->getId(),
|
||||
FeaturePostJob::ANONYMOUS => true,
|
||||
]);
|
||||
});
|
||||
|
||||
$this->assert->areEqual($post2->getId(), PropertyModel::get(PropertyModel::FeaturedPostId));
|
||||
$this->assert->areEqual(null, PropertyModel::get(PropertyModel::FeaturedPostUserName));
|
||||
}
|
||||
}
|
102
tests/ModelTests/PostModelTest.php
Normal file
102
tests/ModelTests/PostModelTest.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
class PostModelTest extends AbstractTest
|
||||
{
|
||||
public function testFeaturedPostRetrieval()
|
||||
{
|
||||
$post = $this->assert->doesNotThrow(function()
|
||||
{
|
||||
return PostModel::getFeaturedPost();
|
||||
});
|
||||
|
||||
$this->assert->areEqual(null, $post);
|
||||
}
|
||||
|
||||
public function testFeaturingNoPost()
|
||||
{
|
||||
PostModel::featureRandomPost();
|
||||
|
||||
$post = $this->assert->doesNotThrow(function()
|
||||
{
|
||||
return PostModel::getFeaturedPost();
|
||||
});
|
||||
|
||||
$this->assert->areEqual(null, $post);
|
||||
}
|
||||
|
||||
public function testFeaturingRandomPost()
|
||||
{
|
||||
$post = $this->mockPost(Auth::getCurrentUser());
|
||||
|
||||
PostModel::featureRandomPost();
|
||||
|
||||
$this->assert->areEqual($post->getId(), (int) PropertyModel::get(PropertyModel::FeaturedPostId));
|
||||
}
|
||||
|
||||
public function testFeaturingIllegalPosts()
|
||||
{
|
||||
$posts = [];
|
||||
foreach (range(0, 5) as $i)
|
||||
$posts []= $this->mockPost(Auth::getCurrentUser());
|
||||
$posts[0]->setSafety(new PostSafety(PostSafety::Sketchy));
|
||||
$posts[1]->setSafety(new PostSafety(PostSafety::Sketchy));
|
||||
$posts[2]->setHidden(true);
|
||||
$posts[3]->setType(new PostType(PostType::Youtube));
|
||||
$posts[4]->setType(new PostType(PostType::Flash));
|
||||
$posts[5]->setType(new PostType(PostType::Video));
|
||||
foreach ($posts as $post)
|
||||
PostModel::save($post);
|
||||
|
||||
PostModel::featureRandomPost();
|
||||
|
||||
$this->assert->areEqual(null, PropertyModel::get(PropertyModel::FeaturedPostId));
|
||||
}
|
||||
|
||||
public function testAutoFeaturingFirstTime()
|
||||
{
|
||||
$this->mockPost(Auth::getCurrentUser());
|
||||
|
||||
$this->assert->doesNotThrow(function()
|
||||
{
|
||||
PostModel::featureRandomPostIfNecessary();
|
||||
});
|
||||
|
||||
$this->assert->isNotNull(PostModel::getFeaturedPost());
|
||||
}
|
||||
|
||||
public function testAutoFeaturingTooSoon()
|
||||
{
|
||||
$this->mockPost(Auth::getCurrentUser());
|
||||
|
||||
$this->assert->isTrue(PostModel::featureRandomPostIfNecessary());
|
||||
$this->assert->isFalse(PostModel::featureRandomPostIfNecessary());
|
||||
|
||||
$this->assert->isNotNull(PostModel::getFeaturedPost());
|
||||
}
|
||||
|
||||
public function testAutoFeaturingOutdated()
|
||||
{
|
||||
$post = $this->mockPost(Auth::getCurrentUser());
|
||||
$minTimestamp = getConfig()->misc->featuredPostMaxDays * 24 * 3600;
|
||||
|
||||
$this->assert->isTrue(PostModel::featureRandomPostIfNecessary());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUnixTime, time() - $minTimestamp - 1);
|
||||
$this->assert->isTrue(PostModel::featureRandomPostIfNecessary());
|
||||
PropertyModel::set(PropertyModel::FeaturedPostUnixTime, time() - $minTimestamp + 1);
|
||||
$this->assert->isFalse(PostModel::featureRandomPostIfNecessary());
|
||||
|
||||
$this->assert->isNotNull(PostModel::getFeaturedPost());
|
||||
}
|
||||
|
||||
public function testAutoFeaturingDeletedPost()
|
||||
{
|
||||
$post = $this->mockPost(Auth::getCurrentUser());
|
||||
|
||||
$this->assert->isTrue(PostModel::featureRandomPostIfNecessary());
|
||||
$this->assert->isNotNull(PostModel::getFeaturedPost());
|
||||
PostModel::remove($post);
|
||||
$anotherPost = $this->mockPost(Auth::getCurrentUser());
|
||||
$this->assert->isTrue(PostModel::featureRandomPostIfNecessary());
|
||||
|
||||
$this->assert->isNotNull(PostModel::getFeaturedPost());
|
||||
}
|
||||
}
|
21
tests/ModelTests/PropertyModelTest.php
Normal file
21
tests/ModelTests/PropertyModelTest.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
class PropertyModelTest extends AbstractTest
|
||||
{
|
||||
public function testGetAndSet()
|
||||
{
|
||||
$this->assert->doesNotThrow(function()
|
||||
{
|
||||
PropertyModel::set(PropertyModel::FeaturedPostId, 100);
|
||||
});
|
||||
$this->assert->areEqual(100, PropertyModel::get(PropertyModel::FeaturedPostId));
|
||||
}
|
||||
|
||||
public function testGetAndSetWithArbitraryKeys()
|
||||
{
|
||||
$this->assert->doesNotThrow(function()
|
||||
{
|
||||
PropertyModel::set('something', 100);
|
||||
});
|
||||
$this->assert->areEqual(100, PropertyModel::get('something'));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue