mirror of
https://github.com/humhub/humhub.git
synced 2025-01-16 21:58:17 +01:00
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:
parent
2a0faf2a22
commit
85767eb055
@ -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)
|
||||
--------------------------
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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'];
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user