5699 allow users to edit invites (#6082)

* #6061 - Administration: Add a confirmation on profile field delete button

* Update ProfileCest.php

* Allow users to invite by link

* Change Button Regenerate Link

* rewording

* fix InviteCest

* fix InviteCest

---------

Co-authored-by: Lucas Bartholemy <luke-@users.noreply.github.com>
Co-authored-by: Lucas Bartholemy <lucas@bartholemy.com>
This commit is contained in:
Marc Farré 2023-02-07 14:14:07 +01:00 committed by GitHub
parent 252e32df46
commit 79cf180122
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 551 additions and 136 deletions

View File

@ -13,5 +13,6 @@ HumHub Changelog (DEVELOP)
- Enh #6035: Added Estonian language
- Fix #5956: Display all newer comments after current comment
- Enh #6061: Administration: Add a confirmation on profile field delete button
- Enh #5699: Allow users to invite by link
- Enh #6081: Added corresponding CSS variables for LESS color variables
- Fix #6022: Fix Changelog Link with new Marketplace URL

View File

@ -0,0 +1,47 @@
<?php
use humhub\modules\user\Module;
use yii\db\Migration;
/**
* Class m230130_140944_rename_auth_invite_setting
*/
class m230130_140944_rename_auth_invite_setting extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
/* @var $module Module */
$module = Yii::$app->getModule('user');
$settingsManager = $module->settings;
$internalUsersCanInvite = $settingsManager->get('auth.internalUsersCanInvite');
$settingsManager->set('auth.internalUsersCanInviteByEmail', $internalUsersCanInvite);
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
echo "m230130_140944_rename_auth_invite_setting cannot be reverted.\n";
return false;
}
/*
// Use up()/down() to run migration code without a transaction.
public function up()
{
}
public function down()
{
echo "m230130_140944_rename_auth_invite_setting cannot be reverted.\n";
return false;
}
*/
}

View File

@ -22,7 +22,8 @@ class AuthenticationSettingsForm extends \yii\base\Model
{
public $internalAllowAnonymousRegistration;
public $internalRequireApprovalAfterRegistration;
public $internalUsersCanInvite;
public $internalUsersCanInviteByEmail;
public $internalUsersCanInviteByLink;
public $showRegistrationUserGroup;
public $blockUsers;
public $defaultUserIdleTimeoutSec;
@ -43,7 +44,8 @@ class AuthenticationSettingsForm extends \yii\base\Model
$module = Yii::$app->getModule('user');
$settingsManager = $module->settings;
$this->internalUsersCanInvite = $settingsManager->get('auth.internalUsersCanInvite');
$this->internalUsersCanInviteByEmail = $settingsManager->get('auth.internalUsersCanInviteByEmail');
$this->internalUsersCanInviteByLink = $settingsManager->get('auth.internalUsersCanInviteByLink');
$this->internalRequireApprovalAfterRegistration = $settingsManager->get('auth.needApproval');
$this->internalAllowAnonymousRegistration = $settingsManager->get('auth.anonymousRegistration');
$this->showRegistrationUserGroup = $settingsManager->get('auth.showRegistrationUserGroup');
@ -62,7 +64,7 @@ class AuthenticationSettingsForm extends \yii\base\Model
public function rules()
{
return [
[['internalUsersCanInvite', 'internalAllowAnonymousRegistration', 'internalRequireApprovalAfterRegistration', 'allowGuestAccess', 'showCaptureInRegisterForm', 'showRegistrationUserGroup', 'blockUsers'], 'boolean'],
[['internalUsersCanInviteByEmail', 'internalUsersCanInviteByLink', 'internalAllowAnonymousRegistration', 'internalRequireApprovalAfterRegistration', 'allowGuestAccess', 'showCaptureInRegisterForm', 'showRegistrationUserGroup', 'blockUsers'], 'boolean'],
['defaultUserProfileVisibility', 'in', 'range' => array_keys(User::getVisibilityOptions(false))],
['defaultUserIdleTimeoutSec', 'integer', 'min' => 20],
[['registrationApprovalMailContent', 'registrationDenialMailContent'], 'string']
@ -77,7 +79,8 @@ class AuthenticationSettingsForm extends \yii\base\Model
return [
'internalRequireApprovalAfterRegistration' => Yii::t('AdminModule.user', 'Require group admin approval after registration'),
'internalAllowAnonymousRegistration' => Yii::t('AdminModule.user', 'New users can register'),
'internalUsersCanInvite' => Yii::t('AdminModule.user', 'Members can invite external users by email'),
'internalUsersCanInviteByEmail' => Yii::t('AdminModule.user', 'Members can invite external users by email'),
'internalUsersCanInviteByLink' => Yii::t('AdminModule.user', 'Members can invite external users by link'),
'showRegistrationUserGroup' => Yii::t('AdminModule.user', 'Show group selection at registration'),
'blockUsers' => Yii::t('AdminModule.user', 'Allow users to block each other'),
'defaultUserIdleTimeoutSec' => Yii::t('AdminModule.user', 'Default user idle timeout, auto-logout (in seconds, optional)'),
@ -100,7 +103,8 @@ class AuthenticationSettingsForm extends \yii\base\Model
$module = Yii::$app->getModule('user');
$settingsManager = $module->settings;
$settingsManager->set('auth.internalUsersCanInvite', $this->internalUsersCanInvite);
$settingsManager->set('auth.internalUsersCanInviteByEmail', $this->internalUsersCanInviteByEmail);
$settingsManager->set('auth.internalUsersCanInviteByLink', $this->internalUsersCanInviteByLink);
$settingsManager->set('auth.needApproval', $this->internalRequireApprovalAfterRegistration);
$settingsManager->set('auth.anonymousRegistration', $this->internalAllowAnonymousRegistration);
$settingsManager->set('auth.showRegistrationUserGroup', $this->showRegistrationUserGroup);

View File

@ -26,7 +26,9 @@ $userModule = Yii::$app->getModule('user');
<?= $form->field($model, 'showCaptureInRegisterForm')->checkbox(); ?>
<?= $form->field($model, 'internalUsersCanInvite')->checkbox(); ?>
<?= $form->field($model, 'internalUsersCanInviteByEmail')->checkbox(); ?>
<?= $form->field($model, 'internalUsersCanInviteByLink')->checkbox(); ?>
<?= $form->field($model, 'internalRequireApprovalAfterRegistration')->checkbox(); ?>

View File

@ -15,8 +15,8 @@ use humhub\modules\ui\form\widgets\ActiveForm;
<?= Button::back(['index'], Yii::t('AdminModule.base', 'Back to overview'))
->right(false) ?>
<?= ModalButton::success(Yii::t('AdminModule.user', 'Send invite'))
->load(['/user/invite'])->icon('invite')->sm() ?>
<?= ModalButton::success(Yii::t('AdminModule.user', 'Invite new people'))
->load(['/user/invite', 'adminIsAlwaysAllowed' => true])->icon('invite')->sm() ?>
</div>
<h4 class="pull-left"><?= Yii::t('AdminModule.user', 'Add new user') ?></h4>

View File

@ -143,6 +143,7 @@ class ConfigController extends Controller
$form->internalRequireApprovalAfterRegistration = false;
$form->internalAllowAnonymousRegistration = false;
$form->canInviteExternalUsersByEmail = false;
$form->canInviteExternalUsersByLink = false;
$form->enableFriendshipModule = false;
}
@ -151,6 +152,7 @@ class ConfigController extends Controller
$form->internalRequireApprovalAfterRegistration = true;
$form->internalAllowAnonymousRegistration = true;
$form->canInviteExternalUsersByEmail = false;
$form->canInviteExternalUsersByLink = false;
$form->enableFriendshipModule = false;
}
@ -159,6 +161,7 @@ class ConfigController extends Controller
$form->internalRequireApprovalAfterRegistration = false;
$form->internalAllowAnonymousRegistration = false;
$form->canInviteExternalUsersByEmail = true;
$form->canInviteExternalUsersByLink = true;
$form->enableFriendshipModule = true;
}
@ -167,6 +170,7 @@ class ConfigController extends Controller
$form->internalRequireApprovalAfterRegistration = false;
$form->internalAllowAnonymousRegistration = true;
$form->canInviteExternalUsersByEmail = true;
$form->canInviteExternalUsersByLink = true;
$form->enableFriendshipModule = false;
}
@ -174,7 +178,8 @@ class ConfigController extends Controller
Yii::$app->getModule('user')->settings->set('auth.needApproval', $form->internalRequireApprovalAfterRegistration);
Yii::$app->getModule('user')->settings->set('auth.anonymousRegistration', $form->internalAllowAnonymousRegistration);
Yii::$app->getModule('user')->settings->set('auth.allowGuestAccess', $form->allowGuestAccess);
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInvite', $form->canInviteExternalUsersByEmail);
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByEmail', $form->canInviteExternalUsersByEmail);
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByLink', $form->canInviteExternalUsersByLink);
Yii::$app->getModule('friendship')->settings->set('enable', $form->enableFriendshipModule);
return $this->redirect(Yii::$app->getModule('installer')->getNextConfigStepUrl());
}

View File

@ -38,6 +38,11 @@ class SecurityForm extends \yii\base\Model
*/
public $canInviteExternalUsersByEmail;
/**
* @var boolean allow invite from external users by link
*/
public $canInviteExternalUsersByLink;
/**
* @var boolean enable friendship system
*/
@ -63,6 +68,7 @@ class SecurityForm extends \yii\base\Model
'internalRequireApprovalAfterRegistration' => Yii::t('InstallerModule.base', 'Newly registered users have to be activated by an admin first'),
'internalAllowAnonymousRegistration' => Yii::t('InstallerModule.base', 'External users can register (show registration form on login)'),
'canInviteExternalUsersByEmail' => Yii::t('InstallerModule.base', 'Registered members can invite new users via email'),
'canInviteExternalUsersByLink' => Yii::t('InstallerModule.base', 'Registered members can invite new users via link'),
'enableFriendshipModule' => Yii::t('InstallerModule.base', 'Allow friendships between members'),
];
}

View File

@ -40,7 +40,8 @@ class InitialData
// Authentication
Yii::$app->getModule('user')->settings->set('auth.needApproval', '0');
Yii::$app->getModule('user')->settings->set('auth.anonymousRegistration', '1');
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInvite', '1');
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByEmail', '1');
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByLink', '1');
Yii::$app->getModule('user')->settings->set('auth.showCaptureInRegisterForm', '1');
// Mailing

View File

@ -23,6 +23,7 @@ use yii\bootstrap\Html;
<?= $form->field($model, 'internalRequireApprovalAfterRegistration')->checkbox(); ?>
<?= $form->field($model, 'allowGuestAccess')->checkbox(); ?>
<?= $form->field($model, 'canInviteExternalUsersByEmail')->checkbox(); ?>
<?= $form->field($model, 'canInviteExternalUsersByLink')->checkbox(); ?>
<?= $form->field($model, 'enableFriendshipModule')->checkbox(); ?>
<hr>

View File

@ -168,7 +168,6 @@ class CreateController extends Controller
}
return $this->renderAjax('invite', [
'canInviteExternal' => $model->canInviteExternal(),
'model' => $model,
'space' => $space
]);

View File

@ -211,7 +211,34 @@ class MembershipController extends ContentContainerController
]);
}
return $this->renderAjax('invite', ['model' => $model, 'space' => $model->space]);
return $this->renderAjax('invite', [
'model' => $model,
'space' => $model->space,
]);
}
/**
* @return string
* @throws \yii\base\Exception
* @throws \yii\web\ForbiddenHttpException
*/
public function actionResetInviteLink()
{
$model = new InviteForm(['space' => $this->getSpace()]);
if (!$model->space || !$model->space->isAdmin()) {
$this->forbidden();
}
$model->getInviteLink(true);
$this->view->saved();
return $this->renderAjax('invite', [
'model' => $model,
'space' => $model->space,
]);
}
/**

View File

@ -2,13 +2,18 @@
namespace humhub\modules\space\models\forms;
use humhub\libs\UUID;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\space\jobs\AddUsersToSpaceJob;
use humhub\modules\space\models\Membership;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\Invite;
use humhub\modules\user\models\User;
use humhub\modules\user\Module;
use Yii;
use yii\base\Exception;
use yii\base\Model;
use yii\helpers\Url;
use yii\validators\EmailValidator;
/**
@ -18,6 +23,8 @@ use yii\validators\EmailValidator;
*/
class InviteForm extends Model
{
public const SESSION_SPACE_INVITE_ID = 'space_invite_id';
/**
* Field for Invite GUIDs
*
@ -30,7 +37,7 @@ class InviteForm extends Model
*
* @var string
*/
public $inviteExternal;
public $inviteEmails;
/**
* Current Space
@ -51,11 +58,6 @@ class InviteForm extends Model
*/
public $inviteIds = [];
/**
* Parsed list of E-Mails of field inviteExternal
*/
public $invitesExternal = [];
/**
* Indicate for add users to space without invite notification
* @var bool
@ -63,6 +65,11 @@ class InviteForm extends Model
public $withoutInvite = false;
public $allRegisteredUsers = false;
/**
* Parsed list of E-Mails of field inviteEmails
*/
protected $_inviteEmails = [];
/**
* Declares the validation rules.
* The rules state that username and password are required,
@ -73,7 +80,7 @@ class InviteForm extends Model
return [
[['withoutInvite', 'allRegisteredUsers'], 'boolean'],
['invite', 'checkInvite'],
['inviteExternal', 'checkInviteExternal'],
['inviteEmails', 'checkInviteExternal'],
];
}
@ -84,7 +91,7 @@ class InviteForm extends Model
{
return [
'invite' => Yii::t('SpaceModule.base', 'Invites'),
'inviteExternal' => Yii::t('SpaceModule.base', 'New user by e-mail (comma separated)'),
'inviteEmails' => Yii::t('SpaceModule.base', 'New user by e-mail (comma separated)'),
];
}
@ -106,7 +113,7 @@ class InviteForm extends Model
$this->inviteMembers();
}
$this->inviteExternal();
$this->inviteExternalByEmail();
return true;
}
@ -168,9 +175,9 @@ class InviteForm extends Model
/**
* Invite external users by mail
*/
public function inviteExternal()
public function inviteExternalByEmail()
{
if ($this->canInviteExternal()) {
if ($this->canInviteByEmail()) {
foreach ($this->getInvitesExternal() as $email) {
$this->space->inviteMemberByEMail($email, Yii::$app->user->id);
}
@ -180,11 +187,25 @@ class InviteForm extends Model
/**
* Checks if external user invitation setting is enabled
*
* @return int
* @return bool
*/
public function canInviteExternal()
public function canInviteByEmail()
{
return Yii::$app->getModule('user')->settings->get('auth.internalUsersCanInvite');
/** @var Module $module */
$module = Yii::$app->getModule('user');
return !Yii::$app->user->isGuest && $module->settings->get('auth.internalUsersCanInviteByEmail');
}
/**
* Checks if external user invitation setting is enabled
*
* @return bool
*/
public function canInviteByLink()
{
/** @var Module $module */
$module = Yii::$app->getModule('user');
return !Yii::$app->user->isGuest && $module->settings->get('auth.internalUsersCanInviteByLink');
}
/**
@ -281,7 +302,7 @@ class InviteForm extends Model
if ($user) {
$this->addUserToInviteList($user);
} else {
$this->invitesExternal[] = $email;
$this->_inviteEmails[] = $email;
}
}
}
@ -308,7 +329,7 @@ class InviteForm extends Model
*/
public function getInvitesExternal()
{
return $this->invitesExternal;
return $this->_inviteEmails;
}
/**
@ -319,11 +340,25 @@ class InviteForm extends Model
public function getEmails()
{
$emails = [];
foreach (explode(',', $this->inviteExternal) as $email) {
foreach (explode(',', $this->inviteEmails) as $email) {
$emails[] = trim($email);
}
return $emails;
}
/**
* @param bool $forceResetToken
* @return string
* @throws Exception
*/
public function getInviteLink($forceResetToken = false)
{
$token = $this->space->settings->get('inviteToken');
if ($forceResetToken || !$token) {
$token = Yii::$app->security->generateRandomString(Invite::LINK_TOKEN_LENGTH);
$this->space->settings->set('inviteToken', $token);
}
return Url::to(['/user/registration', 'spaceId' => $this->space->id, 'token' => $token], true);
}
}

View File

@ -18,14 +18,14 @@ class MailInviteCest
{
$I->wantTo('ensure that users can be invited by mail within the directory');
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInvite', 1);
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByEmail', 1);
$I->amUser2();
$I->amOnSpace1();
$I->see('Invite');
$I->amGoingTo('invite a user by mail');
$I->sendAjaxPostRequest(Url::to(['/space/membership/invite', 'cguid' => $I->getFixtureSpace(0)->guid]), ['InviteForm[inviteExternal]' => 'a@test.de,b@test.de']);
$I->sendAjaxPostRequest(Url::to(['/space/membership/invite', 'cguid' => $I->getFixtureSpace(0)->guid]), ['InviteForm[inviteEmails]' => 'a@test.de,b@test.de']);
$I->seeEmailIsSent(2);

View File

@ -1,5 +1,16 @@
<?= \humhub\modules\space\widgets\InviteModal::widget([
<?php
use humhub\modules\space\models\forms\InviteForm;
use humhub\modules\space\models\Space;
use humhub\modules\space\widgets\InviteModal;
/**
* @var $space Space
* @var $model InviteForm
*/
?>
<?= InviteModal::widget([
'model' => $model,
'submitText' => Yii::t('SpaceModule.base', 'Done'),
'submitAction' => \yii\helpers\Url::to(['/space/create/invite', 'spaceId' => $space->id])
]); ?>
]); ?>

View File

@ -1,6 +1,17 @@
<?= \humhub\modules\space\widgets\InviteModal::widget([
<?php
use humhub\modules\space\models\forms\InviteForm;
use humhub\modules\space\models\Space;
use humhub\modules\space\widgets\InviteModal;
/**
* @var $space Space
* @var $model InviteForm
*/
?>
<?= InviteModal::widget([
'model' => $model,
'submitText' => Yii::t('SpaceModule.base', 'Send'),
'submitAction' => $space->createUrl('/space/membership/invite'),
'searchUrl' => $space->createUrl('/space/membership/search-invite')
]); ?>
]); ?>

View File

@ -31,7 +31,8 @@ class InviteModal extends Widget
}
return $this->render('inviteModal', [
'canInviteExternal' => Yii::$app->getModule('user')->settings->get('auth.internalUsersCanInvite'),
'canInviteByEmail' => $this->model->canInviteByEmail(),
'canInviteByLink' => $this->model->canInviteByLink(),
'submitText' => $this->submitText,
'submitAction' => $this->submitAction,
'model' => $this->model,

View File

@ -1,6 +1,7 @@
<?php
/* @var $this \humhub\modules\ui\view\components\View */
/* @var $canInviteExternal bool */
/* @var $canInviteByEmail bool */
/* @var $canInviteByLink bool */
/* @var $canAddWithoutInvite bool */
/* @var $submitText string */
/* @var $submitAction string */
@ -10,6 +11,8 @@
/* @var $searchUrl string */
use humhub\modules\user\widgets\UserPickerField;
use humhub\widgets\Button;
use humhub\widgets\ModalButton;
use humhub\widgets\ModalDialog;
use yii\bootstrap\ActiveForm;
use humhub\libs\Html;
@ -20,11 +23,11 @@ $modal = ModalDialog::begin([
$modalAnimationClass = ($model->hasErrors()) ? 'shake' : 'fadeIn';
if ($canInviteExternal && $model->hasErrors('inviteExternal')) {
$isInviteExternalTabActiveClass = 'active';
if ($canInviteByEmail && $model->hasErrors('inviteEmails')) {
$isInviteByEmailTabActiveClass = 'active';
$isInviteTabActiveClass = '';
} else {
$isInviteExternalTabActiveClass = '';
$isInviteByEmailTabActiveClass = '';
$isInviteTabActiveClass = 'active';
}
@ -34,26 +37,35 @@ $form = ActiveForm::begin([
]);
?>
<div class="modal-body">
<?php if ($canInviteExternal) : ?>
<?php if ($canInviteByEmail || $canInviteByLink) : ?>
<div class="text-center">
<ul id="tabs" class="nav nav-tabs tabs-center" data-tabs="tabs">
<li class="<?= $isInviteTabActiveClass ?> tab-internal">
<a href="#internal" data-toggle="tab">
<li class="<?= $isInviteTabActiveClass ?> tab-user-picker">
<a href="#user-picker" data-toggle="tab">
<?= Yii::t('SpaceModule.base', 'Pick users'); ?>
</a>
</li>
<li class="<?= $isInviteExternalTabActiveClass ?> tab-external">
<a href="#external" data-toggle="tab">
<?= Yii::t('SpaceModule.base', 'Invite by email'); ?>
</a>
</li>
<?php if ($canInviteByEmail) : ?>
<li class="<?= $isInviteByEmailTabActiveClass ?> tab-invite-by-email">
<a href="#invite-by-email" data-toggle="tab">
<?= Yii::t('SpaceModule.base', 'Invite by email'); ?>
</a>
</li>
<?php endif; ?>
<?php if ($canInviteByLink) : ?>
<li class="tab-invite-by-link">
<a href="#invite-by-link" data-toggle="tab">
<?= Yii::t('SpaceModule.base', 'Invite by link'); ?>
</a>
</li>
<?php endif; ?>
</ul>
</div>
<br/>
<?php endif; ?>
<div class="tab-content">
<div class="tab-pane <?= $isInviteTabActiveClass ?>" id="internal">
<div class="tab-pane <?= $isInviteTabActiveClass ?>" id="user-picker">
<?= Yii::t('SpaceModule.base',
'To invite users to this space, please type their names below to find and pick them.'); ?>
@ -81,45 +93,103 @@ $form = ActiveForm::begin([
</div>
<?php if ($canInviteExternal) : ?>
<div class="<?= $isInviteExternalTabActiveClass ?> tab-pane" id="external">
<?php if ($canInviteByEmail) : ?>
<div class="<?= $isInviteByEmailTabActiveClass ?> tab-pane" id="invite-by-email">
<?= Yii::t('SpaceModule.base',
'You can also invite external users, which are not registered now. Just add their e-mail addresses separated by comma.'); ?>
'You can also invite external users by email, which are not registered now. Just add their e-mail addresses separated by comma.'); ?>
<br><br>
<?= $form->field($model, 'inviteExternal')->textarea([
'id' => 'space-invite-external',
<?= $form->field($model, 'inviteEmails')->textarea([
'id' => 'space-invite-by-email',
'rows' => '3',
'placeholder' => Yii::t('SpaceModule.base', 'Email addresses'),
]); ?>
</div>
<?php endif; ?>
<script <?= Html::nonce() ?>>
$('#inviteform-allregisteredusers').on('change', function () {
var userPicker = humhub.modules.action.Component.instance('#space-invite-user-picker');
<?php if ($canInviteByLink) : ?>
<div class="tab-pane" id="invite-by-link">
<?= Yii::t('SpaceModule.base',
'You can invite external users who are currently not registered via link. All you need to do is share this secure link with them.'); ?>
<br><br>
if ($(this).is(':checked')) {
userPicker.clear();
userPicker.disable();
} else {
userPicker.disable(false);
}
});
$('.tab-internal a').on('shown.bs.tab', function (e) {
$('#space-invite-user-picker').data('picker').focus();
});
$('.tab-external a').on('shown.bs.tab', function (e) {
$('#space-invite-external').focus();
});
</script>
<div><strong><?= Yii::t('SpaceModule.base',
'Invite link') ?></strong></div>
<div class="input-group" style="width: 100%;">
<?= Html::textarea('secureLink', $model->getInviteLink(), ['readonly' => 'readonly', 'class' => 'form-control']) ?>
<?php if (Yii::$app->controller->id === 'membership' && $model->space->isAdmin()) : ?>
<a href="#" class="pull-right"
data-action-confirm-header="<?= Yii::t('SpaceModule.base', 'Create new link') ?>",
data-action-confirm="<?= Yii::t('SpaceModule.base', 'Please note that any links you have previously created will become invalid as soon as you create a new one. Would you like to proceed?') ?>"
data-action-click="ui.modal.load"
data-action-click-url="<?= $model->space->createUrl('/space/membership/reset-invite-link') ?>">
<small><?= Yii::t('SpaceModule.base', 'Create new link'); ?></small>
</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<div class="modal-footer">
<a href="#" data-action-click="ui.modal.submit" data-action-submit class="btn btn-primary"
<a id="space-invite-submit-btn" href="#" data-action-click="ui.modal.submit" data-action-submit
class="btn btn-primary"
data-ui-loader><?= $submitText ?></a>
<?= Button::primary(Yii::t('SpaceModule.base',
'Send the link via email'))
->link('mailto:' .
'?subject=' . rawurlencode(Yii::t('UserModule.base', 'You\'ve been invited to join {space} on {appName}', ['space' => $model->space->name, 'appName' => Yii::$app->name])) .
'&body=' . rawurlencode($this->renderFile($this->findViewFile('@humhub/modules/user/views/mails/plaintext/UserInviteSpace'), [
'originator' => Yii::$app->user->identity,
'space' => $model->space,
'registrationUrl' => $model->getInviteLink()
])))
->style(['display' => 'none'])
->id('space-invite-send-link-by-email-btn')
->icon('paper-plane')
->loader(false)
?>
</div>
<?php ActiveForm::end() ?>
<?php ModalDialog::end(); ?>
<script <?= Html::nonce() ?>>
$('#inviteform-allregisteredusers').on('change', function () {
var userPicker = humhub.modules.action.Component.instance('#space-invite-user-picker');
if ($(this).is(':checked')) {
userPicker.clear();
userPicker.disable();
} else {
userPicker.disable(false);
}
});
$('.tab-user-picker a').on('shown.bs.tab', function (e) {
$('#space-invite-user-picker').data('picker').focus();
$('#space-invite-submit-btn').show();
$('#space-invite-send-link-by-email-btn').hide();
});
$('.tab-invite-by-email a').on('shown.bs.tab', function (e) {
$('#space-invite-by-email').focus();
$('#space-invite-submit-btn').show();
$('#space-invite-send-link-by-email-btn').hide();
});
$('.tab-invite-by-link a').on('shown.bs.tab', function (e) {
$('#space-invite-by-link').focus();
$('#space-invite-submit-btn').hide();
$('#space-invite-send-link-by-email-btn').show();
});
$(function () {
$('textarea[name="secureLink"]').click(function () {
$(this).select();
navigator.clipboard.writeText($(this).val());
const successMsg = <?= json_encode(Yii::t('SpaceModule.base', 'The secure link has been copied in your clipboard!'), JSON_HEX_TAG) ?>;
humhub.modules.ui.status.success(successMsg);
});
});
</script>

View File

@ -45,14 +45,16 @@ class InviteController extends Controller
* @return string the action result
* @throws \yii\web\HttpException
*/
public function actionIndex()
public function actionIndex($adminIsAlwaysAllowed = false)
{
if (!$this->canInvite()) {
$model = new InviteForm();
$canInviteByEmail = $model->canInviteByEmail($adminIsAlwaysAllowed);
$canInviteByLink = $model->canInviteByLink($adminIsAlwaysAllowed);
if (!$canInviteByEmail && !$canInviteByLink) {
throw new HttpException(403, 'Invite denied!');
}
$model = new InviteForm;
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
foreach ($model->getEmails() as $email) {
$this->createInvite($email);
@ -63,7 +65,12 @@ class InviteController extends Controller
]);
}
return $this->renderAjax('index', ['model' => $model]);
return $this->renderAjax('index', [
'model' => $model,
'canInviteByEmail' => $canInviteByEmail,
'canInviteByLink' => $canInviteByLink,
'adminIsAlwaysAllowed' => $adminIsAlwaysAllowed,
]);
}
/**
@ -89,17 +96,29 @@ class InviteController extends Controller
}
/**
* Checks if current user can invite new members
*
* @return boolean can invite new members
* @param $adminIsAlwaysAllowed
* @return string
* @throws \Throwable
* @throws \yii\base\Exception
* @throws \yii\base\InvalidConfigException
*/
protected function canInvite()
public function actionResetInviteLink($adminIsAlwaysAllowed = false)
{
/** @var Module $module */
$module = Yii::$app->getModule('user');
$model = new InviteForm();
return $module->settings->get('auth.internalUsersCanInvite') ||
Yii::$app->user->can([new ManageUsers(), new ManageGroups()]);
if (!Yii::$app->user->can([ManageUsers::class, ManageGroups::class])) {
$this->forbidden();
}
$model->getInviteLink(true);
$this->view->saved();
return $this->renderAjax('index', [
'model' => $model,
'canInviteByEmail' => $model->canInviteByEmail($adminIsAlwaysAllowed),
'canInviteByLink' => $model->canInviteByLink($adminIsAlwaysAllowed),
'adminIsAlwaysAllowed' => $adminIsAlwaysAllowed,
]);
}
}

View File

@ -9,6 +9,9 @@
namespace humhub\modules\user\controllers;
use humhub\components\access\ControllerAccess;
use humhub\modules\space\models\forms\InviteForm;
use humhub\modules\space\models\Space;
use humhub\modules\user\Module;
use Yii;
use yii\base\Exception;
use yii\web\HttpException;
@ -69,7 +72,8 @@ class RegistrationController extends Controller
$inviteToken = Yii::$app->request->get('token', '');
if ($inviteToken != '') {
$this->handleInviteRegistration($inviteToken, $registration);
$spaceId = Yii::$app->request->get('spaceId');
$this->handleInviteRegistration($inviteToken, $registration, $spaceId);
} elseif (Yii::$app->session->has('authClient')) {
$authClient = Yii::$app->session->get('authClient');
$this->handleAuthClientRegistration($authClient, $registration);
@ -104,16 +108,41 @@ class RegistrationController extends Controller
/**
* @param $inviteToken
* @param Registration $form
* @param int|null $spaceId
* @throws HttpException
*/
protected function handleInviteRegistration($inviteToken, Registration $form)
protected function handleInviteRegistration($inviteToken, Registration $form, $spaceId)
{
$userInvite = Invite::findOne(['token' => $inviteToken]);
if (!$userInvite) {
// If invited by link from a space
if ($spaceId !== null) {
$space = Space::findOne($spaceId);
if (
$space !== null
&& $space->settings->get('inviteToken') === $inviteToken
) {
Yii::$app->setLanguage($space->ownerUser->language);
Yii::$app->session->set(InviteForm::SESSION_SPACE_INVITE_ID, $spaceId);
return;
}
throw new HttpException(404, 'Invalid registration token!');
}
Yii::$app->setLanguage($userInvite->language);
$form->getUser()->email = $userInvite->email;
// If invited by link globally
/* @var $module Module */
$module = Yii::$app->getModule('user');
if ($module->settings->get('registration.inviteToken') === $inviteToken) {
return;
}
// If invited by email
$userInvite = Invite::findOne(['token' => $inviteToken]);
if ($userInvite) {
Yii::$app->setLanguage($userInvite->language);
$form->getUser()->email = $userInvite->email;
return;
}
throw new HttpException(404, 'Invalid registration token!');
}
/**

View File

@ -39,7 +39,8 @@ class Invite extends ActiveRecord
const SOURCE_SELF = 'self';
const SOURCE_INVITE = 'invite';
const TOKEN_LENGTH = 12;
const EMAIL_TOKEN_LENGTH = 12;
const LINK_TOKEN_LENGTH = 14; // Should be different that EMAIL_TOKEN_LENGTH
public $captcha;
@ -105,7 +106,7 @@ class Invite extends ActiveRecord
public function beforeSave($insert)
{
if ($insert && $this->token == '') {
$this->token = Yii::$app->security->generateRandomString(self::TOKEN_LENGTH);
$this->token = Yii::$app->security->generateRandomString(self::EMAIL_TOKEN_LENGTH);
}
return parent::beforeSave($insert);

View File

@ -21,6 +21,7 @@ use humhub\modules\search\interfaces\Searchable;
use humhub\modules\search\jobs\DeleteDocument;
use humhub\modules\search\jobs\UpdateDocument;
use humhub\modules\space\helpers\MembershipHelper;
use humhub\modules\space\models\forms\InviteForm;
use humhub\modules\space\models\Space;
use humhub\modules\user\authclient\Password as PasswordAuth;
use humhub\modules\user\behaviors\Followable;
@ -621,19 +622,26 @@ class User extends ContentContainerActiveRecord implements IdentityInterface, Se
private function setUpApproved()
{
$userInvite = Invite::findOne(['email' => $this->email]);
if ($userInvite !== null) {
// User was invited to a space
if ($userInvite->source == Invite::SOURCE_INVITE) {
$space = Space::findOne(['id' => $userInvite->space_invite_id]);
if ($space != null) {
$space->addMember($this->id);
$spaceInviteId = Yii::$app->session->get(InviteForm::SESSION_SPACE_INVITE_ID);
if ($spaceInviteId !== null) {
Yii::$app->session->remove(InviteForm::SESSION_SPACE_INVITE_ID);
} else {
$userInvite = Invite::findOne(['email' => $this->email]);
if ($userInvite !== null) {
// User was invited to a space
if ($userInvite->source === Invite::SOURCE_INVITE) {
$spaceInviteId = $userInvite->space_invite_id;
}
// Delete/Cleanup Invite Entry
$userInvite->delete();
}
}
// Delete/Cleanup Invite Entry
$userInvite->delete();
if ($spaceInviteId !== null) {
$space = Space::findOne(['id' => $spaceInviteId]);
if ($space !== null) {
$space->addMember($this->id);
}
}
// Auto Add User to the default spaces

View File

@ -8,13 +8,19 @@
namespace humhub\modules\user\models\forms;
use humhub\modules\admin\permissions\ManageGroups;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\user\Module;
use Yii;
use yii\base\Exception;
use yii\base\InvalidConfigException;
use yii\base\Model;
use humhub\modules\user\models\User;
use yii\helpers\Url;
/**
* Invite Form Model
*
*
* @since 1.1
*/
class Invite extends Model
@ -63,7 +69,7 @@ class Invite extends Model
/**
* E-Mails entered in form
*
*
* @return array the emails
*/
public function getEmails()
@ -76,4 +82,56 @@ class Invite extends Model
return $emails;
}
/**
* Checks if external user invitation setting is enabled
*
* @param bool $adminIsAlwaysAllowed
* @return bool
* @throws InvalidConfigException
* @throws \Throwable
*/
public function canInviteByEmail(bool $adminIsAlwaysAllowed = false)
{
/** @var Module $module */
$module = Yii::$app->getModule('user');
return
(!Yii::$app->user->isGuest && $module->settings->get('auth.internalUsersCanInviteByEmail'))
|| ($adminIsAlwaysAllowed && Yii::$app->user->can([ManageUsers::class, ManageGroups::class]));
}
/**
* Checks if external user invitation setting is enabled
*
* @param bool $adminIsAlwaysAllowed
* @return bool
* @throws \Throwable
* @throws InvalidConfigException
*/
public function canInviteByLink(bool $adminIsAlwaysAllowed = false)
{
/** @var Module $module */
$module = Yii::$app->getModule('user');
return
(!Yii::$app->user->isGuest && $module->settings->get('auth.internalUsersCanInviteByLink'))
|| ($adminIsAlwaysAllowed && Yii::$app->user->can([ManageUsers::class, ManageGroups::class]));
}
/**
* @param bool $forceResetToken
* @return string
* @throws Exception
*/
public function getInviteLink($forceResetToken = false)
{
/* @var $module Module */
$module = Yii::$app->getModule('user');
$settings = $module->settings;
$token = $settings->get('registration.inviteToken');
if ($forceResetToken || !$token) {
$token = Yii::$app->security->generateRandomString(\humhub\modules\user\models\Invite::LINK_TOKEN_LENGTH);
$settings->set('registration.inviteToken', $token);
}
return Url::to(['/user/registration', 'token' => $token], true);
}
}

View File

@ -23,7 +23,7 @@ class InviteCest
$I->amUser();
DirectoryMemberPage::openBy($I);
$I->click('Send invite');
$I->click('Invite new people');
$I->waitForText('Invite new people', null, '#globalModal');
$I->amGoingTo('invite an already existing user email');

View File

@ -18,13 +18,13 @@ class MailInviteCest
{
$I->wantTo('ensure that users can be invited by mail within the directory');
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInvite', 1);
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByEmail', 1);
$I->amUser2();
$I->amOnDirectory()->clickMembers();
$I->amGoingTo('invite a user by mail');
$I->see('Send invite', 'a');
$I->see('Invite new people', 'a');
$I->sendAjaxPostRequest(Url::to(['/user/invite']), ['Invite[emails]' => 'a@test.de,b@test.de']);
$I->seeEmailIsSent(2);
@ -64,7 +64,7 @@ class MailInviteCest
$I->wantTo('ensure that users can be invited by mail within the directory');
// Should also work with deactivated invite setting
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInvite', 0);
Yii::$app->getModule('user')->settings->set('auth.internalUsersCanInviteByEmail', 0);
$I->amAdmin();
$I->amGoingTo('invte a user by mail');

View File

@ -1,33 +1,109 @@
<?php
use humhub\libs\Html;
use humhub\modules\admin\permissions\ManageGroups;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\ui\view\components\View;
use humhub\modules\user\models\forms\Invite;
use humhub\widgets\Button;
use humhub\widgets\ModalButton;
use humhub\widgets\ModalDialog;
use yii\helpers\Url;
use yii\bootstrap\ActiveForm;
/**
* @var $this View
* @var $model Invite
* @var $canInviteByEmail bool
* @var $canInviteByLink bool
* @var $adminIsAlwaysAllowed bool
*/
?>
<div class="modal-dialog modal-dialog-small animated fadeIn">
<div class="modal-content">
<?php $form = ActiveForm::begin(); ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title"
id="myModalLabel"><?php echo Yii::t('UserModule.invite', '<strong>Invite</strong> new people'); ?></h4>
</div>
<div class="modal-body">
<br/>
<?php ModalDialog::begin([
'header' => Yii::t('UserModule.invite', '<strong>Invite</strong> new people'),
]) ?>
<div class="modal-body">
<?php echo Yii::t('UserModule.invite', 'Please add the email addresses of people you want to invite below.'); ?>
<br/><br/>
<div class="form-group">
<?php echo $form->field($model, 'emails')->textarea(['rows' => '3', 'placeholder' => Yii::t('UserModule.invite', 'Email address(es)'), 'id' => 'emails'])->label(false)->hint(Yii::t('UserModule.invite', 'Separate multiple email addresses by comma.')); ?>
<?php if ($canInviteByEmail && $canInviteByLink): ?>
<!-- Nav tabs -->
<ul class="nav nav-tabs">
<li class="nav-item active">
<a class="nav-link" data-toggle="tab"
href="#invite-by-email"><?= Yii::t('UserModule.base', 'Invite by email') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab"
href="#invite-by-link"><?= Yii::t('UserModule.base', 'Invite by link') ?></a>
</li>
</ul>
<?php endif; ?>
<!-- Tab panes -->
<div class="tab-content">
<?php if ($canInviteByEmail) : ?>
<div class="tab-pane active" id="invite-by-email">
<br/>
<strong><?= Yii::t('UserModule.invite', 'Please add the email addresses of people you want to invite below.') ?></strong>
<br/><br/>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'emails')->textarea(['rows' => '3', 'placeholder' => Yii::t('UserModule.invite', 'Email address(es)'), 'id' => 'emails'])->label(false)->hint(Yii::t('UserModule.invite', 'Separate multiple email addresses by comma.')); ?>
<a href="#" class="btn btn-primary" data-action-click="ui.modal.submit"
data-action-url="<?= Url::to(['/user/invite']) ?>" data-ui-loader>
<?= Yii::t('UserModule.invite', 'Send invite') ?>
</a>
<?php ActiveForm::end(); ?>
</div>
</div>
<div class="modal-footer">
<a href="#" class="btn btn-primary" data-action-click="ui.modal.submit" data-action-url="<?= Url::to(['/user/invite']) ?>" data-ui-loader>
<?= Yii::t('UserModule.invite', 'Send invite') ?>
</a>
</div>
<?php endif; ?>
<?php ActiveForm::end(); ?>
<?php if ($canInviteByLink) : ?>
<div class="tab-pane<?= $canInviteByEmail ? '' : ' active' ?>" id="invite-by-link">
<br/>
<strong><?= Yii::t('SpaceModule.base',
'You can invite external users who are currently not registered via link. All you need to do is share this secure link with them.') ?></strong>
<br/><br/>
<div class="input-group" style="width: 100%;">
<?= Html::textarea('secureLink', $model->getInviteLink(), ['readonly' => 'readonly', 'class' => 'form-control']) ?>
<?php if (Yii::$app->user->can([ManageUsers::class, ManageGroups::class])) : ?>
<a href="#" class="pull-right"
data-action-confirm-header="<?= Yii::t('SpaceModule.base', 'Create new link') ?>",
data-action-confirm="<?= Yii::t('SpaceModule.base', 'Please note that any links you have previously created will become invalid as soon as you create a new one. Would you like to proceed?') ?>"
data-action-click="ui.modal.load"
data-action-click-url="<?= Url::to(['/user/invite/reset-invite-link', 'adminIsAlwaysAllowed' => $adminIsAlwaysAllowed]) ?>">
<small><?= Yii::t('SpaceModule.base', 'Create new link'); ?></small>
</a>
<?php endif; ?>
</div>
<br>
<?= Button::primary(Yii::t('SpaceModule.base',
'Send the link via email'))
->link('mailto:' .
'?subject=' . rawurlencode(Yii::t('UserModule.invite', 'You\'ve been invited to join %appName%', ['%appName%' => Yii::$app->name])) .
'&body=' . rawurlencode($this->renderFile($this->findViewFile('@humhub/modules/user/views/mails/plaintext/UserInvite'), [
'originator' => Yii::$app->user->identity,
'registrationUrl' => $model->getInviteLink()
])))
->id('global-invite-send-link-by-email-btn')
->icon('paper-plane')
->loader(false)
?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="modal-footer">
<?= ModalButton::cancel(Yii::t('base', 'Close')) ?>
</div>
<?php ModalDialog::end() ?>
<script <?= Html::nonce() ?>>
$(function () {
$('textarea[name="secureLink"]').click(function () {
$(this).select();
navigator.clipboard.writeText($(this).val());
const successMsg = <?= json_encode(Yii::t('UserModule.base', 'The secure link has been copied in your clipboard!'), JSON_HEX_TAG) ?>;
humhub.modules.ui.status.success(successMsg);
});
});
</script>

View File

@ -9,6 +9,7 @@ namespace humhub\modules\user\widgets;
use humhub\modules\ui\menu\MenuLink;
use humhub\modules\ui\menu\widgets\Menu;
use humhub\modules\user\models\forms\Invite;
use Yii;
/**
@ -31,9 +32,10 @@ class PeopleHeadingButtons extends Menu
public function init()
{
if (!Yii::$app->user->isGuest && Yii::$app->getModule('user')->settings->get('auth.internalUsersCanInvite')) {
$invite = new Invite();
if ($invite->canInviteByLink() || $invite->canInviteByEmail()) {
$this->addEntry(new MenuLink([
'label' => Yii::t('UserModule.base', 'Send invite'),
'label' => Yii::t('UserModule.base', 'Invite new people'),
'url' => ['/user/invite'],
'sortOrder' => 100,
'icon' => 'invite',

View File

@ -7,7 +7,8 @@ return [
['name' => 'authInternal', 'value' => '1', 'module_id' => 'authentication'],
['name' => 'auth.needApproval', 'value' => '0', 'module_id' => 'user'],
['name' => 'auth.anonymousRegistration', 'value' => '1', 'module_id' => 'user'],
['name' => 'auth.internalUsersCanInvite', 'value' => '1', 'module_id' => 'user'],
['name' => 'auth.internalUsersCanInviteByEmail', 'value' => '1', 'module_id' => 'user'],
['name' => 'auth.internalUsersCanInviteByLink', 'value' => '1', 'module_id' => 'user'],
['name' => 'mailer.transportType', 'value' => 'file', 'module_id' => 'base'],
['name' => 'mailer.systemEmailAddress', 'value' => 'social@example.com', 'module_id' => 'base'],
['name' => 'mailer.systemEmailName', 'value' => 'My Social Network', 'module_id' => 'base'],