View own invisible profile (#6012)

* View own invisible profile

* Update CHANGELOG.md

* Improve content container visibility filter

* Implement AbstractActiveQueryContentContainer

* Test invisible user
This commit is contained in:
Yuriy Bakhtin 2023-01-10 19:22:23 +04:00 committed by GitHub
parent 2a0faf2a22
commit 85767eb055
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 182 additions and 98 deletions

View File

@ -10,6 +10,7 @@ HumHub Changelog
- Fix #5997: Possible NULL param value in Birthday field (PHP 8.1)
- Enh #6001: Added new `ContentActiveFixture` and migrated `PostFixture` to it
- Fix #6007: Fix number of space members
- Fix #6012: View own invisible profile
1.13.0 (December 21, 2022)
--------------------------

View File

@ -0,0 +1,69 @@
<?php
namespace humhub\modules\content\components;
use humhub\modules\user\models\User;
use yii\db\ActiveQuery;
/**
* AbstractActiveQueryContentContainer is used for Active Query of the ContentContainerActiveRecord (Space, User)
*
* @since 1.13.1
*/
abstract class AbstractActiveQueryContentContainer extends ActiveQuery
{
/**
* Query keywords will be broken down into array needles with this length
* Meaning, if you search for "word1 word2 word3" and MAX_SEARCH_NEEDLES being 2
* word3 will be left out, and search will only look for word1, word2.
*
* @var int
*/
const MAX_SEARCH_NEEDLES = 5;
/**
* Filter query by visible records for the given or current user
*
* @param User|null $user
* @return ActiveQuery
*/
abstract public function visible(?User $user = null): ActiveQuery;
/**
* Performs a container text search
*
* @param string|array $keywords
* @param array|null $fields if empty the fields will be used from the method getSearchableFields()
* @return ActiveQuery
*/
abstract public function search($keywords, ?array $fields = null): ActiveQuery;
/**
* Returns a list of fields to be included in a container search.
*
* @return array
*/
abstract protected function getSearchableFields(): array;
/**
* Filter this query by keyword
*
* @param string $keyword
* @param array|null $fields if empty the fields will be used from the method getSearchableFields()
* @return ActiveQuery
*/
abstract public function searchKeyword(string $keyword, ?array $fields = null): ActiveQuery;
/**
* @param string|array $keywords
* @return array
*/
protected function setUpKeywords($keywords): array
{
if (!is_array($keywords)) {
$keywords = explode(' ', $keywords);
}
return array_slice($keywords, 0, static::MAX_SEARCH_NEEDLES);
}
}

View File

@ -8,17 +8,13 @@
namespace humhub\modules\content\components;
use humhub\modules\admin\permissions\ManageSpaces;
use humhub\components\Controller;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\space\models\Space;
use humhub\modules\user\helpers\AuthHelper;
use humhub\modules\user\models\User;
use Yii;
use yii\web\HttpException;
use humhub\components\Controller;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\content\components\ContentContainerModule;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\components\ContentContainerControllerAccess;
/**
* Controller is the base class of web controllers which acts in scope of a ContentContainer (e.g. Space or User).
@ -149,25 +145,23 @@ class ContentContainerController extends Controller
}
/**
* @param $guid
* @param string|null $guid
* @return ContentContainerActiveRecord|null
*/
private function getContentContainerByGuid($guid)
private function getContentContainerByGuid(?string $guid): ?ContentContainerActiveRecord
{
if (!empty($guid)) {
$contentContainer = ContentContainer::findOne(['guid' => $guid]);
if ($contentContainer !== null) {
/* @var Space|User $contentContainerClass */
$contentContainerClass = $contentContainer->class;
$query = $contentContainerClass::find()->where(['guid' => $guid]);
if (!Yii::$app->user->can(new ManageSpaces())) {
$query->visible();
}
return $query->one();
}
if (empty($guid)) {
return null;
}
return null;
$contentContainer = ContentContainer::findOne(['guid' => $guid]);
if ($contentContainer === null) {
return null;
}
/* @var Space|User $contentContainerClass */
$contentContainerClass = $contentContainer->class;
return $contentContainerClass::find()->where(['guid' => $guid])->visible()->one();
}
}

View File

@ -8,11 +8,8 @@
namespace humhub\modules\content\models;
use humhub\components\behaviors\PolymorphicRelation;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\User;
use yii\db\ActiveRecord;
/**

View File

@ -10,9 +10,10 @@
namespace humhub\modules\space\components;
use humhub\events\ActiveQueryEvent;
use humhub\modules\admin\permissions\ManageSpaces;
use humhub\modules\content\components\AbstractActiveQueryContentContainer;
use humhub\modules\space\models\Membership;
use humhub\modules\space\models\Space;
use humhub\modules\user\components\ActiveQueryUser;
use humhub\modules\user\models\User;
use humhub\modules\user\Module;
use Yii;
@ -24,10 +25,8 @@ use yii\db\ActiveQuery;
*
* @since 1.4
*/
class ActiveQuerySpace extends ActiveQuery
class ActiveQuerySpace extends AbstractActiveQueryContentContainer
{
const MAX_SEARCH_NEEDLES = 5;
/**
* @event Event an event that is triggered when only visible spaces are requested via [[visible()]].
*/
@ -36,10 +35,10 @@ class ActiveQuerySpace extends ActiveQuery
/**
* Only returns spaces which are visible for this user
*
* @param User|null $user
* @return ActiveQuerySpace the query
* @inheritdoc
* @return self
*/
public function visible(User $user = null)
public function visible(?User $user = null): ActiveQuery
{
$this->trigger(self::EVENT_CHECK_VISIBILITY, new ActiveQueryEvent(['query' => $this]));
@ -52,6 +51,10 @@ class ActiveQuerySpace extends ActiveQuery
}
if ($user !== null) {
if ($user->can(ManageSpaces::class)) {
return $this;
}
$this->andWhere(['OR',
['IN', 'space.visibility', [Space::VISIBILITY_ALL, Space::VISIBILITY_REGISTERED_ONLY]],
['AND',
@ -67,13 +70,18 @@ class ActiveQuerySpace extends ActiveQuery
}
/**
* Performs a space full text search
*
* @param string|array $keywords
* @param array $columns
* @return ActiveQuerySpace the query
* @inerhitdoc
*/
public function search($keywords, $columns = ['space.name', 'space.description', 'contentcontainer.tags_cached'])
protected function getSearchableFields(): array
{
return ['space.name', 'space.description', 'contentcontainer.tags_cached'];
}
/**
* @inheritdoc
* @return self
*/
public function search($keywords, ?array $fields = null): ActiveQuery
{
if (empty($keywords)) {
return $this;
@ -81,26 +89,36 @@ class ActiveQuerySpace extends ActiveQuery
$this->joinWith('contentContainerRecord');
if (!is_array($keywords)) {
$keywords = explode(' ', $keywords);
}
foreach (array_slice($keywords, 0, static::MAX_SEARCH_NEEDLES) as $keyword) {
$conditions = [];
foreach ($columns as $field) {
$conditions[] = ['LIKE', $field, $keyword];
}
$this->andWhere(array_merge(['OR'], $conditions));
foreach ($this->setUpKeywords($keywords) as $keyword) {
$this->searchKeyword($keyword, $fields);
}
return $this;
}
/**
* @inheritdoc
* @return self
*/
public function searchKeyword(string $keyword, ?array $fields = null): ActiveQuery
{
if (empty($fields)) {
$fields = $this->getSearchableFields();
}
$conditions = [];
foreach ($fields as $field) {
$conditions[] = ['LIKE', $field, $keyword];
}
return $this->andWhere(array_merge(['OR'], $conditions));
}
/**
* Exclude blocked spaces for the given $user or for the current User
*
* @param User $user
* @return ActiveQueryUser the query
* @param User|null $user
* @return self
*/
public function filterBlockedSpaces(?User $user = null): ActiveQuerySpace
{

View File

@ -10,10 +10,12 @@ namespace humhub\modules\user\components;
use humhub\events\ActiveQueryEvent;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\content\components\AbstractActiveQueryContentContainer;
use humhub\modules\user\models\fieldtype\BaseTypeVirtual;
use humhub\modules\user\models\Group;
use humhub\modules\user\models\GroupUser;
use humhub\modules\user\models\ProfileField;
use humhub\modules\user\models\User;
use humhub\modules\user\models\User as UserModel;
use humhub\modules\user\Module;
use Yii;
@ -24,17 +26,8 @@ use yii\db\ActiveQuery;
*
* @author luke
*/
class ActiveQueryUser extends ActiveQuery
class ActiveQueryUser extends AbstractActiveQueryContentContainer
{
/**
* Query keywords will be broken down into array needles with this length
* Meaning, if you search for "word1 word2 word3" and MAX_SEARCH_NEEDLES being 2
* word3 will be left out, and search will only look for word1, word2.
*
* @var string
*/
const MAX_SEARCH_NEEDLES = 5;
/**
* @event Event an event that is triggered when only visible users are requested via [[visible()]].
*/
@ -73,20 +66,36 @@ class ActiveQueryUser extends ActiveQuery
* Returns only users that should appear in user lists or in the search results.
* Also only active (enabled) users are returned.
*
* @return ActiveQueryUser the query
* @since 1.2.3
* @inheritdoc
* @return self
*/
public function visible()
public function visible(?User $user = null): ActiveQuery
{
$this->trigger(self::EVENT_CHECK_VISIBILITY, new ActiveQueryEvent(['query' => $this]));
if ($user === null && !Yii::$app->user->isGuest) {
try {
$user = Yii::$app->user->getIdentity();
} catch (\Throwable $e) {
Yii::error($e, 'user');
}
}
$allowedVisibilities = [UserModel::VISIBILITY_ALL];
if (!Yii::$app->user->isGuest) {
if ($user !== null) {
if ((new PermissionManager(['subject' => $user]))->can(ManageUsers::class)) {
return $this;
}
$allowedVisibilities[] = UserModel::VISIBILITY_REGISTERED_ONLY;
}
return $this->active()
->andWhere(['IN', 'user.visibility', $allowedVisibilities]);
->andWhere(['OR',
['user.id' => $user->id], // User can view own profile
['IN', 'user.visibility', $allowedVisibilities]
]);
}
@ -104,14 +113,10 @@ class ActiveQueryUser extends ActiveQuery
}
/**
* Performs a user full text search
*
* @param string|array $keywords
* @param array|null $fields if empty all searchable profile fields will be used
*
* @return ActiveQueryUser the query
* @inheritdoc
* @return self
*/
public function search($keywords, $fields = null)
public function search($keywords, ?array $fields = null): ActiveQuery
{
if (empty($keywords)) {
return $this;
@ -120,21 +125,20 @@ class ActiveQueryUser extends ActiveQuery
$this->joinWith('profile')->joinWith('contentContainerRecord');
foreach ($this->setUpKeywords($keywords) as $keyword) {
$this->andWhere(array_merge(['OR'], $this->createKeywordCondition($keyword, $fields)));
$this->searchKeyword($keyword, $fields);
}
return $this;
}
/**
* @param $keyword
* @param null $fields
* @return array
* @inheritdoc
* @return self
*/
protected function createKeywordCondition($keyword, $fields = null)
public function searchKeyword(string $keyword, ?array $fields = null): ActiveQuery
{
if (empty($fields)) {
$fields = $this->getSearchableUserFields();
$fields = $this->getSearchableFields();
}
$conditions = [];
@ -148,20 +152,7 @@ class ActiveQueryUser extends ActiveQuery
$conditions[] = array_merge(['OR'], $subConditions);
}
return $conditions;
}
/**
* @param $keywords
* @return array
*/
protected function setUpKeywords($keywords)
{
if (!is_array($keywords)) {
$keywords = explode(' ', $keywords);
}
return array_slice($keywords, 0, static::MAX_SEARCH_NEEDLES);
return $this->andWhere(array_merge(['OR'], $conditions));
}
/**
@ -216,11 +207,9 @@ class ActiveQueryUser extends ActiveQuery
}
/**
* Returns a list of fields to be included in a user search.
*
* @return array
* @inheritdoc
*/
private function getSearchableUserFields()
protected function getSearchableFields(): array
{
$fields = ['user.username', 'contentcontainer.tags_cached'];

View File

@ -30,7 +30,26 @@ class InvisibleUserCest
$I->click('Save');
$I->seeSuccess();
$I->amGoingTo('be sure Sara Tester is visible');
$I->amGoingTo('be sure Sara Tester is visible in administration');
// Administration users list
$I->amOnRoute(['/admin/user']);
$I->waitForText($userName);
$I->amGoingTo('be sure Sara Tester is visible for owner');
$I->amUser2(true);
// People
$I->amOnRoute(['/people']);
$I->waitForText('People');
$I->see($userName);
// Space members
$I->amOnSpace1();
$I->waitForText('Space members');
$I->click('Members', '.statistics');
$I->waitForText('Members', null, '#globalModal');
$I->see($userName, '#globalModal');
$I->amGoingTo('be sure Sara Tester is invisible for other users without permissions');
$I->amUser1(true);
// People
$I->amOnRoute(['/people']);
$I->waitForText('People');
@ -41,9 +60,6 @@ class InvisibleUserCest
$I->click('Members', '.statistics');
$I->waitForText('Members', null, '#globalModal');
$I->dontSee($userName, '#globalModal');
// Administration users list
$I->amOnRoute(['/admin/user']);
$I->waitForText($userName);
}
}