Merge branch 'fix'
This commit is contained in:
Generated
+1
@@ -3,3 +3,4 @@
|
||||
!/codeStyles
|
||||
!/fileTemplates
|
||||
!/inspectionProfiles
|
||||
!misc.xml
|
||||
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PhpEntryPointsManager">
|
||||
<pattern value="\Espo\Controllers\*" member="*Action*" />
|
||||
<pattern value="\Espo\Modules\*\Controllers\*" member="*Action*" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -55,7 +55,20 @@ class ApplicationUser
|
||||
*/
|
||||
public function setupSystemUser(): void
|
||||
{
|
||||
$user = $this->entityManagerProxy->getEntityById(User::ENTITY_TYPE, self::SYSTEM_USER_ID);
|
||||
$user = $this->entityManagerProxy
|
||||
->getRDBRepository(User::ENTITY_TYPE)
|
||||
->select([
|
||||
'id',
|
||||
'name',
|
||||
'userName',
|
||||
'type',
|
||||
'isActive',
|
||||
'firstName',
|
||||
'lastName',
|
||||
'deleted',
|
||||
])
|
||||
->where(['id' => self::SYSTEM_USER_ID])
|
||||
->findOne();
|
||||
|
||||
if (!$user) {
|
||||
throw new RuntimeException("System user is not found.");
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"providerClassName": "Espo\\Modules\\Crm\\Tools\\Activities\\PopupNotificationsProvider",
|
||||
"serviceName": "Activities",
|
||||
"methodName": "getPopupNotifications",
|
||||
"interval": 15,
|
||||
"useWebSocket": true,
|
||||
"portalDisabled": true,
|
||||
"view": "crm:views/meeting/popup-notification"
|
||||
|
||||
@@ -29,6 +29,19 @@
|
||||
|
||||
/** @todo Remove in v8.0. */
|
||||
|
||||
class_alias('Laminas\\Mail\\Protocol\\Smtp', 'Zend\\Mail\\Protocol\\Smtp');
|
||||
class_alias('Laminas\\Mail\\Protocol\\Imap', 'Zend\\Mail\\Protocol\\Imap');
|
||||
class_alias('Laminas\\Mail\\Message', 'Zend\\Mail\\Message');
|
||||
$map = [
|
||||
['Laminas\\Mail\\Protocol\\Smtp', 'Zend\\Mail\\Protocol\\Smtp'],
|
||||
['Laminas\\Mail\\Protocol\\Imap', 'Zend\\Mail\\Protocol\\Imap'],
|
||||
['Laminas\\Mail\\Message', 'Zend\\Mail\\Message'],
|
||||
];
|
||||
|
||||
foreach ($map as $item) {
|
||||
$className = $item[0];
|
||||
$alias = $item[1];
|
||||
|
||||
if (!class_exists($className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
class_alias($className, $alias);
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ return [
|
||||
'emailNotificationsDelay' => 30,
|
||||
'emailMessageMaxSize' => 10,
|
||||
'notificationsCheckInterval' => 10,
|
||||
'popupNotificationsCheckInterval' => 15,
|
||||
'maxEmailAccountCount' => 2,
|
||||
'followCreatedEntities' => false,
|
||||
'b2cMode' => false,
|
||||
|
||||
@@ -85,7 +85,10 @@
|
||||
},
|
||||
"User": {
|
||||
"dashboardTemplate": false,
|
||||
"workingTimeCalendar": false,
|
||||
"workingTimeCalendar": {
|
||||
"read": "yes",
|
||||
"edit": "no"
|
||||
},
|
||||
"password": false,
|
||||
"passwordConfirm": false,
|
||||
"auth2FA": false,
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"nonAdminReadOnly": true
|
||||
},
|
||||
"workingTimeCalendar": {
|
||||
"onlyAdmin": true
|
||||
"nonAdminReadOnly": true
|
||||
},
|
||||
"accounts": {
|
||||
"nonAdminReadOnly": true
|
||||
|
||||
@@ -441,7 +441,8 @@
|
||||
},
|
||||
"workingTimeCalendar": {
|
||||
"type": "belongsTo",
|
||||
"entity": "WorkingTimeCalendar"
|
||||
"entity": "WorkingTimeCalendar",
|
||||
"noJoin": true
|
||||
},
|
||||
"workingTimeRanges": {
|
||||
"type": "hasMany",
|
||||
|
||||
@@ -156,6 +156,7 @@ define('views/email/list', ['views/list'], function (Dep) {
|
||||
let $row = $container.find(`.list-row[data-id="${m.id}"]`).first();
|
||||
|
||||
$row.draggable({
|
||||
cancel: 'input,textarea,button,select,option,.dropdown-menu',
|
||||
helper: () => {
|
||||
let text = this.translate('Moving to Folder', 'labels', 'Email');
|
||||
|
||||
|
||||
@@ -33,8 +33,10 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
template: 'notification/badge',
|
||||
|
||||
notificationsCheckInterval: 10,
|
||||
groupedCheckInterval: 15,
|
||||
|
||||
timeout: null,
|
||||
groupedTimeout: null,
|
||||
|
||||
popupNotificationsData: null,
|
||||
|
||||
@@ -58,6 +60,10 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
if (this.groupedTimeout) {
|
||||
clearTimeout(this.groupedTimeout);
|
||||
}
|
||||
|
||||
for (let name in this.popupTimeouts) {
|
||||
clearTimeout(this.popupTimeouts[name]);
|
||||
}
|
||||
@@ -69,6 +75,9 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
this.notificationsCheckInterval = this.getConfig().get('notificationsCheckInterval') ||
|
||||
this.notificationsCheckInterval;
|
||||
|
||||
this.groupedCheckInterval = this.getConfig().get('popupNotificationsCheckInterval') ||
|
||||
this.groupedCheckInterval;
|
||||
|
||||
this.lastId = 0;
|
||||
this.shownNotificationIds = [];
|
||||
this.closedNotificationIds = [];
|
||||
@@ -80,10 +89,10 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
|
||||
window.addEventListener('storage', e => {
|
||||
if (e.key === 'messageClosePopupNotificationId') {
|
||||
var id = localStorage.getItem('messageClosePopupNotificationId');
|
||||
let id = localStorage.getItem('messageClosePopupNotificationId');
|
||||
|
||||
if (id) {
|
||||
var key = 'popup-' + id;
|
||||
let key = 'popup-' + id;
|
||||
|
||||
if (this.hasView(key)) {
|
||||
this.markPopupRemoved(id);
|
||||
@@ -105,7 +114,6 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
|
||||
afterRender: function () {
|
||||
this.$badge = this.$el.find('.notifications-button');
|
||||
this.$icon = this.$el.find('.notifications-button .icon');
|
||||
this.$number = this.$el.find('.number-badge');
|
||||
|
||||
this.runCheckUpdates(true);
|
||||
@@ -119,14 +127,16 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
.appendTo('body');
|
||||
}
|
||||
|
||||
var popupNotificationsData = this.popupNotificationsData =
|
||||
let popupNotificationsData = this.popupNotificationsData =
|
||||
this.getMetadata().get('app.popupNotifications') || {};
|
||||
|
||||
for (var name in popupNotificationsData) {
|
||||
for (let name in popupNotificationsData) {
|
||||
this.checkPopupNotifications(name);
|
||||
}
|
||||
|
||||
this.checkGroupedPopupNotifications();
|
||||
if (this.hasGroupedPopupNotifications()) {
|
||||
this.checkGroupedPopupNotifications();
|
||||
}
|
||||
},
|
||||
|
||||
playSound: function () {
|
||||
@@ -186,6 +196,8 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
checkUpdates: function (isFirstCheck) {
|
||||
@@ -197,7 +209,7 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
.getRequest('Notification/action/notReadCount')
|
||||
.then(count => {
|
||||
if (!isFirstCheck && count > this.unreadCount) {
|
||||
var messageBlockPlayNotificationSound =
|
||||
let messageBlockPlayNotificationSound =
|
||||
localStorage.getItem('messageBlockPlayNotificationSound');
|
||||
|
||||
if (!messageBlockPlayNotificationSound) {
|
||||
@@ -240,45 +252,57 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
);
|
||||
},
|
||||
|
||||
checkGroupedPopupNotifications: function () {
|
||||
var toCheck = false;
|
||||
|
||||
for (var name in this.popupNotificationsData) {
|
||||
var data = this.popupNotificationsData[name] || {};
|
||||
/**
|
||||
* @private
|
||||
* @return {boolean}
|
||||
*/
|
||||
hasGroupedPopupNotifications: function () {
|
||||
for (let name in this.popupNotificationsData) {
|
||||
let data = this.popupNotificationsData[name] || {};
|
||||
|
||||
if (!data.grouped) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data.portalDisabled && this.getUser().isPortal()) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
toCheck = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!toCheck) {
|
||||
return;
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
checkGroupedPopupNotifications: function () {
|
||||
if (!this.checkBypass()) {
|
||||
Espo.Ajax.getRequest('PopupNotification/action/grouped')
|
||||
.then(result => {
|
||||
for (let type in result) {
|
||||
let list = result[type];
|
||||
|
||||
list.forEach(item => this.showPopupNotification(type, item));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Espo.Ajax.getRequest('PopupNotification/action/grouped')
|
||||
.then(result => {
|
||||
for (const type in result) {
|
||||
const list = result[type];
|
||||
|
||||
list.forEach(item => this.showPopupNotification(type, item));
|
||||
}
|
||||
});
|
||||
this.groupedTimeout = setTimeout(
|
||||
() => this.checkGroupedPopupNotifications(),
|
||||
this.groupedCheckInterval * 1000
|
||||
);
|
||||
},
|
||||
|
||||
checkPopupNotifications: function (name, isNotFirstCheck) {
|
||||
var data = this.popupNotificationsData[name] || {};
|
||||
let data = this.popupNotificationsData[name] || {};
|
||||
|
||||
var url = data.url;
|
||||
var interval = data.interval;
|
||||
var disabled = data.disabled || false;
|
||||
let url = data.url;
|
||||
let interval = data.interval;
|
||||
let disabled = data.disabled || false;
|
||||
|
||||
var isFirstCheck = !isNotFirstCheck;
|
||||
let isFirstCheck = !isNotFirstCheck;
|
||||
|
||||
if (disabled) {
|
||||
return;
|
||||
@@ -288,10 +312,10 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
return;
|
||||
}
|
||||
|
||||
var useWebSocket = this.useWebSocket && data.useWebSocket;
|
||||
let useWebSocket = this.useWebSocket && data.useWebSocket;
|
||||
|
||||
if (useWebSocket) {
|
||||
var category = 'popupNotifications.' + (data.webSocketCategory || name);
|
||||
let category = 'popupNotifications.' + (data.webSocketCategory || name);
|
||||
|
||||
this.getHelper().webSocketManager.subscribe(category, (c, response) => {
|
||||
if (!response.list) {
|
||||
@@ -356,19 +380,19 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
},
|
||||
|
||||
showPopupNotification: function (name, data, isNotFirstCheck) {
|
||||
var view = this.popupNotificationsData[name].view;
|
||||
let view = this.popupNotificationsData[name].view;
|
||||
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = data.id || null;
|
||||
let id = data.id || null;
|
||||
|
||||
if (id) {
|
||||
id = name + '_' + id;
|
||||
|
||||
if (~this.shownNotificationIds.indexOf(id)) {
|
||||
var notificationView = this.getView('popup-' + id);
|
||||
let notificationView = this.getView('popup-' + id);
|
||||
|
||||
if (notificationView) {
|
||||
notificationView.trigger('update-data', data.data);
|
||||
@@ -406,7 +430,7 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
},
|
||||
|
||||
markPopupRemoved: function (id) {
|
||||
var index = this.shownNotificationIds.indexOf(id);
|
||||
let index = this.shownNotificationIds.indexOf(id);
|
||||
|
||||
if (index > -1) {
|
||||
this.shownNotificationIds.splice(index, 1);
|
||||
@@ -437,7 +461,7 @@ define('views/notification/badge', ['view'], function (Dep) {
|
||||
showNotifications: function () {
|
||||
this.closeNotifications();
|
||||
|
||||
var $container = $('<div>').attr('id', 'notifications-panel');
|
||||
let $container = $('<div>').attr('id', 'notifications-panel');
|
||||
|
||||
$container.appendTo(this.$el.find('.notifications-panel-container'));
|
||||
|
||||
|
||||
@@ -192,6 +192,16 @@ define('views/user/record/edit', ['views/record/edit', 'views/user/record/detail
|
||||
});
|
||||
}
|
||||
|
||||
if (this.model.isAdmin() || this.model.isRegular()) {
|
||||
layout.push({
|
||||
"label": "Misc",
|
||||
"name": "misc",
|
||||
"rows": [
|
||||
[{"name": "workingTimeCalendar"}, false]
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (this.type == 'edit' && this.getUser().isAdmin() && !this.model.isApi()) {
|
||||
layout.push({
|
||||
label: 'Password',
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "7.3.0",
|
||||
"version": "7.3.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "7.3.0",
|
||||
"version": "7.3.1",
|
||||
"description": "Open-source CRM.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
@@ -32,11 +32,14 @@ use Espo\Entities\EmailAccount;
|
||||
use Espo\Entities\User;
|
||||
use Espo\Entities\Preferences;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Database\Helper as DatabaseHelper;
|
||||
|
||||
class BeforeUpgrade
|
||||
{
|
||||
public function run(Container $container): void
|
||||
{
|
||||
$this->addUserColumn($container->get('config'));
|
||||
$this->migrateSmtp($container->get('entityManager'));
|
||||
}
|
||||
|
||||
@@ -121,4 +124,21 @@ class BeforeUpgrade
|
||||
|
||||
$entityManager->saveEntity($preferences);
|
||||
}
|
||||
|
||||
private function addUserColumn(Config $config)
|
||||
{
|
||||
$databaseHelper = new DatabaseHelper($config);
|
||||
|
||||
$pdo = $databaseHelper->createPdoConnection();
|
||||
|
||||
$query = "
|
||||
ALTER TABLE `user` ADD `working_time_calendar_id` VARCHAR(24)
|
||||
DEFAULT NULL COLLATE `utf8mb4_unicode_ci`
|
||||
";
|
||||
|
||||
try {
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user