Merge branch 'develop' of github.com:humhub/humhub into develop

This commit is contained in:
Lucas Bartholemy 2024-04-26 18:03:46 +02:00
commit 17547f812b
18 changed files with 288 additions and 77 deletions

View File

@ -7,8 +7,13 @@ HumHub Changelog
- Fix #6909: Fix the marketplace searching when a module config file has missed fields
- Enh #6920: Enhancing of meta search for advanced search providers
- Enh #6952: Improve container title encoding in header
- Fix #6954: Search out of viewport on mobile
- Enh #6950: Ability to reset some notification settings to all users without resetting "Like" notifications by email
- Fix #6954: Search out of viewport on mobile
- Fix #6962: People filter - Hide follower options if Following is disabled in the User module
- Fix #6961: Fix people dropdown filter without defined keys
- Fix #6967: Use same order for meta searching that is used for content page searching by default
- Enh #6968: Meta search: open external links in new window
1.16.0-beta.2 (April 9, 2024)
-----------------------------

View File

@ -77,6 +77,7 @@ class ContentSearchProvider implements MetaSearchProviderInterface
$resultSet = $module->getSearchDriver()->search(new SearchRequest([
'keyword' => $this->getKeyword(),
'pageSize' => $maxResults,
'orderBy' => SearchRequest::ORDER_BY_SCORE,
]));
$results = [];

View File

@ -42,7 +42,7 @@ class SearchRequest extends Model
public $contentContainer = [];
public $orderBy = 'content.created_at';
public $orderBy = self::ORDER_BY_CREATION_DATE;
public ?SearchQuery $searchQuery = null;

View File

@ -38,6 +38,11 @@ class NotificationManager
*/
public const EVENT_SEARCH_MODULE_NOTIFICATIONS = 'searchModuleNotifications';
/**
* User setting name to know if the user has modified default notification settings
*/
public const IS_TOUCHED_SETTINGS = 'is_touched_settings';
/**
*
* @var array Target configuration.
@ -266,7 +271,7 @@ class NotificationManager
return ContentContainerSetting::find()
->where('contentcontainer_setting.contentcontainer_id=user.contentcontainer_id')
->andWhere(['contentcontainer_setting.module_id' => 'notification'])
->andWhere(['contentcontainer_setting.name' => 'notification.like_email']);
->andWhere(['contentcontainer_setting.name' => self::IS_TOUCHED_SETTINGS]);
}
/**
@ -282,7 +287,7 @@ class NotificationManager
$result = array_merge($memberSpaces, $followSpaces);
if ($this->isUntouchedSettings($user)) {
if (!static::isTouchedSettings($user)) {
$result = array_merge($result, Space::find()
->where(['guid' => Yii::$app->getModule('notification')->settings->getSerialized('sendNotificationSpaces')])
->visible($user)
@ -293,9 +298,14 @@ class NotificationManager
return $result;
}
private function isUntouchedSettings(User $user)
/**
* @throws \Throwable
*/
public static function isTouchedSettings(User $user): bool
{
return Yii::$app->getModule('notification')->settings->user($user)->get('notification.like_email') === null;
/** @var Module $module */
$module = Yii::$app->getModule('notification');
return (bool)$module->settings->user($user)?->get(self::IS_TOUCHED_SETTINGS);
}
/**
@ -379,6 +389,7 @@ class NotificationManager
*/
public function setDesktopNoficationSettings($value = 0, User $user = null)
{
/** @var Module $module */
$module = Yii::$app->getModule('notification');
$settingManager = ($user) ? $module->settings->user($user) : $module->settings;
$settingManager->set('enable_html5_desktop_notifications', $value);

View File

@ -0,0 +1,55 @@
<?php
use humhub\modules\content\models\ContentContainerSetting;
use yii\db\Expression;
use yii\db\Migration;
use yii\db\Query;
/**
* Class m240422_162959_new_is_untouched_settings
*/
class m240422_162959_new_is_untouched_settings extends Migration
{
/**
* Inserts new rows into the `content_container_setting` table to add the "is_touched_settings"
* setting for the "notification" module where the "name" is "notification.like_email" which was
* previously used to know if the setting was modified by the user or not
*
* {@inheritdoc}
*/
public function safeUp()
{
$rows = (new Query())
->select([
"module_id",
"contentcontainer_id",
new Expression("'is_touched_settings' as name"),
new Expression("'1' as value"),
])
->from(ContentContainerSetting::tableName())
->where([
'name' => 'notification.like_email',
'module_id' => 'notification',
])
->all();
$query = Yii::$app->db->createCommand()
->batchInsert(
ContentContainerSetting::tableName(),
['module_id', 'contentcontainer_id', 'name', 'value'],
$rows,
);
$query->execute();
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
echo "m240422_162959_new_is_untouched_settings cannot be reverted.\n";
return false;
}
}

View File

@ -8,15 +8,17 @@
namespace humhub\modules\notification\models\forms;
use humhub\components\Module;
use humhub\modules\admin\permissions\ManageSettings;
use humhub\modules\admin\permissions\ManageUsers;
use humhub\modules\content\models\ContentContainerSetting;
use humhub\modules\notification\components\NotificationCategory;
use humhub\modules\notification\components\NotificationManager;
use humhub\modules\notification\targets\BaseTarget;
use humhub\modules\user\models\User;
use Yii;
use yii\base\Model;
use yii\web\HttpException;
use humhub\modules\notification\targets\BaseTarget;
use humhub\modules\admin\permissions\ManageSettings;
/**
* Description of NotificationSettings
@ -104,13 +106,12 @@ class NotificationSettings extends Model
/**
* Checks if this form has already been saved before.
* @return bool
* @throws \Throwable
*/
public function isUserSettingLoaded()
public function isTouchedSettings(): bool
{
if ($this->user) {
return $this->getSettings()->get('enable_html5_desktop_notifications') !== null ||
$this->getSettings()->get('notification.like_email') !== null;
return NotificationManager::isTouchedSettings($this->user);
}
return false;
@ -170,6 +171,10 @@ class NotificationSettings extends Model
$settings = $this->getSettings();
if ($this->user) {
$settings->set(NotificationManager::IS_TOUCHED_SETTINGS, true);
}
// Save all active settings
foreach ($this->settings as $settingKey => $value) {
$settings->set($settingKey, $value);
@ -221,6 +226,7 @@ class NotificationSettings extends Model
public function getSettings()
{
/** @var Module $module */
$module = Yii::$app->getModule('notification');
return ($this->user) ? $module->settings->user($this->user) : $module->settings;
@ -244,7 +250,8 @@ class NotificationSettings extends Model
}
$settings = $this->getSettings();
$settings->delete('enable_html5_desktop_notifications');
$settings?->delete(NotificationManager::IS_TOUCHED_SETTINGS);
$settings?->delete('enable_html5_desktop_notifications');
foreach ($this->targets() as $target) {
foreach ($this->categories() as $category) {
$settings->delete($target->getSettingKey($category));
@ -265,10 +272,11 @@ class NotificationSettings extends Model
/**
* Resets all settings stored for all current user
* @throws \Throwable
*/
public function resetAllUserSettings()
{
$notificationSettings = ['enable_html5_desktop_notifications'];
$notificationSettings = [NotificationManager::IS_TOUCHED_SETTINGS, 'enable_html5_desktop_notifications'];
foreach ($this->targets() as $target) {
foreach ($this->categories() as $category) {
$notificationSettings[] = $target->getSettingKey($category);
@ -282,7 +290,9 @@ class NotificationSettings extends Model
Yii::$app->notification->resetSpaces();
$settingsManager = Yii::$app->getModule('notification')->settings->user();
$settingsManager->reload();
/** @var Module $module */
$module = Yii::$app->getModule('notification');
$settingsManager = $module->settings->user();
$settingsManager?->reload();
}
}

View File

@ -31,7 +31,7 @@ use yii\helpers\Url;
<br/>
<button type="submit" class="btn btn-primary" data-ui-loader><?= Yii::t('base', 'Save'); ?></button>
<?php if ($model->isUserSettingLoaded()): ?>
<?php if ($model->isTouchedSettings()): ?>
<a href="#" class="btn btn-default pull-right" data-action-click="post"
data-action-url="<?= Url::to(['reset']) ?>"
data-ui-loader><?= Yii::t('ActivityModule.base', 'Reset to defaults') ?></a>

View File

@ -0,0 +1,72 @@
<?php
use humhub\modules\user\models\fieldtype\CheckboxList;
use humhub\modules\user\models\fieldtype\Select;
use humhub\modules\user\models\ProfileField;
use yii\db\Migration;
use yii\helpers\Json;
/**
* Class m240423_170311_profile_checkbox_list_field
*/
class m240423_170311_profile_checkbox_list_field extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$fields = ProfileField::find()
->where(['IN', 'field_type_class', [CheckboxList::class, Select::class]]);
foreach ($fields->all() as $field) {
/* @var ProfileField $field */
$fixedConfig = $this->getFixedProfileFieldConfig($field);
if ($fixedConfig !== null) {
$field->field_type_config = $fixedConfig;
$field->save();
}
}
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
echo "m240423_170311_profile_checkbox_list_field cannot be reverted.\n";
return false;
}
private function getFixedProfileFieldConfig(ProfileField $field): ?string
{
$config = Json::decode($field->field_type_config);
if (!isset($config['options']) || $config['options'] === '') {
return null;
}
$keyType = $field->field_type_class === Select::class ? 'index' : 'value';
$fixedOptions = [];
$index = 0;
$fixed = false;
foreach (preg_split('/[\r\n]+/', $config['options']) as $option) {
if (strpos($option, '=>') === false) {
// Fix an option without a Key
$fixed = true;
$fixedOptions[] = ($keyType === 'index' ? $index++ : trim($option)) . '=>' . $option;
} else {
// Leave a correct option as is
$fixedOptions[] = $option;
}
}
if ($fixed === false) {
return null;
}
$config['options'] = implode("\r\n", $fixedOptions);
return Json::encode($config);
}
}

View File

@ -410,4 +410,49 @@ class BaseType extends Model
public function loadDefaults(Profile $profile)
{
}
/**
* Validate options which must be as associative array with format Key=>Value
*
* @param string $attribute
* @return void
*/
public function validateListOptions(string $attribute): void
{
if (!is_string($this->$attribute) || $this->$attribute === '') {
return;
}
foreach (preg_split('/[\r\n]+/', $this->$attribute) as $option) {
if (strpos($option, '=>') === false) {
$this->addError($attribute, Yii::t('UserModule.profile', 'Each line must be formatted as Key=>Value!'));
return;
}
}
}
/**
* Returns a list of possible options
*
* @return array
*/
public function getSelectItems(): array
{
$items = [];
if (!isset($this->options) || !is_string($this->options)) {
return $items;
}
foreach (preg_split('/[\r\n]+/', $this->options) as $option) {
if (strpos($option, '=>') !== false) {
list($key, $value) = explode('=>', $option, 2);
$items[trim($key)] = Yii::t($this->profileField->getTranslationCategory(), trim($value));
} else {
$items[trim($option)] = Yii::t($this->profileField->getTranslationCategory(), trim($option));
}
}
return $items;
}
}

View File

@ -43,7 +43,7 @@ class CheckboxList extends BaseType
/**
* @var string
*/
public $other_value;
public $other;
/**
* @inheritdoc
@ -51,7 +51,8 @@ class CheckboxList extends BaseType
public function rules()
{
return [
[['options', 'other'], 'safe'],
[['options'], 'validateListOptions'],
[['other'], 'safe'],
[['allowOther'], 'integer'],
];
}
@ -112,7 +113,7 @@ class CheckboxList extends BaseType
Yii::$app->db->createCommand($sql)->execute();
}
return parent::delete();
parent::delete();
}
/**
@ -146,23 +147,11 @@ class CheckboxList extends BaseType
}
/**
* Returns a list of possible options
*
* @return array
* @inheritdoc
*/
public function getSelectItems()
public function getSelectItems(): array
{
$items = [];
foreach (explode("\n", $this->options) as $option) {
if (strpos($option, "=>") !== false) {
list($key, $value) = explode("=>", $option);
$items[trim($key)] = Yii::t($this->profileField->getTranslationCategory(), trim($value));
} else {
$items[trim($option)] = Yii::t($this->profileField->getTranslationCategory(), trim($option));
}
}
$items = parent::getSelectItems();
if ($this->allowOther) {
$items['other'] = Yii::t('UserModule.profile', 'Other:');

View File

@ -22,6 +22,17 @@ use Yii;
*/
class CountrySelect extends Select
{
/**
* @inheritdoc
*/
public function rules()
{
return [
[['options'], 'safe'],
];
}
/**
* Returns Form Definition for edit/create this field.
*
@ -46,11 +57,9 @@ class CountrySelect extends Select
}
/**
* Returns a list of possible options
*
* @return array
* @inheritdoc
*/
public function getSelectItems()
public function getSelectItems(): array
{
$items = [];

View File

@ -15,7 +15,7 @@ use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* ProfileFieldTypeSelect handles numeric profile fields.
* Select handles profile select list fields.
*
* @package humhub.modules_core.user.models
* @since 0.5
@ -49,7 +49,7 @@ class Select extends BaseType
public function rules()
{
return [
[['options'], 'safe'],
[['options'], 'validateListOptions'],
];
}
@ -68,7 +68,7 @@ class Select extends BaseType
'options' => [
'type' => 'textarea',
'label' => Yii::t('UserModule.profile', 'Possible values'),
'class' => 'form-control',
'class' => 'form-control autosize',
'hint' => Yii::t('UserModule.profile', 'One option per line. Key=>Value Format (e.g. yes=>Yes)'),
],
],
@ -112,28 +112,6 @@ class Select extends BaseType
], $options));
}
/**
* Returns a list of possible options
*
* @return array
*/
public function getSelectItems()
{
$items = [];
foreach (explode("\n", $this->options) as $option) {
if (strpos($option, "=>") !== false) {
list($key, $value) = explode("=>", $option);
$items[trim($key)] = Yii::t($this->profileField->getTranslationCategory(), trim($value));
} else {
$items[] = $option;
}
}
return $items;
}
/**
* @inheritdoc
*/

View File

@ -148,7 +148,9 @@ class ProfileCest
$I->createProfileField('Test textarea field', TextArea::class, ['visible' => true, 'editable' => true]);
$I->createProfileField('Test checkboxlist field', CheckboxList::class, [
'visible' => true,
'checkboxlist-options' => 'First option' . "\r\n" . 'Second option' . "\r\n" . 'Third option',
'checkboxlist-options' => 'First option => First option' . "\r\n" .
'Second option => Second option' . "\r\n" .
'Third option => Third option',
]);
$I->amGoingTo('test profile fields visibility by user');

View File

@ -89,7 +89,7 @@ class PeopleFilterPicker extends BasePicker
$result = [];
foreach ($this->selection as $item) {
if (!$item) {
if ($item === '' || $item === null) {
continue;
}

View File

@ -82,19 +82,23 @@ class PeopleFilters extends DirectoryFilters
// Connection
$connectionOptions = [
'' => Yii::t('UserModule.base', 'All'),
'followers' => Yii::t('UserModule.base', 'Followers'),
'following' => Yii::t('UserModule.base', 'Following'),
];
if (!Yii::$app->getModule('user')->disableFollow) {
$connectionOptions['followers'] = Yii::t('UserModule.base', 'Followers');
$connectionOptions['following'] = Yii::t('UserModule.base', 'Following');
}
if (Yii::$app->getModule('friendship')->settings->get('enable')) {
$connectionOptions['friends'] = Yii::t('UserModule.base', 'Friends');
$connectionOptions['pending_friends'] = Yii::t('UserModule.base', 'Pending Requests');
}
$this->addFilter('connection', [
'title' => Yii::t('SpaceModule.base', 'Status'),
'type' => 'dropdown',
'options' => $connectionOptions,
'sortOrder' => 400,
]);
if (count($connectionOptions) > 1) {
$this->addFilter('connection', [
'title' => Yii::t('SpaceModule.base', 'Status'),
'type' => 'dropdown',
'options' => $connectionOptions,
'sortOrder' => 400,
]);
}
// Profile fields
$profileFields = ProfileField::find()

View File

@ -81,6 +81,31 @@ class MetaSearchService
return Url::to($params);
}
/**
* Get a link target depending on URL,
* external URLs should be opened in a new window
*
* @param string|null $url
* @return string|null
*/
public function getLinkTarget(?string $url = null): ?string
{
if ($url === null) {
$url = $this->getUrl();
}
if (!is_string($url) || Url::isRelative($url)) {
return null;
}
$url = parse_url($url);
if (!isset($url['host']) || $url['host'] !== Yii::$app->request->hostName) {
return '_blank';
}
return null;
}
/**
* Check if a searching has been done
*

View File

@ -23,13 +23,17 @@ use humhub\widgets\Button;
<?php if ($provider->getService()->isSearched()) : ?>
<?php if ($provider->getService()->hasResults()) : ?>
<?php foreach ($provider->getService()->getResults() as $record) : ?>
<a href="<?= $record->getUrl() ?>" class="search-provider-record">
<?= Html::beginTag('a', [
'href' => $record->getUrl(),
'class' => 'search-provider-record',
'target' => $provider->getService()->getLinkTarget($record->getUrl()),
]) ?>
<span class="search-provider-record-image"><?= $record->getImage() ?></span>
<span class="search-provider-record-text">
<span><?= $record->getTitle() ?></span>
<span><?= $record->getDescription() ?></span>
</span>
</a>
<?= Html::endTag('a') ?>
<?php endforeach; ?>
<?php else : ?>
<div class="search-provider-no-results"><?= Yii::t('base', 'No results') ?></div>
@ -38,6 +42,7 @@ use humhub\widgets\Button;
<?= Button::defaultType($provider->getAllResultsText())
->link($provider->getService()->getUrl())
->cssClass('search-provider-show-all')
->options(['target' => $provider->getService()->getLinkTarget()])
->loader(false) ?>
</div>
<?php endif; ?>

View File

@ -270,7 +270,7 @@ humhub.module('ui.picker', function (module, require, $) {
*/
Picker.prototype.templateResult = function (item) {
// If no item id is given the function was called for the search term.
if (!item.id) {
if (typeof(item.id) === 'undefined') {
return loader.set($('<div></div>'), {'css': {'padding': '4px'}});
}