Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend

This commit is contained in:
Taras Machyshyn
2017-11-14 13:26:40 +02:00
36 changed files with 641 additions and 203 deletions
+32 -5
View File
@@ -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': {
@@ -0,0 +1,57 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2017 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* 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\Core\Formula\Functions\ArrayGroup;
use \Espo\Core\Exceptions\Error;
class PushType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
if (!property_exists($item, 'value') || !is_array($item->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;
}
}
+48 -25
View File
@@ -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;
}
}
@@ -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 .= "<br>";
$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;
}
}
@@ -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 .= "<br>";
$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;
}
}
@@ -65,7 +65,7 @@ class AdminNotificationManager
{
$notificationList = [];
if ($this->getConfig()->get('adminNotificationsDisabled')) {
if (!$this->getConfig()->get('adminNotifications')) {
return [];
}
@@ -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'],
+10
View File
@@ -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');
+7 -2
View File
@@ -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');
}
+1 -1
View File
@@ -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()
{
+1 -1
View File
@@ -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()
{
+21 -2
View File
@@ -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;
}
+14 -1
View File
@@ -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'];
}
}
@@ -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"
}
]
}
}
}
}
}
@@ -15,7 +15,6 @@
"lastName": {
"type": "varchar",
"maxLength": 100,
"required": true,
"default":""
},
"title": {
@@ -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();
@@ -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": {
@@ -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."
}
}
@@ -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."
@@ -59,6 +59,18 @@
{
"name": "followEntityOnStreamPost"
}
],
[
false,
{
"name": "followCreatedEntities"
}
],
[
false,
{
"name": "followCreatedEntityTypeList"
}
]
]
},
@@ -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]
]
}
]
@@ -60,7 +60,10 @@
"autoFollowEntityTypeList": false,
"emailReplyForceHtml": false,
"emailReplyToAllByDefault": false,
"signature": false
"signature": false,
"followCreatedEntities": false,
"followEntityOnStreamPost": false,
"doNotFillAssignedUserIfNotRequired": false
},
"Call": {
"reminders": false
@@ -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"]
@@ -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
}
}
}
@@ -433,6 +433,9 @@
"max": 200,
"default": 20,
"required": true
},
"adminNotifications": {
"type": "bool"
}
}
}
+46 -39
View File
@@ -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;
}
+10 -10
View File
@@ -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)
+6 -2
View File
@@ -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});
},
+4 -4
View File
@@ -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) {
+25 -15
View File
@@ -134,7 +134,12 @@ Espo.define('views/import/step2', 'view', function (Dep) {
$cell = $('<td>').append($select);
$row.append($cell);
$cell = $('<td>').html(d.value);
var value = d.value;
if (value.length > 200) {
value = value.substr(0, 200) + '...';
}
$cell = $('<td>').html(value);
$row.append($cell);
if (~['update', 'createAndUpdate'].indexOf(this.formData.action)) {
@@ -175,14 +180,14 @@ Espo.define('views/import/step2', 'view', function (Dep) {
},
getAttributeList: function () {
var fields = this.getMetadata().get('entityDefs.' + this.scope + '.fields');
var fields = this.getMetadata().get(['entityDefs', this.scope, 'fields']) || {};
var fieldList = [];
fieldList.push('id');
var attributeList = [];
attributeList.push('id');
for (var field in fields) {
var d = fields[field];
if (!~this.allowedFieldList.indexOf(field) && (d.readOnly || d.disabled || d.importDisabled)) {
if (!~this.allowedFieldList.indexOf(field) && (((d.readOnly || d.disabled) && !d.importNotDisabled) || d.importDisabled)) {
continue;
}
@@ -190,14 +195,14 @@ Espo.define('views/import/step2', 'view', function (Dep) {
(this.getMetadata().get('entityDefs.' + this.scope + '.fields.' + field + '.typeList' ) || []).map(function (item) {
return item.replace(/\s/g, '_');
}, this).forEach(function (item) {
fieldList.push(field + Espo.Utils.upperCaseFirst(item));
attributeList.push(field + Espo.Utils.upperCaseFirst(item));
}, this);
continue;
}
if (d.type == 'link') {
fieldList.push(field + 'Name');
fieldList.push(field + 'Id');
attributeList.push(field + 'Name');
attributeList.push(field + 'Id');
}
if (~['linkMultiple', 'foreign'].indexOf(d.type)) {
@@ -205,23 +210,28 @@ Espo.define('views/import/step2', 'view', function (Dep) {
}
if (d.type == 'personName') {
fieldList.push(field);
attributeList.push(field);
}
var type = d.type;
var actualFields = this.getFieldManager().getActualAttributes(type, field);
actualFields.forEach(function (f) {
if (fieldList.indexOf(f) === -1) {
fieldList.push(f);
var actualAttributeList = this.getFieldManager().getActualAttributeList(type, field);
if (!actualAttributeList.length) {
actualAttributeList = [field];
}
actualAttributeList.forEach(function (f) {
if (attributeList.indexOf(f) === -1) {
attributeList.push(f);
}
}, this);
}
fieldList = fieldList.sort(function (v1, v2) {
attributeList = attributeList.sort(function (v1, v2) {
return this.translate(v1, 'fields', this.scope).localeCompare(this.translate(v2, 'fields', this.scope));
}.bind(this));
return fieldList
return attributeList
},
getFieldDropdown: function (num, name) {
@@ -100,6 +100,8 @@ Espo.define('views/preferences/record/edit', 'views/record/edit', function (Dep)
this.hideField('dashboardLayout');
}
this.controlFollowCreatedEntityListVisibility();
this.listenTo(this.model, 'change:followCreatedEntities', this.controlFollowCreatedEntityListVisibility);
var hideNotificationPanel = true;
if (!this.getConfig().get('assignmentEmailNotifications') || this.model.get('isPortalUser')) {
@@ -153,6 +155,14 @@ Espo.define('views/preferences/record/edit', 'views/record/edit', function (Dep)
}, this);
},
controlFollowCreatedEntityListVisibility: function () {
if (!this.model.get('followCreatedEntities')) {
this.showField('followCreatedEntityTypeList');
} else {
this.hideField('followCreatedEntityTypeList');
}
},
actionReset: function () {
this.confirm(this.translate('resetPreferencesConfirmation', 'messages'), function () {
$.ajax({
+2 -2
View File
@@ -12,8 +12,8 @@
"composer/semver": "^1.4",
"zendframework/zend-servicemanager": "2.6.0",
"tecnickcom/tcpdf": "^6.2",
"php-mime-mail-parser/php-mime-mail-parser": "^2.5",
"zbateson/mail-mime-parser": "0.4.4",
"php-mime-mail-parser/php-mime-mail-parser": "2.9.3",
"zbateson/mail-mime-parser": "0.4.5",
"phpoffice/phpexcel": "^1.8"
},
"autoload": {
Generated
+12 -12
View File
@@ -4,8 +4,8 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "ec99337bafb77d31fccfd7bd47d9ccfe",
"content-hash": "bd159982d9c3db5c6a1f9915652ac173",
"hash": "b7a79ea5683921a46868e4c2ef74febc",
"content-hash": "54fe23c745b7027a9abeeaa5ddac9b43",
"packages": [
{
"name": "composer/semver",
@@ -688,16 +688,16 @@
},
{
"name": "php-mime-mail-parser/php-mime-mail-parser",
"version": "2.5.0",
"version": "2.9.3",
"source": {
"type": "git",
"url": "https://github.com/php-mime-mail-parser/php-mime-mail-parser.git",
"reference": "9e4da9b71fa653782fa622ddb4363709aabcb714"
"reference": "c6884c7bc77adbf55979db99841195b232fd30f1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/9e4da9b71fa653782fa622ddb4363709aabcb714",
"reference": "9e4da9b71fa653782fa622ddb4363709aabcb714",
"url": "https://api.github.com/repos/php-mime-mail-parser/php-mime-mail-parser/zipball/c6884c7bc77adbf55979db99841195b232fd30f1",
"reference": "c6884c7bc77adbf55979db99841195b232fd30f1",
"shasum": ""
},
"require": {
@@ -764,7 +764,7 @@
"mailparse",
"mime"
],
"time": "2016-10-07 16:46:22"
"time": "2017-11-02 05:49:00"
},
{
"name": "phpoffice/phpexcel",
@@ -1022,16 +1022,16 @@
},
{
"name": "zbateson/mail-mime-parser",
"version": "0.4.4",
"version": "0.4.5",
"source": {
"type": "git",
"url": "https://github.com/zbateson/MailMimeParser.git",
"reference": "ed9bb727468cff384262046b8e81c744d9090d44"
"reference": "ac1488579b40defa68902b081b576caa8fbff8fa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zbateson/MailMimeParser/zipball/ed9bb727468cff384262046b8e81c744d9090d44",
"reference": "ed9bb727468cff384262046b8e81c744d9090d44",
"url": "https://api.github.com/repos/zbateson/MailMimeParser/zipball/ac1488579b40defa68902b081b576caa8fbff8fa",
"reference": "ac1488579b40defa68902b081b576caa8fbff8fa",
"shasum": ""
},
"require": {
@@ -1069,7 +1069,7 @@
"parser",
"php-imap"
],
"time": "2017-07-10 01:00:32"
"time": "2017-10-11 06:35:19"
},
{
"name": "zendframework/zend-crypt",
+7 -2
View File
@@ -34,7 +34,13 @@ if (!$app->isInstalled()) {
exit;
}
$url = !empty($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['REQUEST_URI'];
$url = $_SERVER['REQUEST_URI'];
$portalId = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
if (!isset($portalId)) {
$url = $_SERVER['REDIRECT_URL'];
$portalId = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
}
$a = explode('?', $url);
if (substr($a[0], -1) !== '/') {
@@ -46,7 +52,6 @@ if (substr($a[0], -1) !== '/') {
exit();
}
$portalId = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1];
if ($portalId) {
$app->setBasePath('../../');
} else {
@@ -1718,6 +1718,31 @@ class FormulaTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($actual);
}
function testArrayPush()
{
$item = json_decode('
{
"type": "array\\\\push",
"value": [
{
"type": "value",
"value": ["Test", "Hello"]
},
{
"type": "value",
"value": "1"
},
{
"type": "value",
"value": "2"
}
]
}
');
$actual = $this->formula->process($item, $this->entity);
$this->assertEquals(['Test', 'Hello', '1', '2'], $actual);
}
function testArrayLength()
{
$item = json_decode('
+151 -52
View File
@@ -76,11 +76,26 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
'\\Espo\\Hooks\\Note\\Notifications' => 14,
);
$this->assertTrue( $this->reflection->invokeMethod('isHookExists', array('\\Espo\\Hooks\\Note\\Mentions', $data)) );
$this->assertTrue( $this->reflection->invokeMethod('isHookExists', array('\\Espo\\Modules\\Crm\\Hooks\\Note\\Mentions', $data)) );
$this->assertTrue( $this->reflection->invokeMethod('isHookExists', array('\\Espo\\Modules\\Test\\Hooks\\Note\\Mentions', $data)) );
$this->assertTrue( $this->reflection->invokeMethod('isHookExists', array('\\Espo\\Modules\\Test\\Hooks\\Common\\Stream', $data)) );
$this->assertFalse( $this->reflection->invokeMethod('isHookExists', array('\\Espo\\Hooks\\Note\\TestHook', $data)) );
$data = array (
array (
'className' => '\\Espo\\Hooks\\Note\\Stream',
'order' => 8,
),
array (
'className' => '\\Espo\\Hooks\\Note\\Mentions',
'order' => 9,
),
array (
'className' => '\\Espo\\Hooks\\Note\\Notifications',
'order' => 14,
),
);
$this->assertTrue( $this->reflection->invokeMethod('hookExists', array('\\Espo\\Hooks\\Note\\Mentions', $data)) );
$this->assertTrue( $this->reflection->invokeMethod('hookExists', array('\\Espo\\Modules\\Crm\\Hooks\\Note\\Mentions', $data)) );
$this->assertTrue( $this->reflection->invokeMethod('hookExists', array('\\Espo\\Modules\\Test\\Hooks\\Note\\Mentions', $data)) );
$this->assertTrue( $this->reflection->invokeMethod('hookExists', array('\\Espo\\Modules\\Test\\Hooks\\Common\\Stream', $data)) );
$this->assertFalse( $this->reflection->invokeMethod('hookExists', array('\\Espo\\Hooks\\Note\\TestHook', $data)) );
}
public function testSortHooks()
@@ -90,26 +105,50 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
array (
'afterSave' =>
array (
'\\Espo\\Hooks\\Common\\AssignmentEmailNotification' => 9,
'\\Espo\\Hooks\\Common\\Notifications' => 10,
'\\Espo\\Hooks\\Common\\Stream' => 9,
array (
'className' => '\\Espo\\Hooks\\Common\\AssignmentEmailNotification',
'order' => 9,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Notifications',
'order' => 10,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Stream',
'order' => 9,
),
),
'beforeSave' =>
array (
'\\Espo\\Hooks\\Common\\Formula' => 5,
'\\Espo\\Hooks\\Common\\NextNumber' => 10,
'\\Espo\\Hooks\\Common\\CurrencyConverted' => 1,
array (
'className' => '\\Espo\\Hooks\\Common\\Formula',
'order' => 5,
),
array (
'className' => '\\Espo\\Hooks\\Common\\NextNumber',
'order' => 10,
),
array (
'className' => '\\Espo\\Hooks\\Common\\CurrencyConverted',
'order' => 1,
),
),
),
'Note' =>
array (
'beforeSave' =>
array (
'\\Espo\\Hooks\\Note\\Mentions' => 9,
array (
'className' => '\\Espo\\Hooks\\Note\\Mentions',
'order' => 9,
),
),
'afterSave' =>
array (
'\\Espo\\Hooks\\Note\\Notifications' => 14,
array (
'className' => '\\Espo\\Hooks\\Note\\Notifications',
'order' => 14,
),
),
),
);
@@ -119,26 +158,50 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
array (
'afterSave' =>
array (
'\\Espo\\Hooks\\Common\\AssignmentEmailNotification' => 9,
'\\Espo\\Hooks\\Common\\Stream' => 9,
'\\Espo\\Hooks\\Common\\Notifications' => 10,
array (
'className' => '\\Espo\\Hooks\\Common\\AssignmentEmailNotification',
'order' => 9,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Stream',
'order' => 9,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Notifications',
'order' => 10,
),
),
'beforeSave' =>
array (
'\\Espo\\Hooks\\Common\\CurrencyConverted' => 1,
'\\Espo\\Hooks\\Common\\Formula' => 5,
'\\Espo\\Hooks\\Common\\NextNumber' => 10,
array (
'className' => '\\Espo\\Hooks\\Common\\CurrencyConverted',
'order' => 1,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Formula',
'order' => 5,
),
array (
'className' => '\\Espo\\Hooks\\Common\\NextNumber',
'order' => 10,
),
),
),
'Note' =>
array (
'beforeSave' =>
array (
'\\Espo\\Hooks\\Note\\Mentions' => 9,
array (
'className' => '\\Espo\\Hooks\\Note\\Mentions',
'order' => 9,
),
),
'afterSave' =>
array (
'\\Espo\\Hooks\\Note\\Notifications' => 14,
array (
'className' => '\\Espo\\Hooks\\Note\\Notifications',
'order' => 14,
),
),
),
);
@@ -174,7 +237,10 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
array (
'beforeSave' =>
array (
'\\tests\\unit\\testData\\Hooks\\testCase1\\custom\\Espo\\Custom\\Hooks\\Note\\Mentions' => 7,
array (
'className' => '\\tests\\unit\\testData\\Hooks\\testCase1\\custom\\Espo\\Custom\\Hooks\\Note\\Mentions',
'order' => 7,
),
),
),
);
@@ -210,7 +276,10 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
array (
'beforeSave' =>
array (
'\\tests\\unit\\testData\\Hooks\\testCase2\\application\\Espo\\Modules\\Crm\\Hooks\\Note\\Mentions' => 9,
array (
'className' => '\\tests\\unit\\testData\\Hooks\\testCase2\\application\\Espo\\Modules\\Crm\\Hooks\\Note\\Mentions',
'order' => 9,
),
),
),
);
@@ -246,7 +315,10 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
array (
'beforeSave' =>
array (
'\\tests\\unit\\testData\\Hooks\\testCase2\\application\\Espo\\Modules\\Test\\Hooks\\Note\\Mentions' => 9,
array (
'className' => '\\tests\\unit\\testData\\Hooks\\testCase2\\application\\Espo\\Modules\\Test\\Hooks\\Note\\Mentions',
'order' => 9,
),
),
),
);
@@ -280,7 +352,10 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
array (
'beforeSave' =>
array (
'\\tests\\unit\\testData\\Hooks\\testCase3\\application\\Espo\\Hooks\\Note\\Mentions' => 9,
array (
'className' => '\\tests\\unit\\testData\\Hooks\\testCase3\\application\\Espo\\Hooks\\Note\\Mentions',
'order' => 9,
),
),
),
);
@@ -288,54 +363,78 @@ class HookManagerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($result, $this->reflection->getProperty('data'));
}
public function testMergeHooks()
public function testGetHookList()
{
$data = array (
$this->reflection->setProperty('data', array (
'Common' =>
array (
'afterSave' =>
array (
'\\Espo\\Hooks\\Common\\AssignmentEmailNotification' => 7,
'\\Espo\\Hooks\\Common\\Stream' => 9,
'\\Espo\\Hooks\\Common\\Notifications' => 10,
array (
'className' => '\\Espo\\Hooks\\Common\\AssignmentEmailNotification',
'order' => 9,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Stream',
'order' => 9,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Notifications',
'order' => 10,
),
),
'beforeSave' =>
array (
'\\Espo\\Hooks\\Common\\CurrencyConverted' => 1,
'\\Espo\\Hooks\\Common\\Formula' => 5,
'\\Espo\\Hooks\\Common\\NextNumber' => 10,
array (
'className' => '\\Espo\\Hooks\\Common\\CurrencyConverted',
'order' => 1,
),
array (
'className' => '\\Espo\\Hooks\\Common\\Formula',
'order' => 5,
),
array (
'className' => '\\Espo\\Hooks\\Common\\NextNumber',
'order' => 10,
),
),
),
'Note' =>
array (
'beforeSave' =>
array (
'\\Espo\\Hooks\\Note\\Mentions' => 9,
array (
'className' => '\\Espo\\Hooks\\Note\\Mentions',
'order' => 9,
),
),
'afterSave' =>
array (
'\\Espo\\Hooks\\Note\\Notifications' => 8,
array (
'className' => '\\Espo\\Hooks\\Note\\Btest',
'order' => 9,
),
array (
'className' => '\\Espo\\Hooks\\Note\\Notifications',
'order' => 14,
),
),
),
));
$resultBeforeSave = array(
'\\Espo\\Hooks\\Common\\CurrencyConverted',
'\\Espo\\Hooks\\Common\\Formula',
'\\Espo\\Hooks\\Note\\Mentions',
'\\Espo\\Hooks\\Common\\NextNumber',
);
$result = array(
'afterSave' =>
array (
'\\Espo\\Hooks\\Common\\AssignmentEmailNotification' => 7,
'\\Espo\\Hooks\\Note\\Notifications' => 8,
'\\Espo\\Hooks\\Common\\Stream' => 9,
'\\Espo\\Hooks\\Common\\Notifications' => 10,
),
'beforeSave' =>
array (
'\\Espo\\Hooks\\Common\\CurrencyConverted' => 1,
'\\Espo\\Hooks\\Common\\Formula' => 5,
'\\Espo\\Hooks\\Note\\Mentions' => 9,
'\\Espo\\Hooks\\Common\\NextNumber' => 10,
),
$resultAfterSave = array(
'\\Espo\\Hooks\\Common\\AssignmentEmailNotification',
'\\Espo\\Hooks\\Note\\Btest',
'\\Espo\\Hooks\\Common\\Stream',
'\\Espo\\Hooks\\Common\\Notifications',
'\\Espo\\Hooks\\Note\\Notifications',
);
$this->assertEquals($result, $this->reflection->invokeMethod('mergeHooks', array($data['Common'], $data['Note'])));
}
}