- Enh: Moved pinned stream logic and container stream logic into custom stream filter class

- Enh: Added profile stream unit tests
This commit is contained in:
buddh4 2020-07-06 20:48:48 +02:00
parent 8841a7bdd4
commit 0c915b3f4b
18 changed files with 933 additions and 109 deletions

View File

@ -8,10 +8,9 @@
namespace humhub\modules\stream\actions;
use Yii;
use humhub\modules\content\models\Content;
use yii\db\ArrayExpression;
use yii\db\Expression;
use humhub\modules\content\components\ContentContainerActiveRecord;
use modules\stream\models\ContentContainerStreamQuery;
use yii\base\InvalidConfigException;
/**
* ContentContainerStream is used to stream contentcontainers (space or users) content.
@ -25,68 +24,22 @@ class ContentContainerStream extends Stream
{
/**
* @var \humhub\modules\content\components\ContentContainerActiveRecord
* @inheritdoc
*/
public $streamQueryClass = ContentContainerStreamQuery::class;
/**
* @var ContentContainerActiveRecord
*/
public $contentContainer;
/**
* @inheritdoc
* @throws InvalidConfigException
*/
public function init()
protected function initQuery($options = [])
{
parent::init();
if ($this->contentContainer) {
$this->handleContentContainer();
}
}
/**
* Limits the stream to a given ContentContainer and adds basic visibility handling.
*/
protected function handleContentContainer()
{
// Limit to this content container
$this->activeQuery->andWhere(['content.contentcontainer_id' => $this->contentContainer->contentcontainer_id]);
// Limit to public posts when no member
if (!$this->contentContainer->canAccessPrivateContent($this->user)) {
if (!Yii::$app->user->isGuest) {
$this->activeQuery->andWhere("content.visibility=" . Content::VISIBILITY_PUBLIC . " OR content.created_by = :userId", [':userId' => $this->user->id]);
} else {
$this->activeQuery->andWhere("content.visibility=" . Content::VISIBILITY_PUBLIC);
}
}
$this->handlePinnedContent();
}
/**
* Handles ordering of pinned content entries.
*/
protected function handlePinnedContent()
{
// Add all pinned contents to initial request
if ($this->isInitialRequest()) {
// Get number of pinned contents
$pinnedQuery = clone $this->activeQuery;
$pinnedQuery->andWhere(['AND', ['content.pinned' => 1], ['content.contentcontainer_id' => $this->contentContainer->contentcontainer_id]]);
$pinnedContent = $pinnedQuery->select('content.id')->column();
if(!empty($pinnedContent)) {
// Increase query result limit to ensure all pinned entries are included in the first request
$this->activeQuery->limit += count($pinnedContent);
// Modify order - pinned content first
$oldOrder = $this->activeQuery->orderBy;
$this->activeQuery->orderBy("");
$this->activeQuery->addOrderBy(new Expression('CASE WHEN `content`.`id` IN ('.implode(',', $pinnedContent).') THEN 1 else 0 END DESC'));
$this->activeQuery->addOrderBy($oldOrder);
}
} else {
// All pinned entries of this container were loaded within the initial request, so don't include them here!
$this->activeQuery->andWhere(['OR', ['content.pinned' => 0], ['<>', 'content.contentcontainer_id', $this->contentContainer->contentcontainer_id]]);
}
$options['container'] = $this->contentContainer;
return parent::initQuery($options);
}
}

View File

@ -203,13 +203,28 @@ abstract class Stream extends Action
* Initializes the StreamQuery instance. This can be used to add or remove stream filters or set query defaults.
* By default [[streamQueryClass]] property will be used to initialize the instance.
*
* @since 1.6
* Example usage:
*
* ```php
* protected function initQuery($options = [])
* {
* $query = parent::initQuery($options);
* $query->addFilterHandler(new MyStreamFilter(['container' => $this->contentContainer]));
* return $query;
* }
* ```
*
* @param array $options instance attribute options
* @return StreamQuery
* @since 1.6
*/
protected function initQuery()
protected function initQuery($options = [])
{
$streamQueryClass = $this->streamQueryClass;
return $streamQueryClass::find($this->includes, $this->excludes)->forUser($this->user);
/* @var $instance StreamQuery */
$instance = $streamQueryClass::find($this->includes, $this->excludes)->forUser($this->user);
$instance->setAttributes($options);
return $instance;
}
protected function setActionSettings()
@ -278,6 +293,7 @@ abstract class Stream extends Action
* Is inital stream requests (show first stream content)
*
* @return boolean Is initial request
* @deprecated since 1.6 use StreamQuery::isInitialQuery
*/
protected function isInitialRequest()
{

View File

@ -0,0 +1,30 @@
<?php
namespace modules\stream\models;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\stream\models\filters\ContentContainerStreamFilter;
use humhub\modules\stream\models\filters\PinnedContentStreamFilter;
use humhub\modules\stream\models\WallStreamQuery;
class ContentContainerStreamQuery extends WallStreamQuery
{
/**
* @var ContentContainerActiveRecord
*/
public $container;
/**
* @inheritdoc
* @throws \yii\base\InvalidConfigException
*/
protected function beforeApplyFilters()
{
$this->addFilterHandler(new ContentContainerStreamFilter(['container' => $this->container]));
$this->addFilterHandler(new PinnedContentStreamFilter(['container' => $this->container]));
parent::beforeApplyFilters();
}
}

View File

@ -2,6 +2,7 @@
namespace humhub\modules\stream\models;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\stream\models\filters\StreamQueryFilter;
use Yii;
use yii\base\InvalidConfigException;
@ -13,7 +14,6 @@ use humhub\modules\stream\models\filters\ContentTypeStreamFilter;
use humhub\modules\stream\models\filters\DefaultStreamFilter;
use humhub\modules\stream\models\filters\OriginatorStreamFilter;
use humhub\modules\stream\models\filters\TopicStreamFilter;
use humhub\modules\ui\filter\models\QueryFilter;
use humhub\modules\content\models\Content;
use humhub\modules\user\models\User;
@ -30,6 +30,11 @@ class StreamQuery extends Model
*/
const EVENT_BEFORE_FILTER = 'beforeFilter';
/**
* @event Event triggered after filterHandlers are applied.
*/
const EVENT_AFTER_FILTER = 'afterFilter';
/**
* Default channels
*/
@ -66,6 +71,12 @@ class StreamQuery extends Model
*/
public $user;
/**
* The user which requested the stream. By default the current user identity.
* @var ContentContainerActiveRecord
*/
public $container;
/**
* Can be set to filter content of a specific user
* @var \humhub\modules\user\models\User
@ -511,29 +522,126 @@ class StreamQuery extends Model
}
/**
* Sets up the filter queries.
* Sets up and apply filters.
*
* @throws \yii\base\InvalidConfigException
*/
protected function setupFilters()
{
$this->trigger(static::EVENT_BEFORE_FILTER);
$this->beforeApplyFilters();
foreach ($this->filterHandlers as $handler) {
$this->initHandler($handler)->apply();
$this->prepareHandler($handler)->apply();
}
$this->afterApplyFilters();
}
/**
* Is called right before applying query filters.
*
* Subclasses may use this function to add additional filters or modify existing filters.
*
* ```php
* protected function beforeApplyFilters()
* {
* parent::beforeApplyFilters();
* $this->addFilterHandler(MyStreamFilter::class);
* }
* ```
*
* @throws \yii\base\InvalidConfigException
* @since 1.6
*/
protected function beforeApplyFilters()
{
$this->trigger(static::EVENT_BEFORE_FILTER);
}
/**
* Is called right after applying query filters.
*
* @throws \yii\base\InvalidConfigException
* @since 1.6
*/
protected function afterApplyFilters()
{
$this->trigger(static::EVENT_AFTER_FILTER);
}
/**
* Adds a new filter handler to this query. This function accepts either a class name string or handler instance.
* Note, this function will automatically set the following filter properties:
*
* - streamQuery: this streamQuery instance
* - query: this activeQuery
* - formName: this formName usually StreamQuery
*
* Usage:
*
* ```php
* // Use this if you need to set some initial filter data
* $filter = $streamQuery->addFilterHandler(new MyFilterHandler(['container' => $this->container]))
*
* // or just
* $filter = $streamQuery->addFilterHandler(AnotherFilter::class)
* ```
*
* @param string|StreamQueryFilter $handler
* @param bool $overwrite whether or not to overwrite existing filters of the same class
* @return StreamQueryFilter initialized stream filter
* @throws InvalidConfigException
* @since 1.6
*/
public function addFilterHandler($handler, $overwrite = true)
{
if($overwrite) {
$this->removeFilterHandler($handler);
}
$handler = $this->prepareHandler($handler);
return $this->filterHandlers[] = $handler;
}
/**
* Can be used to remove filters by filter class.
*
* @param $handler
* @return StreamQueryFilter
* @throws InvalidConfigException
* @since 1.6
*/
public function addFilterHandler($handler)
public function removeFilterHandler($handlerToRemove)
{
$handler = $this->initHandler($handler);
return $this->filterHandlers[] = $handler;
$result = [];
$handlerToRemoveClass = is_string($handlerToRemove) ? $handlerToRemove : get_class($handlerToRemove);
foreach ($this->filterHandlers as $handler) {
if(!is_a($handler, $handlerToRemoveClass, true)) {
$result[] = $handler;
}
}
$this->filterHandlers = $result;
}
/**
* Can be used to search for a filter handler by class.
*
* @param $handler
* @return StreamQueryFilter
* @throws InvalidConfigException
* @since 1.6
*/
public function getFilterHandler($handlerToRemove)
{
$handlerToRemoveClass = is_string($handlerToRemove) ? $handlerToRemove : get_class($handlerToRemove);
foreach ($this->filterHandlers as $handler) {
if(is_a($handler, $handlerToRemoveClass, true)) {
return $this->prepareHandler($handler);
}
}
return null;
}
/**
@ -542,7 +650,7 @@ class StreamQuery extends Model
* @throws InvalidConfigException
* @since 1.6
*/
private function initHandler($handler)
private function prepareHandler($handler)
{
if (is_string($handler)) {
$handler = Yii::createObject([
@ -569,4 +677,14 @@ class StreamQuery extends Model
{
return $this->limit == 1 || $this->contentId != null;
}
/**
* Is inital stream requests (show first stream content)
*
* @return boolean Whether or not this query is considered as initial stream query.
*/
public function isInitialQuery()
{
return $this->from === null && $this->to === null && !$this->isSingleContentQuery();
}
}

View File

@ -0,0 +1,55 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\stream\models\filters;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\models\Content;
use Yii;
/**
* This stream filter will only include content related to a given [[ContentContainerActiveRecord]] and furthermore
* only includes private content if the query user is allowed to access private content of this container.
*
* @package humhub\modules\stream\models\filters
* @since 1.6
*/
class ContentContainerStreamFilter extends StreamQueryFilter
{
/**
* @var ContentContainerActiveRecord
*/
public $container;
/**
* @inheritDoc
*/
public function apply()
{
if(!$this->container) {
return;
}
$user = $this->streamQuery->user;
// Limit to this content container
$this->query->andWhere(['content.contentcontainer_id' => $this->container->contentcontainer_id]);
// Limit to public posts when no member
if (!$this->container->canAccessPrivateContent($user)) {
if(Yii::$app->user->isGuest) {
$this->query->andWhere('content.visibility = :visibility', [':visibility' => Content::VISIBILITY_PUBLIC]);
} else {
$this->query->andWhere('content.visibility = :visibility OR content.created_by = :userId', [
':visibility' => Content::VISIBILITY_PUBLIC,
':userId' => Yii::$app->user->id
]);
}
}
}
}

View File

@ -0,0 +1,65 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\stream\models\filters;
use humhub\modules\content\components\ContentContainerActiveRecord;
use yii\db\Expression;
/**
* This stream filter manages the stream order of container streams with pinned content support. Once added to a StreamQuery,
* this filter will be active and can not be deactivated by request parameter.
*
* When active, pinned content entries of the given container will be prepended to the StreamQuery result.
* This is done by modifying the query limit and query order.
*
* Since this filter modifies the query order, it should be added at the end of the filter list.
*
* @package humhub\modules\stream\models\filters
* @since 1.6
*/
class PinnedContentStreamFilter extends StreamQueryFilter
{
/**
* @var ContentContainerActiveRecord
*/
public $container;
/**
* @inheritDoc
*/
public function apply()
{
// Currently we only support pinned entries on container streams
if(!$this->container) {
return;
}
// Add all pinned contents to initial request
if ($this->streamQuery->isInitialQuery()) {
// Get number of pinned contents
$pinnedQuery = clone $this->query;
$pinnedQuery->andWhere(['AND', ['content.pinned' => 1], ['content.contentcontainer_id' => $this->container->contentcontainer_id]]);
$pinnedContent = $pinnedQuery->select('content.id')->column();
if(!empty($pinnedContent)) {
// Increase query result limit to ensure all pinned entries are included in the first request
$this->query->limit += count($pinnedContent);
// Modify order - pinned content first
$oldOrder = $this->query->orderBy;
$this->query->orderBy("");
$this->query->addOrderBy(new Expression('CASE WHEN `content`.`id` IN ('.implode(',', $pinnedContent).') THEN 1 else 0 END DESC'));
$this->query->addOrderBy($oldOrder);
}
} else {
// All pinned entries of this container were loaded within the initial request, so don't include them here!
$this->query->andWhere(['OR', ['content.pinned' => 0], ['<>', 'content.contentcontainer_id', $this->container->contentcontainer_id]]);
}
}
}

View File

@ -19,6 +19,9 @@ abstract class StreamQueryFilter extends QueryFilter
*/
public $streamQuery;
/**
* @inheritDoc
*/
public $autoLoad = self::AUTO_LOAD_GET;
/**

View File

@ -23,7 +23,10 @@ abstract class Filter extends Model
*/
public $formName;
public $autoLoad = true;
/**
* @var bool Whether or not to automatically load the filter state from request.
*/
public $autoLoad = self::AUTO_LOAD_ALL;
public abstract function apply();

View File

@ -3,14 +3,8 @@
namespace humhub\modules\user\stream;
use humhub\modules\space\models\Space;
use humhub\modules\stream\actions\ContentContainerStream;
use humhub\modules\user\models\User;
use humhub\modules\user\models\User as UserModel;
use humhub\modules\user\Module;
use humhub\modules\user\stream\filters\IncludeAllContributionsFilter;
use Yii;
use yii\base\InvalidConfigException;
/**
* ProfileStream
@ -20,30 +14,19 @@ use yii\base\InvalidConfigException;
class ProfileStreamAction extends ContentContainerStream
{
/**
* @var IncludeAllContributionsFilter
* @inheritdoc
*/
public $includeAllContributionsFilter;
public function initQuery()
{
$query = parent::initQuery();
$this->includeAllContributionsFilter = $query->addFilterHandler(new IncludeAllContributionsFilter(['user' => $this->contentContainer]));
return $query;
}
public $streamQueryClass = ProfileStreamQuery::class;
/**
* @inheritdoc
*/
protected function handleContentContainer()
protected function beforeRun()
{
if (!($this->contentContainer instanceof User)) {
throw new InvalidConfigException('ContentContainer must be related to a User record.');
if(!$this->contentContainer instanceof User) {
return false;
}
if($this->user && $this->includeAllContributionsFilter->isActive()) {
$this->handlePinnedContent();
} else {
parent::handleContentContainer();
}
return parent::beforeRun();
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace humhub\modules\user\stream;
use humhub\modules\stream\actions\ContentContainerStream;
use humhub\modules\stream\models\filters\ContentContainerStreamFilter;
use humhub\modules\stream\models\WallStreamQuery;
use humhub\modules\user\models\User;
use humhub\modules\user\stream\filters\IncludeAllContributionsFilter;
use modules\stream\models\ContentContainerStreamQuery;
/**
* ProfileStream
*
* @package humhub\modules\user\components
*/
class ProfileStreamQuery extends ContentContainerStreamQuery
{
/**
* @var bool|null can be used to set a default state for the IncludeAllContributionsFilter
*/
public $includeContributions;
/**
* @inheritdoc
*/
public function beforeApplyFilters()
{
parent::beforeApplyFilters();
$this->removeFilterHandler(ContentContainerStreamFilter::class);
$this->addFilterHandler(new IncludeAllContributionsFilter([
'container' => $this->container,
'filters' => $this->includeContributions ? [IncludeAllContributionsFilter::ID] : []
]));
}
}

View File

@ -3,19 +3,22 @@
namespace humhub\modules\user\stream\filters;
use humhub\modules\space\models\Space;
use humhub\modules\stream\models\filters\StreamQueryFilter;
use humhub\modules\stream\models\filters\ContentContainerStreamFilter;
use humhub\modules\user\models\User;
use Yii;
class IncludeAllContributionsFilter extends StreamQueryFilter
/**
* Class IncludeAllContributionsFilter
* @package humhub\modules\user\stream\filters
*/
class IncludeAllContributionsFilter extends ContentContainerStreamFilter
{
const ID = 'includeAllContributions';
/**
* @var User
* @var array
*/
public $user;
public $filters = [];
/**
@ -28,51 +31,56 @@ class IncludeAllContributionsFilter extends StreamQueryFilter
];
}
/**
* @inheritdoc
*/
public function apply()
{
if(!$this->isActive()) {
return;
return parent::apply();
}
$queryUser = $this->streamQuery->user;
$this->query->leftJoin('space', 'contentcontainer.pk=space.id AND contentcontainer.class=:spaceClass', [':spaceClass' => Space::class]);
$this->query->leftJoin('user cuser', 'contentcontainer.pk=cuser.id AND contentcontainer.class=:userClass', [':userClass' => User::class]);
$this->query->leftJoin('space_membership',
'contentcontainer.pk=space_membership.space_id AND contentcontainer.class=:spaceClass AND space_membership.user_id=:userId',
[':userId' => $this->user->id, ':spaceClass' => Space::class]
[':userId' => $queryUser->id, ':spaceClass' => Space::class]
);
$this->query->andWhere([
'OR',
['content.created_by' => $this->user->id],
['content.contentcontainer_id' => $this->user->contentcontainer_id]
['content.created_by' => $this->container->id],
['content.contentcontainer_id' => $this->container->contentcontainer_id]
]);
// Build Access Check based on Space Content Container
$conditionSpace = 'space.id IS NOT NULL AND ('; // space content
$conditionSpace .= ' (space_membership.status=3)'; // user is space member
$conditionSpace .= ' OR (content.visibility=1 AND space.visibility != 0)'; // visibile space and public content
$conditionSpace .= ' OR (content.visibility=1 AND space.visibility != 0)'; // visible space and public content
$conditionSpace .= ')';
// Build Access Check based on User Content Container
$conditionUser = 'cuser.id IS NOT NULL AND ('; // user content
$conditionUser .= ' (content.visibility = 1) OR'; // public visible content
$conditionUser .= ' (content.visibility = 0 AND content.contentcontainer_id=' . $this->user->contentContainerRecord->id . ')'; // private content of user
$conditionUser .= ' (content.visibility = 0 AND content.contentcontainer_id=' . $queryUser->contentcontainer_id . ')'; // private content of user
if (Yii::$app->getModule('friendship')->getIsEnabled()) {
$this->query->leftJoin('user_friendship cff', 'cuser.id=cff.user_id AND cff.friend_user_id=:fuid', [':fuid' => $this->user->id]);
$this->query->leftJoin('user_friendship cff', 'cuser.id=cff.user_id AND cff.friend_user_id=:fuid', [':fuid' => $queryUser->id]);
$conditionUser .= ' OR (content.visibility = 0 AND cff.id IS NOT NULL)'; // users are friends
}
$conditionUser .= ')';
// Created content of is always visible
$conditionUser .= 'OR content.created_by=' . $this->user->id;
$conditionUser .= 'OR content.created_by=' . $queryUser->id;
$this->query->andWhere("{$conditionSpace} OR {$conditionUser} OR content.contentcontainer_id IS NULL");
}
public function isActive()
{
return in_array(static::ID, $this->filters);
return $this->container instanceof User && $this->streamQuery->user !== null && in_array(static::ID, $this->filters);
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace humhub\modules\user\tests\codeception\_support;
use humhub\modules\content\models\Content;
use humhub\modules\post\models\Post;
use humhub\modules\user\models\User;
use humhub\modules\user\stream\ProfileStreamQuery;
use tests\codeception\_support\HumHubDbTestCase;
class ProfileStreamTest extends HumHubDbTestCase
{
/**
* @var User
*/
protected $user;
public function _before()
{
Post::deleteAll();
Content::deleteAll();
$this->enableFriendships(false);
$this->user = $this->becomeUser('User1');
}
/**
* @return ProfileStreamQuery
*/
protected function createQuery($includeContributions = true)
{
return new ProfileStreamQuery(['container' => $this->user, 'includeContributions' => $includeContributions]);
}
protected function createProfilePost($visibility, $message = 'My first post!')
{
return $this->createPost($this->user, $visibility, $message);
}
protected function createPost($container, $visibility, $message = 'My first post!')
{
$post = new Post($container, $visibility, ['message' => $message]);
$this->assertTrue($post->save());
return $post;
}
}

View File

@ -0,0 +1,133 @@
<?php
namespace modules\user\tests\codeception\unit\stream;
use humhub\modules\content\models\Content;
use humhub\modules\post\models\Post;
use humhub\modules\space\models\Space;
use humhub\modules\user\tests\codeception\_support\ProfileStreamTest;
use yii\base\Exception;
class PinnedContentStreamTest extends ProfileStreamTest
{
/**
* Make sure profile owner sees public space contributions
* @throws Exception
*/
public function testPinnedProfilePostIsFirst()
{
$post1 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'First');
$post2 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Second');
$post3 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Third');
$this->pinPost($post2);
$result = $this->createQuery()->all();
$this->assertCount(3, $result);
$this->assertEquals($post2->content->id, $result[0]->id);
$this->assertEquals($post3->content->id, $result[1]->id);
$this->assertEquals($post1->content->id, $result[2]->id);
}
/**
* Make sure profile owner sees public space contributions
* @throws Exception
*/
public function testMultiplePinnedProfilePostsAreFirst()
{
$post1 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'First');
$post2 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Second');
$post3 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Third');
$this->pinPost($post2);
$this->pinPost($post1);
$result = $this->createQuery()->all();
$this->assertCount(3, $result);
$this->assertEquals($post2->content->id, $result[0]->id);
$this->assertEquals($post1->content->id, $result[1]->id);
$this->assertEquals($post3->content->id, $result[2]->id);
}
/**
* Make sure contributed posts are not actually pinned to the profile stream, but visible
* @throws Exception
*/
public function testPinnedSpacePostIsNotPinnedToProfileStream()
{
$post1 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'First');
$post2 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Second');
$spacePost = $this->createPost(Space::findOne(2), Content::VISIBILITY_PUBLIC, 'Space');
$post3 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Third');
// We pin a profile and a contributed post
$this->pinPost($post2);
$this->pinPost($spacePost);
$result = $this->createQuery()->all();
$this->assertCount(4, $result);
$this->assertEquals($post2->content->id, $result[0]->id); // Pinned post first
$this->assertEquals($post3->content->id, $result[1]->id);
$this->assertEquals($spacePost->content->id, $result[2]->id);
$this->assertEquals($post1->content->id, $result[3]->id);
}
/**
* Make sure non initial queries contain pinned contribution posts
*/
public function testPinnedContributionPostIsLoadedInNonInitialQuery()
{
$spacePost = $this->createPost(Space::findOne(2), Content::VISIBILITY_PUBLIC, 'Space');
$post1 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'First');
$post2 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Second');
$post3 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Third');
$this->pinPost($spacePost);
$result = $this->createQuery()->from($post2->content->id)->all();
$this->assertCount(2, $result);
$this->assertEquals($post1->content->id, $result[0]->id);
$this->assertEquals($spacePost->content->id, $result[1]->id);
}
/**
* Make sure when loading a stream with content, the last entry in the result is not a pinned content in case there are
* unpinned entries available
*/
public function testLastStreamEntryOfInitialRequestNotPinned()
{
$post1 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'First');
$post2 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Second');
$post3 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Third');
$post4 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Forth');
$post5 = $this->createProfilePost(Content::VISIBILITY_PUBLIC, 'Fifth');
$this->pinPost($post1);
$this->pinPost($post2);
$result = $this->createQuery()->limit(2)->all();
$this->assertCount(4, $result);
$this->assertEquals($post2->content->id, $result[0]->id);
$this->assertEquals($post1->content->id, $result[1]->id);
$this->assertEquals($post5->content->id, $result[2]->id);
$this->assertEquals($post4->content->id, $result[3]->id);
// Second query
$result2 = $this->createQuery()->from($post4->content->id)->all();
$this->assertCount(1, $result2);
$this->assertEquals($post3->content->id, $result2[0]->id);
}
protected function pinPost(Post $post)
{
$this->assertEquals(1, $post->content->updateAttributes(['pinned' => 1]));
}
}

View File

@ -0,0 +1,126 @@
<?php
namespace modules\user\tests\codeception\unit\stream;
use humhub\modules\content\models\Content;
use humhub\modules\space\models\Space;
use humhub\modules\user\tests\codeception\_support\ProfileStreamTest;
use yii\base\Exception;
class ProfileStreamIncludeContributionsTest extends ProfileStreamTest
{
/**
* Make sure profile owner sees public space contributions
* @throws Exception
*/
public function testProfileOwnerSeesPublicSpacePost()
{
$post = $this->createPost(Space::findOne(2), Content::VISIBILITY_PUBLIC);
$result = $this->createQuery(true)->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile owner does not see public space contributions if contribution filter not active
* @throws Exception
*/
public function testProfileOwnerDoesNotSeePublicSpacePostWithoutContributionsFilter()
{
$this->createPost(Space::findOne(2), Content::VISIBILITY_PUBLIC);
$this->assertEmpty( $this->createQuery(false)->all());
}
/**
* Make sure profile owner sees private space contributions
* @throws Exception
*/
public function testProfileOwnerSeesPrivateSpacePost()
{
$post = $this->createPost(Space::findOne(2), Content::VISIBILITY_PRIVATE);
$result = $this->createQuery(true)->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile owner does not see private space contributions if contribution filter not active
* @throws Exception
*/
public function testProfileOwnerDoesNotSeePrivateSpacePostWithoutContributionsFilter()
{
$this->createPost(Space::findOne(2), Content::VISIBILITY_PRIVATE);
$this->assertEmpty( $this->createQuery(false)->all());
}
/**
* Make sure user sees private contributions of member spaces in another profile stream
*
* @throws Exception
*/
public function testUserDoesNotSeePrivateSpacePostAsNonMember()
{
$this->createPost(Space::findOne(2), Content::VISIBILITY_PRIVATE);
// User2 is not member of space2
$this->becomeUser('User2');
$this->assertEmpty($this->createQuery(true)->all());
}
/**
* Make sure user does not see private contributions of non member spaces in another profile stream
*
* @throws Exception
*/
public function testUserSeesPrivateSpacePostAsMember()
{
$post = $this->createPost(Space::findOne(3), Content::VISIBILITY_PRIVATE);
// User2 is not member of space3
$this->becomeUser('User2');
$result = $this->createQuery(true)->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure user sees public contributions of member spaces in another profile stream
*
* @throws Exception
*/
public function testUserSeesPublicSpacePostAsNonMember()
{
$post = $this->createPost(Space::findOne(2), Content::VISIBILITY_PUBLIC);
// User2 is not member of space2
$this->becomeUser('User2');
$result = $this->createQuery(true)->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure user sees public contributions of non member spaces in another profile stream
*
* @throws Exception
*/
public function testUserSeesPublicSpacePostAsMember()
{
$post = $this->createPost(Space::findOne(3), Content::VISIBILITY_PUBLIC);
// User2 is not member of space3
$this->becomeUser('User2');
$result = $this->createQuery(true)->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace humhub\modules\user\tests\codeception\unit\stream;
use humhub\modules\comment\models\Comment;
use humhub\modules\content\models\Content;
use humhub\modules\content\widgets\richtext\ProsemirrorRichText;
use humhub\modules\content\widgets\richtext\RichText;
use humhub\modules\post\models\Post;
use humhub\modules\space\models\Space;
use humhub\modules\stream\models\StreamQuery;
use humhub\modules\user\models\Mentioning;
use humhub\modules\user\models\User;
use humhub\modules\user\notifications\Mentioned;
use humhub\modules\user\stream\filters\IncludeAllContributionsFilter;
use humhub\modules\user\stream\ProfileStreamQuery;
use humhub\modules\user\tests\codeception\_support\ProfileStreamTest;
use tests\codeception\_support\HumHubDbTestCase;
use yii\base\Exception;
class SimpleProfileStreamNoContributionsTest extends SimpleProfileStreamTest
{
/**
* @return ProfileStreamQuery
*/
protected function createQuery($includeContributions = false)
{
return new ProfileStreamQuery(['container' => $this->user, 'includeContributions' => $includeContributions]);
}
/**
* @throws Exception
*/
public function testEmptyStream()
{
$this->assertEmpty($this->createQuery()->all());
}
/**
* Make sure profile owner sees public content
* @throws Exception
*/
public function testProfileOwnerPublicProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PUBLIC);
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile owner sees private content
* @throws Exception
*/
public function testProfileOwnerPrivateProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PRIVATE);
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure public content is visible for non friend users if friendship system is not active
*
* @throws Exception
*/
public function testUserAccessPublicProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PUBLIC);
$this->becomeUser('User2');
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile stream does not include private content for non friends if friendship system is deactivated
* @throws Exception
*/
public function testUserAccessPrivateProfilePost()
{
$this->createProfilePost(Content::VISIBILITY_PRIVATE);
$this->becomeUser('User2');
$this->assertEmpty($this->createQuery()->all());
}
/**
* Make sure guests can see public profile posts
* @throws Exception
*/
public function testGuestAccessPublicProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PUBLIC);
$this->logout();
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile stream does not include private content for non friends if friendship system is deactivated
* @throws Exception
*/
public function testGuestAccessPrivateProfilePost()
{
$this->createProfilePost(Content::VISIBILITY_PRIVATE);
$this->logout();
$this->assertEmpty($this->createQuery()->all());
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace humhub\modules\user\tests\codeception\unit\stream;
use humhub\modules\comment\models\Comment;
use humhub\modules\content\models\Content;
use humhub\modules\content\widgets\richtext\ProsemirrorRichText;
use humhub\modules\content\widgets\richtext\RichText;
use humhub\modules\post\models\Post;
use humhub\modules\space\models\Space;
use humhub\modules\stream\models\StreamQuery;
use humhub\modules\user\models\Mentioning;
use humhub\modules\user\models\User;
use humhub\modules\user\notifications\Mentioned;
use humhub\modules\user\stream\filters\IncludeAllContributionsFilter;
use humhub\modules\user\stream\ProfileStreamQuery;
use humhub\modules\user\tests\codeception\_support\ProfileStreamTest;
use tests\codeception\_support\HumHubDbTestCase;
use yii\base\Exception;
class SimpleProfileStreamTest extends ProfileStreamTest
{
/**
* @throws Exception
*/
public function testEmptyStream()
{
$this->assertEmpty($this->createQuery()->all());
}
/**
* Make sure profile owner sees public content
* @throws Exception
*/
public function testProfileOwnerPublicProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PUBLIC);
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile owner sees private content
* @throws Exception
*/
public function testProfileOwnerPrivateProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PRIVATE);
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure public content is visible for non friend users if friendship system is not active
*
* @throws Exception
*/
public function testUserAccessPublicProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PUBLIC);
$this->becomeUser('User2');
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile stream does not include private content for non friends if friendship system is deactivated
* @throws Exception
*/
public function testUserAccessPrivateProfilePost()
{
$this->createProfilePost(Content::VISIBILITY_PRIVATE);
$this->becomeUser('User2');
$this->assertEmpty($this->createQuery()->all());
}
/**
* Make sure guests can see public profile posts
* @throws Exception
*/
public function testGuestAccessPublicProfilePost()
{
$post = $this->createProfilePost(Content::VISIBILITY_PUBLIC);
$this->logout();
$result = $this->createQuery()->all();
$this->assertCount(1, $result);
$this->assertEquals($post->content->id, $result[0]->id);
}
/**
* Make sure profile stream does not include private content for non friends if friendship system is deactivated
* @throws Exception
*/
public function testGuestAccessPrivateProfilePost()
{
$this->createProfilePost(Content::VISIBILITY_PRIVATE);
$this->logout();
$this->assertEmpty($this->createQuery()->all());
}
}

View File

@ -16,6 +16,11 @@ class ProfileStreamFilterNavigation extends WallStreamFilterNavigation
{
parent::initFilters();
// IncludeAllContributionsFilter currently only supported for non guest users
if(Yii::$app->user->isGuest) {
return;
}
/** @var Module $module */
$module = Yii::$app->getModule('user');

View File

@ -291,6 +291,7 @@ class HumHubDbTestCase extends Unit
{
$user = User::findOne(['username' => $userName]);
Yii::$app->user->switchIdentity($user);
return $user;
}
public function logout()