Compare commits

...

25 Commits

Author SHA1 Message Date
Yuri Kuznetsov 7af2d1d3ac v 2020-09-30 15:00:22 +03:00
Yuri Kuznetsov 63670e3245 ldap fix 2020-09-30 12:24:36 +03:00
Yuri Kuznetsov 389931bf22 cs fix 2020-09-30 12:24:31 +03:00
Yuri Kuznetsov 76a67d15a3 ldap fix 2020-09-30 12:18:17 +03:00
Yuri Kuznetsov 8e65f2d375 cleanup 2020-09-30 12:17:58 +03:00
Yuri Kuznetsov 56737f5d67 ldap fix 2020-09-30 12:04:22 +03:00
Yuri Kuznetsov 20f8fae2f0 fix email import 2020-09-30 10:16:58 +03:00
Yuri Kuznetsov 17b5d77d33 fix json function 2020-09-30 09:44:45 +03:00
Yuri Kuznetsov cbe32c88b6 email: move to folder from trash 2020-09-30 09:18:22 +03:00
Yuri Kuznetsov dee291f3f6 email folder select modal css fix 2020-09-30 09:13:59 +03:00
Yuri Kuznetsov 0b20c8e13f fix docs link 2020-09-29 15:16:32 +03:00
Yuri Kuznetsov c0af0173e9 json retrieve function 2020-09-29 13:28:39 +03:00
Yuri Kuznetsov 79f4c4b5d2 cs fix 2020-09-29 13:07:48 +03:00
Yuri Kuznetsov 31c8be2ee1 cs fixes 2020-09-29 10:06:49 +03:00
Yuri Kuznetsov 061b237032 portal fixes 2020-09-29 10:05:54 +03:00
Yuri Kuznetsov d3466e201d fix 2020-09-29 09:38:43 +03:00
Taras Machyshyn c69c7d2194 Added new options in upgrade command 2020-09-28 19:46:19 +03:00
Yuri Kuznetsov 05e1624e47 fix notice 2020-09-28 16:04:18 +03:00
Yuri Kuznetsov 03e32412ca varchar 150 max length by default 2020-09-28 15:25:58 +03:00
Yuri Kuznetsov 451a2376a8 fix field manager 2020-09-28 15:22:38 +03:00
Yuri Kuznetsov 167c774446 email: only read access in poral roles 2020-09-28 15:11:08 +03:00
Yuri Kuznetsov 054262a002 fix tester 2020-09-28 13:10:26 +03:00
Yuri Kuznetsov ecbc3c537f cs fix 2020-09-28 12:57:25 +03:00
Yuri Kuznetsov d51bbfdfab fix portal role clear cache 2020-09-28 12:36:09 +03:00
Yuri Kuznetsov ae343cddbe fix dashlet layout field 2020-09-28 12:20:38 +03:00
27 changed files with 453 additions and 182 deletions
@@ -99,7 +99,7 @@ class FieldManager
$fieldManagerTool->create($scope, $name, get_object_vars($data));
try {
$this->dataManager->rebuild($scope);
$this->dataManager->rebuild([$scope]);
}
catch (Error $e) {
$fieldManagerTool->delete($scope, $data->name);
@@ -131,7 +131,7 @@ class FieldManager
$fieldManagerTool->update($scope, $name, get_object_vars($data));
if ($fieldManagerTool->isChanged()) {
$this->dataManager->rebuild($scope);
$this->dataManager->rebuild([$scope]);
} else {
$this->dataManager->clearCache();
}
+11 -3
View File
@@ -32,6 +32,11 @@ namespace Espo\Controllers;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\{
Authentication\LDAP\Utils as LDAPUtils,
Authentication\LDAP\Client as LDAPClient,
};
class Settings extends \Espo\Core\Controllers\Base
{
protected function getConfigData()
@@ -86,11 +91,14 @@ class Settings extends \Espo\Core\Controllers\Base
$data = get_object_vars($data);
$ldapUtils = new \Espo\Core\Utils\Authentication\LDAP\Utils();
$ldapUtils = new LDAPUtils();
$options = $ldapUtils->normalizeOptions($data);
$ldapClient = new \Espo\Core\Utils\Authentication\LDAP\Client($options);
$ldapClient->bind(); //an exception if no connection
$ldapClient = new LDAPClient($options);
//an exception if no connection
$ldapClient->bind();
return true;
}
+2
View File
@@ -86,6 +86,7 @@ class Application
{
if (!$className || !class_exists($className)) {
$this->getLog()->error("Application runner '{$className}' does not exist.");
return;
}
@@ -161,6 +162,7 @@ class Application
protected function initAutoloads()
{
$autoload = $this->getInjectableFactory()->create(Autoload::class);
$autoload->register();
}
@@ -37,10 +37,6 @@ class Utils
protected $options = null;
/**
* Association between LDAP and Espo fields
* @var array
*/
protected $fieldMap = [
'host' => 'ldapHost',
'port' => 'ldapPort',
@@ -73,11 +69,6 @@ class Utils
'portalUserRolesIds' => 'ldapPortalUserRolesIds',
];
/**
* Permitted Espo Options
*
* @var array
*/
protected $permittedEspoOptions = [
'createEspoUser',
'userNameAttribute',
@@ -96,9 +87,7 @@ class Utils
];
/**
* accountCanonicalForm Map between Espo and Laminas value
*
* @var array
* accountCanonicalForm Map between Espo and Laminas value.
*/
protected $accountCanonicalFormMap = [
'Dn' => 1,
@@ -107,7 +96,6 @@ class Utils
'Principal' => 4,
];
public function __construct(Config $config = null)
{
if (isset($config)) {
@@ -121,7 +109,7 @@ class Utils
}
/**
* Get Options from espo config according to $this->fieldMap
* Get Options from espo config according to $this->fieldMap.
*
* @return array
*/
@@ -131,10 +119,10 @@ class Utils
return $this->options;
}
$options = array();
$options = [];
foreach ($this->fieldMap as $ldapName => $espoName) {
$option = $this->getConfig()->get($espoName);
if (isset($option)) {
$options[$ldapName] = $option;
}
@@ -162,7 +150,7 @@ class Utils
}
/**
* Get an ldap option
* Get an ldap option.
*
* @param string $name
* @param mixed $returns Return value
@@ -182,7 +170,7 @@ class Utils
}
/**
* Get Laminas options for using Laminas\Ldap
* Get Laminas options for using Laminas\Ldap.
*
* @return array
*/
@@ -193,5 +181,4 @@ class Utils
return $zendOptions;
}
}
@@ -44,8 +44,12 @@ use Espo\Core\{
Utils\Language,
ApplicationUser,
Authentication\Result,
Authentication\LDAP\Utils as LDAPUtils,
Authentication\LDAP\Client as LDAPClient,
};
use Exception;
class LDAP extends Espo
{
private $utils;
@@ -76,12 +80,9 @@ class LDAP extends Espo
$this->isPortal = $isPortal;
$this->utils = new LDAP\Utils($config);
$this->utils = new LDAPUtils($config);
}
/**
* User field name => option name (LDAP attribute).
*/
protected $ldapFieldMap = [
'userName' => 'userNameAttribute',
'firstName' => 'userFirstNameAttribute',
@@ -91,17 +92,11 @@ class LDAP extends Espo
'phoneNumber' => 'userPhoneNumberAttribute',
];
/**
* User field name => option name.
*/
protected $userFieldMap = [
'teamsIds' => 'userTeamsIds',
'defaultTeamId' => 'userDefaultTeamId',
];
/**
* User field name => option name.
*/
protected $portalUserFieldMap = [
'portalsIds' => 'portalUserPortalsIds',
'portalRolesIds' => 'portalUserRolesIds',
@@ -137,7 +132,8 @@ class LDAP extends Espo
/* Login LDAP system user (ldapUsername, ldapPassword) */
try {
$ldapClient->bind();
} catch (\Exception $e) {
}
catch (Exception $e) {
$options = $this->utils->getLdapClientOptions();
$GLOBALS['log']->error('LDAP: Could not connect to LDAP server ['.$options['host'].'], details: ' . $e->getMessage());
@@ -152,7 +148,8 @@ class LDAP extends Espo
if (!isset($adminUser)) {
try {
$userDn = $this->findLdapUserDnByUsername($username);
} catch (\Exception $e) {
}
catch (Exception $e) {
$GLOBALS['log']->error('Error while finding DN for ['.$username.'], details: ' . $e->getMessage() . '.');
}
@@ -171,8 +168,11 @@ class LDAP extends Espo
try {
$ldapClient->bind($userDn, $password);
} catch (\Exception $e) {
}
catch (
Exception $e) {
$GLOBALS['log']->error('LDAP: Authentication failed for user ['.$username.'], details: ' . $e->getMessage());
return Result::fail();
}
}
@@ -187,6 +187,7 @@ class LDAP extends Espo
if (!isset($user)) {
if (!$this->utils->getOption('createEspoUser')) {
$this->useSystemUser();
throw new Error($this->language->translate('ldapUserInEspoNotFound', 'messages', 'User'));
}
@@ -212,8 +213,9 @@ class LDAP extends Espo
$options = $this->utils->getLdapClientOptions();
try {
$this->ldapClient = new LDAP\Client($options);
} catch (\Exception $e) {
$this->ldapClient = new LDAPClient($options);
}
catch (Exception $e) {
$GLOBALS['log']->error('LDAP error: ' . $e->getMessage());
}
}
@@ -222,12 +224,7 @@ class LDAP extends Espo
}
/**
* Login by authorization token
*
* @param string $username
* @param \Espo\Entities\AuthToken $authToken
*
* @return \Espo\Entities\User | null
* Login by authorization token.
*/
protected function loginByToken($username, AuthToken $authToken = null)
{
@@ -239,11 +236,14 @@ class LDAP extends Espo
$user = $this->entityManager->getEntity('User', $userId);
$tokenUsername = $user->get('userName');
if (strtolower($username) != strtolower($tokenUsername)) {
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$GLOBALS['log']->alert(
'Unauthorized access attempt for user ['.$username.'] from IP ['.$ip.']'
);
return null;
}
@@ -257,11 +257,7 @@ class LDAP extends Espo
}
/**
* Login user with administrator rights
*
* @param string $username
* @param string $password
* @return \Espo\Entities\User | null
* Login user with administrator rights.
*/
protected function adminLogin($username, $password)
{
@@ -279,12 +275,7 @@ class LDAP extends Espo
}
/**
* Create Espo user with data gets from LDAP server
*
* @param array $userData LDAP entity data
* @param boolean $isPortal Is portal user
*
* @return \Espo\Entities\User
* Create Espo user with data gets from LDAP server.
*/
protected function createUser(array $userData, $isPortal = false)
{
@@ -320,17 +311,14 @@ class LDAP extends Espo
$user = $this->entityManager->getEntity('User');
$user->set($data);
$this->entityManager->saveEntity($user);
return $this->entityManager->getEntity('User', $user->id);
}
/**
* Find LDAP user DN by his username
*
* @param string $username
*
* @return string | null
* Find LDAP user DN by his username.
*/
protected function findLdapUserDnByUsername($username)
{
@@ -338,12 +326,15 @@ class LDAP extends Espo
$options = $this->utils->getOptions();
$loginFilterString = '';
if (!empty($options['userLoginFilter'])) {
$loginFilterString = $this->convertToFilterFormat($options['userLoginFilter']);
}
$searchString = '(&(objectClass='.$options['userObjectClass'].')('.$options['userNameAttribute'].'='.$username.')'.$loginFilterString.')';
$result = $ldapClient->search($searchString, null, LDAP\Client::SEARCH_SCOPE_SUB);
$searchString = '(&(objectClass='.$options['userObjectClass'].')('.$options['userNameAttribute'].'='.$username.')'.
$loginFilterString.')';
$result = $ldapClient->search($searchString, null, LDAPClient::SEARCH_SCOPE_SUB);
$GLOBALS['log']->debug('LDAP: user search string: "' . $searchString . '"');
foreach ($result as $item) {
@@ -352,30 +343,25 @@ class LDAP extends Espo
}
/**
* Check and convert filter item into LDAP format
*
* @param string $filter E.g. "memberof=CN=externalTesters,OU=groups,DC=espo,DC=local"
*
* @return string
* Check and convert filter item into LDAP format.
*/
protected function convertToFilterFormat($filter)
{
$filter = trim($filter);
if (substr($filter, 0, 1) != '(') {
$filter = '(' . $filter;
}
if (substr($filter, -1) != ')') {
$filter = $filter . ')';
}
return $filter;
}
/**
* Load fields for a user
*
* @param string $type
*
* @return array
* Load fields for a user.
*/
protected function loadFields($type)
{
@@ -384,6 +370,7 @@ class LDAP extends Espo
$typeMap = $type . 'FieldMap';
$fields = [];
foreach ($this->$typeMap as $fieldName => $fieldValue) {
if (isset($options[$fieldValue])) {
$fields[$fieldName] = $options[$fieldValue];
@@ -72,9 +72,10 @@ class Upgrade implements Command
{
$params = $this->normalizeParams($options, $flagList, $argumentList);
$versionInfo = $this->getVersionInfo();
$fromVersion = $this->config->get('version');
$toVersion = $params->toVersion ?? null;
$versionInfo = $this->getVersionInfo($toVersion);
$nextVersion = $versionInfo->nextVersion ?? null;
$lastVersion = $infoData->lastVersion ?? null;
@@ -189,10 +190,22 @@ class Upgrade implements Command
$params->singleProcess = true;
}
if (in_array('patch', $flagList)) {
$currentVersion = $this->config->get('version');
if (preg_match('/^(.*)\.(.*)\..*$/', $currentVersion, $match)) {
$options['toVersion'] = $match[1] . '.' . $match[2];
}
}
if (!empty($options['step'])) {
$params->step = $options['step'];
}
if (!empty($options['toVersion'])) {
$params->toVersion = $options['toVersion'];
}
return $params;
}
@@ -343,12 +356,16 @@ class Upgrade implements Command
return $phpExecutablePath;
}
protected function getVersionInfo()
protected function getVersionInfo($toVersion = null)
{
$url = 'https://s.espocrm.com/upgrade/next/';
$url = $this->config->get('upgradeNextVersionUrl', $url);
$url .= '?fromVersion=' . $this->config->get('version');
if ($toVersion) {
$url .= '&toVersion=' . $toVersion;
}
$ch = curl_init();
curl_setopt($ch, \CURLOPT_SSL_VERIFYPEER, true);
@@ -69,6 +69,7 @@ abstract class BaseFunction
if (!$this->entity) {
throw new NotPassedEntity('function: ' . $this->name);
}
return $this->entity;
}
@@ -119,12 +120,15 @@ abstract class BaseFunction
protected function throwBadArgumentType(?int $index = null, ?string $type = null)
{
$msg = 'function: ' . $this->name;
if ($index !== null) {
$msg .= ', index: ' . $index;
if ($type) {
$msg .= ', should be: ' . $type;
}
}
throw new BadArgumentType($msg);
}
@@ -134,12 +138,15 @@ abstract class BaseFunction
protected function throwBadArgumentValue(?int $index = null, ?string $msg = null)
{
$string = 'function: ' . $this->name;
if ($index !== null) {
$string .= ', index: ' . $index;
if ($msg) {
$string .= ', ' . $msg;
}
}
throw new BadArgumentValue($string);
}
@@ -147,11 +154,13 @@ abstract class BaseFunction
* Throw Error exception.
*/
protected function throwError(?string $msg = null)
{
$string = 'function: ' . $this->name;
if ($msg) {
$string .= ', ' . $msg;
}
throw new Error($string);
}
@@ -160,7 +169,10 @@ abstract class BaseFunction
*/
protected function logBadArgumentType(int $index, string $type)
{
if (!$this->log) return;
if (!$this->log) {
return;
}
$this->log->warning("Formula function: {$this->name}, argument {$index} should be '{$type}'.");
}
@@ -169,7 +181,10 @@ abstract class BaseFunction
*/
protected function log(string $msg, string $level = 'notice')
{
if (!$this->log) return;
if (!$this->log) {
return;
}
$this->log->log($level, 'Formula function: ' . $this->name . ', ' . $msg);
}
}
@@ -0,0 +1,110 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://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\JsonGroup;
use Espo\Core\Formula\{
Functions\BaseFunction,
ArgumentList,
};
class RetrieveType extends BaseFunction
{
public function process(ArgumentList $args)
{
if (count($args) < 2) {
$this->throwTooFewArguments();
}
$jsonString = $this->evaluate($args[0]);
$path = $this->evaluate($args[1]);
if (!is_string($jsonString)) {
$this->throwBadArgumentType(1, 'string');
}
if (!is_string($path)) {
$this->throwBadArgumentType(2, 'string');
}
if ($path === '') {
$this->throwBadArgumentValue(2);
}
$item = json_decode($jsonString);
$pathArray = $this->splitPath($path);
return $this->retrieveAttribute($item, $pathArray);
}
private function splitPath(string $path) : array
{
$pathArray = preg_split('/(?<!\\\)\./', $path);
foreach ($pathArray as $i => $item) {
$pathArray[$i] = str_replace('\.', '.', $item);
}
return $pathArray;
}
private function retrieveAttribute($item, array $path)
{
if (!count($path)) {
return $item;
}
$key = array_shift($path);
if (is_array($item)) {
$key = intval($key);
$subItem = $item[$key] ?? null;
if (is_null($subItem)) {
return null;
}
return $this->retrieveAttribute($subItem, $path);
}
if (is_object($item)) {
$subItem = $item->$key ?? null;
if (is_null($subItem)) {
return null;
}
return $this->retrieveAttribute($subItem, $path);
}
return null;
}
}
+24 -6
View File
@@ -189,7 +189,9 @@ class Importer
if ($duplicate = $this->findDuplicate($email)) {
if ($duplicate->get('status') != 'Being Imported') {
$duplicate = $this->entityManager->getEntity('Email', $duplicate->id);
$this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList);
return $duplicate;
}
}
@@ -197,6 +199,7 @@ class Importer
if ($parser->checkMessageAttribute($message, 'date')) {
try {
$dt = new DateTime($parser->getMessageAttribute($message, 'date'));
if ($dt) {
$dateSent = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');
$email->set('dateSent', $dateSent);
@@ -209,6 +212,7 @@ class Importer
if ($parser->checkMessageAttribute($message, 'delivery-Date')) {
try {
$dt = new DateTime($parser->getMessageAttribute($message, 'delivery-Date'));
if ($dt) {
$deliveryDate = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');
$email->set('delivery-Date', $deliveryDate);
@@ -222,7 +226,7 @@ class Importer
$parser->fetchContentParts($email, $message, $inlineAttachmentList);
if ($this->filtersMatcher->match($email, $filterList)) {
return false;
return null;
}
} else {
$email->set('body', 'Not fetched. The email size exceeds the limit.');
@@ -233,15 +237,24 @@ class Importer
$replied = null;
if ($parser->checkMessageAttribute($message, 'in-Reply-To') && $parser->getMessageAttribute($message, 'in-Reply-To')) {
if (
$parser->checkMessageAttribute($message, 'in-Reply-To') && $parser->getMessageAttribute($message, 'in-Reply-To')
) {
$arr = explode(' ', $parser->getMessageAttribute($message, 'in-Reply-To'));
$inReplyTo = $arr[0];
if ($inReplyTo) {
if ($inReplyTo[0] !== '<') $inReplyTo = '<' . $inReplyTo . '>';
$replied = $this->entityManager->getRepository('Email')->where(array(
'messageId' => $inReplyTo
))->findOne();
if ($inReplyTo[0] !== '<') {
$inReplyTo = '<' . $inReplyTo . '>';
}
$replied = $this->entityManager
->getRepository('Email')
->where([
'messageId' => $inReplyTo
])
->findOne();
if ($replied) {
$email->set('repliedId', $replied->id);
$repliedTeamIdList = $replied->getLinkMultipleIdList('teams');
@@ -255,6 +268,7 @@ class Importer
if ($parser->checkMessageAttribute($message, 'references') && $parser->getMessageAttribute($message, 'references')) {
$references = $parser->getMessageAttribute($message, 'references');
$delimiter = ' ';
if (strpos($references, '>,')) {
$delimiter = ',';
}
@@ -278,6 +292,7 @@ class Importer
if (!empty($parentType) && !empty($parentId)) {
if ($parentType == 'Lead') {
$parent = $this->entityManager->getEntity('Lead', $parentId);
if ($parent && $parent->get('status') == 'Converted') {
if ($parent->get('createdAccountId')) {
$account = $this->entityManager->getEntity('Account', $parent->get('createdAccountId'));
@@ -301,6 +316,7 @@ class Importer
$email->set('parentType', $parentType);
$email->set('parentId', $parentId);
$parentFound = true;
}
}
@@ -310,6 +326,7 @@ class Importer
if (!$parentFound) {
if ($replied && $replied->get('parentId') && $replied->get('parentType')) {
$parentFound = $this->entityManager->getEntity($replied->get('parentType'), $replied->get('parentId'));
if ($parentFound) {
$email->set('parentType', $replied->get('parentType'));
$email->set('parentId', $replied->get('parentId'));
@@ -318,6 +335,7 @@ class Importer
}
if (!$parentFound) {
$from = $email->get('from');
if ($from) {
$parentFound = $this->findParent($email, $from);
}
+44 -34
View File
@@ -33,6 +33,12 @@ class Url
{
public static function detectPortalId() : ?string
{
$portalId = $_SERVER['ESPO_PORTAL_ID'] ?? null;
if ($portalId) {
return $portalId;
}
$url = $_SERVER['REQUEST_URI'];
$portalId = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1] ?? null;
@@ -41,57 +47,61 @@ class Url
$portalId = null;
}
if (!isset($portalId)) {
$url = $_SERVER['REDIRECT_URL'];
$portalId = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1] ?? null;
if ($portalId) {
return $portalId;
}
$url = $_SERVER['REDIRECT_URL'] ?? null;
if (!$url) {
return null;
}
$portalId = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1] ?? null;
return $portalId;
}
public static function detectUrl() : string
protected static function detectIsCustomUrl() : bool
{
$url = $_SERVER['REQUEST_URI'];
$portalId = explode('/', $url)[count(explode('/', $_SERVER['SCRIPT_NAME'])) - 1] ?? null;
if (strpos($url, '=') !== false) {
$portalId = null;
}
if (!isset($portalId)) {
return $_SERVER['REDIRECT_URL'];
}
return $url;
return (bool) ($_SERVER['ESPO_PORTAL_IS_CUSTOM_URL'] ?? false);
}
public static function normalizeUrl(string $url) : string
public static function detectIsInPortalDir() : bool
{
$urlParts = explode('?', $url);
$isCustomUrl = self::detectIsCustomUrl();
if (substr($urlParts[0], -1) === '/') {
return $url;
if ($isCustomUrl) {
return false;
}
$url = $urlParts[0] . '/';
if (count($urlParts) > 1) {
$url .= '?' . $urlParts[1];
}
return $url;
}
public function detectIsInDir() : bool
{
$url = $_SERVER['REQUEST_URI'];
$a = explode('?', $url);
$url = rtrim($a[0], '/');
return strpos($url, '/') !== false;
return strpos($url, '/portal') !== false ;
}
public static function detectIsInPortalWithId() : bool
{
if (!self::detectIsInPortalDir()) {
return false;
}
$url = $_SERVER['REQUEST_URI'];
$a = explode('?', $url);
$url = rtrim($a[0], '/');
$folders = explode('/', $url);
if (count($folders) > 1 && $folders[count($folders) - 2] === 'portal') {
return true;
}
return false;
}
}
+22 -8
View File
@@ -29,9 +29,12 @@
namespace Espo\Core\Utils;
use Espo\Core\Exceptions\Error;
use Espo\Core\{
Exceptions\Error,
Utils\File\Manager as FileManager,
};
use Espo\Core\Utils\File\Manager as FileManager;
use StdClass;
/**
* Reads and writes the main config file.
@@ -88,6 +91,7 @@ class Config
$keys = explode('.', $name);
$lastBranch = $this->loadConfig();
foreach ($keys as $keyName) {
if (isset($lastBranch[$keyName]) && (is_array($lastBranch) || is_object($lastBranch))) {
if (is_array($lastBranch)) {
@@ -111,6 +115,7 @@ class Config
$keys = explode('.', $name);
$lastBranch = $this->loadConfig();
foreach ($keys as $keyName) {
if (isset($lastBranch[$keyName]) && (is_array($lastBranch) || is_object($lastBranch))) {
if (is_array($lastBranch)) {
@@ -136,14 +141,16 @@ class Config
}
if (!is_array($name)) {
$name = array($name => $value);
$name = [$name => $value];
}
foreach ($name as $key => $value) {
if (in_array($key, $this->associativeArrayAttributeList) && is_object($value)) {
$value = (array) $value;
}
$this->data[$key] = $value;
if (!$dontMarkDirty) {
$this->changedData[$key] = $value;
}
@@ -157,7 +164,9 @@ class Config
{
if (array_key_exists($name, $this->data)) {
unset($this->data[$name]);
$this->removeData[] = $name;
return true;
}
@@ -180,10 +189,11 @@ class Config
$configPath = $this->getConfigPath();
if (!file_exists($configPath)) {
throw new Error('Config file ['. $configPath .'] is not found.');
throw new Error("Config file '{$configPath}' is not found.");
}
$data = include($configPath);
if (!is_array($data)) {
$data = include($configPath);
}
@@ -201,7 +211,8 @@ class Config
}
if (!is_array($data)) {
$GLOBALS['log']->error('Invalid config data ['. var_export($data, true) .'] while saving to ['. $configPath .'].');
$GLOBALS['log']->error("Invalid config data while saving to '{$configPath}'.");
throw new Error('Invalid config data while saving.');
}
@@ -211,14 +222,16 @@ class Config
if ($result) {
$reloadedData = include($configPath);
if (!is_array($reloadedData) || $microtime !== ($reloadedData['microtime'] ?? null)) {
$result = $this->getFileManager()->putPhpContents($configPath, $data, true, false);
}
}
if ($result) {
$this->changedData = array();
$this->removeData = array();
$this->changedData = [];
$this->removeData = [];
$this->loadConfig(true);
}
@@ -244,6 +257,7 @@ class Config
$this->data = $this->getFileManager()->getPhpContents($configPath);
$systemConfig = $this->getFileManager()->getPhpContents($this->systemConfigPath);
$this->data = Util::merge($systemConfig, $this->data);
return $this->data;
@@ -252,7 +266,7 @@ class Config
/**
* Get all parameters.
*/
public function getAllData() : \StdClass
public function getAllData() : StdClass
{
return (object) $this->loadConfig();
}
+6 -17
View File
@@ -41,6 +41,7 @@ use Espo\Core\{
Utils\Config,
Portal\Application as PortalApplication,
Portal\ApplicationRunners\Client,
Portal\Utils\Url,
Api\Request,
Api\Response,
};
@@ -64,26 +65,14 @@ class Portal implements EntryPoint
{
$data = $data ?? (object) [];
$id = $request->get('id') ?? $data->id ?? null;
$id = $data->id ?? Url::detectPortalId();
if (!$id) {
$url = $_SERVER['REQUEST_URI'];
$id = $this->config->get('defaultPortalId');
}
$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->config->get('defaultPortalId');
}
if (!$id) {
throw new NotFound();
}
if (!$id) {
throw new NotFound("Portal ID not detected.");
}
$application = new PortalApplication($id);
@@ -12,7 +12,10 @@
},
{
"name":"maxLength",
"type":"int"
"type":"int",
"default": 150,
"min": 1,
"max": 65535
},
{
"name":"trim",
@@ -3,7 +3,9 @@
"layouts": false,
"tab": true,
"acl": true,
"aclPortal": "recordAllAccountContactOwnNo",
"aclPortal": true,
"aclPortalLevelList": ["all", "account", "contact", "own", "no"],
"aclPortalActionList": ["read"],
"notifications": true,
"object": true,
"customizable": true,
+4 -1
View File
@@ -716,7 +716,10 @@ class Email extends Record implements
$update = $this->entityManager->getQueryBuilder()->update()
->in('EmailUser')
->set(['folderId' => $folderId])
->set([
'folderId' => $folderId,
'inTrash' => false,
])
->where([
'deleted' => false,
'userId' => $userId,
+1 -1
View File
@@ -76,7 +76,7 @@ class Portal extends Record implements
protected function clearRolesCache()
{
$this->fileManager->removeInDir('data/cache/application/acl-portal');
$this->fileManager->removeInDir('data/cache/application/aclPortal');
$this->dataManager->updateCacheTimestamp();
}
}
+1 -1
View File
@@ -57,7 +57,7 @@ class PortalRole extends Record implements
protected function clearRolesCache()
{
$this->fileManager->removeInDir('data/cache/application/acl-portal');
$this->fileManager->removeInDir('data/cache/application/aclPortal');
$this->dataManager->updateCacheTimestamp();
}
}
+1 -1
View File
@@ -675,7 +675,7 @@ class User extends Record implements
protected function clearPortalRolesCache()
{
$this->fileManager->removeInDir('data/cache/application/acl-portal');
$this->fileManager->removeInDir('data/cache/application/aclPortal');
$this->dataManager->updateCacheTimestamp();
}
@@ -1,4 +1,4 @@
<ul class="list-group">
<ul class="list-group no-side-margin">
<li data-id="inbox" class="list-group-item">
<a href="javascript:" data-action="selectFolder" data-id="inbox" class="side-link">{{translate 'inbox' category='presetFilters' scope='Email'}}</a>
</li>
@@ -56,7 +56,7 @@ define('views/admin/formula/modals/add-function', ['views/modal', 'model'], func
setup: function () {
this.header = this.translate('Function');
this.documentationUrl = 'https://www.espocrm.com/documentation/administration/formula/';
this.documentationUrl = 'https://docs.espocrm.com/administration/formula/';
this.functionDataList = this.getMetadata().get('app.formula.functionList') || [];
},
@@ -93,9 +93,8 @@ define('views/email/record/row-actions/default', 'views/record/row-actions/defau
}
});
}
}
if (this.model.get('isUsers')) {
if (!this.model.get('isImportant')) {
if (!this.model.get('inTrash')) {
@@ -117,17 +116,17 @@ define('views/email/record/row-actions/default', 'views/record/row-actions/defau
});
}
}
if (this.model.get('isUsers') && this.model.get('status') !== 'Draft') {
if (!this.model.get('inTrash')) {
list.push({
action: 'moveToFolder',
label: 'Move to Folder',
data: {
id: this.model.id
}
});
}
list.push({
action: 'moveToFolder',
label: 'Move to Folder',
data: {
id: this.model.id
}
});
}
if (this.options.acl.delete) {
list.push({
action: 'quickRemove',
@@ -137,6 +136,7 @@ define('views/email/record/row-actions/default', 'views/record/row-actions/defau
}
});
}
return list;
},
@@ -165,9 +165,9 @@ define('views/settings/fields/dashboard-layout', ['views/fields/base', 'lib!grid
},
removeDashlet: function (id) {
var grid = this.$gridstack.data('gridstack');
var $item = this.$gridstack.find('.grid-stack-item[data-id="'+id+'"]');
grid.removeWidget($item, true);
this.grid.removeWidget($item, true);
var layout = this.dashboardLayout[this.currentTab].layout;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta1",
"version": "6.0.0-beta2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta1",
"version": "6.0.0-beta2",
"description": "",
"main": "index.php",
"repository": {
+2 -11
View File
@@ -41,19 +41,10 @@ if (!$app->isInstalled()) {
exit;
}
$url = Url::detectUrl();
$normalizedUrl = Url::normalizeUrl($url);
if ($url !== $normalizedUrl) {
header("Location: " . $normalizedUrl);
exit;
}
if (Url::detectIsInDir()) {
if (Url::detectIsInPortalDir()) {
$basePath = '../';
if (Url::detectPortalId()) {
if (Url::detectIsInPortalWithId()) {
$basePath = '../../';
}
+5 -5
View File
@@ -40,13 +40,13 @@ class Utils
*/
public static function getLatestBuildedPath($path)
{
$archives = array();
$archives = [];
$buildDir = dir($path);
while ($folderName = $buildDir->read()) {
if ($folderName === '.'|| $folderName === '..' || empty($folderName)) continue;
$pattern = '/^EspoCRM-([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-((a|alpha|b|beta|pre|rc)(\.[0-9]+)?)?)?$/';
$pattern = '/^EspoCRM-([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-((a|alpha|b|beta|pre|rc)([0-9]+)?)?)?$/';
if (preg_match($pattern, $folderName)) {
$archives[] = $folderName;
@@ -61,14 +61,14 @@ class Utils
protected static function sortVersions(& $existVersions)
{
usort($existVersions, array("\\tests\\integration\\Core\\Utils", "versionCmp"));
usort($existVersions, ["\\tests\\integration\\Core\\Utils", "versionCmp"]);
}
public static function versionCmp($a, $b)
{
$order = array('a' => 0, 'alpha' => 1, 'b' => 2, 'beta' => 3, 'pre' => 4, 'rc' => 5);
$order = ['a' => 0, 'alpha' => 1, 'b' => 2, 'beta' => 3, 'pre' => 4, 'rc' => 5];
$ma = $mb = array();
$ma = $mb = [];
$pattern = '/^EspoCRM-([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-((a|alpha|b|beta|pre|rc)[0-9]+)?)?$/';
@@ -319,4 +319,119 @@ class EvaluatorTest extends \PHPUnit\Framework\TestCase
$this->assertTrue(is_float($value));
}
public function testJsonRetrieve1()
{
$value = (object) [
'a' => 'test',
];
$expression = "json\\retrieve(\$value, 'a')";
$result = $this->evaluator->process($expression, null, (object) [
'value' => json_encode($value),
]);
$this->assertEquals('test', $result);
}
public function testJsonRetrieve2()
{
$value = [
0 => 'test',
];
$expression = "json\\retrieve(\$value, '0')";
$result = $this->evaluator->process($expression, null, (object) [
'value' => json_encode($value),
]);
$this->assertEquals('test', $result);
}
public function testJsonRetrieve3()
{
$value = (object) [
'a' => [
'ab' => 'test'
],
];
$expression = "json\\retrieve(\$value, 'a.ab')";
$result = $this->evaluator->process($expression, null, (object) [
'value' => json_encode($value),
]);
$this->assertEquals('test', $result);
}
public function testJsonRetrieve4()
{
$value = (object) [
'a' => [
'ab' => 'test'
],
];
$expression = "json\\retrieve(\$value, 'b.c')";
$result = $this->evaluator->process($expression, null, (object) [
'value' => json_encode($value),
]);
$this->assertEquals(null, $result);
}
public function testJsonRetrieve5()
{
$value = (object) [
'a' => [
'ab' => 'test'
],
];
$expression = "json\\retrieve(\$value, '0')";
$result = $this->evaluator->process($expression, null, (object) [
'value' => json_encode($value),
]);
$this->assertEquals(null, $result);
}
public function testJsonRetrieve6()
{
$value = [
0 => (object) [
'a' => 'test'
],
];
$expression = "json\\retrieve(\$value, '0.a')";
$result = $this->evaluator->process($expression, null, (object) [
'value' => json_encode($value),
]);
$this->assertEquals('test', $result);
}
public function testJsonRetrieve7()
{
$value = (object) [
'a.b' => (object) [
'c' => 'test'
],
];
$expression = "json\\retrieve(\$value, 'a\\.b.c')";
$result = $this->evaluator->process($expression, null, (object) [
'value' => json_encode($value),
]);
$this->assertEquals('test', $result);
}
}