Merge branch 'develop' into next

This commit is contained in:
Lucas Bartholemy 2025-04-06 09:39:21 +02:00
commit 80b4797057
53 changed files with 896 additions and 316 deletions

View File

@ -17,10 +17,18 @@ HumHub Changelog
- Fix #7443: Exclude inactive users from `followers` and `following` lists
- Fix #7447: Update the comment counter when deleting a comment
- Fix #7456: Fix setting manager backwards compatibility
- Enh #7468: Remove deprecated widget `DataSaved`
1.17.2 (Unreleased)
----------------------
- Fix #7457: Fix changing of space visibility
- Fix #7464: Fix searching with not full latin words
- Fix #7465: Formatted Arabic numbers displays 0 instead of the number
- Fix #7471: Fix advanced searching by space filter
- Fix #7472: Fix missing fields when creating a new user from admin
- Fix #7477: Refactor Registration Form Options
- Enh #7455: Add Share Intend feature for the Mobile app
- Fix #7482: Fix the post submit button title after back from draft mode
1.17.1 (March 6, 2025)
----------------------

View File

@ -1,8 +1,9 @@
Module Migration Guide
======================
1.18 (TBA)
-------------------------
Version 1.18
------------
### Deprecated
- `\humhub\components\Application::isInstalled()` use `\humhub\components\Application::hasState()` instead
- `\humhub\components\Application::isDatabaseInstalled()` use `\humhub\components\Application::hasState()` instead
@ -13,7 +14,7 @@ Module Migration Guide
- The following Mailer settings keys have been renamed to work with `.env`:
| Old Key | New Key |
|----------------------------------|-------------------------------|
|----------------------------------|--------------------------------|
| `mailer.transportType` | `mailerTransportType` |
| `mailer.dsn` | `mailerDsn` |
| `mailer.hostname` | `mailerHostname` |
@ -27,13 +28,18 @@ Module Migration Guide
| `mailer.systemEmailName` | `mailerSystemEmailName` |
| `mailer.systemEmailReplyTo` | `mailerSystemEmailReplyTo` |
Version 1.17.1
### Removed deprecations
- Widget class `\humhub\widgets\DataSaved`, the related code `Yii::$app->getSession()->setFlash('data-saved', Yii::t('base', 'Saved'));` must be replaced with `$this->view->saved();` on controllers
Version 1.17.2
---------------
### Behaviour change
- Method signature changed - `humhub\modules\user\models\fieldtype\BaseType::getUserValue(User $user, bool $raw = true, bool $encode = true): ?string`
- Constructor changed - `humhub\modules\user\models\forms\Registration` and properties (`$enablePasswordForm`, `$enableMustChangePassword`, `$enableEmailField`) are now private
Version 1.17
-------------------------

View File

@ -56,11 +56,15 @@ class HForm extends \yii\base\Component
*/
public $markedAsSubmitted = false;
public function __construct($definition = [], $primaryModel = null)
public function __construct($definition = [], $primaryModel = null, array $config = [])
{
$this->definition = $definition;
$this->primaryModel = $primaryModel;
if (!empty($config)) {
Yii::configure($this, $config);
}
$this->init();
$this->trigger(static::EVENT_AFTER_INIT);
}

View File

@ -90,8 +90,13 @@ class Formatter extends \yii\i18n\Formatter
*/
public function asShortInteger($value, $options = [], $textOptions = [])
{
list($params, $position) = $this->formatNumber($value, null, 2, 1000, $options, $textOptions);
$params['nFormatted'] = floor((float)$params['nFormatted']);
list($params, $position) = $this->formatNumber($value, 0, 2, 1000, $options, $textOptions);
if ($position < 3 && mb_strlen($params['nFormatted']) === 4) {
// Convert 1000K to 1M or 1000M to 1B
$params['nFormatted'] = mb_substr($params['nFormatted'], 0, 1);
$position++;
}
switch ($position) {
case 0:

View File

@ -307,7 +307,7 @@ class SettingController extends Controller
$timeAgo = strtotime($form->logsDateLimit);
Log::deleteAll(['<', 'log_time', $timeAgo]);
Yii::$app->getSession()->setFlash('data-saved', Yii::t('AdminModule.settings', 'Saved'));
$this->view->saved();
return $this->redirect([
'/admin/setting/logs',
]);

View File

@ -261,10 +261,8 @@ class UserController extends Controller
public function actionAdd()
{
$registration = new Registration();
$registration->enableEmailField = true;
$registration = new Registration(enableEmailField: true, enableMustChangePassword: true);
$registration->enableUserApproval = false;
$registration->enableMustChangePassword = true;
if ($registration->submitted('save') && $registration->validate() && $registration->register()) {
return $this->redirect(['edit', 'id' => $registration->getUser()->id]);

View File

@ -6,7 +6,6 @@ use humhub\modules\admin\models\forms\DesignSettingsForm;
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\modules\web\pwa\widgets\SiteIcon;
use humhub\widgets\Button;
use humhub\widgets\DataSaved;
use yii\helpers\Html;
use yii\helpers\Url;
@ -106,6 +105,5 @@ $iconUrl = SiteIcon::getUrl(140);
<hr>
<?= Html::submitButton(Yii::t('AdminModule.settings', 'Save'), ['class' => 'btn btn-primary', 'data-ui-loader' => ""]); ?>
<?= DataSaved::widget(); ?>
<?php ActiveForm::end(); ?>
</div>

View File

@ -1,11 +1,9 @@
<?php
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\widgets\DataSaved;
use yii\helpers\Html;
use humhub\modules\admin\models\forms\StatisticSettingsForm;
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\modules\ui\form\widgets\CodeMirrorInputWidget;
use yii\web\View;
use yii\helpers\Html;
/* @var $model StatisticSettingsForm */
@ -27,8 +25,6 @@ use yii\web\View;
<?= Html::submitButton(Yii::t('AdminModule.settings', 'Save'), ['class' => 'btn btn-primary', 'data-ui-loader' => ""]); ?>
<?= DataSaved::widget(); ?>
<?php ActiveForm::end(); ?>
<?php $this->endContent(); ?>

View File

@ -1,13 +1,13 @@
<?php
use humhub\commands\CronController;
use humhub\modules\content\Events;
use humhub\commands\IntegrityController;
use humhub\modules\content\Events;
use humhub\modules\content\models\Content;
use humhub\modules\content\Module;
use humhub\modules\content\widgets\WallEntryAddons;
use humhub\modules\user\models\User;
use humhub\modules\space\models\Space;
use humhub\modules\user\models\User;
return [
'id' => 'content',

View File

@ -24,6 +24,7 @@ use yii\base\Exception;
use yii\base\InvalidConfigException;
use yii\db\IntegrityException;
use yii\web\ForbiddenHttpException;
use yii\web\HttpException;
use yii\web\NotFoundHttpException;
use yii\web\Response;
@ -421,4 +422,16 @@ class ContentController extends Controller
'disableInputs' => $disableInputs,
]);
}
/**
* Triggered after content creation
*/
public function actionRedirectToContentContainer($contentId)
{
$content = Content::findOne(['id' => $contentId]);
if ($content === null) {
throw new HttpException(404, 'Content not found!');
}
return $this->redirect($content->container->getUrl());
}
}

View File

@ -0,0 +1,103 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2025 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\content\controllers;
use humhub\components\behaviors\AccessControl;
use humhub\components\Controller;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\content\models\forms\ShareIntendTargetForm;
use humhub\modules\space\helpers\CreateContentPermissionHelper;
use humhub\modules\user\widgets\UserPicker;
use Yii;
use yii\web\HttpException;
/**
* Allows sharing files from the mobile app
*
* @since 1.17.2
*/
abstract class ShareIntendController extends Controller
{
abstract public function actionCreate();
abstract protected function getCreatePermissionClass(): string;
private const SESSION_KEY_TARGET_GUID = 'shareIntendTargetGuid';
public ?ContentContainerActiveRecord $shareTarget = null;
public function behaviors()
{
return [
'acl' => [
'class' => AccessControl::class,
],
];
}
public function beforeAction($action)
{
if (!parent::beforeAction($action)) {
return false;
}
\humhub\modules\file\controllers\ShareIntendController::checkShareFileGuids();
if (!in_array($action->id, ['index', 'container-search-json'])) {
$this->initShareTarget();
}
return true;
}
private function initShareTarget(): void
{
$shareTargetGuid = Yii::$app->session->get(self::SESSION_KEY_TARGET_GUID);
$this->shareTarget = ContentContainer::findRecord($shareTargetGuid);
if ($this->shareTarget === null) {
throw new HttpException('500', 'No target to share found!');
}
}
public function actionIndex()
{
Yii::$app->session->remove(self::SESSION_KEY_TARGET_GUID);
$model = new ShareIntendTargetForm();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
Yii::$app->session->set(self::SESSION_KEY_TARGET_GUID, $model->targetContainerGuid);
$this->initShareTarget();
return $this->actionCreate();
}
return $this->renderAjax('@content/views/share-intend/index', [
'model' => $model,
'fileList' => \humhub\modules\file\controllers\ShareIntendController::getShareFileGuids(),
]);
}
public function actionContainerSearchJson()
{
$containers = CreateContentPermissionHelper::findSpaces(
$this->getCreatePermissionClass(),
Yii::$app->request->get('keyword'),
Yii::$app->user->identity,
);
if (ShareIntendTargetForm::canPostInOwnProfile()) {
$currentUser = UserPicker::createJSONUserInfo(Yii::$app->user->identity);
$currentUser['text'] = Yii::t('base', 'My Profile');
array_unshift($containers, $currentUser);
}
return $this->asJson($containers);
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace humhub\modules\content\models\forms;
use humhub\modules\post\models\Post;
use humhub\modules\user\Module;
use Yii;
use yii\base\Model;
use yii\helpers\Url;
class ShareIntendTargetForm extends Model
{
public $targetContainerGuid;
/**
* @inheritdoc
*/
public function rules(): array
{
return [
[['targetContainerGuid'], 'required'],
];
}
public function attributeHints(): array
{
return [
'targetContainerGuid' => static::canPostInOwnProfile() ?
Yii::t('ContentModule.base', 'Select target Space/Profile.') :
Yii::t('ContentModule.base', 'Select target Space.'),
];
}
public function getContainerSearchUrl(): string
{
return Url::to(['container-search-json']);
}
public static function canPostInOwnProfile(): bool
{
/** @var Module $userModule */
$userModule = Yii::$app->getModule('user');
return
!$userModule->profileDisableStream // The profile stream is enabled
&& !Yii::$app->user->isGuest
&& (new Post(Yii::$app->user->identity))->content->canEdit(); // Can post in own profile
}
}

View File

@ -5,6 +5,7 @@
humhub.module('content.form', function (module, require, $) {
var CREATE_FORM_ROOT_SELECTOR = '#contentFormBody';
var CREATE_FORM_ROOT_SELECTOR_MODAL = CREATE_FORM_ROOT_SELECTOR + 'Modal';
var object = require('util').object;
var client = require('client');
@ -22,26 +23,32 @@ humhub.module('content.form', function (module, require, $) {
object.inherits(CreateForm, Widget);
CreateForm.prototype.init = function () {
this.$.hide();
this.menu = this.$.parent().prev('#contentFormMenu');
// Hide options by default
$('.contentForm_options').hide();
this.isModal = this.$.is(CREATE_FORM_ROOT_SELECTOR_MODAL)
if (!this.isModal) {
this.$.hide();
this.menu = this.$.parent().prev('#contentFormMenu'); // #contentFormMenuModal doesn't exist
// Hide options by default
this.$.find('.contentForm_options').hide();
}
this.setDefaultVisibility();
this.$.fadeIn('fast');
this.showMenu();
if (!module.config['disabled']) {
$('#contentFormBody').on('click.humhub:content:form dragover.humhub:content:form', function (evt) {
this.$.on('click.humhub:content:form dragover.humhub:content:form', function (evt) {
// Prevent fading in for topic remove button clicks
if ($(evt.target).closest('.topic-remove-label').length) {
return;
}
$('.contentForm_options').fadeIn();
});
if (!this.isModal) {
this.$.find('.contentForm_options').fadeIn();
}
}.bind(this));
} else {
$('#contentFormBody').find('.humhub-ui-richtext').trigger('disable');
this.$.find('.humhub-ui-richtext').trigger('disable');
}
};
@ -65,11 +72,19 @@ humhub.module('content.form', function (module, require, $) {
event.trigger('humhub:content:beforeSubmit', this);
client.submit(evt).then(function (response) {
that.$.find(".preferences, .fileinput-button").show();
$('.contentForm_options .preferences, .fileinput-button').show();
that.$.find('.contentForm_options .preferences, .fileinput-button').show();
if (!response.errors) {
event.trigger('humhub:content:newEntry', response.output, this);
event.trigger('humhub:content:afterSubmit', response.output, this);
that.resetForm();
if ($('#share-intend-modal').length) {
$("#globalModal").modal("hide");
// If the dashboard stream is not displayed on the current page, redirect to the content container, to make sure the user sees the new content
if (response.url && !$('.dashboard-wall-stream').length) {
client.pjax.redirect(module.config.redirectToContentContainerUrl.replace('the-content-id', response.data.id));
}
} else {
that.resetForm();
}
} else {
that.handleError(response);
}
@ -82,12 +97,15 @@ humhub.module('content.form', function (module, require, $) {
/**
* Todo: this is post form only, this needs to be added to post module perhaps by calling $form.trigger('humhub:form:clear');
*
* As the form for share intend is in a modal, we don't need to reset it
*
* @returns {undefined}
*/
CreateForm.prototype.resetForm = function () {
// Reset Form (Empty State)
$('.contentForm_options').hide();
var $contentForm = $('.contentForm');
this.$.find('.contentForm_options').hide();
var $contentForm = this.$.find('.contentForm');
$contentForm.filter(':text').val('');
$contentForm.filter('textarea').val('').trigger('autosize.resize');
$contentForm.attr('checked', false);
@ -98,30 +116,29 @@ humhub.module('content.form', function (module, require, $) {
this.resetFileUpload();
this.resetState();
$('#public').attr('checked', false);
$('#contentFormBody').find('.humhub-ui-richtext').trigger('clear');
this.$.find('.humhub-ui-richtext').trigger('clear');
};
CreateForm.prototype.resetSettingInputs = function () {
$('#notifyUserContainer').hide();
Widget.instance('#notifyUserInput').reset();
$('#postTopicContainer').hide();
this.$.find('.notifyUserContainer').hide();
Widget.instance('#notifyUserInput' + (this.isModal ? 'Modal' : '')).reset();
$('#postTopicContainer' + (this.isModal ? 'Modal' : '')).hide();
var topicPicker = Widget.instance('#postTopicInput');
var topicPicker = Widget.instance('#postTopicInput' + (this.isModal ? 'Modal' : ''));
if (topicPicker) {
topicPicker.reset();
}
};
CreateForm.prototype.resetFilePreview = function () {
var preview = Widget.instance($('#contentFormFiles_preview'));
var preview = Widget.instance($('#contentFormFiles_preview' + (this.isModal ? 'Modal' : '')));
if (preview) {
preview.reset();
}
};
CreateForm.prototype.resetFileUpload = function () {
var upload = Widget.instance($('#contentForm_message-file-upload'));
var upload = Widget.instance($('#contentFormFiles_progress' + (this.isModal ? 'Modal' : '')));
if (upload) {
upload.reset();
}
@ -146,7 +163,7 @@ humhub.module('content.form', function (module, require, $) {
};
CreateForm.prototype.changeVisibility = function () {
if (!$('#contentForm_visibility').prop('checked')) {
if (!this.$.find('.contentForm_visibility').prop('checked')) {
this.setPublicVisibility();
} else {
this.setPrivateVisibility();
@ -162,26 +179,26 @@ humhub.module('content.form', function (module, require, $) {
};
CreateForm.prototype.setPublicVisibility = function () {
$('#contentForm_visibility').prop("checked", true);
$('#contentForm_visibility_entry').html('<i class="fa fa-lock"></i>' + module.text(['makePrivate']));
$('.label-public').removeClass('hidden');
this.$.find('.contentForm_visibility').prop("checked", true);
this.$.find('.contentForm_visibility_entry').html('<i class="fa fa-lock"></i>' + module.text(['makePrivate']));
this.$.find('.label-public').removeClass('hidden');
};
CreateForm.prototype.setPrivateVisibility = function () {
$('#contentForm_visibility').prop("checked", false);
$('#contentForm_visibility_entry').html('<i class="fa fa-unlock"></i>' + module.text(['makePublic']));
$('.label-public').addClass('hidden');
this.$.find('.contentForm_visibility').prop("checked", false);
this.$.find('.contentForm_visibility_entry').html('<i class="fa fa-unlock"></i>' + module.text(['makePublic']));
this.$.find('.label-public').addClass('hidden');
};
CreateForm.prototype.notifyUser = function () {
$('#notifyUserContainer').show();
Widget.instance('#notifyUserInput').focus();
this.$.find('.notifyUserContainer').show();
Widget.instance('#notifyUserInput' + (this.isModal ? 'Modal' : '')).focus();
};
CreateForm.prototype.setTopics = function () {
$('#postTopicContainer').show();
$('#postTopicContainer' + (this.isModal ? 'Modal' : '')).show();
var topicPicker = Widget.instance('#postTopicInput');
var topicPicker = Widget.instance('#postTopicInput' + (this.isModal ? 'Modal' : ''));
if (topicPicker) {
topicPicker.focus();
}
@ -190,7 +207,7 @@ humhub.module('content.form', function (module, require, $) {
CreateForm.prototype.changeState = function (state, title, buttonTitle) {
const stateInput = this.$.find('input[name=state]');
let stateLabel = this.$.find('.label-content-state');
const button = this.$.find('#post_submit_button');
const button = $('#post_submit_button' + (this.isModal ? '_modal' : ''));
if (!stateLabel.length) {
stateLabel = $('<span>').addClass('label label-warning label-content-state');
@ -217,27 +234,34 @@ humhub.module('content.form', function (module, require, $) {
stateLabel.show().html(title);
button.html(buttonTitle);
this.$.find('.preferences [data-action-click=notifyUser]').parent().hide();
this.$.find('#notifyUserContainer').hide();
this.$.find('.notifyUserContainer').hide();
}
CreateForm.prototype.resetState = function () {
const stateInput = this.$.find('input[name=state]');
const button = this.$.find('#post_submit_button');
const button = $('#post_submit_button' + (this.isModal ? '_modal' : ''));
const initial = stateInput.data('initial');
if (initial !== undefined) {
stateInput.val(initial.state);
button.data('htmlOld', initial.buttonTitle).removeAttr('style');
loader.reset(button);
if (loader.is(button)) {
button.data('htmlOld', initial.buttonTitle).removeAttr('style');
loader.reset(button);
} else {
button.html(initial.buttonTitle);
}
}
this.$.find('input[name^=scheduled]').remove();
this.$.find('.label-content-state').hide();
this.$.find('.preferences [data-action-click=notifyUser]').parent().show();
const notifyUserContainer = this.$.find('#notifyUserContainer');
const notifyUserContainer = this.$.find('.notifyUserContainer');
if (notifyUserContainer.find('ul .select2-selection__clear').length) {
notifyUserContainer.show();
}
}
/**
* Schedule is not available for share intend because it is already in a modal
*/
CreateForm.prototype.scheduleOptions = function (evt) {
const that = this;
const modalGlobal = modal.global.$;
@ -275,6 +299,9 @@ humhub.module('content.form', function (module, require, $) {
});
}
/**
* Schedule is not available for share intend because it is already in a modal
*/
CreateForm.prototype.setScheduleOption = function (name, value) {
let input = this.$.find('input[name=' + name + ']');
@ -290,10 +317,16 @@ humhub.module('content.form', function (module, require, $) {
input.val(value);
}
/**
* Schedule is not available for share intend because it is already in a modal
*/
CreateForm.prototype.resetScheduleOption = function (name) {
this.setScheduleOption(name);
}
/**
* CreateFormMenu is not available for share intend
*/
const CreateFormMenu = Widget.extend();
CreateFormMenu.prototype.init = function () {
@ -343,6 +376,13 @@ humhub.module('content.form', function (module, require, $) {
}
};
var initModal = function () {
var $rootModal = $(CREATE_FORM_ROOT_SELECTOR_MODAL);
if ($rootModal.length) {
instance = Widget.instance($rootModal);
}
};
var unload = function () {
instance = undefined;
}
@ -352,6 +392,7 @@ humhub.module('content.form', function (module, require, $) {
CreateFormMenu: CreateFormMenu,
instance: instance,
init: init,
initModal: initModal,
initOnPjaxLoad: true,
unload: unload
});

View File

@ -165,7 +165,7 @@ class MysqlDriver extends AbstractDriver
$term = preg_replace('/-+(\*?)$/', '$1', $term);
// Wrap a keyword in quotes to avoid error with the special chars in the sql MATCH-AGAINST expression
return preg_match('#[^\p{L}\d\*\'`\-\_]#', $term) ? '"' . $term . '"' : $term;
return preg_match('#[^\p{L}\d\*\'`\-\_]#u', $term) ? '"' . $term . '"' : $term;
}
protected function addQueryFilterVisibility(ActiveQuery $query): ActiveQuery

View File

@ -0,0 +1,44 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2025 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
use humhub\modules\content\models\forms\ShareIntendTargetForm;
use humhub\modules\content\widgets\ContentContainerPickerField;
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\modules\ui\view\components\View;
use humhub\widgets\ModalButton;
use humhub\widgets\ModalDialog;
/**
* @var $this View
* @var $model ShareIntendTargetForm
* @var $fileList string[]
*/
?>
<?php ModalDialog::begin([
'header' => Yii::t('ContentModule.base', 'Share'),
]) ?>
<?php $form = ActiveForm::begin() ?>
<div class="modal-body">
<?= $form->field($model, 'targetContainerGuid')->widget(ContentContainerPickerField::class, [
'maxSelection' => 1,
'minInput' => 0,
'focus' => true,
'url' => $model->getContainerSearchUrl(),
'options' => ['data-action-change' => 'ui.modal.submit'],
])->label(false) ?>
</div>
<div class="modal-footer">
<?= ModalButton::defaultType(Yii::t('base', 'Back'))
->load(['/file/share-intend', 'fileList' => $fileList]) ?>
</div>
<?php ActiveForm::end() ?>
<?php ModalDialog::end() ?>

View File

@ -0,0 +1,64 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*
*/
namespace humhub\modules\content\widgets;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\ui\form\widgets\BasePicker;
/**
* Mutliselect input field for selecting Space guids or current user Profile
*
* This widget has no default route, so the `url` param is required.
*
* @since 1.17.2
*/
class ContentContainerPickerField extends BasePicker
{
/**
* @inheritdoc
* The 'guid' value is default for UserPickerField
*/
public $itemKey = 'guid';
/**
* @inheritdoc
*/
public $itemClass = ContentContainer::class;
/**
* @inheritdoc
*/
protected function getAttributes()
{
return array_merge(parent::getAttributes(), [
'data-tags' => 'false',
]);
}
/**
* @inheritdoc
*/
protected function getItemText($item)
{
return $this->itemClass === ContentContainer::class ?
$item->getPolymorphicRelation()->displayName :
$item->displayName;
}
/**
* @inheritdoc
*/
protected function getItemImage($item)
{
return $this->itemClass === ContentContainer::class ?
$item->getPolymorphicRelation()->getProfileImage()->getUrl() :
$item->getProfileImage()->getUrl();
}
}

View File

@ -105,7 +105,7 @@ class SearchFilters extends DirectoryFilters
'sortOrder' => 500,
]);*/
$this->addFilter('space', [
$this->addFilter('contentContainer', [
'title' => Yii::t('ContentModule.search', 'Space'),
'type' => 'widget',
'widget' => SpacePickerField::class,

View File

@ -38,6 +38,16 @@ abstract class WallCreateContentForm extends Widget
*/
public $contentContainer;
/**
* Pre-uploaded File GUIDs to be attached to the new content
*/
public array $fileList = [];
/**
* The widget is executed in a modal window
*/
public bool $isModal = false;
/**
* @inheritdoc
*/

View File

@ -8,10 +8,10 @@
namespace humhub\modules\content\widgets;
use humhub\libs\Sort;
use humhub\modules\content\widgets\stream\WallStreamEntryWidget;
use humhub\components\Widget;
use humhub\libs\Sort;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\widgets\stream\WallStreamEntryWidget;
use yii\web\HttpException;
/**

View File

@ -24,7 +24,12 @@ use yii\web\HttpException;
class WallCreateContentFormFooter extends Widget
{
/**
* @var string form submit route/url (required)
* @var WallCreateContentForm null (required)
*/
public ?WallCreateContentForm $wallCreateContentForm = null;
/**
* @var string form submit route/url (automatically set if `wallCreateContentForm` is provided)
*/
public $submitUrl;
@ -51,6 +56,10 @@ class WallCreateContentFormFooter extends Widget
throw new HttpException(500, 'No Content Container given!');
}
if ($this->wallCreateContentForm !== null) {
$this->submitUrl = $this->wallCreateContentForm->submitUrl;
}
parent::init();
}
@ -61,6 +70,8 @@ class WallCreateContentFormFooter extends Widget
{
return $this->render('@humhub/modules/content/widgets/views/wallCreateContentFormFooter', [
'contentContainer' => $this->contentContainer,
'fileList' => $this->wallCreateContentForm?->fileList ?? [],
'isModal' => $this->wallCreateContentForm?->isModal ?? false,
'submitUrl' => $this->contentContainer->createUrl($this->submitUrl),
'submitButtonText' => $this->submitButtonText,
'canSwitchVisibility' => $this->contentContainer->visibility !== Space::VISIBILITY_NONE && $this->contentContainer->can(CreatePublicContent::class),

View File

@ -1,11 +1,12 @@
<?php
use humhub\modules\content\assets\ContentFormAsset;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\widgets\WallCreateContentForm;
use humhub\modules\content\widgets\WallCreateContentMenu;
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\modules\content\assets\ContentFormAsset;
use humhub\modules\space\models\Space;
use humhub\modules\ui\form\widgets\ActiveForm;
use yii\helpers\Url;
/* @var $wallCreateContentForm WallCreateContentForm */
/* @var $defaultVisibility int */
@ -19,14 +20,15 @@ $this->registerJsConfig('content.form', [
'text' => [
'makePrivate' => Yii::t('ContentModule.base', 'Change to "Private"'),
'makePublic' => Yii::t('ContentModule.base', 'Change to "Public"'),
'info.archived' => Yii::t('ContentModule.base', 'This space is archived.')
]
'info.archived' => Yii::t('ContentModule.base', 'This space is archived.'),
],
'redirectToContentContainerUrl' => Url::to(['/content/content/redirect-to-content-container', 'contentId' => 'the-content-id']),
]);
?>
<?php if (WallCreateContentMenu::canCreateEntry($contentContainer, 'form')) : ?>
<div class="panel panel-default clearfix">
<div class="panel-body" id="contentFormBody" style="display:none;"
<div class="panel-body" id="contentFormBody<?= $wallCreateContentForm->isModal ? 'Modal' : '' ?>" class="content-form-body" style="display:none;"
data-action-component="content.form.CreateForm">
<?php $form = ActiveForm::begin(['acknowledge' => true]); ?>

View File

@ -5,9 +5,9 @@
* @license https://www.humhub.com/licences
*/
use humhub\modules\content\assets\ContentFormAsset;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\widgets\WallCreateContentMenu;
use humhub\modules\content\assets\ContentFormAsset;
/* @var $contentContainer ContentContainerActiveRecord */
/* @var $formClass string */
@ -19,4 +19,4 @@ ContentFormAsset::register($this);
<?php if ($formClass) : ?>
<?= $formClass::widget(['contentContainer' => $contentContainer]) ?>
<?php endif; ?>
<?php endif; ?>

View File

@ -5,6 +5,7 @@
* @license https://www.humhub.com/licences
*/
use humhub\libs\Html;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\content\models\Content;
use humhub\modules\file\handler\BaseFileHandler;
@ -17,20 +18,21 @@ use humhub\modules\ui\icon\widgets\Icon;
use humhub\modules\user\widgets\UserPickerField;
use humhub\widgets\Button;
use humhub\widgets\Link;
use yii\helpers\Html;
/* @var $submitUrl string */
/* @var $submitButtonText string */
/* @var $fileHandlers BaseFileHandler[] */
/* @var $canSwitchVisibility bool */
/* @var $contentContainer ContentContainerActiveRecord */
/* @var $fileList array */
/* @var $isModal bool */
/* @var $pickerUrl string */
/* @var $scheduleUrl string */
?>
<div id="notifyUserContainer" class="form-group" style="margin-top:15px;display:none">
<div class="notifyUserContainer form-group" style="margin-top:15px;display:none">
<?= UserPickerField::widget([
'id' => 'notifyUserInput',
'id' => 'notifyUserInput' . ($isModal ? 'Modal' : ''),
'url' => $pickerUrl,
'formName' => 'notifyUserInput',
'maxSelection' => 10,
@ -39,34 +41,35 @@ use yii\helpers\Html;
]) ?>
</div>
<div id="postTopicContainer" class="form-group" style="margin-top:15px;display:none">
<div id="postTopicContainer<?= $isModal ? 'Modal' : '' ?>" class="form-group" style="margin-top:15px;display:none">
<?= TopicPicker::widget([
'id' => 'postTopicInput',
'id' => 'postTopicInput' . ($isModal ? 'Modal' : ''),
'name' => 'postTopicInput',
'contentContainer' => $contentContainer
]); ?>
'contentContainer' => $contentContainer,
]) ?>
</div>
<?= Html::hiddenInput('containerGuid', $contentContainer->guid); ?>
<?= Html::hiddenInput('containerClass', get_class($contentContainer)); ?>
<?= Html::hiddenInput('containerGuid', $contentContainer->guid) ?>
<?= Html::hiddenInput('containerClass', get_class($contentContainer)) ?>
<div class="contentForm_options">
<hr>
<div class="btn_container">
<?= Button::info($submitButtonText)->action('submit', $submitUrl)->id('post_submit_button')->submit() ?>
<?= Button::info($submitButtonText)->action('submit', $submitUrl)->id('post_submit_button' . ($isModal ? '_modal' : ''))->submit() ?>
<?php $uploadButton = UploadButton::widget([
'id' => 'contentFormFiles',
'id' => 'contentFormFiles' . ($isModal ? 'Modal' : ''),
'tooltip' => Yii::t('ContentModule.base', 'Attach Files'),
'progress' => '#contentFormFiles_progress',
'preview' => '#contentFormFiles_preview',
'dropZone' => '#contentFormBody',
'max' => Yii::$app->getModule('content')->maxAttachedFiles
'progress' => '#contentFormFiles_progress' . ($isModal ? 'Modal' : ''),
'preview' => '#contentFormFiles_preview' . ($isModal ? 'Modal' : ''),
'dropZone' => '#contentFormBody' . ($isModal ? 'Modal' : ''),
'max' => Yii::$app->getModule('content')->maxAttachedFiles,
'fileList' => $fileList,
]); ?>
<?= FileHandlerButtonDropdown::widget(['primaryButton' => $uploadButton, 'handlers' => $fileHandlers, 'cssButtonClass' => 'btn-default']); ?>
<!-- public checkbox -->
<?= Html::checkbox('visibility', '', ['id' => 'contentForm_visibility', 'class' => 'contentForm hidden', 'aria-hidden' => 'true']); ?>
<?= Html::checkbox('visibility', '', ['class' => 'contentForm_visibility contentForm hidden', 'aria-hidden' => 'true']); ?>
<!-- state data -->
<?= Html::hiddenInput('state', Content::STATE_PUBLISHED) ?>
@ -95,7 +98,7 @@ use yii\helpers\Html;
<?php if ($canSwitchVisibility): ?>
<li>
<?= Link::withAction(Yii::t('ContentModule.base', 'Change to "Public"'), 'changeVisibility')
->id('contentForm_visibility_entry')->icon('unlock') ?>
->cssClass('contentForm_visibility_entry')->icon('unlock') ?>
</li>
<?php endif; ?>
<li>
@ -104,19 +107,28 @@ use yii\helpers\Html;
->options([
'data-state' => Content::STATE_DRAFT,
'data-state-title' => Yii::t('ContentModule.base', 'Draft'),
'data-button-title' => Yii::t('ContentModule.base', 'Save as draft')
'data-button-title' => Yii::t('ContentModule.base', 'Save as draft'),
]) ?>
</li>
<li>
<?= Link::withAction(Yii::t('ContentModule.base', 'Schedule publication'), 'scheduleOptions', $scheduleUrl)
->icon('clock-o') ?>
</li>
<?php if (!$isModal): ?>
<li>
<?= Link::withAction(Yii::t('ContentModule.base', 'Schedule publication'), 'scheduleOptions', $scheduleUrl)
->icon('clock-o') ?>
</li>
<?php endif; ?>
</ul>
</li>
</ul>
</div>
</div>
<?= UploadProgress::widget(['id' => 'contentFormFiles_progress']) ?>
<?= FilePreview::widget(['id' => 'contentFormFiles_preview', 'edit' => true, 'options' => ['style' => 'margin-top:10px;']]); ?>
<?= UploadProgress::widget([
'id' => 'contentFormFiles_progress' . ($isModal ? 'Modal' : ''),
]) ?>
<?= FilePreview::widget([
'id' => 'contentFormFiles_preview' . ($isModal ? 'Modal' : ''),
'edit' => true,
'items' => $fileList,
'options' => ['style' => 'margin-top:10px;'],
]) ?>
</div><!-- /contentForm_Options -->

View File

@ -2,11 +2,11 @@
namespace humhub\modules\dashboard\widgets;
use humhub\modules\post\widgets\Form;
use Yii;
use humhub\modules\stream\widgets\StreamViewer;
use humhub\components\Widget;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\post\widgets\Form;
use humhub\modules\stream\widgets\StreamViewer;
use Yii;
class DashboardContent extends Widget
{
@ -33,6 +33,7 @@ class DashboardContent extends Widget
}
echo StreamViewer::widget([
'options' => ['class' => 'dashboard-wall-stream'],
'streamAction' => '//dashboard/dashboard/stream',
'showFilters' => (bool)Yii::$app->getModule('dashboard')->settings->get('showProfilePostForm'),
'messageStreamEmpty' => $messageStreamEmpty,

View File

@ -0,0 +1,96 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2025 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\file\controllers;
use humhub\components\behaviors\AccessControl;
use humhub\components\Controller;
use humhub\modules\file\models\File;
use Yii;
use yii\web\HttpException;
use yii\web\NotFoundHttpException;
/**
* Modules can be added as an additional target by
* registering an event on `ShareIntendController::EVENT_INIT`
* and adding the following method to the Events class:
*
* ```
* public static function onShareIntendControllerInit($event)
* {
* $event->sender->shareTargets[] = [
* 'title' => 'Your Module',
* 'route' => '/your-module/share-intend/index',
* ];
* }
* ```
*
* The module must have the ShareIntendController and view,
* similar to the Post module.
* The controller must extend \humhub\modules\content\controllers\ShareIntendController
*/
final class ShareIntendController extends Controller
{
public $shareTargets = [];
public function behaviors()
{
return [
'acl' => [
'class' => AccessControl::class,
],
];
}
public function init()
{
$this->shareTargets[] = [
'title' => Yii::t('FileModule.base', 'Share as Post'),
'route' => '/post/share-intend',
];
parent::init();
}
public function actionIndex()
{
$fileList = Yii::$app->request->get('fileList');
if (!$fileList) {
throw new NotFoundHttpException('No files to share found!');
}
// Check if the files exists, and if user is the owner of the files
if (File::find()->where(['guid' => $fileList])->andWhere(['created_by' => Yii::$app->user->id])->count() !== count($fileList)) {
throw new NotFoundHttpException('Files not uploaded correctly!');
}
if (count($this->shareTargets) === 0) {
throw new NotFoundHttpException('No sharing targets found!');
}
Yii::$app->session->set('shareIntendFiles', $fileList);
return $this->renderAjax('index', [
'shareTargets' => $this->shareTargets,
]);
}
public static function checkShareFileGuids(): void
{
$fileGuids = Yii::$app->session->get('shareIntendFiles');
if (empty($fileGuids)) {
throw new HttpException('500', 'No files to share found!');
}
}
public static function getShareFileGuids(): ?array
{
return Yii::$app->session->get('shareIntendFiles');
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2025 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
use humhub\modules\ui\view\components\View;
use humhub\widgets\ModalButton;
use humhub\widgets\ModalDialog;
use yii\helpers\Url;
/**
* @var $this View
* @var $shareTargets array
*/
?>
<?php ModalDialog::begin(['header' => Yii::t('FileModule.base', 'Share')]) ?>
<div class="modal-body">
<?php foreach ($shareTargets as $target): ?>
<a class="btn btn-primary btn-block" data-action-click="ui.modal.load"
data-action-url="<?= Url::to([$target['route']]) ?>">
<?= $target['title'] ?>
</a>
<?php endforeach; ?>
</div>
<div class="modal-footer">
<?= ModalButton::cancel() ?>
</div>
<?php ModalDialog::end() ?>

View File

@ -159,6 +159,11 @@ class UploadInput extends JsWidget
*/
public $attach = true;
/**
* Pre-uploaded File GUIDs to be attached to the new content
*/
public array $fileList = [];
public function init()
{
parent::init();
@ -183,6 +188,9 @@ class UploadInput extends JsWidget
$result .= Html::hiddenInput($this->submitName, $file->guid);
}
}
foreach ((array)$this->fileList as $file) {
$result .= Html::hiddenInput($this->submitName, $file);
}
return $result;
}

View File

@ -0,0 +1,28 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2025 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\modules\post\controllers;
use humhub\modules\post\permissions\CreatePost;
final class ShareIntendController extends \humhub\modules\content\controllers\ShareIntendController
{
public function actionCreate()
{
return $this->renderAjax('create', [
'shareTarget' => $this->shareTarget,
'fileList' => \humhub\modules\file\controllers\ShareIntendController::getShareFileGuids(),
]);
}
protected function getCreatePermissionClass(): string
{
return CreatePost::class;
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2025 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
use humhub\libs\Html;
use humhub\modules\content\components\ContentContainerActiveRecord;
use humhub\modules\post\widgets\Form;
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\modules\ui\view\components\View;
use humhub\widgets\ModalButton;
use humhub\widgets\ModalDialog;
use yii\helpers\StringHelper;
/**
* @var $this View
* @var $fileList array
* @var $shareTarget ContentContainerActiveRecord
*/
?>
<?php ModalDialog::begin([
'id' => 'share-intend-modal',
'header' => Yii::t('FileModule.base', 'Share in {targetDisplayName}', [
'targetDisplayName' => $shareTarget->guid === Yii::$app->user->identity->guid ?
Yii::t('base', 'My Profile') :
Html::encode(StringHelper::truncate($shareTarget->displayName, 10)),
]),
]) ?>
<?php $form = ActiveForm::begin() ?>
<div class="modal-body">
<div id="space-content-create-form" data-stream-create-content="stream.wall.WallStream">
<?= Form::widget([
'contentContainer' => $shareTarget,
'fileList' => $fileList,
'isModal' => true,
]) ?>
</div>
</div>
<div class="modal-footer">
<?= ModalButton::defaultType(Yii::t('base', 'Back'))
->load(['/post/share-intend']) ?>
</div>
<?php ActiveForm::end() ?>
<?php ModalDialog::end() ?>
<script <?= Html::nonce() ?>>
$(function () {
humhub.modules.content.form.initModal();
$('#share-intend-modal').find('input[type=text], textarea, .ProseMirror').eq(0).trigger('click').focus();
});
</script>

View File

@ -45,8 +45,8 @@ class Form extends WallCreateContentForm
return array_merge([
'post' => $post,
'wallCreateContentForm' => $this,
'mentioningUrl' => $canCreatePostInSpace ? Url::to([$this->mentioningUrl, 'id' => $this->contentContainer->id]) : null,
'submitUrl' => $this->submitUrl,
], $additionalParams);
}

View File

@ -1,20 +1,21 @@
<?php
use humhub\modules\content\widgets\richtext\RichTextField;
use humhub\modules\content\widgets\WallCreateContentForm;
use humhub\modules\content\widgets\WallCreateContentFormFooter;
use humhub\modules\post\models\Post;
use humhub\modules\ui\form\widgets\ActiveForm;
/* @var WallCreateContentForm $wallCreateContentForm */
/* @var string $mentioningUrl */
/* @var ActiveForm $form */
/* @var Post $post */
/* @var string $submitUrl */
?>
<?= $form->field($post, 'message')->widget(RichTextField::class, [
'id' => 'contentForm_message',
'id' => 'contentForm_message' . ($wallCreateContentForm->isModal ? 'Modal' : ''),
'form' => $form,
'layout' => RichTextField::LAYOUT_INLINE,
'layout' => $wallCreateContentForm->isModal ? RichTextField::LAYOUT_BLOCK : RichTextField::LAYOUT_INLINE,
'pluginOptions' => ['maxHeight' => '300px'],
'placeholder' => Yii::t("PostModule.base", "What's on your mind?"),
'name' => 'message',
@ -25,5 +26,5 @@ use humhub\modules\ui\form\widgets\ActiveForm;
<?= WallCreateContentFormFooter::widget([
'contentContainer' => $post->content->container,
'submitUrl' => $submitUrl,
'wallCreateContentForm' => $wallCreateContentForm,
]) ?>

View File

@ -8,8 +8,8 @@
namespace humhub\modules\space\controllers;
use humhub\components\Controller;
use humhub\components\behaviors\AccessControl;
use humhub\components\Controller;
use humhub\modules\content\widgets\ContainerTagPicker;
use humhub\modules\space\models\Space;
use humhub\modules\space\widgets\Chooser;
@ -43,7 +43,7 @@ class BrowseController extends Controller
/**
* Returns a workspace list by json
*
* It can be filtered by by keyword.
* It can be filtered by keyword.
*/
public function actionSearchJson()
{

View File

@ -0,0 +1,96 @@
<?php
namespace humhub\modules\space\helpers;
use humhub\libs\BasePermission;
use humhub\modules\content\models\ContentContainerDefaultPermission;
use humhub\modules\space\models\Space;
use humhub\modules\space\widgets\Chooser;
use Yii;
use yii\db\Expression;
use yii\web\IdentityInterface;
final class CreateContentPermissionHelper
{
/**
* Returns a list of Spaces where the user has a special permission (e.g. CreatePost).
*
* @param string $permissionClass
* @param string|null $keyword
* @param IdentityInterface|null $user
* @return array
*/
public static function findSpaces(
string $permissionClass,
?string $keyword = null,
?IdentityInterface $user = null,
): array {
$user = $user ?? Yii::$app->user->identity;
$spaces = Space::find()
->visible($user)
->filterBlockedSpaces($user)
->andWhere(['space.status' => Space::STATUS_ENABLED]);
if ($keyword) {
$spaces->search($keyword);
}
if (!$user->isSystemAdmin()) {
// Check the User can create a Post in the searched Spaces
$spaces->leftJoin('space_membership', 'space_membership.space_id = space.id')
->leftJoin(
'contentcontainer_permission',
'contentcontainer_permission.contentcontainer_id = space.contentcontainer_id
AND contentcontainer_permission.group_id = space_membership.group_id
AND contentcontainer_permission.permission_id = :permission_id',
)
->andWhere(['space_membership.user_id' => $user->id])
->andWhere(['OR',
// Allowed by default
['AND',
['IN', 'space_membership.group_id', self::getDefaultAllowedGroups($permissionClass)],
['IS', 'contentcontainer_permission.permission_id', new Expression('NULL')],
],
// Set to allow
['contentcontainer_permission.state' => $permissionClass::STATE_ALLOW],
])
->addParams(['permission_id' => $permissionClass]);
}
$result = [];
foreach ($spaces->all() as $space) {
$result[] = Chooser::getSpaceResult($space);
}
return $result;
}
private static function getDefaultAllowedGroups(string $permissionClass): array
{
$defaultAllowedGroups = (new $permissionClass())->defaultAllowedGroups;
/* @var ContentContainerDefaultPermission[] $defaultPermissions */
$defaultPermissions = ContentContainerDefaultPermission::find()
->where(['contentcontainer_class' => Space::class])
->andWhere(['permission_id' => $permissionClass])
->all();
foreach ($defaultPermissions as $defaultPermission) {
switch ($defaultPermission->state) {
case BasePermission::STATE_ALLOW:
if (!in_array($defaultPermission->group_id, $defaultAllowedGroups)) {
$defaultAllowedGroups[] = $defaultPermission->group_id;
}
break;
case BasePermission::STATE_DENY:
if (($i = array_search($defaultPermission->group_id, $defaultAllowedGroups)) !== false) {
unset($defaultAllowedGroups[$i]);
}
break;
}
}
return $defaultAllowedGroups;
}
}

View File

@ -2,7 +2,6 @@
use humhub\modules\space\models\Space;
use humhub\modules\space\modules\manage\widgets\SecurityTabMenu;
use humhub\widgets\DataSaved;
use yii\bootstrap\ActiveForm;
use humhub\libs\Html;
@ -47,8 +46,6 @@ use humhub\libs\Html;
],
); ?>
<?= DataSaved::widget(); ?>
<?php ActiveForm::end(); ?>
</div>
</div>

View File

@ -2,10 +2,9 @@
namespace humhub\modules\space\widgets;
use humhub\modules\content\widgets\ContentContainerPickerField;
use humhub\modules\space\models\Space;
use humhub\modules\ui\form\widgets\BasePicker;
use Yii;
use yii\helpers\Html;
/**
* Mutliselect input field for selecting space guids.
@ -14,30 +13,19 @@ use yii\helpers\Html;
* @since 1.2
* @author buddha
*/
class SpacePickerField extends BasePicker
class SpacePickerField extends ContentContainerPickerField
{
/**
* @inheritdoc
* Min guids string value of Space model equal 2
*/
public $minInput = 2;
public $itemClass = Space::class;
/**
* @inheritdoc
*/
public $defaultRoute = '/space/browse/search-json';
public $itemClass = Space::class;
public $itemKey = 'guid';
/**
* @inheritdoc
* Min guids string value
*/
protected function getAttributes()
{
return array_merge(parent::getAttributes(), [
'data-tags' => 'false',
]);
}
public $minInput = 2;
/**
* @inheritdoc
@ -56,21 +44,4 @@ class SpacePickerField extends BasePicker
return $result;
}
/**
* @inheritdoc
*/
protected function getItemText($item)
{
return $item->getDisplayName();
}
/**
* @inheritdoc
*/
protected function getItemImage($item)
{
return Image::widget(['space' => $item, 'width' => 24]);
}
}

View File

@ -80,14 +80,14 @@ abstract class BasePicker extends JsInputWidget
public $maxSelection = 50;
/**
* Minimum character input before triggering search query.
* Minimum characters input before triggering search query.
*
* @var int
*/
public $minInput = 3;
/**
* Minimum character input before triggering search query.
* Maximum characters input before triggering search query.
*
* @var int
*/

View File

@ -471,9 +471,9 @@ class PermissionManager extends Component
if (
$returnOnlyChangeable && !$permission->canChangeState($groupId) ||
(
$this->contentContainer &&
array_key_exists($permission->moduleId, $this->contentContainer->moduleManager->getInstallable())
&& !$this->contentContainer->moduleManager->isEnabled($permission->moduleId)
$permission->contentContainer &&
array_key_exists($permission->moduleId, $permission->contentContainer->moduleManager->getInstallable())
&& !$permission->contentContainer->moduleManager->isEnabled($permission->moduleId)
)
) {
continue;

View File

@ -92,7 +92,7 @@ class RegistrationController extends Controller
$inviteRegistrationService->populateRegistration($registration);
} elseif (Yii::$app->session->has('authClient')) {
$authClient = Yii::$app->session->get('authClient');
$this->handleAuthClientRegistration($authClient, $registration);
$registration = $this->createRegistrationByAuthClient($authClient);
} else {
Yii::warning('Registration failed: No token (query) or authclient (session) found!', 'user');
Yii::$app->session->setFlash('error', 'Registration failed.');
@ -173,7 +173,7 @@ class RegistrationController extends Controller
* @param Registration $registration
* @throws Exception
*/
protected function handleAuthClientRegistration(ClientInterface $authClient, Registration $registration)
protected function createRegistrationByAuthClient(ClientInterface $authClient): Registration
{
$attributes = $authClient->getUserAttributes();
@ -181,7 +181,8 @@ class RegistrationController extends Controller
throw new Exception("No user id given by authclient!");
}
$registration->enablePasswordForm = false;
$registration = new Registration(enablePasswordForm: false);
if ($authClient instanceof ApprovalBypass) {
$registration->enableUserApproval = false;
}
@ -191,5 +192,7 @@ class RegistrationController extends Controller
$registration->getUser()->setAttributes($attributes, false);
$registration->getProfile()->setAttributes($attributes, false);
return $registration;
}
}

View File

@ -36,17 +36,17 @@ class Registration extends HForm
/**
* @var bool show password creation form
*/
public $enablePasswordForm = true;
private $enablePasswordForm;
/**
* @var bool show checkbox to force to change password on first log in
*/
public $enableMustChangePassword = false;
private $enableMustChangePassword;
/**
* @var bool show e-mail field
*/
public $enableEmailField = false;
private $enableEmailField;
/**
* @var bool|null require user approval by admin after registration.
@ -73,6 +73,21 @@ class Registration extends HForm
*/
private $_profile = null;
public function __construct(
$definition = [],
$primaryModel = null,
array $config = [],
bool $enableEmailField = false,
bool $enablePasswordForm = true,
bool $enableMustChangePassword = false,
) {
$this->enableEmailField = $enableEmailField;
$this->enablePasswordForm = $enablePasswordForm;
$this->enableMustChangePassword = $enableMustChangePassword;
parent::__construct($definition, $primaryModel, $config);
}
/**
* @inheritdoc
*/
@ -102,7 +117,10 @@ class Registration extends HForm
if ($this->enablePasswordForm) {
$this->definition['elements']['Password'] = $this->getPasswordFormDefinition();
}
$this->definition['elements']['Profile'] = array_merge(['type' => 'form'], $this->getProfile()->getFormDefinition());
$this->definition['elements']['Profile'] = array_merge(
['type' => 'form'],
$this->getProfile()->getFormDefinition(),
);
$this->definition['buttons'] = [
'save' => [
'type' => 'submit',
@ -181,7 +199,9 @@ class Registration extends HForm
{
$groupModels = Group::getRegistrationGroups($this->getUser());
$groupFieldType = (Yii::$app->getModule('user')->settings->get('auth.showRegistrationUserGroup') && count($groupModels) > 1)
$groupFieldType = (Yii::$app->getModule('user')->settings->get('auth.showRegistrationUserGroup') && count(
$groupModels,
) > 1)
? 'dropdownlist'
: 'hidden'; // TODO: Completely hide the element instead of current <input type="hidden">
@ -266,7 +286,6 @@ class Registration extends HForm
}
if ($this->models['User']->save()) {
// Save User Profile
$this->models['Profile']->user_id = $this->models['User']->id;
$this->models['Profile']->save();
@ -290,7 +309,10 @@ class Registration extends HForm
if ($authClient !== null) {
(new AuthClientUserService($this->models['User']))->add($authClient);
$authClient->trigger(BaseClient::EVENT_CREATE_USER, new UserEvent(['identity' => $this->models['User']]));
$authClient->trigger(
BaseClient::EVENT_CREATE_USER,
new UserEvent(['identity' => $this->models['User']]),
);
}
$this->trigger(self::EVENT_AFTER_REGISTRATION, new UserEvent(['identity' => $this->models['User']]));

View File

@ -123,9 +123,7 @@ class AuthClientService
return null;
}
$registration = new Registration();
$registration->enablePasswordForm = false;
$registration->enableEmailField = true;
$registration = new Registration(enableEmailField: true, enablePasswordForm: false);
if ($this->authClient instanceof ApprovalBypass) {
$registration->enableUserApproval = false;

View File

@ -33,11 +33,11 @@ class AccountCest
$I->see('Sprache');
$I->see('Speichern');
$I->click('Save');
$I->waitForElementVisible('.data-saved', 5);
$I->seeSuccess();
$I->selectOption('#accountsettings-language', 'English(US)');
$I->click('Save');
$I->waitForElementVisible('.data-saved', 5);
$I->seeSuccess();
*/
$I->seeSuccess('Saved');

View File

@ -1,19 +1,9 @@
<?php
use humhub\widgets\DataSaved;
?>
<div class="panel-heading">
<?= Yii::t('UserModule.account', '<strong>Your</strong> profile'); ?><?php echo DataSaved::widget(); ?>
<?= Yii::t('UserModule.account', '<strong>Your</strong> profile'); ?>
</div>
<?= humhub\modules\user\widgets\AccountProfileMenu::widget(); ?>
<div class="panel-body">
<?php echo $content; ?>
<?= $content ?>
</div>

View File

@ -1,12 +1,10 @@
<?php
use humhub\widgets\DataSaved;
humhub\modules\user\widgets\AccountMenu::markAsActive(['/user/account/edit-settings']);
?>
<div class="panel-heading">
<?= Yii::t('UserModule.account', '<strong>Account</strong> Settings') ?><?php echo DataSaved::widget(); ?>
<?= Yii::t('UserModule.account', '<strong>Account</strong> Settings') ?>
</div>
<div class="panel-body">
<?= Yii::t('UserModule.account', 'Define basic settings for your profile. You can add tags that fit you, choose the language and your time zone and block impolite users.') ?>
@ -17,8 +15,3 @@ humhub\modules\user\widgets\AccountMenu::markAsActive(['/user/account/edit-setti
<div class="panel-body">
<?= $content; ?>
</div>

View File

@ -1,7 +1,6 @@
<?php
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\widgets\DataSaved;
use yii\helpers\Html;
?>
@ -21,12 +20,5 @@ use yii\helpers\Html;
<hr>
<?php echo Html::submitButton(Yii::t('UserModule.account', 'Save'), ['name' => 'save', 'class' => 'btn btn-primary', 'data-ui-loader' => '']); ?>
<!-- show flash message after saving -->
<?php echo DataSaved::widget(); ?>
<?php ActiveForm::end(); ?>
<?php $this->endContent(); ?>

View File

@ -1,7 +1,6 @@
<?php
use humhub\modules\ui\form\widgets\ActiveForm;
use humhub\widgets\DataSaved;
use yii\bootstrap\Html;
?>
@ -23,6 +22,5 @@ use yii\bootstrap\Html;
<hr>
<?php echo Html::submitButton(Yii::t('UserModule.account', 'Save'), ['class' => 'btn btn-primary', 'data-ui-loader' => '']); ?>
<?php echo DataSaved::widget(); ?>
<?php ActiveForm::end(); ?>
<?php $this->endContent(); ?>

View File

@ -3,11 +3,11 @@
namespace humhub\modules\user\widgets;
use humhub\libs\BasePermission;
use humhub\modules\user\models\UserFilter;
use Yii;
use yii\base\Widget;
use yii\helpers\Html;
use yii\helpers\Url;
use humhub\modules\user\models\UserFilter;
/**
* UserPickerWidget displays a user picker instead of an input field.
@ -254,7 +254,7 @@ class UserPicker extends Widget
* @param type $permission
* @return type
*/
private static function createJSONUserInfo($user, $permission = null, $priority = null)
public static function createJSONUserInfo($user, $permission = null, $priority = null)
{
$disabled = false;

View File

@ -2,7 +2,7 @@
namespace humhub\modules\user\widgets;
use humhub\modules\ui\form\widgets\BasePicker;
use humhub\modules\content\widgets\ContentContainerPickerField;
use humhub\modules\user\models\User;
use Yii;
use yii\helpers\Url;
@ -13,8 +13,10 @@ use yii\helpers\Url;
* @since 1.2
* @author buddha
*/
class UserPickerField extends BasePicker
class UserPickerField extends ContentContainerPickerField
{
public $itemClass = User::class;
/**
* @inheritdoc
*/
@ -25,24 +27,6 @@ class UserPickerField extends BasePicker
*/
public $jsWidget = 'user.picker.UserPicker';
/**
* @inheritdoc
* The 'guid' value is default for UserPickerField
*/
public $itemKey = 'guid';
/**
* @inheritdoc
*/
public function init()
{
$this->itemClass = User::class;
if (empty($this->itemKey)) {
$this->itemKey = 'guid';
}
parent::init();
}
/**
* @inheritdoc
*/
@ -50,26 +34,16 @@ class UserPickerField extends BasePicker
{
if (!$this->url) {
// provide the space id if the widget is calling from a space
if (Yii::$app->controller->id == 'space') {
if (Yii::$app->controller->id === 'space') {
return Url::to([$this->defaultRoute, 'space_id' => Yii::$app->controller->getSpace()->id]);
} else {
return Url::to([$this->defaultRoute]);
}
return Url::to([$this->defaultRoute]);
}
return parent::getUrl();
}
/**
* @inheritdoc
*/
protected function getAttributes()
{
return array_merge(parent::getAttributes(), [
'data-tags' => 'false',
]);
}
/**
* @inheritdoc
*/
@ -88,8 +62,7 @@ class UserPickerField extends BasePicker
if ($this->placeholder && !$this->placeholderMore) {
$result['placeholder-more'] = $this->placeholder;
} else {
$result['placeholder-more'] = ($this->placeholderMore) ? $this->placeholderMore
: Yii::t('UserModule.chooser', 'Add user');
$result['placeholder-more'] = ($this->placeholderMore) ?: Yii::t('UserModule.chooser', 'Add user');
}
$result['no-result'] = Yii::t('UserModule.chooser', 'No users found for the given query.');
@ -103,20 +76,4 @@ class UserPickerField extends BasePicker
}
return $result;
}
/**
* @inheritdoc
*/
protected function getItemText($item)
{
return $item->displayName;
}
/**
* @inheritdoc
*/
protected function getItemImage($item)
{
return $item->getProfileImage()->getUrl();
}
}

View File

@ -23,23 +23,56 @@ class FormatterTest extends Unit
999 => '999',
1000 => '1K',
1234 => '1K',
9990 => '9K',
9990 => '10K',
123456 => '123K',
123999 => '123K',
999999 => '999K',
123999 => '124K',
899999 => '900K',
999999 => '1M',
1234567 => '1M',
123456789 => '123M',
123999500 => '124M',
999999499 => '999M',
999999500 => '1000M',
999999499 => '1B',
999999500 => '1B',
1234567899 => '1B',
123456789999 => '123B',
12345678999999 => '12345B',
999999999499999 => '999999B',
12345678999999 => '12346B',
999999999499999 => '1000000B',
999999999500000 => '1000000B',
];
foreach ($testNumbers as $numberValue => $result) {
$this->assertEquals(Yii::$app->formatter->asShortInteger($numberValue), $result);
}
}
public function testAsShortIntegerArabic()
{
Yii::$app->formatter->locale = 'ar';
$testNumbers = [
1 => '١',
12 => '١٢',
123 => '١٢٣',
999 => '٩٩٩',
1000 => '١K',
1234 => '١K',
9990 => '١٠K',
123456 => '١٢٣K',
123999 => '١٢٤K',
899999 => ٠٠K',
999999 => '١M',
1234567 => '١M',
123456789 => '١٢٣M',
123999500 => '١٢٤M',
999999499 => '١B',
999999500 => '١B',
1234567899 => '١B',
123456789999 => '١٢٣B',
12345678999999 => '١٢٣٤٦B',
999999999499999 => '١٠٠٠٠٠٠B',
999999999500000 => '١٠٠٠٠٠٠B',
];
foreach ($testNumbers as $numberValue => $result) {
$this->assertEquals(Yii::$app->formatter->asShortInteger($numberValue), $result);
}
}
}

View File

@ -1,29 +0,0 @@
<?php
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
namespace humhub\widgets;
/**
* DataSavedWidget shows an flash message after saving
*
* @deprecated since 1.2 use \humhub\modules\ui\view\components\View::saved
* @package humhub.widgets
* @since 0.5
* @author Andreas Strobel
*/
class DataSaved extends \yii\base\Widget
{
/**
* Displays / Run the Widget
*/
public function run()
{
return $this->render('dataSaved', []);
}
}

View File

@ -1,15 +0,0 @@
<!-- check if flash message exists -->
<?php if(Yii::$app->getSession()->hasFlash('data-saved')): ?>
<script <?= \humhub\libs\Html::nonce() ?>>
$(function() {
humhub.modules.log.success('<?php echo Yii::$app->getSession()->getFlash('data-saved'); ?>', true);
});
</script>
<?php endif; ?>

View File

@ -23,7 +23,7 @@
}
}
#contentFormBody {
.content-form-body {
.form-group, .help-block-error {
margin: 0;
}
@ -312,7 +312,7 @@
.wall-entry-controls a {
font-size: 11px;
color: @text-color-soft;
color: @text-color-soft;
margin-top: 10px;
margin-bottom: 0;
}

View File

@ -114,14 +114,6 @@
border-color: @border-color-danger;
}
//
// data-saved feedback Deprecated since 1.2
// --------------------------------------------------
.data-saved {
padding-left: 10px;
color: @link;
}
img.bounceIn {
animation-duration: 800ms;
}

View File

@ -2921,25 +2921,25 @@ div.nested-comments-root div.comment-container {
#content-topic-bar .label {
margin-left: 4px;
}
#contentFormBody .form-group,
#contentFormBody .help-block-error {
.content-form-body .form-group,
.content-form-body .help-block-error {
margin: 0;
}
#contentFormBody .contentForm_options .form-group {
.content-form-body .contentForm_options .form-group {
margin-bottom: 15px;
}
#contentFormBody .contentForm_options .form-group .checkbox label {
.content-form-body .contentForm_options .form-group .checkbox label {
padding-left: 22px;
}
#contentFormBody .contentForm_options .form-group .checkbox label input[type=checkbox] {
.content-form-body .contentForm_options .form-group .checkbox label input[type=checkbox] {
position: absolute;
top: 4px;
left: 0;
}
#contentFormBody .contentForm_options .form-group .checkbox label input[type=checkbox]:focus {
.content-form-body .contentForm_options .form-group .checkbox label input[type=checkbox]:focus {
border-color: #ccc !important;
}
#contentFormBody .contentForm_options .form-group .checkbox label input[type=checkbox]:focus:checked {
.content-form-body .contentForm_options .form-group .checkbox label input[type=checkbox]:focus:checked {
border-color: #21A1B3 !important;
}
.placeholder-empty-stream {
@ -4186,10 +4186,6 @@ ul.tour-list li.completed a {
background-color: #fff6f6;
border-color: #ff8989;
}
.data-saved {
padding-left: 10px;
color: #21A1B3;
}
img.bounceIn {
animation-duration: 800ms;
}