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
|
<?php
|
||||||
class FeaturePostJob extends AbstractPostJob
|
class FeaturePostJob extends AbstractPostJob
|
||||||
{
|
{
|
||||||
|
const ANONYMOUS = 'anonymous';
|
||||||
|
|
||||||
public function execute()
|
public function execute()
|
||||||
{
|
{
|
||||||
$post = $this->post;
|
$post = $this->post;
|
||||||
|
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostId, $post->getId());
|
PropertyModel::set(PropertyModel::FeaturedPostId, $post->getId());
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostDate, time());
|
PropertyModel::set(PropertyModel::FeaturedPostUnixTime, time());
|
||||||
PropertyModel::set(PropertyModel::FeaturedPostUserName, Auth::getCurrentUser()->getName());
|
|
||||||
|
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', [
|
Logger::log('{user} featured {post} on main page', [
|
||||||
'user' => TextHelper::reprPost(Auth::getCurrentUser()),
|
'user' => TextHelper::reprPost(PropertyModel::get(PropertyModel::FeaturedPostUserName)),
|
||||||
'post' => TextHelper::reprPost($post)]);
|
'post' => TextHelper::reprPost($post)]);
|
||||||
|
|
||||||
return $post;
|
return $post;
|
||||||
|
|
|
@ -7,14 +7,14 @@ class StaticPagesController
|
||||||
$context->transport->postCount = PostModel::getCount();
|
$context->transport->postCount = PostModel::getCount();
|
||||||
$context->viewName = 'static-main';
|
$context->viewName = 'static-main';
|
||||||
|
|
||||||
$featuredPost = $this->getFeaturedPost();
|
PostModel::featureRandomPostIfNecessary();
|
||||||
|
$featuredPost = PostModel::getFeaturedPost();
|
||||||
if ($featuredPost)
|
if ($featuredPost)
|
||||||
{
|
{
|
||||||
$context->featuredPost = $featuredPost;
|
$context->featuredPost = $featuredPost;
|
||||||
$context->featuredPostDate = PropertyModel::get(PropertyModel::FeaturedPostDate);
|
$context->featuredPostUnixTime = PropertyModel::get(PropertyModel::FeaturedPostUnixTime);
|
||||||
$context->featuredPostUser = UserModel::getByNameOrEmail(
|
$context->featuredPostUser = UserModel::tryGetByNameOrEmail(
|
||||||
PropertyModel::get(PropertyModel::FeaturedPostUserName),
|
PropertyModel::get(PropertyModel::FeaturedPostUserName));
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,24 +34,4 @@ class StaticPagesController
|
||||||
$context->path = TextHelper::absolutePath($config->help->paths[$tab]);
|
$context->path = TextHelper::absolutePath($config->help->paths[$tab]);
|
||||||
$context->tab = $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);
|
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 FeaturedPostId = 0;
|
||||||
const FeaturedPostUserName = 1;
|
const FeaturedPostUserName = 1;
|
||||||
const FeaturedPostDate = 2;
|
const FeaturedPostUnixTime = 2;
|
||||||
const DbVersion = 3;
|
const DbVersion = 3;
|
||||||
|
|
||||||
static $allProperties = null;
|
static $allProperties;
|
||||||
static $loaded = false;
|
static $loaded;
|
||||||
|
|
||||||
|
public static function init()
|
||||||
|
{
|
||||||
|
self::$allProperties = null;
|
||||||
|
self::$loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getTableName()
|
public static function getTableName()
|
||||||
{
|
{
|
||||||
|
@ -19,16 +25,16 @@ final class PropertyModel implements IModel
|
||||||
|
|
||||||
public static function loadIfNecessary()
|
public static function loadIfNecessary()
|
||||||
{
|
{
|
||||||
if (!self::$loaded)
|
if (self::$loaded)
|
||||||
{
|
return;
|
||||||
self::$loaded = true;
|
|
||||||
self::$allProperties = [];
|
self::$loaded = true;
|
||||||
$stmt = new Sql\SelectStatement();
|
self::$allProperties = [];
|
||||||
$stmt ->setColumn('*');
|
$stmt = new Sql\SelectStatement();
|
||||||
$stmt ->setTable('property');
|
$stmt ->setColumn('*');
|
||||||
foreach (Database::fetchAll($stmt) as $row)
|
$stmt ->setTable('property');
|
||||||
self::$allProperties[$row['prop_id']] = $row['value'];
|
foreach (Database::fetchAll($stmt) as $row)
|
||||||
}
|
self::$allProperties[$row['prop_id']] = $row['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get($propertyId)
|
public static function get($propertyId)
|
||||||
|
@ -68,23 +74,4 @@ final class PropertyModel implements IModel
|
||||||
self::$allProperties[$propertyId] = $value;
|
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>,
|
</a>,
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php $x = round((time() - $this->context->featuredPostDate) / (24 * 3600.)) ?>
|
<?php $x = round((time() - $this->context->featuredPostUnixTime) / (24 * 3600.)) ?>
|
||||||
<?php if ($x == 0): ?>
|
<?php if ($x == 0): ?>
|
||||||
today
|
today
|
||||||
<?php elseif ($x == 1):?>
|
<?php elseif ($x == 1):?>
|
||||||
|
|
|
@ -84,6 +84,7 @@ function prepareEnvironment($testEnvironment)
|
||||||
Access::init();
|
Access::init();
|
||||||
Logger::init();
|
Logger::init();
|
||||||
Mailer::init();
|
Mailer::init();
|
||||||
|
PropertyModel::init();
|
||||||
|
|
||||||
\Chibi\Database::connect(
|
\Chibi\Database::connect(
|
||||||
$config->main->dbDriver,
|
$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