diff --git a/application/Espo/Core/Export/Xlsx.php b/application/Espo/Core/Export/Xlsx.php
index a2b4a9fa37..2ea6e4bf0b 100644
--- a/application/Espo/Core/Export/Xlsx.php
+++ b/application/Espo/Core/Export/Xlsx.php
@@ -249,6 +249,14 @@ class Xlsx extends \Espo\Core\Injectable
$type = $this->getInjection('metadata')->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
}
}
+ if ($type === 'foreign') {
+ $linkName = $this->getInjection('metadata')->get(['entityDefs', $entityType, 'fields', $name, 'link']);
+ $foreignField = $this->getInjection('metadata')->get(['entityDefs', $entityType, 'fields', $name, 'field']);
+ $foreignScope = $this->getInjection('metadata')->get(['entityDefs', $entityType, 'links', $linkName, 'entity']);
+ if ($foreignScope) {
+ $type = $this->getInjection('metadata')->get(['entityDefs', $foreignScope, 'fields', $foreignField, 'type'], $type);
+ }
+ }
$typesCache[$name] = $type;
$link = null;
@@ -304,8 +312,29 @@ class Xlsx extends \Espo\Core\Injectable
$sheet->setCellValue("$col$rowNumber", \PHPExcel_Shared_Date::PHPToExcel(strtotime($row[$name])));
}
} else if ($type == 'datetime' || $type == 'datetimeOptional') {
- if (isset($row[$name])) {
- $sheet->setCellValue("$col$rowNumber", \PHPExcel_Shared_Date::PHPToExcel(strtotime($row[$name])));
+ $value = null;
+ if ($type == 'datetimeOptional') {
+ if (isset($row[$name . 'Date']) && $row[$name . 'Date']) {
+ $value = $row[$name . 'Date'];
+ }
+ }
+ if (!$value) {
+ if (isset($row[$name])) {
+ $value = $row[$name];
+ }
+ }
+ if ($value && strlen($value) > 11) {
+ try {
+ $timeZone = $this->getInjection('config')->get('timeZone');
+ $dt = new \DateTime($value);
+ $dt->setTimezone(new \DateTimeZone($timeZone));
+ $value = $dt->format($this->getInjection('dateTime')->getInternalDateTimeFormat());
+ } catch (\Exception $e) {
+ $value = '';
+ }
+ }
+ if ($value) {
+ $sheet->setCellValue("$col$rowNumber", \PHPExcel_Shared_Date::PHPToExcel(strtotime($value)));
}
} else if ($type == 'image') {
if (isset($row[$name . 'Id']) && $row[$name . 'Id']) {
@@ -394,11 +423,9 @@ class Xlsx extends \Espo\Core\Injectable
foreach ($fieldList as $i => $name) {
$col = $azRange[$i];
-
$type = $typesCache[$name];
-
- switch($type) {
+ switch ($type) {
case 'currency':
case 'currencyConverted': {
diff --git a/application/Espo/Core/Formula/Functions/ArrayGroup/PushType.php b/application/Espo/Core/Formula/Functions/ArrayGroup/PushType.php
new file mode 100644
index 0000000000..a9ab3732de
--- /dev/null
+++ b/application/Espo/Core/Formula/Functions/ArrayGroup/PushType.php
@@ -0,0 +1,57 @@
+value)) {
+ throw new Error('Bad \'Array\\push\' definition.');
+ }
+ if (count($item->value) < 2) {
+ throw new Error('Bad arguments passed to \'Array\\push\'.');
+ }
+ $list = $this->evaluate($item->value[0]);
+ if (!is_array($list)) {
+ return false;
+ }
+
+ foreach ($item->value as $i => $v) {
+ if ($i === 0) continue;
+ $element = $this->evaluate($item->value[$i]);
+ $list[] = $element;
+ }
+
+ return $list;
+ }
+}
\ No newline at end of file
diff --git a/application/Espo/Core/HookManager.php b/application/Espo/Core/HookManager.php
index 7470a99bef..8be8f89a1f 100644
--- a/application/Espo/Core/HookManager.php
+++ b/application/Espo/Core/HookManager.php
@@ -37,6 +37,8 @@ class HookManager
private $data;
+ private $hookListHash = array();
+
private $hooks;
protected $cacheFile = 'data/cache/application/hooks.php';
@@ -105,18 +107,10 @@ class HookManager
$this->loadHooks();
}
- $hookList = array();
+ $hookList = $this->getHookList($scope, $hookName);
- if (isset($this->data['Common'])) {
- $hookList = $this->data['Common'];
- }
-
- if (isset($this->data[$scope])) {
- $hookList = $this->mergeHooks($hookList, $this->data[$scope]);
- }
-
- if (!empty($hookList[$hookName])) {
- foreach ($hookList[$hookName] as $className => $classOrder) {
+ if (!empty($hookList)) {
+ foreach ($hookList as $className) {
if (empty($this->hooks[$className])) {
$this->hooks[$className] = $this->createHookByClassName($className);
if (empty($this->hooks[$className])) continue;
@@ -174,8 +168,11 @@ class HookManager
foreach($hookMethods as $hookType) {
$entityHookData = isset($hookData[$normalizedScopeName][$hookType]) ? $hookData[$normalizedScopeName][$hookType] : array();
- if (!$this->isHookExists($className, $entityHookData)) {
- $hookData[$normalizedScopeName][$hookType][$className] = $className::$order;
+ if (!$this->hookExists($className, $entityHookData)) {
+ $hookData[$normalizedScopeName][$hookType][] = array(
+ 'className' => $className,
+ 'order' => $className::$order
+ );
}
}
}
@@ -198,7 +195,7 @@ class HookManager
{
foreach ($hooks as $scopeName => &$scopeHooks) {
foreach ($scopeHooks as $hookName => &$hookList) {
- asort($hookList);
+ usort($hookList, array($this, 'cmpHooks'));
}
}
@@ -206,21 +203,38 @@ class HookManager
}
/**
- * Merge hooks for two entities
+ * Get sorted hook list
*
- * @param array $hookList1
- * @param array $hookList2
+ * @param string $scope
+ * @param string $hookName
*
* @return array
*/
- protected function mergeHooks(array $hookList1, array $hookList2)
+ protected function getHookList($scope, $hookName)
{
- $mergedHookList = array_merge_recursive($hookList1, $hookList2);
- foreach ($mergedHookList as $hookType => &$hookList) {
- asort($hookList);
+ $key = $scope . '_' . $hookName;
+
+ if (!isset($this->hookListHash[$key])) {
+ $hookList = array();
+
+ if (isset($this->data['Common'][$hookName])) {
+ $hookList = $this->data['Common'][$hookName];
+ }
+
+ if (isset($this->data[$scope][$hookName])) {
+ $hookList = array_merge($hookList, $this->data[$scope][$hookName]);
+ usort($hookList, array($this, 'cmpHooks'));
+ }
+
+ $normalizedList = array();
+ foreach ($hookList as $hookData) {
+ $normalizedList[] = $hookData['className'];
+ }
+
+ $this->hookListHash[$key] = $normalizedList;
}
- return $mergedHookList;
+ return $this->hookListHash[$key];
}
/**
@@ -231,16 +245,25 @@ class HookManager
*
* @return boolean
*/
- protected function isHookExists($className, array $hookData)
+ protected function hookExists($className, array $hookData)
{
$class = preg_replace('/^.*\\\(.*)$/', '$1', $className);
- foreach ($hookData as $hookName => $hookOrder) {
- if (preg_match('/\\\\'.$class.'$/', $hookName)) {
+ foreach ($hookData as $hookData) {
+ if (preg_match('/\\\\'.$class.'$/', $hookData['className'])) {
return true;
}
}
return false;
}
+
+ protected function cmpHooks($a, $b)
+ {
+ if ($a['order'] == $b['order']) {
+ return 0;
+ }
+
+ return ($a['order'] < $b['order']) ? -1 : 1;
+ }
}
\ No newline at end of file
diff --git a/application/Espo/Core/Mail/Parsers/MailMimeParser.php b/application/Espo/Core/Mail/Parsers/MailMimeParser.php
index f802d372e4..c4b745cac7 100644
--- a/application/Espo/Core/Mail/Parsers/MailMimeParser.php
+++ b/application/Espo/Core/Mail/Parsers/MailMimeParser.php
@@ -147,8 +147,30 @@ class MailMimeParser
{
$this->loadContent($message);
- $bodyPlain = $this->getMessage($message)->getTextContent();
- $bodyHtml = $this->getMessage($message)->getHtmlContent();
+ $bodyPlain = '';
+ $bodyHtml = '';
+
+ $htmlPartCount = $this->getMessage($message)->getHtmlPartCount();
+ $textPartCount = $this->getMessage($message)->getTextPartCount();
+
+ if (!$htmlPartCount) {
+ $bodyHtml = $this->getMessage($message)->getHtmlContent();
+ }
+ if (!$textPartCount) {
+ $bodyPlain = $this->getMessage($message)->getTextContent();
+ }
+
+ for ($i = 0; $i < $htmlPartCount; $i++) {
+ if ($i) $bodyHtml .= "
";
+ $inlinePart = $this->getMessage($message)->getHtmlPart($i);
+ $bodyHtml .= $inlinePart->getContent();
+ }
+
+ for ($i = 0; $i < $textPartCount; $i++) {
+ if ($i) $bodyPlain .= "\n";
+ $inlinePart = $this->getMessage($message)->getTextPart($i);
+ $bodyPlain .= $inlinePart->getContent();
+ }
if ($bodyHtml) {
$email->set('isHtml', true);
@@ -174,7 +196,12 @@ class MailMimeParser
$disposition = $attachmentObj->getHeaderValue('Content-Disposition');
$attachment = $this->getEntityManager()->getEntity('Attachment');
- $attachment->set('name', $attachmentObj->getHeaderParameter('Content-Disposition', 'filename', 'unnamed'));
+
+ $filename = $attachmentObj->getHeaderParameter('Content-Disposition', 'filename', null);
+ if ($filename === null) {
+ $filename = $attachmentObj->getHeaderParameter('Content-Type', 'name', 'unnamed');
+ }
+ $attachment->set('name', $filename);
$attachment->set('type', $attachmentObj->getHeaderValue('Content-Type'));
$contentId = $attachmentObj->getHeaderValue('Content-ID');
@@ -186,6 +213,7 @@ class MailMimeParser
if ($disposition == 'inline') {
$attachment->set('role', 'Inline Attachment');
} else {
+ $disposition = 'attachment';
$attachment->set('role', 'Attachment');
}
@@ -201,8 +229,10 @@ class MailMimeParser
} else if ($disposition == 'inline') {
if ($contentId) {
$inlineIds[$contentId] = $attachment->id;
+ $inlineAttachmentList[] = $attachment;
+ } else {
+ $email->addLinkMultipleId('attachments', $attachment->id);
}
- $inlineAttachmentList[] = $attachment;
}
}
diff --git a/application/Espo/Core/Mail/Parsers/PhpMimeMailParser.php b/application/Espo/Core/Mail/Parsers/PhpMimeMailParser.php
index 40f076fd62..3716fe8eda 100644
--- a/application/Espo/Core/Mail/Parsers/PhpMimeMailParser.php
+++ b/application/Espo/Core/Mail/Parsers/PhpMimeMailParser.php
@@ -137,8 +137,26 @@ class PhpMimeMailParser
{
$this->loadContent($message);
- $bodyPlain = $this->getParser($message)->getMessageBody('text');
- $bodyHtml = $this->getParser($message)->getMessageBody('html');
+ $bodyPlain = '';
+ $bodyHtml = '';
+
+ $inlinePartTextList = $this->getParser($message)->getInlineParts('text');
+ $inlinePartHtmlList = $this->getParser($message)->getInlineParts('html');
+ if (empty($inlinePartTextList)) {
+ $bodyPlain = $this->getParser($message)->getMessageBody('text');
+ }
+ if (empty($inlinePartHtmlList)) {
+ $bodyHtml = $this->getParser($message)->getMessageBody('html');
+ }
+
+ foreach ($inlinePartTextList as $i => $inlinePart) {
+ if ($i) $bodyPlain .= "\n";
+ $bodyPlain .= $inlinePart;
+ }
+ foreach ($inlinePartHtmlList as $i => $inlinePart) {
+ if ($i) $bodyHtml .= "
";
+ $bodyHtml .= $inlinePart;
+ }
if ($bodyHtml) {
$email->set('isHtml', true);
@@ -186,8 +204,10 @@ class PhpMimeMailParser
} else if ($disposition == 'inline') {
if ($contentId) {
$inlineIds[$contentId] = $attachment->id;
+ $inlineAttachmentList[] = $attachment;
+ } else {
+ $email->addLinkMultipleId('attachments', $attachment->id);
}
- $inlineAttachmentList[] = $attachment;
}
}
diff --git a/application/Espo/Core/Utils/AdminNotificationManager.php b/application/Espo/Core/Utils/AdminNotificationManager.php
index 236ec7dba3..084b258ed8 100644
--- a/application/Espo/Core/Utils/AdminNotificationManager.php
+++ b/application/Espo/Core/Utils/AdminNotificationManager.php
@@ -65,7 +65,7 @@ class AdminNotificationManager
{
$notificationList = [];
- if ($this->getConfig()->get('adminNotificationsDisabled')) {
+ if (!$this->getConfig()->get('adminNotifications')) {
return [];
}
diff --git a/application/Espo/Core/defaults/config.php b/application/Espo/Core/defaults/config.php
index 3a65bc1573..8e1fb7999b 100644
--- a/application/Espo/Core/defaults/config.php
+++ b/application/Espo/Core/defaults/config.php
@@ -104,6 +104,7 @@ return array (
'tabList' => ["Account", "Contact", "Lead", "Opportunity", "Case", "Email", "Calendar", "Meeting", "Call", "Task", "_delimiter_", "Document", "Campaign", "KnowledgeBaseArticle", "Stream", "User"],
'quickCreateList' => ["Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case", "Email"],
'exportDisabled' => false,
+ 'adminNotifications' => true,
'assignmentEmailNotifications' => false,
'assignmentEmailNotificationsEntityList' => ['Lead', 'Opportunity', 'Task', 'Case'],
'assignmentNotificationsEntityList' => ['Meeting', 'Call', 'Task', 'Email'],
diff --git a/application/Espo/Entities/User.php b/application/Espo/Entities/User.php
index 5eae30659b..bad4f58ef4 100644
--- a/application/Espo/Entities/User.php
+++ b/application/Espo/Entities/User.php
@@ -36,11 +36,21 @@ class User extends \Espo\Core\Entities\Person
return $this->get('isAdmin');
}
+ public function isSystem()
+ {
+ return $this->id === 'system';
+ }
+
public function isActive()
{
return $this->get('isActive');
}
+ public function isPortal()
+ {
+ return $this->isPortalUser();
+ }
+
public function isPortalUser()
{
return $this->get('isPortalUser');
diff --git a/application/Espo/EntryPoints/Portal.php b/application/Espo/EntryPoints/Portal.php
index 91a188322e..5f5102c47a 100644
--- a/application/Espo/EntryPoints/Portal.php
+++ b/application/Espo/EntryPoints/Portal.php
@@ -44,9 +44,14 @@ class Portal extends \Espo\Core\EntryPoints\Base
} else if (!empty($data['id'])) {
$id = $data['id'];
} else {
- $url = !empty($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['REQUEST_URI'];
-
+ $url = $_SERVER['REQUEST_URI'];
$id = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
+
+ if (!isset($id)) {
+ $url = $_SERVER['REDIRECT_URL'];
+ $id = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
+ }
+
if (!$id) {
$id = $this->getConfig()->get('defaultPortalId');
}
diff --git a/application/Espo/Hooks/Common/Formula.php b/application/Espo/Hooks/Common/Formula.php
index de3863d760..b7efff899f 100644
--- a/application/Espo/Hooks/Common/Formula.php
+++ b/application/Espo/Hooks/Common/Formula.php
@@ -34,7 +34,7 @@ use Espo\Core\Utils\Util;
class Formula extends \Espo\Core\Hooks\Base
{
- public static $order = 5;
+ public static $order = 11;
protected function init()
{
diff --git a/application/Espo/Hooks/Common/NextNumber.php b/application/Espo/Hooks/Common/NextNumber.php
index c9e48e5ceb..13646169b6 100644
--- a/application/Espo/Hooks/Common/NextNumber.php
+++ b/application/Espo/Hooks/Common/NextNumber.php
@@ -34,7 +34,7 @@ use Espo\Core\Utils\Util;
class NextNumber extends \Espo\Core\Hooks\Base
{
- public static $order = 10;
+ public static $order = 9;
protected function init()
{
diff --git a/application/Espo/Hooks/Common/Stream.php b/application/Espo/Hooks/Common/Stream.php
index 39aed27732..c6dcb862bf 100644
--- a/application/Espo/Hooks/Common/Stream.php
+++ b/application/Espo/Hooks/Common/Stream.php
@@ -56,6 +56,11 @@ class Stream extends \Espo\Core\Hooks\Base
return $this->getInjection('serviceFactory');
}
+ protected function getPreferences()
+ {
+ return $this->getInjection('container')->get('preferences');
+ }
+
protected function checkHasStream(Entity $entity)
{
$entityType = $entity->getEntityType();
@@ -185,9 +190,23 @@ class Stream extends \Espo\Core\Hooks\Base
$createdById = $entity->get('createdById');
if (
- ($this->getConfig()->get('followCreatedEntities') || $this->getUser()->get('isPortalUser'))
+ !$this->getUser()->isSystem()
&&
- !empty($createdById)
+ $createdById
+ &&
+ $createdById === $this->getUser()->id
+ &&
+ (
+ $this->getUser()->isPortalUser()
+ ||
+ $this->getPreferences()->get('followCreatedEntities')
+ ||
+ (
+ is_array($this->getPreferences()->get('followCreatedEntityTypeList'))
+ &&
+ in_array($entityType, $this->getPreferences()->get('followCreatedEntityTypeList'))
+ )
+ )
) {
$userIdList[] = $createdById;
}
diff --git a/application/Espo/Modules/Crm/Entities/Lead.php b/application/Espo/Modules/Crm/Entities/Lead.php
index 8ed65c1a37..6634a7c947 100644
--- a/application/Espo/Modules/Crm/Entities/Lead.php
+++ b/application/Espo/Modules/Crm/Entities/Lead.php
@@ -25,12 +25,25 @@
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
- ************************************************************************/
+ ************************************************************************/
namespace Espo\Modules\Crm\Entities;
class Lead extends \Espo\Core\Entities\Person
{
+ protected function _getName()
+ {
+ if (!array_key_exists('name', $this->valuesContainer) || !$this->valuesContainer['name']) {
+ if ($this->get('accountName')) {
+ return $this->get('accountName');
+ } else if ($this->get('emailAddress')) {
+ return $this->get('emailAddress');
+ } else if ($this->get('phoneNumber')) {
+ return $this->get('phoneNumber');
+ }
+ }
+ return $this->valuesContainer['name'];
+ }
}
diff --git a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Lead.json b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Lead.json
index 7b8614c83a..c57b1e14db 100644
--- a/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Lead.json
+++ b/application/Espo/Modules/Crm/Resources/metadata/clientDefs/Lead.json
@@ -122,5 +122,27 @@
"style": "success"
}
],
- "boolFilterList": ["onlyMy"]
+ "boolFilterList": ["onlyMy"],
+ "dynamicLogic": {
+ "fields": {
+ "name": {
+ "required": {
+ "conditionGroup": [
+ {
+ "type": "isEmpty",
+ "attribute": "accountName"
+ },
+ {
+ "type": "isEmpty",
+ "attribute": "emailAddress"
+ },
+ {
+ "type": "isEmpty",
+ "attribute": "phoneNumber"
+ }
+ ]
+ }
+ }
+ }
+ }
}
diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json
index 4ec060913b..115b734c92 100644
--- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json
+++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json
@@ -15,7 +15,6 @@
"lastName": {
"type": "varchar",
"maxLength": 100,
- "required": true,
"default":""
},
"title": {
diff --git a/application/Espo/Repositories/Preferences.php b/application/Espo/Repositories/Preferences.php
index 2a5c0997e0..ff0d1d34f3 100644
--- a/application/Espo/Repositories/Preferences.php
+++ b/application/Espo/Repositories/Preferences.php
@@ -34,11 +34,12 @@ use Espo\Core\Utils\Json;
class Preferences extends \Espo\Core\ORM\Repository
{
- protected $defaultAttributeListFromSettings = array(
+ protected $defaultAttributeListFromSettings = [
'decimalMark',
'thousandSeparator',
- 'exportDelimiter'
- );
+ 'exportDelimiter',
+ 'followCreatedEntities'
+ ];
protected $data = array();
diff --git a/application/Espo/Resources/i18n/de_DE/Preferences.json b/application/Espo/Resources/i18n/de_DE/Preferences.json
index 2f43c877ad..fd13916561 100644
--- a/application/Espo/Resources/i18n/de_DE/Preferences.json
+++ b/application/Espo/Resources/i18n/de_DE/Preferences.json
@@ -31,7 +31,8 @@
"emailReplyToAllByDefault": "Standardmäßig Allen antworten",
"dashboardLayout": "Dashboard Layout",
"emailReplyForceHtml": "E-Mail Antwort als HTML",
- "doNotFillAssignedUserIfNotRequired": "Zugwiesenen Benutzer nicht ausfüllen wenn optional"
+ "doNotFillAssignedUserIfNotRequired": "Zugwiesenen Benutzer nicht ausfüllen wenn optional",
+ "followCreatedEntities": "Eigene Einträge abonnieren"
},
"options": {
"weekStart": {
diff --git a/application/Espo/Resources/i18n/en_US/Preferences.json b/application/Espo/Resources/i18n/en_US/Preferences.json
index d68a28a9c4..7c61201c04 100644
--- a/application/Espo/Resources/i18n/en_US/Preferences.json
+++ b/application/Espo/Resources/i18n/en_US/Preferences.json
@@ -21,18 +21,20 @@
"receiveAssignmentEmailNotifications": "Email notifications upon assignment",
"receiveMentionEmailNotifications": "Email notifications about mentions in posts",
"receiveStreamEmailNotifications": "Email notifications about posts and status updates",
- "autoFollowEntityTypeList": "Auto-Follow",
+ "autoFollowEntityTypeList": "Global Auto-Follow",
"signature": "Email Signature",
"dashboardTabList": "Tab List",
"defaultReminders": "Default Reminders",
"theme": "Theme",
"useCustomTabList": "Custom Tab List",
"tabList": "Tab List",
- "emailReplyToAllByDefault": "Email Reply to All by Default",
+ "emailReplyToAllByDefault": "Email Reply to all by default",
"dashboardLayout": "Dashboard Layout",
"emailReplyForceHtml": "Email Reply in HTML",
- "doNotFillAssignedUserIfNotRequired": "Do not fill Assigned User if not required",
- "followEntityOnStreamPost": "Auto-follow entity after posting in Stream"
+ "doNotFillAssignedUserIfNotRequired": "Do not pre-fill assigned user on record creation",
+ "followEntityOnStreamPost": "Auto-follow record after posting in Stream",
+ "followCreatedEntities": "Auto-follow created records",
+ "followCreatedEntityTypeList": "Auto-follow created records of specific entity types"
},
"links": {
},
@@ -50,6 +52,9 @@
"Locale": "Locale"
},
"tooltips": {
- "autoFollowEntityTypeList": "User will automatically follow all new records of the selected entity types, will see information in the stream and receive notifications."
+ "autoFollowEntityTypeList": "Automatically follow ALL new records (created by any user) of the selected entity types. To be able to see information in the stream and receive notifications about all records in the system.",
+ "doNotFillAssignedUserIfNotRequired": "When create record assigned user won't be filled with own user unless the field is required.",
+ "followCreatedEntities": "When create new records they will be automatically followed even if assigned to another user.",
+ "followCreatedEntityTypeList": "When create new records of selected entity types they will be followed automatically even if assigned to another user."
}
}
diff --git a/application/Espo/Resources/i18n/en_US/Settings.json b/application/Espo/Resources/i18n/en_US/Settings.json
index 95bbf753e5..e355f9acb6 100644
--- a/application/Espo/Resources/i18n/en_US/Settings.json
+++ b/application/Espo/Resources/i18n/en_US/Settings.json
@@ -63,7 +63,7 @@
"streamEmailNotificationsEntityList": "Stream email notifications scopes",
"b2cMode": "B2C Mode",
"avatarsDisabled": "Disable Avatars",
- "followCreatedEntities": "Follow Created Entities",
+ "followCreatedEntities": "Follow created records",
"displayListViewRecordCount": "Display Total Count (on List View)",
"theme": "Theme",
"userThemesDisabled": "Disable User Themes",
@@ -89,7 +89,8 @@
"currencyFormat": "Currency Format",
"currencyDecimalPlaces": "Currency Decimal Places",
"aclStrictMode": "ACL Strict Mode",
- "aclAllowDeleteCreated": "Allow to remove created records"
+ "aclAllowDeleteCreated": "Allow to remove created records",
+ "adminNotifications": "System notifications in administration panel"
},
"options": {
"weekStart": {
@@ -148,7 +149,8 @@
"Mass Email": "Mass Email",
"Test Connection": "Test Connection",
"Connecting": "Connecting...",
- "Activities": "Activities"
+ "Activities": "Activities",
+ "Admin Notifications": "Admin Notifications"
},
"messages": {
"ldapTestConnection": "The connection successfully established."
diff --git a/application/Espo/Resources/layouts/Preferences/detail.json b/application/Espo/Resources/layouts/Preferences/detail.json
index da8e45b3ca..c292fbe0c3 100644
--- a/application/Espo/Resources/layouts/Preferences/detail.json
+++ b/application/Espo/Resources/layouts/Preferences/detail.json
@@ -59,6 +59,18 @@
{
"name": "followEntityOnStreamPost"
}
+ ],
+ [
+ false,
+ {
+ "name": "followCreatedEntities"
+ }
+ ],
+ [
+ false,
+ {
+ "name": "followCreatedEntityTypeList"
+ }
]
]
},
diff --git a/application/Espo/Resources/layouts/Settings/notifications.json b/application/Espo/Resources/layouts/Settings/notifications.json
index 6df89d7cbf..882fbb6cab 100644
--- a/application/Espo/Resources/layouts/Settings/notifications.json
+++ b/application/Espo/Resources/layouts/Settings/notifications.json
@@ -2,17 +2,23 @@
{
"label": "In-app Notifications",
"rows": [
- [{"name": "assignmentNotificationsEntityList"}],
- [{"name": "notificationSoundsDisabled"}]
+ [{"name": "assignmentNotificationsEntityList"}, false],
+ [{"name": "notificationSoundsDisabled"}, false]
]
},
{
"label": "Email Notifications",
"rows": [
[{"name": "assignmentEmailNotifications"}, {"name": "mentionEmailNotifications"}],
- [{"name": "assignmentEmailNotificationsEntityList"}],
+ [{"name": "assignmentEmailNotificationsEntityList"}, false],
[{"name": "streamEmailNotifications"}, {"name": "portalStreamEmailNotifications"}],
- [{"name": "streamEmailNotificationsEntityList"}]
+ [{"name": "streamEmailNotificationsEntityList"}, false]
+ ]
+ },
+ {
+ "label": "Admin Notifications",
+ "rows": [
+ [{"name": "adminNotifications"}, false]
]
}
]
\ No newline at end of file
diff --git a/application/Espo/Resources/metadata/app/aclPortal.json b/application/Espo/Resources/metadata/app/aclPortal.json
index 08e0edb680..c4319e9bcb 100644
--- a/application/Espo/Resources/metadata/app/aclPortal.json
+++ b/application/Espo/Resources/metadata/app/aclPortal.json
@@ -60,7 +60,10 @@
"autoFollowEntityTypeList": false,
"emailReplyForceHtml": false,
"emailReplyToAllByDefault": false,
- "signature": false
+ "signature": false,
+ "followCreatedEntities": false,
+ "followEntityOnStreamPost": false,
+ "doNotFillAssignedUserIfNotRequired": false
},
"Call": {
"reminders": false
diff --git a/application/Espo/Resources/metadata/clientDefs/DynamicLogic.json b/application/Espo/Resources/metadata/clientDefs/DynamicLogic.json
index 15dfa5a2c3..9334299d35 100644
--- a/application/Espo/Resources/metadata/clientDefs/DynamicLogic.json
+++ b/application/Espo/Resources/metadata/clientDefs/DynamicLogic.json
@@ -98,6 +98,14 @@
"view": "views/admin/dynamic-logic/conditions/field-types/base",
"typeList": ["equals", "notEquals", "isEmpty", "isNotEmpty"]
},
+ "email": {
+ "view": "views/admin/dynamic-logic/conditions/field-types/base",
+ "typeList": ["isEmpty", "isNotEmpty"]
+ },
+ "phone": {
+ "view": "views/admin/dynamic-logic/conditions/field-types/base",
+ "typeList": ["isEmpty", "isNotEmpty"]
+ },
"text": {
"view": "views/admin/dynamic-logic/conditions/field-types/base",
"typeList": ["isEmpty", "isNotEmpty"]
diff --git a/application/Espo/Resources/metadata/entityDefs/Preferences.json b/application/Espo/Resources/metadata/entityDefs/Preferences.json
index aa5a236542..d1cafa0bfb 100644
--- a/application/Espo/Resources/metadata/entityDefs/Preferences.json
+++ b/application/Espo/Resources/metadata/entityDefs/Preferences.json
@@ -150,11 +150,22 @@
"notStorable": true
},
"doNotFillAssignedUserIfNotRequired": {
- "type": "bool"
+ "type": "bool",
+ "tooltip": true
},
"followEntityOnStreamPost": {
"type": "bool",
"default": true
+ },
+ "followCreatedEntities": {
+ "type": "bool",
+ "tooltip": true
+ },
+ "followCreatedEntityTypeList": {
+ "type": "multiEnum",
+ "view": "views/preferences/fields/auto-follow-entity-type-list",
+ "translation": "Global.scopeNamesPlural",
+ "tooltip": true
}
}
}
diff --git a/application/Espo/Resources/metadata/entityDefs/Settings.json b/application/Espo/Resources/metadata/entityDefs/Settings.json
index 37dfba1d00..004cd89ae2 100644
--- a/application/Espo/Resources/metadata/entityDefs/Settings.json
+++ b/application/Espo/Resources/metadata/entityDefs/Settings.json
@@ -433,6 +433,9 @@
"max": 200,
"default": 20,
"required": true
+ },
+ "adminNotifications": {
+ "type": "bool"
}
}
}
diff --git a/application/Espo/Services/Import.php b/application/Espo/Services/Import.php
index 4c8b482675..ce1576b557 100644
--- a/application/Espo/Services/Import.php
+++ b/application/Espo/Services/Import.php
@@ -463,7 +463,6 @@ class Import extends \Espo\Services\Record
$isNew = $entity->isNew();
-
if (!empty($params['defaultValues'])) {
if (is_object($params['defaultValues'])) {
$v = get_object_vars($params['defaultValues']);
@@ -581,8 +580,6 @@ class Import extends \Espo\Services\Record
$result = array();
- $a = $entity->toArray();
-
try {
if ($isNew) {
$isDuplicate = false;
@@ -646,7 +643,7 @@ class Import extends \Espo\Services\Record
return ['firstName' => $firstName, 'lastName' => $lastName];
}
- protected function parseValue(Entity $entity, $field, $value, $params = array())
+ protected function parseValue(Entity $entity, $attribute, $value, $params = array())
{
$decimalMark = '.';
if (!empty($params['decimalMark'])) {
@@ -672,45 +669,55 @@ class Import extends \Espo\Services\Record
}
}
- $fieldDefs = $entity->getFields();
+ $type = $entity->getAttributeType($attribute);
- if (!empty($fieldDefs[$field])) {
- $type = $fieldDefs[$field]['type'];
+ switch ($type) {
+ case Entity::DATE:
+ $dt = \DateTime::createFromFormat($dateFormat, $value);
+ if ($dt) {
+ return $dt->format('Y-m-d');
+ }
+ break;
+ case Entity::DATETIME:
+ $timezone = new \DateTimeZone(isset($params['timezone']) ? $params['timezone'] : 'UTC');
+ $dt = \DateTime::createFromFormat($dateFormat . ' ' . $timeFormat, $value, $timezone);
+ if ($dt) {
+ $dt->setTimezone(new \DateTimeZone('UTC'));
+ return $dt->format('Y-m-d H:i:s');
+ }
+ break;
+ case Entity::FLOAT:
+ $currencyAttribute = $attribute . 'Currency';
+ if ($entity->hasAttribute($currencyAttribute)) {
+ if (!$entity->has($currencyAttribute)) {
+ $entity->set($currencyAttribute, $defaultCurrency);
+ }
+ }
- switch ($type) {
- case Entity::DATE:
- $dt = \DateTime::createFromFormat($dateFormat, $value);
- if ($dt) {
- return $dt->format('Y-m-d');
- }
- break;
- case Entity::DATETIME:
- $timezone = new \DateTimeZone(isset($params['timezone']) ? $params['timezone'] : 'UTC');
- $dt = \DateTime::createFromFormat($dateFormat . ' ' . $timeFormat, $value, $timezone);
- if ($dt) {
- $dt->setTimezone(new \DateTimeZone('UTC'));
- return $dt->format('Y-m-d H:i:s');
- }
- break;
- case Entity::FLOAT:
- $currencyField = $field . 'Currency';
- if ($entity->hasField($currencyField)) {
- if (!$entity->has($currencyField)) {
- $entity->set($currencyField, $defaultCurrency);
- }
- }
+ $a = explode($decimalMark, $value);
+ $a[0] = preg_replace('/[^A-Za-z0-9\-]/', '', $a[0]);
- $a = explode($decimalMark, $value);
- $a[0] = preg_replace('/[^A-Za-z0-9\-]/', '', $a[0]);
-
- if (count($a) > 1) {
- return $a[0] . '.' . $a[1];
- } else {
- return $a[0];
- }
- break;
- }
+ if (count($a) > 1) {
+ return $a[0] . '.' . $a[1];
+ } else {
+ return $a[0];
+ }
+ break;
+ case Entity::JSON_OBJECT:
+ $value = \Espo\Core\Utils\Json::decode($value);
+ return $value;
+ case Entity::JSON_ARRAY:
+ if (!is_string($value)) return;
+ if (!strlen($value)) return;
+ if ($value[0] === '[') {
+ $value = \Espo\Core\Utils\Json::decode($value);
+ return $value;
+ } else {
+ $value = explode(',', $value);
+ return $value;
+ }
}
+
return $value;
}
diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php
index 57e3772696..ac4a8d82af 100644
--- a/application/Espo/Services/Record.php
+++ b/application/Espo/Services/Record.php
@@ -1387,10 +1387,6 @@ class Record extends \Espo\Core\Services\Base
}
foreach ($collection as $entity) {
- if (is_null($attributeList)) {
-
- }
-
$this->loadAdditionalFieldsForExport($entity);
if (method_exists($exportObj, 'loadAdditionalFields')) {
$exportObj->loadAdditionalFields($entity, $fieldList);
@@ -1447,19 +1443,23 @@ class Record extends \Espo\Core\Services\Base
throw new Error();
}
- protected function getAttributeFromEntityForExport(Entity $entity, $field)
+ protected function getAttributeFromEntityForExport(Entity $entity, $attribute)
{
- $methodName = 'getAttribute' . ucfirst($field). 'FromEntityForExport';
+ $methodName = 'getAttribute' . ucfirst($attribute). 'FromEntityForExport';
if (method_exists($this, $methodName)) {
return $this->$methodName($entity);
}
$defs = $entity->getAttributes();
- if (!empty($defs[$field]) && !empty($defs[$field]['type'])) {
- $type = $defs[$field]['type'];
+ if (!empty($defs[$attribute]) && !empty($defs[$attribute]['type'])) {
+ $type = $defs[$attribute]['type'];
switch ($type) {
+ case 'jsonObject':
+ $value = $entity->get($attribute);
+ return \Espo\Core\Utils\Json::encode($value);
+ break;
case 'jsonArray':
- $value = $entity->get($field);
+ $value = $entity->get($attribute);
if (is_array($value)) {
return implode(',', $value);
} else {
@@ -1471,7 +1471,7 @@ class Record extends \Espo\Core\Services\Base
break;
}
}
- return $entity->get($field);
+ return $entity->get($attribute);
}
public function prepareEntityForOutput(Entity $entity)
diff --git a/client/src/views/fields/base.js b/client/src/views/fields/base.js
index b05ed6d7f9..93d19ca2ed 100644
--- a/client/src/views/fields/base.js
+++ b/client/src/views/fields/base.js
@@ -262,13 +262,13 @@ Espo.define('views/fields/base', 'view', function (Dep) {
this.on('after:render', function () {
if (this.mode === 'edit') {
- if (this.isRequired()) {
+ if (this.hasRequiredMarker()) {
this.showRequiredSign();
} else {
this.hideRequiredSign();
}
} else {
- if (this.isRequired()) {
+ if (!this.hasRequiredMarker()) {
this.hideRequiredSign();
}
}
@@ -580,6 +580,10 @@ Espo.define('views/fields/base', 'view', function (Dep) {
}
},
+ hasRequiredMarker: function () {
+ return this.isRequired();
+ },
+
fetchToModel: function () {
this.model.set(this.fetch(), {silent: true});
},
diff --git a/client/src/views/fields/person-name.js b/client/src/views/fields/person-name.js
index 65f8da62d6..17e4f4cf4e 100644
--- a/client/src/views/fields/person-name.js
+++ b/client/src/views/fields/person-name.js
@@ -82,7 +82,7 @@ Espo.define('views/fields/person-name', 'views/fields/varchar', function (Dep) {
},
validateRequired: function () {
- var isRequired = this.model.getFieldParam(this.name, 'required');
+ var isRequired = this.isRequired();
var validate = function (name) {
if (this.model.isRequired(name)) {
@@ -109,11 +109,11 @@ Espo.define('views/fields/person-name', 'views/fields/varchar', function (Dep) {
return result;
},
- isRequired: function () {
+ hasRequiredMarker: function () {
+ if (this.isRequired()) return true;
return this.model.getFieldParam(this.salutationField, 'required') ||
this.model.getFieldParam(this.firstField, 'required') ||
- this.model.getFieldParam(this.lastField, 'required') ||
- this.model.getFieldParam(this.name, 'required');
+ this.model.getFieldParam(this.lastField, 'required');
},
fetch: function (form) {
diff --git a/client/src/views/import/step2.js b/client/src/views/import/step2.js
index 649eb5111d..4ad7b9716d 100644
--- a/client/src/views/import/step2.js
+++ b/client/src/views/import/step2.js
@@ -134,7 +134,12 @@ Espo.define('views/import/step2', 'view', function (Dep) {
$cell = $('