mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-10 12:47:02 +00:00
Compare commits
84 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e6bc02256 | ||
|
|
2a52d3c52e | ||
|
|
a9623ff698 | ||
|
|
f774b4bbcf | ||
|
|
da2e2dbf20 | ||
|
|
c054712828 | ||
|
|
44d772a32f | ||
|
|
a90971b762 | ||
|
|
09d7e8abde | ||
|
|
1ddfcff441 | ||
|
|
1a672a8aa3 | ||
|
|
a6fb23302e | ||
|
|
85ed99a1dc | ||
|
|
6e9f368a0a | ||
|
|
84a3688525 | ||
|
|
3c08986889 | ||
|
|
477828beb9 | ||
|
|
fadc6c46ba | ||
|
|
c1b7e06308 | ||
|
|
e86ec32dd7 | ||
|
|
c8bd2ec1a1 | ||
|
|
3cf94bb728 | ||
|
|
0f8621d923 | ||
|
|
2d9e9a5e93 | ||
|
|
abeb5fc0e3 | ||
|
|
47e61cecdd | ||
|
|
17aaf08717 | ||
|
|
89e849d851 | ||
|
|
42098f477a | ||
|
|
e44ee9ca03 | ||
|
|
c4861c5efa | ||
|
|
4380175c8b | ||
|
|
8ff46bba74 | ||
|
|
06f1305f23 | ||
|
|
43723697f7 | ||
|
|
99d6a0ab32 | ||
|
|
c2809ca091 | ||
|
|
108432c268 | ||
|
|
970242e860 | ||
|
|
dff2e344a4 | ||
|
|
24617f4a41 | ||
|
|
2ae0f36241 | ||
|
|
eec372c6df | ||
|
|
011e83fac6 | ||
|
|
dd8e55c2ac | ||
|
|
00fcd5a9b6 | ||
|
|
3dda43c70a | ||
|
|
34029c6f6f | ||
|
|
cc307e3217 | ||
|
|
e3c45a5039 | ||
|
|
7cbc24feb8 | ||
|
|
2ff248a4c4 | ||
|
|
0010976803 | ||
|
|
bd422323ee | ||
|
|
6a66d99017 | ||
|
|
1f7d92b209 | ||
|
|
71c72136f6 | ||
|
|
7f57e64292 | ||
|
|
300a0ece75 | ||
|
|
3172ba213e | ||
|
|
5685ccbffd | ||
|
|
3e372f753d | ||
|
|
51ea8a5e1c | ||
|
|
d3c634b89f | ||
|
|
eca341d7ec | ||
|
|
454ca3b136 | ||
|
|
e3b8b450a4 | ||
|
|
c15c486603 | ||
|
|
c6554afaae | ||
|
|
0d6b5ce68c | ||
|
|
2830400da5 | ||
|
|
aa8edf1ee9 | ||
|
|
f1bf439f7b | ||
|
|
220992fc37 | ||
|
|
de52af79c5 | ||
|
|
c555102b5f | ||
|
|
4c05175dbe | ||
|
|
de543fa2db | ||
|
|
a0f85bd55a | ||
|
|
7273caac36 | ||
|
|
d8383dc38a | ||
|
|
7824ab8dc9 | ||
|
|
7320bf68e5 | ||
|
|
d7b9492f15 |
@@ -149,6 +149,7 @@ module.exports = function (grunt) {
|
||||
'build/tmp/custom/Espo/Custom/*',
|
||||
'!build/tmp/custom/Espo/Custom/.htaccess',
|
||||
'build/tmp/install/config.php',
|
||||
'build/tmp/vendor/*/*/.git',
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -69,7 +69,7 @@ class Attachment extends \Espo\Core\Controllers\Record
|
||||
$response->setHeader('Content-Type', $fileData->type);
|
||||
$response->setHeader('Content-Disposition', 'Content-Disposition: attachment; filename="'.$fileData->name.'"');
|
||||
if ($fileData->size) {
|
||||
$response->setHeader('Content-Length', $fileData->size);
|
||||
$response->setHeader('Content-Length', strlen($fileData->contents));
|
||||
}
|
||||
|
||||
return $fileData->contents;
|
||||
|
||||
@@ -185,6 +185,8 @@ class Auth
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->processException($response, $e);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($authResult && ($authResult->isSuccess() || $authResult->isSecondStepRequired())) {
|
||||
|
||||
@@ -50,10 +50,19 @@ class ErrorOutput
|
||||
404 => 'Page Not Found',
|
||||
409 => 'Conflict',
|
||||
500 => 'Internal Server Error',
|
||||
503 => 'Service Unavailable',
|
||||
];
|
||||
|
||||
protected $allowedStatusCodeList = [
|
||||
200, 201, 400, 401, 403, 404, 409, 500,
|
||||
200,
|
||||
201,
|
||||
400,
|
||||
401,
|
||||
403,
|
||||
404,
|
||||
409,
|
||||
500,
|
||||
503,
|
||||
];
|
||||
|
||||
protected $ignorePrintXStatusReasonExceptionClassNameList = [
|
||||
|
||||
@@ -305,11 +305,11 @@ class LDAP extends Espo
|
||||
$data[$fieldName] = $fieldValue;
|
||||
}
|
||||
|
||||
$this->useSystemUser();
|
||||
|
||||
$user = $this->entityManager->getEntity('User');
|
||||
$user->set($data);
|
||||
|
||||
$this->applicationUser->setUser($user);
|
||||
|
||||
$this->entityManager->saveEntity($user);
|
||||
|
||||
return $this->entityManager->getEntity('User', $user->id);
|
||||
|
||||
@@ -57,6 +57,7 @@ class EntryPointManager
|
||||
public function checkAuthRequired(string $name) : bool
|
||||
{
|
||||
$className = $this->getClassName($name);
|
||||
|
||||
if (!$className) {
|
||||
throw new NotFound("EntryPoint {$name} not found.");
|
||||
}
|
||||
@@ -72,6 +73,7 @@ class EntryPointManager
|
||||
public function checkNotStrictAuth(string $name) : bool
|
||||
{
|
||||
$className = $this->getClassName($name);
|
||||
|
||||
if (!$className) {
|
||||
throw new NotFound("EntryPoint {$name} not found.");
|
||||
}
|
||||
@@ -82,6 +84,7 @@ class EntryPointManager
|
||||
public function run(string $name, Request $request, Response $response, ?StdClass $data = null)
|
||||
{
|
||||
$className = $this->getClassName($name);
|
||||
|
||||
if (!$className) {
|
||||
throw new NotFound("EntryPoint {$name} not found.");
|
||||
}
|
||||
@@ -94,6 +97,7 @@ class EntryPointManager
|
||||
protected function getClassName(string $name) : ?string
|
||||
{
|
||||
$name = ucfirst($name);
|
||||
|
||||
return $this->classFinder->find('EntryPoints', $name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
namespace Espo\Core\ExternalAccount\OAuth2;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Client
|
||||
{
|
||||
const AUTH_TYPE_URI = 0;
|
||||
@@ -79,7 +81,7 @@ class Client
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
if (!extension_loaded('curl')) {
|
||||
throw new \Exception('CURL extension not found.');
|
||||
throw new Exception('CURL extension not found.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,16 +141,21 @@ class Client
|
||||
switch ($this->tokenType) {
|
||||
case self::TOKEN_TYPE_URI:
|
||||
$params[$this->accessTokenParamName] = $this->accessToken;
|
||||
|
||||
break;
|
||||
|
||||
case self::TOKEN_TYPE_BEARER:
|
||||
$httpHeaders['Authorization'] = 'Bearer ' . $this->accessToken;
|
||||
|
||||
break;
|
||||
|
||||
case self::TOKEN_TYPE_OAUTH:
|
||||
$httpHeaders['Authorization'] = 'OAuth ' . $this->accessToken;
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('Unknown access token type.');
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unknown access token type.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,15 +164,16 @@ class Client
|
||||
|
||||
private function execute($url, $params, $httpMethod, array $httpHeaders = [])
|
||||
{
|
||||
$curlOptions = array(
|
||||
$curlOptions = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $httpMethod
|
||||
);
|
||||
CURLOPT_CUSTOMREQUEST => $httpMethod,
|
||||
];
|
||||
|
||||
switch ($httpMethod) {
|
||||
case self::HTTP_METHOD_POST:
|
||||
$curlOptions[CURLOPT_POST] = true;
|
||||
|
||||
case self::HTTP_METHOD_PUT:
|
||||
case self::HTTP_METHOD_PATCH:
|
||||
if (is_array($params)) {
|
||||
@@ -173,36 +181,49 @@ class Client
|
||||
} else {
|
||||
$postFields = $params;
|
||||
}
|
||||
|
||||
$curlOptions[CURLOPT_POSTFIELDS] = $postFields;
|
||||
|
||||
break;
|
||||
|
||||
case self::HTTP_METHOD_HEAD:
|
||||
$curlOptions[CURLOPT_NOBODY] = true;
|
||||
|
||||
case self::HTTP_METHOD_DELETE:
|
||||
case self::HTTP_METHOD_GET:
|
||||
|
||||
if (strpos($url, '?') === false) {
|
||||
$url .= '?';
|
||||
}
|
||||
|
||||
if (is_array($params)) {
|
||||
$url .= http_build_query($params, null, '&');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$curlOptions[CURLOPT_URL] = $url;
|
||||
|
||||
$curlOptHttpHeader = array();
|
||||
$curlOptHttpHeader = [];
|
||||
|
||||
foreach ($httpHeaders as $key => $value) {
|
||||
if (is_int($key) && !is_string($key)) {
|
||||
$curlOptHttpHeader[] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$curlOptHttpHeader[] = "{$key}: {$value}";
|
||||
}
|
||||
|
||||
$curlOptions[CURLOPT_HTTPHEADER] = $curlOptHttpHeader;
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt_array($ch, $curlOptions);
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||
@@ -230,18 +251,20 @@ class Client
|
||||
$resultArray = null;
|
||||
|
||||
if ($curlError = curl_error($ch)) {
|
||||
throw new \Exception($curlError);
|
||||
} else {
|
||||
throw new Exception($curlError);
|
||||
}
|
||||
else {
|
||||
$resultArray = json_decode($responceBody, true);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return array(
|
||||
return [
|
||||
'result' => (null !== $resultArray) ? $resultArray: $responceBody,
|
||||
'code' => intval($httpCode),
|
||||
'contentType' => $contentType,
|
||||
'header' => $responceHeader,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function getAccessToken($url, $grantType, array $params)
|
||||
@@ -249,18 +272,24 @@ class Client
|
||||
$params['grant_type'] = $grantType;
|
||||
|
||||
$httpHeaders = [];
|
||||
switch ($this->tokenType) {
|
||||
|
||||
switch ($this->authType) {
|
||||
case self::AUTH_TYPE_URI:
|
||||
case self::AUTH_TYPE_FORM:
|
||||
$params['client_id'] = $this->clientId;
|
||||
$params['client_secret'] = $this->clientSecret;
|
||||
|
||||
break;
|
||||
|
||||
case self::AUTH_TYPE_AUTHORIZATION_BASIC:
|
||||
$params['client_id'] = $this->clientId;
|
||||
|
||||
$httpHeaders['Authorization'] = 'Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \Exception();
|
||||
throw new Exception("Bad auth type.");
|
||||
}
|
||||
|
||||
return $this->execute($url, $params, self::HTTP_METHOD_POST, $httpHeaders);
|
||||
|
||||
@@ -42,7 +42,7 @@ class AddressBuilder
|
||||
|
||||
protected $state;
|
||||
|
||||
protected $portalCode;
|
||||
protected $postalCode;
|
||||
|
||||
public function clone(AddressValue $address) : self
|
||||
{
|
||||
|
||||
@@ -46,7 +46,7 @@ class AddressValue
|
||||
|
||||
protected $state = null;
|
||||
|
||||
protected $portalCode = null;
|
||||
protected $postalCode = null;
|
||||
|
||||
public function getStreet() : ?string
|
||||
{
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\ArrayGroup;
|
||||
|
||||
use Espo\Core\Formula\{
|
||||
Functions\BaseFunction,
|
||||
ArgumentList,
|
||||
};
|
||||
|
||||
class JoinType extends BaseFunction
|
||||
{
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
if (count($args) < 2) {
|
||||
$this->throwTooFewArguments();
|
||||
}
|
||||
|
||||
$list = $this->evaluate($args[0]);
|
||||
|
||||
$separator = $this->evaluate($args[1]);
|
||||
|
||||
if (!is_string($separator)) {
|
||||
$this->throwBadArgumentValue(2, 'string');
|
||||
}
|
||||
|
||||
if (is_null($list)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return implode($separator, $list);
|
||||
}
|
||||
}
|
||||
@@ -38,18 +38,19 @@ class ModuloType extends BaseFunction
|
||||
{
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
$result = 1;
|
||||
|
||||
foreach ($args as $subItem) {
|
||||
$part = $this->evaluate($subItem);
|
||||
|
||||
if (!is_float($part) && !is_int($part)) {
|
||||
$part = floatval($part);
|
||||
}
|
||||
|
||||
$result %= $part;
|
||||
if (count($args) < 2) {
|
||||
$this->throwTooFewArguments();
|
||||
}
|
||||
|
||||
$result = $this->evaluate($args[0]);
|
||||
$part = $this->evaluate($args[1]);
|
||||
|
||||
if (!is_float($part) && !is_int($part)) {
|
||||
$part = floatval($part);
|
||||
}
|
||||
|
||||
$result %= $part;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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\UtilGroup;
|
||||
|
||||
use Espo\Core\Formula\{
|
||||
ArgumentList,
|
||||
};
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
class GenerateIdType
|
||||
{
|
||||
public function process(ArgumentList $args)
|
||||
{
|
||||
return Util::generateId();
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,8 @@ use Espo\Core\{
|
||||
Utils\Config,
|
||||
};
|
||||
|
||||
use LogicException;
|
||||
use UnexpectedValueException;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class EspoFileHandler extends MonologStreamHandler
|
||||
{
|
||||
@@ -60,35 +60,34 @@ class EspoFileHandler extends MonologStreamHandler
|
||||
protected function write(array $record): void
|
||||
{
|
||||
if (!$this->url) {
|
||||
throw new LogicException(
|
||||
'Missing logger path. Check logger params in the data/config.php.'
|
||||
throw new RuntimeException(
|
||||
"Missing a logger file path. Check logger params in `data/config.php`."
|
||||
);
|
||||
}
|
||||
|
||||
$this->errorMessage = null;
|
||||
try {
|
||||
if (!is_writable($this->url)) {
|
||||
$this->fileManager->checkCreateFile($this->url);
|
||||
}
|
||||
|
||||
if (!is_writable($this->url)) {
|
||||
$this->fileManager->checkCreateFile($this->url);
|
||||
}
|
||||
if (!is_writable($this->url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_writable($this->url)) {
|
||||
set_error_handler([$this, 'customErrorHandler']);
|
||||
|
||||
$this->fileManager->appendContents($this->url, $this->pruneMessage($record));
|
||||
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
if (isset($this->errorMessage)) {
|
||||
throw new UnexpectedValueException(
|
||||
sprintf('File "%s" could not be opened: ' . $this->errorMessage, $this->url)
|
||||
$this->fileManager->appendContents(
|
||||
$this->url,
|
||||
$this->pruneMessage($record)
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$msg = "Could not write file `" . $this->url . "`.";
|
||||
|
||||
private function customErrorHandler($code, $msg)
|
||||
{
|
||||
$this->errorMessage = $msg;
|
||||
if ($e->getMessage()) {
|
||||
$msg .= " Error message: " . $e->getMessage();
|
||||
}
|
||||
|
||||
throw new RuntimeException($msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected function pruneMessage(array $record)
|
||||
@@ -97,6 +96,7 @@ class EspoFileHandler extends MonologStreamHandler
|
||||
|
||||
if (strlen($message) > $this->maxErrorMessageLength) {
|
||||
$record['message'] = substr($message, 0, $this->maxErrorMessageLength) . '...';
|
||||
|
||||
$record['formatted'] = $this->getFormatter()->format($record);
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ class Tcpdf extends \TCPDF
|
||||
$html = str_replace('{pageNumber}', '{{:png:}}', $html);
|
||||
$html = str_replace('{pageAbsoluteNumber}', '{{:pnp:}}', $html);
|
||||
} else {
|
||||
$html = str_replace('{pageNumber}', '{{:pnp:}', $html);
|
||||
$html = str_replace('{pageNumber}', '{{:pnp:}}', $html);
|
||||
$html = str_replace('{pageAbsoluteNumber}', '{{:pnp:}}', $html);
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,12 @@ class NamespaceLoader
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type == 'classmap') {
|
||||
$classLoader->$methodName($list[$type]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($list[$type] as $prefix => $path) {
|
||||
if (!$skipVendorNamespaces) {
|
||||
$vendorNamespaces = $this->getVendorNamespaces($path);
|
||||
|
||||
@@ -165,13 +165,22 @@ class Job
|
||||
public function hasScheduledJobOnMinute(string $scheduledJobId, string $time) : bool
|
||||
{
|
||||
$dateObj = new DateTime($time);
|
||||
$timeWithoutSeconds = $dateObj->format('Y-m-d H:i:');
|
||||
|
||||
$fromString = $dateObj->format('Y-m-d H:i:00');
|
||||
$toString = $dateObj->format('Y-m-d H:i:59');
|
||||
|
||||
$job = $this->getEntityManager()->getRepository('Job')
|
||||
->select(['id'])
|
||||
->where([
|
||||
'scheduledJobId' => $scheduledJobId,
|
||||
'executeTime*' => $timeWithoutSeconds . '%',
|
||||
'status' => [ // This forces usage of an appropriate index.
|
||||
CronManager::PENDING,
|
||||
CronManager::READY,
|
||||
CronManager::RUNNING,
|
||||
CronManager::SUCCESS,
|
||||
],
|
||||
'executeTime>=' => $fromString,
|
||||
'executeTime<=' => $toString,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
|
||||
@@ -29,8 +29,11 @@
|
||||
|
||||
namespace Espo\Core\Utils\Database;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Core\{
|
||||
Utils\Metadata,
|
||||
Utils\File\Manager as FileManager,
|
||||
Utils\Config,
|
||||
};
|
||||
|
||||
class Converter
|
||||
{
|
||||
@@ -40,11 +43,7 @@ class Converter
|
||||
|
||||
private $config;
|
||||
|
||||
private $schemaConverter;
|
||||
|
||||
private $schemaFromMetadata = null;
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Config $config = null)
|
||||
public function __construct(Metadata $metadata, FileManager $fileManager, Config $config = null)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->fileManager = $fileManager;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Driver\PDO\MySQL;
|
||||
|
||||
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
|
||||
use Doctrine\DBAL\Driver\PDO\Connection;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use PDO;
|
||||
|
||||
// Espo: requires for the issue https://github.com/doctrine/dbal/issues/4496
|
||||
use Espo\Core\Utils\Database\DBAL\Schema\MySQLSchemaManager;
|
||||
use Doctrine\DBAL\Connection as MySQLDriverConnection;
|
||||
// End: espo
|
||||
|
||||
final class Driver extends AbstractMySQLDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return Connection
|
||||
*/
|
||||
public function connect(array $params)
|
||||
{
|
||||
$driverOptions = $params['driverOptions'] ?? [];
|
||||
|
||||
if (! empty($params['persistent'])) {
|
||||
$driverOptions[PDO::ATTR_PERSISTENT] = true;
|
||||
}
|
||||
|
||||
return new Connection(
|
||||
$this->constructPdoDsn($params),
|
||||
$params['user'] ?? '',
|
||||
$params['password'] ?? '',
|
||||
$driverOptions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the MySQL PDO DSN.
|
||||
*
|
||||
* @param mixed[] $params
|
||||
*
|
||||
* @return string The DSN.
|
||||
*/
|
||||
protected function constructPdoDsn(array $params)
|
||||
{
|
||||
$dsn = 'mysql:';
|
||||
if (isset($params['host']) && $params['host'] !== '') {
|
||||
$dsn .= 'host=' . $params['host'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['port'])) {
|
||||
$dsn .= 'port=' . $params['port'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['dbname'])) {
|
||||
$dsn .= 'dbname=' . $params['dbname'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['unix_socket'])) {
|
||||
$dsn .= 'unix_socket=' . $params['unix_socket'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['charset'])) {
|
||||
$dsn .= 'charset=' . $params['charset'] . ';';
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
// Espo: requires for the issue https://github.com/doctrine/dbal/issues/4496
|
||||
public function getSchemaManager(MySQLDriverConnection $conn, AbstractPlatform $platform)
|
||||
{
|
||||
return new MySQLSchemaManager($conn, $platform);
|
||||
}
|
||||
// End: espo
|
||||
}
|
||||
@@ -92,6 +92,9 @@ class Comparator extends OriginalComparator
|
||||
if ($length2 > $length1) {
|
||||
$changedProperties[] = 'length';
|
||||
}
|
||||
if ($length2 < $length1) {
|
||||
$column2->setLength($length1);
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
if ($properties1['fixed'] !== $properties2['fixed']) {
|
||||
@@ -136,7 +139,10 @@ class Comparator extends OriginalComparator
|
||||
}
|
||||
|
||||
// Espo: skip collation changes
|
||||
if ($key == 'collation') continue;
|
||||
if ($key == 'collation') {
|
||||
$column2->setPlatformOption('collation', $platformOptions1['collation']);
|
||||
continue;
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
$changedProperties[] = $key;
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\MySQLSchemaManager as OriginalComparator;
|
||||
|
||||
// Espo: requires for the issue https://github.com/doctrine/dbal/issues/4496
|
||||
|
||||
//use Doctrine\DBAL\Platforms\MariaDb1027Platform;
|
||||
//use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Espo\Core\Utils\Database\DBAL\Platforms\{
|
||||
MariaDb1027Platform,
|
||||
MySQLPlatform,
|
||||
};
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
// Espo: end
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_shift;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function strpos;
|
||||
use function strtok;
|
||||
use function strtolower;
|
||||
use function strtr;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
class MySQLSchemaManager extends OriginalComparator
|
||||
{
|
||||
private const MARIADB_ESCAPE_SEQUENCES = [
|
||||
'\\0' => "\0",
|
||||
"\\'" => "'",
|
||||
'\\"' => '"',
|
||||
'\\b' => "\b",
|
||||
'\\n' => "\n",
|
||||
'\\r' => "\r",
|
||||
'\\t' => "\t",
|
||||
'\\Z' => "\x1a",
|
||||
'\\\\' => '\\',
|
||||
'\\%' => '%',
|
||||
'\\_' => '_',
|
||||
|
||||
// Internally, MariaDB escapes single quotes using the standard syntax
|
||||
"''" => "'",
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
$dbType = strtok($dbType, '(), ');
|
||||
assert(is_string($dbType));
|
||||
|
||||
$length = $tableColumn['length'] ?? strtok('(), ');
|
||||
|
||||
$fixed = null;
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
$scale = null;
|
||||
$precision = null;
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
|
||||
// In cases where not connected to a database DESCRIBE $table does not return 'Comment'
|
||||
if (isset($tableColumn['comment'])) {
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
}
|
||||
|
||||
switch ($dbType) {
|
||||
case 'char':
|
||||
case 'binary':
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'real':
|
||||
case 'numeric':
|
||||
case 'decimal':
|
||||
if (
|
||||
preg_match(
|
||||
'([A-Za-z]+\(([0-9]+),([0-9]+)\))',
|
||||
$tableColumn['type'],
|
||||
$match
|
||||
) === 1
|
||||
) {
|
||||
$precision = $match[1];
|
||||
$scale = $match[2];
|
||||
$length = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'tinytext':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_TINYTEXT;
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_TEXT;
|
||||
break;
|
||||
|
||||
case 'mediumtext':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT;
|
||||
break;
|
||||
|
||||
case 'tinyblob':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_TINYBLOB;
|
||||
break;
|
||||
|
||||
case 'blob':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_BLOB;
|
||||
break;
|
||||
|
||||
case 'mediumblob':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB;
|
||||
break;
|
||||
|
||||
case 'tinyint':
|
||||
case 'smallint':
|
||||
case 'mediumint':
|
||||
case 'int':
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
case 'year':
|
||||
$length = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->_platform instanceof MariaDb1027Platform) {
|
||||
$columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']);
|
||||
} else {
|
||||
$columnDefault = $tableColumn['default'];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length !== null ? (int) $length : null,
|
||||
'unsigned' => strpos($tableColumn['type'], 'unsigned') !== false,
|
||||
'fixed' => (bool) $fixed,
|
||||
'default' => $columnDefault,
|
||||
'notnull' => $tableColumn['null'] !== 'YES',
|
||||
'scale' => null,
|
||||
'precision' => null,
|
||||
'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false,
|
||||
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
|
||||
? $tableColumn['comment']
|
||||
: null,
|
||||
];
|
||||
|
||||
if ($scale !== null && $precision !== null) {
|
||||
$options['scale'] = (int) $scale;
|
||||
$options['precision'] = (int) $precision;
|
||||
}
|
||||
|
||||
$column = new Column($tableColumn['field'], Type::getType($type), $options);
|
||||
|
||||
if (isset($tableColumn['characterset'])) {
|
||||
$column->setPlatformOption('charset', $tableColumn['characterset']);
|
||||
}
|
||||
|
||||
if (isset($tableColumn['collation'])) {
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?string $columnDefault): ?string
|
||||
{
|
||||
if ($columnDefault === 'NULL' || $columnDefault === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) {
|
||||
return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES);
|
||||
}
|
||||
|
||||
switch ($columnDefault) {
|
||||
case 'current_timestamp()':
|
||||
return $platform->getCurrentTimestampSQL();
|
||||
|
||||
case 'curdate()':
|
||||
return $platform->getCurrentDateSQL();
|
||||
|
||||
case 'curtime()':
|
||||
return $platform->getCurrentTimeSQL();
|
||||
}
|
||||
|
||||
return $columnDefault;
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,8 @@
|
||||
|
||||
namespace Espo\Core\Utils\Database;
|
||||
|
||||
use PDO;
|
||||
use ReflectionClass;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\Error,
|
||||
Utils\Util,
|
||||
Utils\Config,
|
||||
};
|
||||
|
||||
@@ -44,6 +39,9 @@ use Doctrine\DBAL\{
|
||||
Platforms\AbstractPlatform as DbalPlatform,
|
||||
};
|
||||
|
||||
use PDO;
|
||||
use ReflectionClass;
|
||||
|
||||
class Helper
|
||||
{
|
||||
private $config;
|
||||
@@ -53,15 +51,15 @@ class Helper
|
||||
private $pdoConnection;
|
||||
|
||||
protected $dbalDrivers = [
|
||||
'mysqli' => '\\Doctrine\\DBAL\\Driver\\Mysqli\\Driver',
|
||||
'pdo_mysql' => '\\Doctrine\\DBAL\\Driver\\PDO\\MySQL\\Driver',
|
||||
'mysqli' => 'Doctrine\\DBAL\\Driver\\Mysqli\\Driver',
|
||||
'pdo_mysql' => 'Espo\\Core\\Utils\\Database\\DBAL\\Driver\\PDO\\MySQL\\Driver',
|
||||
];
|
||||
|
||||
protected $dbalPlatforms = [
|
||||
'MariaDb1027Platform' => '\\Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MariaDb1027Platform',
|
||||
'MySQL57Platform' => '\\Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQL57Platform',
|
||||
'MySQL80Platform' => '\\Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQL80Platform',
|
||||
'MySQLPlatform' => '\\Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQLPlatform',
|
||||
'MariaDb1027Platform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MariaDb1027Platform',
|
||||
'MySQL57Platform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQL57Platform',
|
||||
'MySQL80Platform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQL80Platform',
|
||||
'MySQLPlatform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQLPlatform',
|
||||
];
|
||||
|
||||
public function __construct(Config $config = null)
|
||||
@@ -106,6 +104,7 @@ class Helper
|
||||
{
|
||||
if (!isset($params)) {
|
||||
$config = $this->getConfig();
|
||||
|
||||
if ($config) {
|
||||
$params = $config->get('database');
|
||||
}
|
||||
@@ -116,6 +115,7 @@ class Helper
|
||||
}
|
||||
|
||||
$driverName = isset($params['driver']) ? $params['driver'] : 'pdo_mysql';
|
||||
|
||||
unset($params['driver']);
|
||||
|
||||
if (!isset($this->dbalDrivers[$driverName])) {
|
||||
@@ -131,23 +131,23 @@ class Helper
|
||||
$driver = new $driverClass();
|
||||
|
||||
$version = $this->getFullDatabaseVersion();
|
||||
|
||||
$platform = $driver->createDatabasePlatformForVersion($version);
|
||||
|
||||
$params['platform'] = $this->createDbalPlatform($platform);
|
||||
|
||||
return new DbalConnection(
|
||||
$params,
|
||||
$driver
|
||||
);
|
||||
return new DbalConnection($params, $driver);
|
||||
}
|
||||
|
||||
protected function createDbalPlatform(DbalPlatform $platform)
|
||||
{
|
||||
$reflect = new ReflectionClass($platform);
|
||||
|
||||
$platformClass = $reflect->getShortName();
|
||||
|
||||
if (isset($this->dbalPlatforms[$platformClass])) {
|
||||
$class = $this->dbalPlatforms[$platformClass];
|
||||
|
||||
return new $class();
|
||||
}
|
||||
|
||||
@@ -155,14 +155,16 @@ class Helper
|
||||
}
|
||||
|
||||
/**
|
||||
* Create PDO connection
|
||||
* @param array $params
|
||||
* @return \Pdo| \PDOException
|
||||
* Create PDO connection.
|
||||
*
|
||||
* @param array $params
|
||||
* @return PDO|\PDOException
|
||||
*/
|
||||
public function createPdoConnection(array $params = null)
|
||||
{
|
||||
if (!isset($params)) {
|
||||
$config = $this->getConfig();
|
||||
|
||||
if ($config) {
|
||||
$params = $config->get('database');
|
||||
}
|
||||
@@ -176,40 +178,45 @@ class Helper
|
||||
$port = empty($params['port']) ? '' : ';port=' . $params['port'];
|
||||
$dbname = empty($params['dbname']) ? '' : ';dbname=' . $params['dbname'];
|
||||
|
||||
$options = array();
|
||||
$options = [];
|
||||
|
||||
if (isset($params['sslCA'])) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CA] = $params['sslCA'];
|
||||
}
|
||||
|
||||
if (isset($params['sslCert'])) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CERT] = $params['sslCert'];
|
||||
}
|
||||
|
||||
if (isset($params['sslKey'])) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_KEY] = $params['sslKey'];
|
||||
}
|
||||
|
||||
if (isset($params['sslCAPath'])) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CAPATH] = $params['sslCAPath'];
|
||||
}
|
||||
|
||||
if (isset($params['sslCipher'])) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CIPHER] = $params['sslCipher'];
|
||||
}
|
||||
|
||||
$dsn = $platform . ':host='.$params['host'].$port.$dbname;
|
||||
$dbh = new \PDO($dsn, $params['user'], $params['password'], $options);
|
||||
|
||||
$dbh = new PDO($dsn, $params['user'], $params['password'], $options);
|
||||
|
||||
return $dbh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum index length. If $tableName is empty get a value for all database tables
|
||||
*
|
||||
* @param string|null $tableName
|
||||
* Get maximum index length. If $tableName is empty get a value for all database tables.
|
||||
*
|
||||
* @param ?string $tableName
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxIndexLength($tableName = null, $default = 1000)
|
||||
{
|
||||
$tableEngine = $this->getTableEngine($tableName);
|
||||
|
||||
if (!$tableEngine) {
|
||||
return $default;
|
||||
}
|
||||
@@ -224,17 +231,18 @@ class Helper
|
||||
if (version_compare($version, '10.2.2') >= 0) {
|
||||
return 3072; //InnoDB, MariaDB 10.2.2+
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'MySQL':
|
||||
if (version_compare($version, '5.7.0') >= 0) {
|
||||
return 3072; //InnoDB, MySQL 5.7+
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return 767; //InnoDB
|
||||
break;
|
||||
}
|
||||
|
||||
return 1000; //MyISAM
|
||||
@@ -263,19 +271,22 @@ class Helper
|
||||
protected function getFullDatabaseVersion()
|
||||
{
|
||||
$connection = $this->getPdoConnection();
|
||||
|
||||
if (!$connection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sth = $connection->prepare("select version()");
|
||||
|
||||
$sth->execute();
|
||||
|
||||
return $sth->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Database version
|
||||
* @return string|null
|
||||
* Get Database version.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getDatabaseVersion()
|
||||
{
|
||||
@@ -287,7 +298,7 @@ class Helper
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table/database tables engine. If $tableName is empty get a value for all database tables
|
||||
* Get table/database tables engine. If $tableName is empty get a value for all database tables.
|
||||
*
|
||||
* @param string|null $tableName
|
||||
*
|
||||
@@ -318,15 +329,16 @@ class Helper
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if full text supports. If $tableName is empty get a value for all database tables
|
||||
* Check if full text is supported. If $tableName is empty get a value for all database tables.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSupportsFulltext($tableName = null, $default = false)
|
||||
public function doesSupportFulltext($tableName = null, $default = false)
|
||||
{
|
||||
$tableEngine = $this->getTableEngine($tableName);
|
||||
|
||||
if (!$tableEngine) {
|
||||
return $default;
|
||||
}
|
||||
@@ -340,33 +352,34 @@ class Helper
|
||||
}
|
||||
|
||||
return false; //InnoDB
|
||||
break;
|
||||
}
|
||||
|
||||
return true; //MyISAM
|
||||
}
|
||||
|
||||
public function isTableSupportsFulltext($tableName, $default = false)
|
||||
public function doesTableSupportFulltext($tableName, $default = false)
|
||||
{
|
||||
return $this->isSupportsFulltext($tableName, $default);
|
||||
return $this->doesSupportFulltext($tableName, $default);
|
||||
}
|
||||
|
||||
public function getPdoDatabaseParam($name, \PDO $pdoConnection)
|
||||
public function getPdoDatabaseParam($name, PDO $pdoConnection)
|
||||
{
|
||||
if (!method_exists($pdoConnection, 'prepare')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sth = $pdoConnection->prepare("SHOW VARIABLES LIKE '" . $name . "'");
|
||||
|
||||
$sth->execute();
|
||||
$res = $sth->fetch(\PDO::FETCH_NUM);
|
||||
|
||||
$res = $sth->fetch(PDO::FETCH_NUM);
|
||||
|
||||
$version = empty($res[1]) ? null : $res[1];
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
public function getPdoDatabaseVersion(\PDO $pdoConnection)
|
||||
public function getPdoDatabaseVersion(PDO $pdoConnection)
|
||||
{
|
||||
return $this->getPdoDatabaseParam('version', $pdoConnection);
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ class Converter
|
||||
|
||||
protected function applyFullTextSearch(array &$ormMetadata, string $entityType)
|
||||
{
|
||||
if (!$this->getDatabaseHelper()->isTableSupportsFulltext(Util::toUnderScore($entityType))) return;
|
||||
if (!$this->getDatabaseHelper()->doesTableSupportFulltext(Util::toUnderScore($entityType))) return;
|
||||
if (!$this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'fullTextSearch'])) return;
|
||||
|
||||
$fieldList = $this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'textFilterFields'], ['name']);
|
||||
|
||||
@@ -31,13 +31,13 @@ namespace Espo\Core\Utils\File;
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\Error,
|
||||
Utils\Config,
|
||||
Utils\Util,
|
||||
Utils\Json,
|
||||
};
|
||||
|
||||
use Exception;
|
||||
use StdClass;
|
||||
use Throwable;
|
||||
|
||||
class Manager
|
||||
{
|
||||
@@ -261,8 +261,8 @@ class Manager
|
||||
$result = (file_put_contents($fullPath, $data, $flags) !== FALSE);
|
||||
}
|
||||
|
||||
if ($result && function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($fullPath);
|
||||
if ($result) {
|
||||
$this->opcacheInvalidate($fullPath);
|
||||
}
|
||||
|
||||
return $result;
|
||||
@@ -620,9 +620,7 @@ class Manager
|
||||
|
||||
$this->getPermissionUtils()->setDefaultPermissions($destFile);
|
||||
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($destFile);
|
||||
}
|
||||
$this->opcacheInvalidate($destFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,9 +722,7 @@ class Manager
|
||||
}
|
||||
|
||||
if (file_exists($filePath) && is_file($filePath)) {
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($filePath, true);
|
||||
}
|
||||
$this->opcacheInvalidate($filePath, true);
|
||||
|
||||
$result &= unlink($filePath);
|
||||
}
|
||||
@@ -1112,4 +1108,16 @@ class Manager
|
||||
|
||||
return preg_replace('/^'. preg_quote($basePath, $dirSeparator) . '/', '', $path);
|
||||
}
|
||||
|
||||
protected function opcacheInvalidate(string $filepath, bool $force = false)
|
||||
{
|
||||
if (!function_exists('opcache_invalidate')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
opcache_invalidate($filepath, $force);
|
||||
}
|
||||
catch (Throwable $e) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ class Permission
|
||||
protected function chmodReal($filename, $mode)
|
||||
{
|
||||
try {
|
||||
$result = chmod($filename, $mode);
|
||||
$result = @chmod($filename, $mode);
|
||||
} catch (\Exception $e) {
|
||||
$result = false;
|
||||
}
|
||||
@@ -400,7 +400,7 @@ class Permission
|
||||
$this->chgrp($filename, $this->getDefaultGroup(true));
|
||||
|
||||
try {
|
||||
$result = chmod($filename, $mode);
|
||||
$result = @chmod($filename, $mode);
|
||||
} catch (\Exception $e) {
|
||||
throw new Error($e->getMessage());
|
||||
}
|
||||
@@ -412,7 +412,7 @@ class Permission
|
||||
protected function chownReal($path, $user)
|
||||
{
|
||||
try {
|
||||
$result = chown($path, $user);
|
||||
$result = @chown($path, $user);
|
||||
} catch (\Exception $e) {
|
||||
throw new Error($e->getMessage());
|
||||
}
|
||||
@@ -423,7 +423,7 @@ class Permission
|
||||
protected function chgrpReal($path, $group)
|
||||
{
|
||||
try {
|
||||
$result = chgrp($path, $group);
|
||||
$result = @chgrp($path, $group);
|
||||
} catch (\Exception $e) {
|
||||
throw new Error($e->getMessage());
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ use Espo\Core\EntryPoints\EntryPoint;
|
||||
use Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
|
||||
class Attachment implements EntryPoint,
|
||||
Di\EntityManagerAware,
|
||||
@@ -52,7 +53,7 @@ class Attachment implements EntryPoint,
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id');
|
||||
|
||||
@@ -88,7 +89,9 @@ class Attachment implements EntryPoint,
|
||||
|
||||
header('Pragma: public');
|
||||
header('Content-Length: ' . filesize($fileName));
|
||||
|
||||
readfile($fileName);
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,15 +29,13 @@
|
||||
|
||||
namespace Espo\EntryPoints;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\EntryPoints\NotStrictAuth;
|
||||
use Espo\Core\Di;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
|
||||
class Avatar extends Image implements Di\MetadataAware
|
||||
{
|
||||
@@ -73,7 +71,7 @@ class Avatar extends Image implements Di\MetadataAware
|
||||
return $colorList[$index];
|
||||
}
|
||||
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$userId = $request->get('id');
|
||||
$size = $request->get('size') ?? null;
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
|
||||
namespace Espo\EntryPoints;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
@@ -43,6 +41,7 @@ use Espo\Core\{
|
||||
Utils\ClientManager,
|
||||
ServiceFactory,
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
};
|
||||
|
||||
class ConfirmOptIn implements EntryPoint
|
||||
@@ -58,19 +57,23 @@ class ConfirmOptIn implements EntryPoint
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
}
|
||||
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id');
|
||||
|
||||
if (!$id) throw new BadRequest();
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$data = $this->serviceFactory->create('LeadCapture')->confirmOptIn($id);
|
||||
|
||||
if ($data->status === 'success') {
|
||||
$action = 'optInConfirmationSuccess';
|
||||
} else if ($data->status === 'expired') {
|
||||
}
|
||||
else if ($data->status === 'expired') {
|
||||
$action = 'optInConfirmationExpired';
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@ use Espo\Core\EntryPoints\{
|
||||
use Espo\Core\{
|
||||
Acl,
|
||||
ORM\EntityManager,
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
};
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
|
||||
class Download implements EntryPoint
|
||||
{
|
||||
protected $fileTypesToShowInline = [
|
||||
@@ -66,11 +66,13 @@ class Download implements EntryPoint
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id');
|
||||
|
||||
if (!$id) throw new BadRequest();
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$attachment = $this->entityManager->getEntity('Attachment', $id);
|
||||
|
||||
@@ -82,34 +84,42 @@ class Download implements EntryPoint
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$sourceId = $attachment->getSourceId();
|
||||
|
||||
if ($this->entityManager->getRepository('Attachment')->hasDownloadUrl($attachment)) {
|
||||
$downloadUrl = $this->entityManager->getRepository('Attachment')->getDownloadUrl($attachment);
|
||||
$downloadUrl = $this->entityManager
|
||||
->getRepository('Attachment')
|
||||
->getDownloadUrl($attachment);
|
||||
|
||||
header('Location: ' . $downloadUrl);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
$fileName = $this->entityManager->getRepository('Attachment')->getFilePath($attachment);
|
||||
$fileName = $this->entityManager
|
||||
->getRepository('Attachment')
|
||||
->getFilePath($attachment);
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$outputFileName = $attachment->get('name');
|
||||
|
||||
$outputFileName = str_replace("\"", "\\\"", $outputFileName);
|
||||
|
||||
$type = $attachment->get('type');
|
||||
|
||||
$disposition = 'attachment';
|
||||
|
||||
if (in_array($type, $this->fileTypesToShowInline)) {
|
||||
$disposition = 'inline';
|
||||
}
|
||||
|
||||
header('Content-Description: File Transfer');
|
||||
|
||||
if ($type) {
|
||||
header('Content-Type: ' . $type);
|
||||
}
|
||||
|
||||
header("Content-Disposition: " . $disposition . ";filename=\"" . $outputFileName . "\"");
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: must-revalidate');
|
||||
@@ -117,6 +127,7 @@ class Download implements EntryPoint
|
||||
header('Content-Length: ' . filesize($fileName));
|
||||
|
||||
readfile($fileName);
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\EntryPoints\EntryPoint;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
@@ -76,7 +77,7 @@ class Image implements EntryPoint,
|
||||
|
||||
protected $allowedFieldList = null;
|
||||
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id');
|
||||
$size = $request->get('size') ?? null;
|
||||
|
||||
@@ -30,15 +30,15 @@
|
||||
namespace Espo\EntryPoints;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\EntryPoints\{
|
||||
NoAuth,
|
||||
};
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
};
|
||||
|
||||
use Espo\Core\Di;
|
||||
|
||||
@@ -51,7 +51,7 @@ class LogoImage extends Image implements Di\ConfigAware
|
||||
|
||||
protected $allowedFieldList = ['companyLogo'];
|
||||
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id');
|
||||
$size = $request->get('size') ?? null;
|
||||
|
||||
@@ -40,6 +40,7 @@ use Espo\Core\{
|
||||
ORM\EntityManager,
|
||||
ServiceFactory,
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
};
|
||||
|
||||
class Pdf implements EntryPoint
|
||||
@@ -53,7 +54,7 @@ class Pdf implements EntryPoint
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
}
|
||||
|
||||
public function run(Request $request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$entityId = $request->get('entityId');
|
||||
$entityType = $request->get('entityType');
|
||||
|
||||
@@ -63,7 +63,7 @@ class Cleanup implements Job
|
||||
|
||||
protected $config;
|
||||
protected $entityManager;
|
||||
protected $metedata;
|
||||
protected $metadata;
|
||||
protected $fileManager;
|
||||
protected $injectableFactory;
|
||||
protected $selectManagerFactory;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* 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/.phpppph
|
||||
* 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
|
||||
@@ -29,12 +29,8 @@
|
||||
|
||||
namespace Espo\Modules\Crm\EntryPoints;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\EntryPoints\{
|
||||
EntryPoint,
|
||||
@@ -42,6 +38,8 @@ use Espo\Core\EntryPoints\{
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
ORM\EntityManager,
|
||||
ServiceFactory,
|
||||
};
|
||||
@@ -59,10 +57,13 @@ class CampaignTrackOpened implements EntryPoint
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
}
|
||||
|
||||
public function run($request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id');
|
||||
if (!$id) throw new BadRequest();
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$queueItemId = $id;
|
||||
|
||||
@@ -83,28 +84,45 @@ class CampaignTrackOpened implements EntryPoint
|
||||
}
|
||||
|
||||
$massEmailId = $queueItem->get('massEmailId');
|
||||
if (!$massEmailId) return;
|
||||
|
||||
if (!$massEmailId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$massEmail = $this->entityManager->getEntity('MassEmail', $massEmailId);
|
||||
if (!$massEmail) return;
|
||||
|
||||
if (!$massEmail) {
|
||||
return;
|
||||
}
|
||||
|
||||
$campaignId = $massEmail->get('campaignId');
|
||||
if (!$campaignId) return;
|
||||
|
||||
if (!$campaignId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$campaign = $this->entityManager->getEntity('Campaign', $campaignId);
|
||||
if (!$campaign) return;
|
||||
|
||||
if (!$campaign) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$target) {
|
||||
return;
|
||||
}
|
||||
|
||||
$campaignService = $this->serviceFactory->create('Campaign');
|
||||
|
||||
$campaignService->logOpened($campaignId, $queueItemId, $target, null, $queueItem->get('isTest'));
|
||||
|
||||
header('Content-Type: image/png');
|
||||
|
||||
$img = imagecreatetruecolor(1, 1);
|
||||
|
||||
imagesavealpha($img, true);
|
||||
|
||||
$color = imagecolorallocatealpha($img, 127, 127, 127, 127);
|
||||
|
||||
imagefill($img, 0, 0, $color);
|
||||
|
||||
imagepng($img);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* 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/.phpppph
|
||||
* 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
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
namespace Espo\Modules\Crm\EntryPoints;
|
||||
|
||||
use Espo\Core\Exceptions;
|
||||
|
||||
use Espo\Modules\Crm\Entities\EmailQueueItem;
|
||||
use Espo\Modules\Crm\Entities\CampaignTrackingUrl;
|
||||
use Espo\{
|
||||
Modules\Crm\Entities\EmailQueueItem,
|
||||
Modules\Crm\Entities\CampaignTrackingUrl,
|
||||
};
|
||||
|
||||
use Espo\Core\EntryPoints\{
|
||||
EntryPoint,
|
||||
@@ -40,6 +40,10 @@ use Espo\Core\EntryPoints\{
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Exceptions\NotFoundSilent,
|
||||
Exceptions\BadRequest,
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
ORM\EntityManager,
|
||||
ServiceFactory,
|
||||
Utils\Hasher,
|
||||
@@ -75,40 +79,60 @@ class CampaignUrl implements EntryPoint
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
public function run($request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$queueItemId = $request->get('queueItemId') ?? null;
|
||||
$trackingUrlId = $request->get('id') ?? null;
|
||||
$emailAddress = $request->get('emailAddress') ?? null;
|
||||
$hash = $request->get('hash') ?? null;
|
||||
$uid = $request->get('uid') ?? null;
|
||||
|
||||
if (!$trackingUrlId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$trackingUrlId) throw new Exceptions\BadRequest();
|
||||
$trackingUrl = $this->entityManager->getEntity('CampaignTrackingUrl', $trackingUrlId);
|
||||
if (!$trackingUrl) throw new Exceptions\NotFound();
|
||||
|
||||
if (!$trackingUrl) {
|
||||
throw new NotFoundSilent("Tracking URL '{$trackingUrlId}' not found.");
|
||||
}
|
||||
|
||||
if ($emailAddress && $hash) {
|
||||
$this->processWithHash($trackingUrl, $emailAddress, $hash);
|
||||
} else {
|
||||
if (!$queueItemId) throw new Exceptions\BadRequest();
|
||||
}
|
||||
else if ($uid && $hash) {
|
||||
$this->processWithUniqueId($trackingUrl, $uid, $hash);
|
||||
}
|
||||
else {
|
||||
if (!$queueItemId) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$queueItem = $this->entityManager->getEntity('EmailQueueItem', $queueItemId);
|
||||
if (!$queueItem) throw new Exceptions\NotFound();
|
||||
|
||||
if (!$queueItem) {
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
$this->processWithQueueItem($trackingUrl, $queueItem);
|
||||
}
|
||||
|
||||
if ($trackingUrl->get('action') === 'Show Message') {
|
||||
$this->displayMessage($trackingUrl->get('message'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($trackingUrl->get('url')) {
|
||||
ob_clean();
|
||||
|
||||
header('Location: ' . $trackingUrl->get('url') . '');
|
||||
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
protected function processWithQueueItem(CampaignTrackingUrl $trackingUrl, EmailQueueItem $queueItem)
|
||||
protected function processWithQueueItem(CampaignTrackingUrl $trackingUrl, EmailQueueItem $queueItem) : void
|
||||
{
|
||||
$target = null;
|
||||
$campaign = null;
|
||||
@@ -121,6 +145,7 @@ class CampaignUrl implements EntryPoint
|
||||
}
|
||||
|
||||
$campaignId = $trackingUrl->get('campaignId');
|
||||
|
||||
if ($campaignId) {
|
||||
$campaign = $this->entityManager->getEntity('Campaign', $campaignId);
|
||||
}
|
||||
@@ -134,23 +159,27 @@ class CampaignUrl implements EntryPoint
|
||||
|
||||
if ($campaign && $target) {
|
||||
$campaignService = $this->serviceFactory->create('Campaign');
|
||||
$campaignService->logClicked($campaignId, $queueItem->id, $target, $trackingUrl, null, $queueItem->get('isTest'));
|
||||
|
||||
$campaignService->logClicked(
|
||||
$campaignId, $queueItem->id, $target, $trackingUrl, null, $queueItem->get('isTest')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function processWithHash(CampaignTrackingUrl $trackingUrl, string $emailAddress, string $hash)
|
||||
protected function processWithHash(CampaignTrackingUrl $trackingUrl, string $emailAddress, string $hash) : void
|
||||
{
|
||||
$hash2 = $this->hasher->hash($emailAddress);
|
||||
$hashActual = $this->hasher->hash($emailAddress);
|
||||
|
||||
if ($hash2 !== $hash) {
|
||||
throw new Exceptions\NotFound();
|
||||
if ($hashActual !== $hash) {
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
$eaRepository = $this->entityManager->getRepository('EmailAddress');
|
||||
|
||||
$ea = $eaRepository->getByAddress($emailAddress);
|
||||
|
||||
if (!$ea) {
|
||||
throw new Exceptions\NotFound();
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
$entityList = $eaRepository->getEntityListByAddressId($ea->id);
|
||||
@@ -163,12 +192,23 @@ class CampaignUrl implements EntryPoint
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayMessage(?string $message)
|
||||
protected function processWithUniqueId(CampaignTrackingUrl $trackingUrl, string $uid, string $hash) : void
|
||||
{
|
||||
$message = $message ?? '';
|
||||
$hashActual = $this->hasher->hash($uid);
|
||||
|
||||
if ($hashActual !== $hash) {
|
||||
throw new NotFoundSilent();
|
||||
}
|
||||
|
||||
$this->hookManager->process('CampaignTrackingUrl', 'afterClick', $trackingUrl, [], [
|
||||
'uid' => $uid,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function displayMessage(?string $message) : void
|
||||
{
|
||||
$data = [
|
||||
'message' => $message,
|
||||
'message' => $message ?? '',
|
||||
'view' => $this->metadata->get(['clientDefs', 'Campaign', 'trackinkUrlMessageView']),
|
||||
'template' => $this->metadata->get(['clientDefs', 'Campaign', 'trackinkUrlMessageTemplate']),
|
||||
];
|
||||
@@ -177,9 +217,10 @@ class CampaignUrl implements EntryPoint
|
||||
Espo.require('crm:controllers/tracking-url', function (Controller) {
|
||||
var controller = new Controller(app.baseController.params, app.getControllerInjection());
|
||||
controller.masterView = app.masterView;
|
||||
controller.doAction('displayMessage', ".json_encode($data).");
|
||||
controller.doAction('displayMessage', " . json_encode($data) . ");
|
||||
});
|
||||
";
|
||||
|
||||
$this->clientManager->display($runScript);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,7 @@
|
||||
|
||||
namespace Espo\Modules\Crm\EntryPoints;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
@@ -42,6 +39,8 @@ use Espo\Core\EntryPoints\{
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
ORM\EntityManager,
|
||||
Utils\ClientManager,
|
||||
HookManager,
|
||||
@@ -61,7 +60,7 @@ class EventConfirmation implements EntryPoint
|
||||
$this->hookManager = $hookManager;
|
||||
}
|
||||
|
||||
public function run($request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$uid = $request->get('uid') ?? null;
|
||||
$action = $request->get('action') ?? null;
|
||||
@@ -102,6 +101,7 @@ class EventConfirmation implements EntryPoint
|
||||
|
||||
$status = 'None';
|
||||
$hookMethodName = 'afterConfirmation';
|
||||
|
||||
if ($action == 'accept') {
|
||||
$status = 'Accepted';
|
||||
} else if ($action == 'decline') {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* 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/.phpppph
|
||||
* 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
|
||||
@@ -29,12 +29,8 @@
|
||||
|
||||
namespace Espo\Modules\Crm\EntryPoints;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\EntryPoints\{
|
||||
EntryPoint,
|
||||
@@ -42,6 +38,8 @@ use Espo\Core\EntryPoints\{
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
ORM\EntityManager,
|
||||
Utils\ClientManager,
|
||||
HookManager,
|
||||
@@ -77,7 +75,7 @@ class SubscribeAgain implements EntryPoint
|
||||
$this->hasher = $hasher;
|
||||
}
|
||||
|
||||
public function run($request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id') ?? null;
|
||||
$emailAddress = $request->get('emailAddress') ?? null;
|
||||
@@ -92,6 +90,7 @@ class SubscribeAgain implements EntryPoint
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$queueItemId = $id;
|
||||
|
||||
$queueItem = $this->entityManager->getEntity('EmailQueueItem', $queueItemId);
|
||||
@@ -104,10 +103,13 @@ class SubscribeAgain implements EntryPoint
|
||||
$target = null;
|
||||
|
||||
$massEmailId = $queueItem->get('massEmailId');
|
||||
|
||||
if ($massEmailId) {
|
||||
$massEmail = $this->entityManager->getEntity('MassEmail', $massEmailId);
|
||||
|
||||
if ($massEmail) {
|
||||
$campaignId = $massEmail->get('campaignId');
|
||||
|
||||
if ($campaignId) {
|
||||
$campaign = $this->entityManager->getEntity('Campaign', $campaignId);
|
||||
}
|
||||
@@ -124,6 +126,7 @@ class SubscribeAgain implements EntryPoint
|
||||
|
||||
if ($massEmail->get('optOutEntirely')) {
|
||||
$emailAddress = $target->get('emailAddress');
|
||||
|
||||
if ($emailAddress) {
|
||||
$ea = $this->entityManager->getRepository('EmailAddress')->getByAddress($emailAddress);
|
||||
|
||||
@@ -190,7 +193,6 @@ class SubscribeAgain implements EntryPoint
|
||||
$this->entityManager->removeEntity($logRecord);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function display(array $actionData)
|
||||
@@ -214,8 +216,6 @@ class SubscribeAgain implements EntryPoint
|
||||
|
||||
protected function processWithHash(string $emailAddress, string $hash)
|
||||
{
|
||||
$secretKey = $this->config->get('hashSecretKey');
|
||||
|
||||
$hash2 = $this->hasher->hash($emailAddress);
|
||||
|
||||
if ($hash2 !== $hash) {
|
||||
@@ -242,7 +242,8 @@ class SubscribeAgain implements EntryPoint
|
||||
'emailAddress' => $emailAddress,
|
||||
'hash' => $hash,
|
||||
]);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,8 @@
|
||||
|
||||
namespace Espo\Modules\Crm\EntryPoints;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
|
||||
use Espo\Core\EntryPoints\{
|
||||
EntryPoint,
|
||||
@@ -42,6 +38,8 @@ use Espo\Core\EntryPoints\{
|
||||
};
|
||||
|
||||
use Espo\Core\{
|
||||
Api\Request,
|
||||
Api\Response,
|
||||
ORM\EntityManager,
|
||||
Utils\ClientManager,
|
||||
HookManager,
|
||||
@@ -81,7 +79,7 @@ class Unsubscribe implements EntryPoint
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
}
|
||||
|
||||
public function run($request)
|
||||
public function run(Request $request, Response $response) : void
|
||||
{
|
||||
$id = $request->get('id') ?? null;
|
||||
$emailAddress = $request->get('emailAddress') ?? null;
|
||||
@@ -89,12 +87,14 @@ class Unsubscribe implements EntryPoint
|
||||
|
||||
if ($emailAddress && $hash) {
|
||||
$this->processWithHash($emailAddress, $hash);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$id) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$queueItemId = $id;
|
||||
|
||||
$queueItem = $this->entityManager->getEntity('EmailQueueItem', $queueItemId);
|
||||
@@ -107,10 +107,13 @@ class Unsubscribe implements EntryPoint
|
||||
$target = null;
|
||||
|
||||
$massEmailId = $queueItem->get('massEmailId');
|
||||
|
||||
if ($massEmailId) {
|
||||
$massEmail = $this->entityManager->getEntity('MassEmail', $massEmailId);
|
||||
|
||||
if ($massEmail) {
|
||||
$campaignId = $massEmail->get('campaignId');
|
||||
|
||||
if ($campaignId) {
|
||||
$campaign = $this->entityManager->getEntity('Campaign', $campaignId);
|
||||
}
|
||||
@@ -127,8 +130,10 @@ class Unsubscribe implements EntryPoint
|
||||
|
||||
if ($massEmail->get('optOutEntirely')) {
|
||||
$emailAddress = $target->get('emailAddress');
|
||||
|
||||
if ($emailAddress) {
|
||||
$ea = $this->entityManager->getRepository('EmailAddress')->getByAddress($emailAddress);
|
||||
|
||||
if ($ea) {
|
||||
$ea->set('optOut', true);
|
||||
$this->entityManager->saveEntity($ea);
|
||||
@@ -181,6 +186,7 @@ class Unsubscribe implements EntryPoint
|
||||
|
||||
if ($campaign && $target) {
|
||||
$campaignService = $this->serviceFactory->create('Campaign');
|
||||
|
||||
$campaignService->logOptedOut(
|
||||
$campaignId, $queueItemId, $target, $queueItem->get('emailAddress'), null, $queueItem->get('isTest')
|
||||
);
|
||||
@@ -208,8 +214,6 @@ class Unsubscribe implements EntryPoint
|
||||
|
||||
protected function processWithHash(string $emailAddress, string $hash)
|
||||
{
|
||||
$secretKey = $this->config->get('hashSecretKey');
|
||||
|
||||
$hash2 = $this->hasher->hash($emailAddress);
|
||||
|
||||
if ($hash2 !== $hash) {
|
||||
@@ -236,8 +240,8 @@ class Unsubscribe implements EntryPoint
|
||||
'emailAddress' => $emailAddress,
|
||||
'hash' => $hash,
|
||||
]);
|
||||
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"editQuick":"crm:views/knowledge-base-article/record/edit-quick",
|
||||
"detailQuick":"crm:views/knowledge-base-article/record/detail-quick",
|
||||
"detail":"crm:views/knowledge-base-article/record/detail",
|
||||
"edit":"crm:views/knowledge-base-article/record/edit",
|
||||
"list":"crm:views/knowledge-base-article/record/list"
|
||||
},
|
||||
"modalViews": {
|
||||
|
||||
@@ -645,8 +645,7 @@
|
||||
"campaign": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Campaign",
|
||||
"foreign": "contacts",
|
||||
"noJoin": true
|
||||
"foreign": "contacts"
|
||||
},
|
||||
"campaignLogRecords": {
|
||||
"type": "hasChildren",
|
||||
|
||||
@@ -84,7 +84,7 @@ class Lead extends PersonService implements
|
||||
}
|
||||
}
|
||||
|
||||
public function getConvertAttributes(string $id)
|
||||
public function getConvertAttributes(string $id) : array
|
||||
{
|
||||
$lead = $this->getEntity($id);
|
||||
|
||||
@@ -107,8 +107,6 @@ class Lead extends PersonService implements
|
||||
|
||||
$attributes = [];
|
||||
|
||||
$target = $this->getEntityManager()->getEntity($entityType);
|
||||
|
||||
$fieldMap = [];
|
||||
|
||||
$fieldList = array_keys($this->getMetadata()->get('entityDefs.Lead.fields', []));
|
||||
@@ -165,7 +163,9 @@ class Lead extends PersonService implements
|
||||
$typeHash = (object) [];
|
||||
|
||||
foreach ($attachmentList as $attachment) {
|
||||
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
|
||||
$attachment = $this->getEntityManager()
|
||||
->getRepository('Attachment')
|
||||
->getCopiedAttachment($attachment);
|
||||
|
||||
if ($attachment) {
|
||||
$idList[] = $attachment->id;
|
||||
@@ -182,11 +182,22 @@ class Lead extends PersonService implements
|
||||
|
||||
continue;
|
||||
}
|
||||
else if ($type === 'linkMultiple') {
|
||||
$attributes[$field . 'Ids'] = $lead->get($leadField . 'Ids');
|
||||
$attributes[$field . 'Names'] = $lead->get($leadField . 'Names');
|
||||
$attributes[$field . 'Columns'] = $lead->get($leadField . 'Columns');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$leadAttributeList = $this->fieldUtil->getAttributeList('Lead', $leadField);
|
||||
|
||||
$attributeList = $this->fieldUtil->getAttributeList($entityType, $field);
|
||||
|
||||
if (count($attributeList) !== count($leadAttributeList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($attributeList as $i => $attribute) {
|
||||
if (in_array($attribute, $ignoreAttributeList)) {
|
||||
continue;
|
||||
@@ -194,10 +205,6 @@ class Lead extends PersonService implements
|
||||
|
||||
$leadAttribute = $leadAttributeList[$i] ?? null;
|
||||
|
||||
if (!$leadAttribute) {
|
||||
throw new Error("Not compatible fields in 'convertFields' map.");
|
||||
}
|
||||
|
||||
if (!$lead->has($leadAttribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -641,7 +641,7 @@ class BaseMapper implements Mapper
|
||||
|
||||
foreach ($conditions as $left => $value) {
|
||||
$columns[] = $left;
|
||||
$valueList[] = $v;
|
||||
$valueList[] = $value;
|
||||
}
|
||||
|
||||
$columns[] = $distantKey;
|
||||
|
||||
@@ -1085,7 +1085,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
}
|
||||
|
||||
protected function convertComplexExpression(
|
||||
?Entity $entity = null, string $attribute, bool $distinct, array &$params
|
||||
?Entity $entity, string $attribute, bool $distinct, array &$params
|
||||
) : string {
|
||||
$function = null;
|
||||
|
||||
@@ -1501,7 +1501,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return $list;
|
||||
}
|
||||
|
||||
protected function getSelectPart(?Entity $entity = null, array &$params) : string
|
||||
protected function getSelectPart(?Entity $entity, array &$params) : string
|
||||
{
|
||||
$itemList = $params['select'] ?? [];
|
||||
|
||||
@@ -1579,7 +1579,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return $selectPart;
|
||||
}
|
||||
|
||||
protected function getSelectPartItemPair(?Entity $entity = null, array &$params, $attribute) : ?array
|
||||
protected function getSelectPartItemPair(?Entity $entity, array &$params, $attribute) : ?array
|
||||
{
|
||||
$maxTextColumnsLength = $params['maxTextColumnsLength'] ?? null;
|
||||
$skipTextColumns = $params['skipTextColumns'] ?? false;
|
||||
@@ -2842,7 +2842,7 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
}
|
||||
|
||||
protected function composeSelectQuery(
|
||||
?string $from = null,
|
||||
?string $from,
|
||||
string $select,
|
||||
?string $alias = null,
|
||||
?string $joins = null,
|
||||
|
||||
@@ -292,7 +292,7 @@ class RDBRelation
|
||||
protected function processCheckForeignEntity(Entity $entity)
|
||||
{
|
||||
if ($this->foreignEntityType && $this->foreignEntityType !== $entity->getEntityType()) {
|
||||
throw new InvalidArgumentException("Entity type doesn't match an entity type of the relation.");
|
||||
throw new RuntimeException("Entity type doesn't match an entity type of the relation.");
|
||||
}
|
||||
|
||||
if (!$entity->id) {
|
||||
@@ -370,7 +370,7 @@ class RDBRelation
|
||||
}
|
||||
|
||||
if ($id === '') {
|
||||
throw new InvalidArgumentException();
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($this->foreignEntityType);
|
||||
@@ -389,7 +389,7 @@ class RDBRelation
|
||||
}
|
||||
|
||||
if ($id === '') {
|
||||
throw new InvalidArgumentException();
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($this->foreignEntityType);
|
||||
@@ -408,7 +408,7 @@ class RDBRelation
|
||||
}
|
||||
|
||||
if ($id === '') {
|
||||
throw new InvalidArgumentException();
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$seed = $this->entityManager->getEntityFactory()->create($this->foreignEntityType);
|
||||
|
||||
@@ -40,7 +40,6 @@ use Espo\ORM\{
|
||||
};
|
||||
|
||||
use RuntimeException;
|
||||
use BadMethodCallException;
|
||||
|
||||
/**
|
||||
* Builds select parameters for related records for RDB repository.
|
||||
|
||||
@@ -41,7 +41,6 @@ use Espo\ORM\{
|
||||
|
||||
use StdClass;
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class RDBRepository extends Repository
|
||||
{
|
||||
@@ -232,7 +231,7 @@ class RDBRepository extends Repository
|
||||
$params = $params ?? [];
|
||||
|
||||
if ($entity->getEntityType() !== $this->entityType) {
|
||||
throw new InvalidArgumentException("Not supported entity type.");
|
||||
throw new RuntimeException("Not supported entity type.");
|
||||
}
|
||||
|
||||
if (!$entity->id) {
|
||||
@@ -283,7 +282,7 @@ class RDBRepository extends Repository
|
||||
$params = $params ?? [];
|
||||
|
||||
if ($entity->getEntityType() !== $this->entityType) {
|
||||
throw new InvalidArgumentException("Not supported entity type.");
|
||||
throw new RuntimeException("Not supported entity type.");
|
||||
}
|
||||
|
||||
if (!$entity->id) {
|
||||
@@ -373,7 +372,7 @@ class RDBRepository extends Repository
|
||||
}
|
||||
|
||||
if ($entity->getEntityType() !== $this->entityType) {
|
||||
throw new InvalidArgumentException("Not supported entity type.");
|
||||
throw new RuntimeException("Not supported entity type.");
|
||||
}
|
||||
|
||||
if ($foreign instanceof Entity) {
|
||||
@@ -421,7 +420,7 @@ class RDBRepository extends Repository
|
||||
}
|
||||
|
||||
if ($entity->getEntityType() !== $this->entityType) {
|
||||
throw new InvalidArgumentException("Not supported entity type.");
|
||||
throw new RuntimeException("Not supported entity type.");
|
||||
}
|
||||
|
||||
$this->beforeRelate($entity, $relationName, $foreign, $columnData, $options);
|
||||
@@ -478,7 +477,7 @@ class RDBRepository extends Repository
|
||||
}
|
||||
|
||||
if ($entity->getEntityType() !== $this->entityType) {
|
||||
throw new InvalidArgumentException("Not supported entity type.");
|
||||
throw new RuntimeException("Not supported entity type.");
|
||||
}
|
||||
|
||||
$this->beforeUnrelate($entity, $relationName, $foreign, $options);
|
||||
@@ -628,7 +627,7 @@ class RDBRepository extends Repository
|
||||
*
|
||||
* @return int|float
|
||||
*/
|
||||
public function sum(string $attributel)
|
||||
public function sum(string $attribute)
|
||||
{
|
||||
return $this->createSelectBuilder()->sum($attribute);
|
||||
}
|
||||
|
||||
@@ -69,13 +69,19 @@ class Attachment extends \Espo\Core\Repositories\Database implements
|
||||
parent::beforeSave($entity, $options);
|
||||
|
||||
$storage = $entity->get('storage');
|
||||
|
||||
if (!$storage) {
|
||||
$entity->set('storage', $this->config->get('defaultFileStorage', null));
|
||||
}
|
||||
|
||||
if ($entity->isNew()) {
|
||||
if (!$entity->has('size') && $entity->has('contents')) {
|
||||
$entity->set('size', mb_strlen($entity->get('contents')));
|
||||
$contents = $entity->get('contents');
|
||||
|
||||
$entity->set(
|
||||
'size',
|
||||
mb_strlen($contents, '8bit')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ return [
|
||||
'version' => '@@version',
|
||||
'timeZone' => 'UTC',
|
||||
'dateFormat' => 'DD.MM.YYYY',
|
||||
'timeFormat' => 'hh:mm',
|
||||
'timeFormat' => 'HH:mm',
|
||||
'weekStart' => 0,
|
||||
'thousandSeparator' => ',',
|
||||
'decimalMark' => '.',
|
||||
@@ -134,6 +134,7 @@ return [
|
||||
'activitiesEntityList' => ['Meeting', 'Call'],
|
||||
'historyEntityList' => ['Meeting', 'Call', 'Email'],
|
||||
'busyRangesEntityList' => ['Meeting', 'Call'],
|
||||
'emailAutoReplySuppressPeriod' => '3 hours',
|
||||
'cleanupJobPeriod' => '1 month',
|
||||
'cleanupActionHistoryPeriod' => '15 days',
|
||||
'cleanupAuthTokenPeriod' => '1 month',
|
||||
|
||||
@@ -170,6 +170,7 @@ return [
|
||||
'ldapPortalUserRolesIds',
|
||||
'ldapPortalUserRolesNames',
|
||||
'cleanupJobPeriod',
|
||||
'emailAutoReplySuppressPeriod',
|
||||
'cleanupActionHistoryPeriod',
|
||||
'adminNotifications',
|
||||
'adminNotificationsNewVersion',
|
||||
|
||||
@@ -179,6 +179,10 @@
|
||||
{
|
||||
"name": "env\\userAttribute",
|
||||
"insertText": "env\\userAttribute(ATTRIBUTE)"
|
||||
},
|
||||
{
|
||||
"name": "util\\generateId",
|
||||
"insertText": "util\\generateId()"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -293,8 +293,6 @@ class EmailAccount extends Record implements
|
||||
|
||||
$importer = new Importer($this->getEntityManager(), $this->getConfig(), $notificator, $this->parserClassName);
|
||||
|
||||
$maxSize = $this->getConfig()->get('emailMessageMaxSize');
|
||||
|
||||
$user = $this->getEntityManager()->getEntity('User', $emailAccount->get('assignedUserId'));
|
||||
if (!$user) {
|
||||
throw new Error();
|
||||
@@ -419,12 +417,7 @@ class EmailAccount extends Record implements
|
||||
}
|
||||
}
|
||||
|
||||
$fetchOnlyHeader = false;
|
||||
if ($maxSize) {
|
||||
if ($storage->getSize($id) > $maxSize * 1024 * 1024) {
|
||||
$fetchOnlyHeader = true;
|
||||
}
|
||||
}
|
||||
$fetchOnlyHeader = $this->checkFetchOnlyHeader($storage, $id);
|
||||
|
||||
$folderData = null;
|
||||
if ($emailAccount->get('emailFolderId')) {
|
||||
@@ -627,4 +620,26 @@ class EmailAccount extends Record implements
|
||||
$handler->applyParams($emailAccount->id, $params);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkFetchOnlyHeader(Imap $storage, string $id): bool
|
||||
{
|
||||
$maxSize = $this->getConfig()->get('emailMessageMaxSize');
|
||||
|
||||
if (!$maxSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$size = $storage->getSize($id);
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($size > $maxSize * 1024 * 1024) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ class InboundEmail extends RecordService implements
|
||||
|
||||
protected $parserClassName = MailMimeParser::class;
|
||||
|
||||
protected $emailAutoReplySuppressPeriod = '3 hours';
|
||||
|
||||
const PORTION_LIMIT = 20;
|
||||
|
||||
protected function getCrypt()
|
||||
@@ -163,8 +165,6 @@ class InboundEmail extends RecordService implements
|
||||
|
||||
$importer = new Importer($this->getEntityManager(), $this->getConfig(), $notificator, $this->parserClassName);
|
||||
|
||||
$maxSize = $this->getConfig()->get('emailMessageMaxSize');
|
||||
|
||||
$teamId = $emailAccount->get('teamId');
|
||||
|
||||
$userId = null;
|
||||
@@ -327,13 +327,7 @@ class InboundEmail extends RecordService implements
|
||||
}
|
||||
}
|
||||
|
||||
$fetchOnlyHeader = false;
|
||||
|
||||
if ($maxSize) {
|
||||
if ($storage->getSize($id) > $maxSize * 1024 * 1024) {
|
||||
$fetchOnlyHeader = true;
|
||||
}
|
||||
}
|
||||
$fetchOnlyHeader = $this->checkFetchOnlyHeader($storage, $id);
|
||||
|
||||
$message = null;
|
||||
$email = null;
|
||||
@@ -813,7 +807,7 @@ class InboundEmail extends RecordService implements
|
||||
|
||||
$d = new DateTime();
|
||||
|
||||
$d->modify('-3 hours');
|
||||
$d->modify('-' . $this->config->get('emailAutoReplySuppressPeriod', $this->emailAutoReplySuppressPeriod));
|
||||
|
||||
$threshold = $d->format('Y-m-d H:i:s');
|
||||
|
||||
@@ -1222,4 +1216,26 @@ class InboundEmail extends RecordService implements
|
||||
$handler->applyParams($emailAccount->id, $params);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkFetchOnlyHeader(Imap $storage, string $id): bool
|
||||
{
|
||||
$maxSize = $this->getConfig()->get('emailMessageMaxSize');
|
||||
|
||||
if (!$maxSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$size = $storage->getSize($id);
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($size > $maxSize * 1024 * 1024) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ use Espo\Core\Exceptions\{
|
||||
|
||||
use Espo\ORM\{
|
||||
Entity,
|
||||
EntityCollection,
|
||||
Collection,
|
||||
EntityManager,
|
||||
};
|
||||
|
||||
@@ -2128,7 +2128,7 @@ class Record implements Crud,
|
||||
return false;
|
||||
}
|
||||
|
||||
public function findDuplicates(Entity $entity, $data = null) : ?EntityCollection
|
||||
public function findDuplicates(Entity $entity, $data = null) : ?Collection
|
||||
{
|
||||
if (!$data) {
|
||||
$data = (object) [];
|
||||
|
||||
@@ -1626,6 +1626,12 @@ class Stream
|
||||
$collection = $this->entityManager->getRepository('User')->find($selectParams);
|
||||
$total = $this->entityManager->getRepository('User')->count($selectParams);
|
||||
|
||||
$userService = $this->serviceFactory->create('User');
|
||||
|
||||
foreach ($collection as $e) {
|
||||
$userService->prepareEntityForOutput($e);
|
||||
}
|
||||
|
||||
return new RecordCollection($collection, $total);
|
||||
}
|
||||
|
||||
|
||||
@@ -570,7 +570,7 @@ class User extends Record implements
|
||||
$sender = $this->emailSender->create();
|
||||
|
||||
if (!$this->emailSender->hasSystemSmtp()) {
|
||||
$sender->withStmpParams([
|
||||
$sender->withSmtpParams([
|
||||
'server' => $this->getConfig()->get('internalSmtpServer'),
|
||||
'port' => $this->getConfig()->get('internalSmtpPort'),
|
||||
'auth' => $this->getConfig()->get('internalSmtpAuth'),
|
||||
|
||||
@@ -65,9 +65,9 @@ class Webhook extends Record implements
|
||||
}
|
||||
}
|
||||
|
||||
protected function filtetInput($data)
|
||||
protected function filterInput($data)
|
||||
{
|
||||
parent::filtetInput($data);
|
||||
parent::filterInput($data);
|
||||
|
||||
unset($data->entityType);
|
||||
unset($data->field);
|
||||
|
||||
@@ -32,10 +32,10 @@ namespace Espo\Tools\LeadCapture;
|
||||
use Espo\Core\{
|
||||
Exceptions\Error,
|
||||
Exceptions\NotFound,
|
||||
Exceptions\BadRequest,
|
||||
ORM\EntityManager,
|
||||
Utils\FieldUtil,
|
||||
Utils\Language,
|
||||
ServicaFactory,
|
||||
HookManager,
|
||||
Mail\EmailSender,
|
||||
Utils\Config,
|
||||
@@ -729,7 +729,7 @@ class LeadCapture
|
||||
]);
|
||||
|
||||
if (!empty($data->description)) {
|
||||
$logRecord->set('description', $description);
|
||||
$logRecord->set('description', $data->description);
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($logRecord);
|
||||
|
||||
6
client/lib/selectize.min.js
vendored
6
client/lib/selectize.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -26,10 +26,12 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('crm:views/knowledge-base-article/record/detail', 'views/record/detail', function (Dep) {
|
||||
define('crm:views/knowledge-base-article/record/detail', 'views/record/detail', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
saveAndContinueEditingAction: true,
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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.
|
||||
************************************************************************/
|
||||
|
||||
define('crm:views/knowledge-base-article/record/edit', 'views/record/edit', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
saveAndContinueEditingAction: true,
|
||||
|
||||
});
|
||||
});
|
||||
@@ -57,7 +57,7 @@ define('crm:views/record/panels/activities', ['views/record/panels/relationship'
|
||||
rows: [
|
||||
[
|
||||
{name: 'ico', view: 'crm:views/fields/ico'},
|
||||
{name: 'name', link: true}
|
||||
{name: 'name', link: true, view: 'views/event/fields/name-for-history'}
|
||||
],
|
||||
[
|
||||
{name: 'assignedUser'},
|
||||
|
||||
@@ -49,10 +49,14 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
this.assignedUserField = this.options.assignedUserField || 'assignedUser';
|
||||
|
||||
var usersFieldDefault = 'users';
|
||||
|
||||
if (!this.model.hasLink('users') && this.model.hasLink('assignedUsers')) {
|
||||
usersFieldDefault = 'assignedUsers';
|
||||
}
|
||||
|
||||
this.eventAssignedUserIsAttendeeDisabled =
|
||||
this.getConfig().get('eventAssignedUserIsAttendeeDisabled') || false;
|
||||
|
||||
this.usersField = this.options.usersField || usersFieldDefault;
|
||||
|
||||
this.userIdList = [];
|
||||
@@ -65,16 +69,24 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
m.hasChanged(this.startField) ||
|
||||
m.hasChanged(this.endField) ||
|
||||
m.hasChanged(this.usersField + 'Ids') ||
|
||||
m.hasChanged(this.assignedUserField + 'Id');
|
||||
if (!isChanged) return;
|
||||
!this.eventAssignedUserIsAttendeeDisabled && m.hasChanged(this.assignedUserField + 'Id');
|
||||
|
||||
if (!isChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m.hasChanged(this.assignedUserField + 'Id') && !m.hasChanged(this.usersField + 'Ids')) {
|
||||
this.initDates(true);
|
||||
|
||||
if (!this.start || !this.end || !this.userIdList.length) {
|
||||
if (!this.timeline) return;
|
||||
if (!this.timeline) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showNoData();
|
||||
|
||||
this.trigger('no-data');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,8 +105,12 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
this.reRender();
|
||||
}
|
||||
} else {
|
||||
if (this.isRemoved()) return;
|
||||
if (this.isRemoved()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trigger('has-data');
|
||||
|
||||
this.reRender();
|
||||
}
|
||||
}, this);
|
||||
@@ -132,13 +148,17 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
this.initGroupsDataSet();
|
||||
this.initDates();
|
||||
|
||||
if (!$timeline.get(0)) return;
|
||||
if (!$timeline.get(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timeline.get(0).innerHTML = '';
|
||||
|
||||
if (!this.start || !this.end || !this.userIdList.length) {
|
||||
this.showNoData();
|
||||
|
||||
this.trigger('no-data');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,15 +171,17 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
this.fetch(this.start, this.end, function (eventList) {
|
||||
var itemsDataSet = new Vis.DataSet(eventList);
|
||||
|
||||
var timeline = this.timeline = new Vis.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, {
|
||||
var timeline = this.timeline =new Vis.Timeline($timeline.get(0), itemsDataSet, this.groupsDataSet, {
|
||||
dataAttributes: 'all',
|
||||
start: this.start.toDate(),
|
||||
end: this.end.toDate(),
|
||||
moment: function (date) {
|
||||
var m = moment(date);
|
||||
|
||||
if (date && date.noTimeZone) {
|
||||
return m;
|
||||
}
|
||||
|
||||
return m.tz(this.getDateTime().getTimeZone());
|
||||
}.bind(this),
|
||||
format: this.getFormatObject(),
|
||||
@@ -195,6 +217,7 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
e.skipClick = true;
|
||||
|
||||
this.blockClick = true;
|
||||
|
||||
setTimeout(function () {this.blockClick = false}.bind(this), 100);
|
||||
|
||||
this.start = moment(e.start);
|
||||
@@ -217,6 +240,7 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
this.addEvent(convertedEventList);
|
||||
|
||||
var itemsDataSet = new this.Vis.DataSet(convertedEventList);
|
||||
|
||||
this.timeline.setItems(itemsDataSet);
|
||||
},
|
||||
|
||||
@@ -242,13 +266,15 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
endS = this.model.get(this.endField + 'Date');
|
||||
}
|
||||
|
||||
if (!startS || !endS) return;
|
||||
if (!startS || !endS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.model.get('isAllDay')) {
|
||||
this.eventStart = moment.tz(startS, this.getDateTime().getTimeZone());
|
||||
this.eventEnd = moment.tz(endS, this.getDateTime().getTimeZone());
|
||||
this.eventEnd.add(1, 'day');
|
||||
} else {
|
||||
}else {
|
||||
this.eventStart = moment.utc(startS).tz(this.getDateTime().getTimeZone());
|
||||
this.eventEnd = moment.utc(endS).tz(this.getDateTime().getTimeZone());
|
||||
}
|
||||
@@ -281,6 +307,7 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
runFetch: function () {
|
||||
this.fetch(this.start, this.end, function (eventList) {
|
||||
var itemsDataSet = new this.Vis.DataSet(eventList);
|
||||
|
||||
this.timeline.setItems(itemsDataSet);
|
||||
}.bind(this));
|
||||
},
|
||||
@@ -304,6 +331,7 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
this.ajaxGetRequest(url).then(function (data) {
|
||||
this.fetchedStart = from.clone();
|
||||
this.fetchedEnd = to.clone();
|
||||
|
||||
var eventList = [];
|
||||
|
||||
for (var userId in data) {
|
||||
@@ -344,6 +372,7 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
};
|
||||
|
||||
var color = this.getColorFromScopeName(this.model.entityType);
|
||||
|
||||
if (color) {
|
||||
o.style += '; border-color: ' + color;
|
||||
var rgb = this.hexToRgb(color);
|
||||
@@ -362,11 +391,17 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
|
||||
convertEventList: function (list) {
|
||||
var resultList = [];
|
||||
|
||||
list.forEach(function (iten) {
|
||||
var event = this.convertEvent(iten);
|
||||
if (!event) return;
|
||||
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
resultList.push(event);
|
||||
}, this);
|
||||
|
||||
return resultList;
|
||||
},
|
||||
|
||||
@@ -410,8 +445,12 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
var assignedUserId = this.model.get(this.assignedUserField + 'Id');
|
||||
|
||||
var names = this.model.get(this.usersField + 'Names') || {};
|
||||
if (assignedUserId) {
|
||||
if (!~userIdList.indexOf(assignedUserId)) userIdList.unshift(assignedUserId);
|
||||
|
||||
if (!this.eventAssignedUserIsAttendeeDisabled && assignedUserId) {
|
||||
if (!~userIdList.indexOf(assignedUserId)) {
|
||||
userIdList.unshift(assignedUserId);
|
||||
}
|
||||
|
||||
names[assignedUserId] = this.model.get(this.assignedUserField + 'Name');
|
||||
}
|
||||
|
||||
@@ -435,8 +474,13 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
if (this.calendarType === 'single') {
|
||||
return name;
|
||||
}
|
||||
|
||||
var avatarHtml = this.getAvatarHtml(id);
|
||||
if (avatarHtml) avatarHtml += ' ';
|
||||
|
||||
if (avatarHtml) {
|
||||
avatarHtml += ' ';
|
||||
}
|
||||
|
||||
var html = avatarHtml + '<span data-id="'+id+'" class="group-title">' + name + '</span>';
|
||||
|
||||
return html;
|
||||
@@ -446,8 +490,11 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
if (this.getConfig().get('avatarsDisabled')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var t;
|
||||
|
||||
var cache = this.getCache();
|
||||
|
||||
if (cache) {
|
||||
t = cache.get('app', 'timestamp');
|
||||
} else {
|
||||
@@ -481,6 +528,7 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
year: ''
|
||||
}
|
||||
};
|
||||
|
||||
return format;
|
||||
},
|
||||
|
||||
@@ -493,10 +541,11 @@ define('crm:views/scheduler/scheduler', ['view', 'lib!vis'], function (Dep, Vis)
|
||||
|
||||
hexToRgb: function (hex) {
|
||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
} : null;
|
||||
},
|
||||
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" data-action="save">{{translate 'Save'}}</button>
|
||||
<button class="btn btn-default" data-action="close">{{translate 'Close'}}</button>
|
||||
{{#unless isCustom}}{{#unless isNew}}<button class="btn btn-default" data-action="resetToDefault">{{translate 'Reset to Default' scope='Admin'}}</button>{{/unless}}{{/unless}}
|
||||
{{#if hasResetToDefault}}
|
||||
<button
|
||||
class="btn btn-default"
|
||||
data-action="resetToDefault"
|
||||
>{{translate 'Reset to Default' scope='Admin'}}</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<a
|
||||
href="#{{model.name}}/view/{{model.id}}"
|
||||
class="link"
|
||||
data-id="{{model.id}}"
|
||||
title="{{value}}"
|
||||
{{#if strikethrough}}style="text-decoration: line-through;"{{/if}}
|
||||
>{{#if value}}{{value}}{{else}}{{translate 'None'}}{{/if}}</a>
|
||||
@@ -501,6 +501,19 @@ define(
|
||||
this.trigger('auth');
|
||||
}.bind(this));
|
||||
|
||||
var userId = data.user.id;
|
||||
var lastUserId = this.storage.get('user', 'lastUserId');
|
||||
|
||||
if (lastUserId !== userId) {
|
||||
if (this.cache) {
|
||||
this.cache.clear('app', 'metadata');
|
||||
}
|
||||
|
||||
this.language.clearCache();
|
||||
}
|
||||
|
||||
this.storage.set('user', 'lastUserId', userId);
|
||||
|
||||
}.bind(this));
|
||||
|
||||
this.baseController.on('logout', function () {
|
||||
|
||||
@@ -30,9 +30,11 @@ define('cache', [], function () {
|
||||
|
||||
var Cache = function (cacheTimestamp) {
|
||||
this.basePrefix = this.prefix;
|
||||
|
||||
if (cacheTimestamp) {
|
||||
this.prefix = this.basePrefix + '-' + cacheTimestamp;
|
||||
}
|
||||
|
||||
if (!this.get('app', 'timestamp')) {
|
||||
this.storeTimestamp();
|
||||
}
|
||||
@@ -44,15 +46,20 @@ define('cache', [], function () {
|
||||
|
||||
handleActuality: function (cacheTimestamp) {
|
||||
var stored = parseInt(this.get('app', 'cacheTimestamp'));
|
||||
|
||||
if (stored) {
|
||||
if (stored !== cacheTimestamp) {
|
||||
this.clear();
|
||||
|
||||
this.set('app', 'cacheTimestamp', cacheTimestamp);
|
||||
|
||||
this.storeTimestamp();
|
||||
}
|
||||
} else {
|
||||
this.clear();
|
||||
|
||||
this.set('app', 'cacheTimestamp', cacheTimestamp);
|
||||
|
||||
this.storeTimestamp();
|
||||
}
|
||||
},
|
||||
@@ -83,8 +90,10 @@ define('cache', [], function () {
|
||||
|
||||
try {
|
||||
var stored = localStorage.getItem(key);
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -93,42 +102,55 @@ define('cache', [], function () {
|
||||
|
||||
if (stored.length > 9 && stored.substr(0, 9) === '__JSON__:') {
|
||||
var jsonString = stored.substr(9);
|
||||
|
||||
try {
|
||||
result = JSON.parse(jsonString);
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
result = stored;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
set: function (type, name, value) {
|
||||
this.checkType(type);
|
||||
|
||||
var key = this.composeKey(type, name);
|
||||
|
||||
if (value instanceof Object || Array.isArray(value)) {
|
||||
value = '__JSON__:' + JSON.stringify(value);
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
catch (error) {
|
||||
console.log('Local storage limit exceeded.');
|
||||
}
|
||||
},
|
||||
|
||||
clear: function (type, name) {
|
||||
var reText;
|
||||
|
||||
if (typeof type !== 'undefined') {
|
||||
if (typeof name === 'undefined') {
|
||||
reText = '^' + this.composeFullPrefix(type);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
reText = '^' + this.composeKey(type, name);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
reText = '^' + this.basePrefix + '-';
|
||||
}
|
||||
|
||||
var re = new RegExp(reText);
|
||||
|
||||
for (var i in localStorage) {
|
||||
if (re.test(i)) {
|
||||
delete localStorage[i];
|
||||
|
||||
@@ -243,12 +243,15 @@ var Espo = Espo || {classMap:{}};
|
||||
|
||||
if (realName in this.libsConfig) {
|
||||
var libData = this.libsConfig[realName] || {};
|
||||
|
||||
path = libData.path || path;
|
||||
exportsTo = libData.exportsTo || exportsTo;
|
||||
exportsAs = libData.exportsAs || exportsAs;
|
||||
noAppCache = libData.noAppCache || noAppCache;
|
||||
//noAppCache = libData.noAppCache || noAppCache;
|
||||
}
|
||||
|
||||
noAppCache = true;
|
||||
|
||||
fetchObject = function () {
|
||||
var from = root;
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model
|
||||
fieldList: this.fieldList,
|
||||
isCustom: this.defs.isCustom,
|
||||
isNew: this.isNew,
|
||||
hasDynamicLogicPanel: this.hasDynamicLogicPanel
|
||||
hasDynamicLogicPanel: this.hasDynamicLogicPanel,
|
||||
hasResetToDefault: !this.defs.isCustom && !this.entityTypeIsCustom && !this.isNew,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -74,13 +75,16 @@ define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model
|
||||
fields: {
|
||||
name: {required: true, maxLength: 100},
|
||||
label: {required: true},
|
||||
tooltipText: {}
|
||||
tooltipText: {},
|
||||
}
|
||||
};
|
||||
|
||||
this.entityTypeIsCustom = !!this.getMetadata().get(['scopes', this.scope, 'isCustom']);
|
||||
|
||||
if (!this.isNew) {
|
||||
this.model.id = this.field;
|
||||
this.model.scope = this.scope;
|
||||
|
||||
this.model.set('name', this.field);
|
||||
this.model.set('label', this.getLanguage().translate(this.field, 'fields', this.scope));
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ define('views/admin/layouts/detail', 'views/admin/layouts/grid', function (Dep)
|
||||
promiseList.push(
|
||||
new Promise(
|
||||
function (resolve) {
|
||||
if (this.getMetadata().get(['clientDefs', scope, 'layoutDefaultSidePanelDisabled'])) resolve();
|
||||
if (this.getMetadata().get(['clientDefs', this.scope, 'layoutDefaultSidePanelDisabled'])) resolve();
|
||||
|
||||
this.getHelper().layoutManager.getOriginal(this.scope, 'defaultSidePanel', this.setId,
|
||||
function (layoutLoaded) {
|
||||
|
||||
@@ -32,6 +32,8 @@ define('views/email-template/record/detail', 'views/record/detail', function (De
|
||||
|
||||
duplicateAction: true,
|
||||
|
||||
saveAndContinueEditingAction: true,
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
this.listenToInsertField();
|
||||
|
||||
@@ -26,15 +26,16 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('views/email-template/record/edit', ['views/record/edit', 'views/email-template/record/detail'], function (Dep, Detail) {
|
||||
define('views/email-template/record/edit', ['views/record/edit', 'views/email-template/record/detail'], function (Dep, Detail) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
saveAndContinueEditingAction: true,
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
Detail.prototype.listenToInsertField.call(this);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
48
client/src/views/event/fields/name-for-history.js
Normal file
48
client/src/views/event/fields/name-for-history.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii 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.
|
||||
************************************************************************/
|
||||
|
||||
define('views/event/fields/name-for-history', 'views/fields/varchar', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
listLinkTemplate: 'event/fields/name-for-history/list-link',
|
||||
|
||||
data: function () {
|
||||
var data = Dep.prototype.data.call(this);
|
||||
|
||||
var status = this.model.get('status');
|
||||
|
||||
var canceledStatusList = this.getMetadata().get('app.calendar.canceledStatusList') || [];
|
||||
|
||||
data.strikethrough = !!~canceledStatusList.indexOf(status);
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
@@ -283,19 +283,27 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
|
||||
var $element = this.$element = this.$el.find('.main-element');
|
||||
|
||||
var valueList = this.getSearchParamsData().valueList || this.searchParams.valueFront || [];
|
||||
|
||||
this.$element.val(valueList.join(':,:'));
|
||||
|
||||
var data = [];
|
||||
|
||||
(this.params.options || []).forEach(function (value) {
|
||||
var label = this.getLanguage().translateOption(value, this.name, this.scope);
|
||||
|
||||
if (this.translatedOptions) {
|
||||
if (value in this.translatedOptions) {
|
||||
label = this.translatedOptions[value];
|
||||
}
|
||||
}
|
||||
|
||||
if (label === '') {
|
||||
return;
|
||||
};
|
||||
|
||||
data.push({
|
||||
value: value,
|
||||
label: label
|
||||
label: label,
|
||||
});
|
||||
}, this);
|
||||
|
||||
@@ -312,11 +320,14 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
|
||||
if (!this.matchAnyWord) {
|
||||
selectizeOptions.score = function (search) {
|
||||
var score = this.getScoreFunction(search);
|
||||
|
||||
search = search.toLowerCase();
|
||||
|
||||
return function (item) {
|
||||
if (item.label.toLowerCase().indexOf(search) === 0) {
|
||||
return score(item);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
@@ -324,16 +335,17 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
|
||||
|
||||
if (this.allowCustomOptions) {
|
||||
selectizeOptions.persist = false;
|
||||
|
||||
selectizeOptions.create = function (input) {
|
||||
return {
|
||||
value: input,
|
||||
label: input
|
||||
label: input,
|
||||
}
|
||||
};
|
||||
selectizeOptions.render = {
|
||||
option_create: function (data, escape) {
|
||||
return '<div class="create"><strong>' + escape(data.input) + '</strong>…</div>';
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -342,6 +354,7 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
|
||||
this.$el.find('.selectize-dropdown-content').addClass('small');
|
||||
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
|
||||
this.handleSearchType(type);
|
||||
|
||||
this.$el.find('select.search-type').on('change', function () {
|
||||
@@ -355,10 +368,13 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
|
||||
|
||||
fetchFromDom: function () {
|
||||
var selected = [];
|
||||
|
||||
this.$el.find('.list-group .list-group-item').each(function (i, el) {
|
||||
var value = $(el).data('value').toString();
|
||||
|
||||
selected.push(value);
|
||||
});
|
||||
|
||||
this.selected = selected;
|
||||
},
|
||||
|
||||
|
||||
@@ -68,16 +68,21 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
events: {
|
||||
'click a.remove-attachment': function (e) {
|
||||
var $div = $(e.currentTarget).parent();
|
||||
|
||||
var id = $div.attr('data-id');
|
||||
|
||||
if (id) {
|
||||
this.deleteAttachment(id);
|
||||
}
|
||||
|
||||
$div.parent().remove();
|
||||
|
||||
this.$el.find('input.file').val(null);
|
||||
},
|
||||
'change input.file': function (e) {
|
||||
var $file = $(e.currentTarget);
|
||||
var files = e.currentTarget.files;
|
||||
|
||||
this.uploadFiles(files);
|
||||
|
||||
$file.replaceWith($file.clone(true));
|
||||
@@ -90,6 +95,7 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
e.preventDefault();
|
||||
|
||||
var id = $(e.currentTarget).data('id');
|
||||
|
||||
var attachmentIdList = this.model.get(this.idsName) || [];
|
||||
|
||||
var typeHash = this.model.get(this.typeHashName) || {};
|
||||
@@ -100,14 +106,18 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
imageIdListLeft.push(id);
|
||||
|
||||
var met = false;
|
||||
|
||||
attachmentIdList.forEach(function (cId) {
|
||||
if (cId === id) {
|
||||
met = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isTypeIsImage(typeHash[cId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (met) {
|
||||
imageIdListLeft.push(cId);
|
||||
} else {
|
||||
@@ -118,6 +128,7 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
var imageIdList = imageIdListLeft.concat(imageIdListRight);
|
||||
|
||||
var imageList = [];
|
||||
|
||||
imageIdList.forEach(function (cId) {
|
||||
imageList.push({
|
||||
id: cId,
|
||||
@@ -182,19 +193,43 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
var sourceDefs = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs']) || {};
|
||||
|
||||
this.sourceList = Espo.Utils.clone(this.params.sourceList || []).filter(function (item) {
|
||||
if (!(item in sourceDefs)) return true;
|
||||
var defs = sourceDefs[item];
|
||||
if (defs.configCheck) {
|
||||
var configCheck = defs.configCheck;
|
||||
if (configCheck) {
|
||||
var arr = configCheck.split('.');
|
||||
if (this.getConfig().getByPath(arr)) {
|
||||
return true;
|
||||
}
|
||||
this.sourceList = Espo.Utils.clone(this.params.sourceList || []);
|
||||
|
||||
this.sourceList = this.sourceList
|
||||
.concat(
|
||||
this.getMetadata().get(['clientDefs', 'Attachment', 'generalSourceList']) || []
|
||||
)
|
||||
.filter(
|
||||
function (item, i, self) {
|
||||
return self.indexOf(item) === i;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
)
|
||||
.filter(
|
||||
function (item) {
|
||||
var defs = sourceDefs[item] || {};
|
||||
|
||||
if (defs.accessDataList) {
|
||||
if (
|
||||
!Espo.Utils.checkAccessDataList(
|
||||
defs.accessDataList, this.getAcl(), this.getUser()
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (defs.configCheck) {
|
||||
var arr = defs.configCheck.split('.');
|
||||
|
||||
if (!this.getConfig().getByPath(arr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
this.listenTo(this.model, 'change:' + this.nameHashName, function () {
|
||||
this.nameHash = _.clone(this.model.get(this.nameHashName)) || {};
|
||||
@@ -222,16 +257,19 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
empty: function () {
|
||||
this.clearIds();
|
||||
|
||||
this.$attachments.empty();
|
||||
},
|
||||
|
||||
handleResize: function () {
|
||||
var width = this.$el.width();
|
||||
|
||||
this.$el.find('img.image-preview').css('maxWidth', width + 'px');
|
||||
},
|
||||
|
||||
deleteAttachment: function (id) {
|
||||
this.removeId(id);
|
||||
|
||||
if (this.model.isNew()) {
|
||||
this.getModelFactory().create('Attachment', function (attachment) {
|
||||
attachment.id = id;
|
||||
@@ -242,40 +280,55 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
getImageUrl: function (id, size) {
|
||||
var url = this.getBasePath() + '?entryPoint=image&id=' + id;
|
||||
|
||||
if (size) {
|
||||
url += '&size=' + size;
|
||||
}
|
||||
|
||||
if (this.getUser().get('portalId')) {
|
||||
url += '&portalId=' + this.getUser().get('portalId');
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
getDownloadUrl: function (id) {
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var url = this.getBasePath() + '?entryPoint=download&id=' + id;
|
||||
|
||||
if (this.getUser().get('portalId')) {
|
||||
url += '&portalId=' + this.getUser().get('portalId');
|
||||
}
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
removeId: function (id) {
|
||||
var arr = _.clone(this.model.get(this.idsName) || []);
|
||||
|
||||
var i = arr.indexOf(id);
|
||||
|
||||
arr.splice(i, 1);
|
||||
|
||||
this.model.set(this.idsName, arr);
|
||||
|
||||
var nameHash = _.clone(this.model.get(this.nameHashName) || {});
|
||||
|
||||
delete nameHash[id];
|
||||
|
||||
this.model.set(this.nameHashName, nameHash);
|
||||
|
||||
var typeHash = _.clone(this.model.get(this.typeHashName) || {});
|
||||
|
||||
delete typeHash[id];
|
||||
|
||||
this.model.set(this.typeHashName, typeHash);
|
||||
},
|
||||
|
||||
clearIds: function (silent) {
|
||||
var silent = silent || false;
|
||||
|
||||
this.model.set(this.idsName, [], {silent: silent});
|
||||
this.model.set(this.nameHashName, {}, {silent: silent});
|
||||
this.model.set(this.typeHashName, {}, {silent: silent})
|
||||
@@ -285,19 +338,25 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
var arr = _.clone(this.model.get(this.idsName) || []);
|
||||
|
||||
arr.push(attachment.id);
|
||||
|
||||
this.model.set(this.idsName, arr);
|
||||
|
||||
var typeHash = _.clone(this.model.get(this.typeHashName) || {});
|
||||
|
||||
typeHash[attachment.id] = attachment.get('type');
|
||||
|
||||
this.model.set(this.typeHashName, typeHash);
|
||||
|
||||
var nameHash = _.clone(this.model.get(this.nameHashName) || {});
|
||||
|
||||
nameHash[attachment.id] = attachment.get('name');
|
||||
|
||||
this.model.set(this.nameHashName, nameHash);
|
||||
},
|
||||
|
||||
getEditPreview: function (name, type, id) {
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var preview = name;
|
||||
|
||||
@@ -309,11 +368,15 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
},
|
||||
|
||||
addAttachmentBox: function (name, type, id, link) {
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var $attachments = this.$attachments;
|
||||
|
||||
var removeLink = '<a href="javascript:" class="remove-attachment pull-right"><span class="fas fa-times"></span></a>';
|
||||
var removeLink = '<a href="javascript:" class="remove-attachment pull-right">'+
|
||||
'<span class="fas fa-times"></span></a>';
|
||||
|
||||
var preview = name;
|
||||
|
||||
if (this.showPreviews && id) {
|
||||
preview = this.getEditPreview(name, type, id);
|
||||
} else {
|
||||
@@ -321,19 +384,27 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
}
|
||||
|
||||
if (link && preview === name) {
|
||||
preview = '<a href="'+this.getBasePath()+'?entryPoint=download&id='+id+'" target="_BLANK">' + preview + '</a>';
|
||||
preview = '<a href="' + this.getBasePath() + '?entryPoint=download&id=' + id + '" target="_BLANK">' +
|
||||
preview + '</a>';
|
||||
}
|
||||
|
||||
var $att = $('<div>').addClass('gray-box')
|
||||
.append(removeLink)
|
||||
.append($('<span class="preview">' + preview + '</span>').css('width', 'cacl(100% - 30px)'));
|
||||
.append(
|
||||
$('<span class="preview">' + preview + '</span>')
|
||||
.css('width', 'cacl(100% - 30px)')
|
||||
);
|
||||
|
||||
var $container = $('<div>').append($att);
|
||||
|
||||
$attachments.append($container);
|
||||
|
||||
if (!id) {
|
||||
var $loading = $('<span class="small uploading-message">' + this.translate('Uploading...') + '</span>');
|
||||
var $loading = $('<span class="small uploading-message">' +
|
||||
this.translate('Uploading...') + '</span>');
|
||||
|
||||
$container.append($loading);
|
||||
|
||||
$att.on('ready', function () {
|
||||
$loading.html(this.translate('Ready'));
|
||||
}.bind(this));
|
||||
@@ -359,7 +430,9 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
var exceedsMaxFileSize = false;
|
||||
|
||||
var maxFileSize = this.params.maxFileSize || 0;
|
||||
|
||||
var appMaxUploadSize = this.getHelper().getAppParam('maxUploadSize') || 0;
|
||||
|
||||
if (!maxFileSize || maxFileSize > appMaxUploadSize) {
|
||||
maxFileSize = appMaxUploadSize;
|
||||
}
|
||||
@@ -374,10 +447,11 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
}
|
||||
if (exceedsMaxFileSize) {
|
||||
var msg = this.translate('fieldMaxFileSizeError', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{max}', maxFileSize);
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{max}', maxFileSize);
|
||||
|
||||
this.showValidationMessage(msg, 'label');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -397,7 +471,9 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
$attachmentBox.find('.remove-attachment').on('click.uploading', function () {
|
||||
canceledList.push(attachment.cid);
|
||||
|
||||
totalCount--;
|
||||
|
||||
if (uploadedCount == totalCount) {
|
||||
this.isUploading = false;
|
||||
if (totalCount) {
|
||||
@@ -409,6 +485,7 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
var attachment = model.clone();
|
||||
|
||||
var fileReader = new FileReader();
|
||||
|
||||
fileReader.onload = function (e) {
|
||||
attachment.set('name', file.name);
|
||||
attachment.set('type', file.type || 'text/plain');
|
||||
@@ -424,9 +501,12 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
function () {
|
||||
if (canceledList.indexOf(attachment.cid) === -1) {
|
||||
$attachmentBox.trigger('ready');
|
||||
|
||||
this.pushAttachment(attachment);
|
||||
|
||||
$attachmentBox.attr('data-id', attachment.id);
|
||||
uploadedCount++;
|
||||
|
||||
if (uploadedCount == totalCount && this.isUploading) {
|
||||
this.model.trigger('attachment-uploaded:' + this.name);
|
||||
this.afterAttachmentsUploaded.call(this);
|
||||
@@ -438,11 +518,14 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
.fail(
|
||||
function () {
|
||||
$attachmentBox.remove();
|
||||
|
||||
totalCount--;
|
||||
|
||||
if (!totalCount) {
|
||||
this.isUploading = false;
|
||||
this.$el.find('.uploading-message').remove();
|
||||
}
|
||||
|
||||
if (uploadedCount == totalCount && this.isUploading) {
|
||||
this.isUploading = false;
|
||||
this.afterAttachmentsUploaded.call(this);
|
||||
@@ -450,7 +533,9 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this);
|
||||
|
||||
fileReader.readAsDataURL(file);
|
||||
|
||||
}, this);
|
||||
}.bind(this));
|
||||
},
|
||||
@@ -460,7 +545,7 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
if (this.mode == 'edit') {
|
||||
if (this.mode === 'edit') {
|
||||
this.$attachments = this.$el.find('div.attachments');
|
||||
|
||||
var ids = this.model.get(this.idsName) || [];
|
||||
@@ -482,8 +567,11 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
this.$el.on('drop', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
e.stopPropagation();
|
||||
|
||||
var e = e.originalEvent;
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||
this.uploadFiles(e.dataTransfer.files);
|
||||
}
|
||||
@@ -492,13 +580,15 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
this.$el.get(0).addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
this.$el.get(0).addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
if (this.mode == 'search') {
|
||||
if (this.mode === 'search') {
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
|
||||
this.handleSearchType(type);
|
||||
}
|
||||
|
||||
@@ -506,6 +596,7 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
if (this.previewSize === 'large') {
|
||||
this.handleResize();
|
||||
this.resizeIsBeingListened = true;
|
||||
|
||||
$(window).on('resize.' + this.cid, function () {
|
||||
this.handleResize();
|
||||
}.bind(this));
|
||||
@@ -517,29 +608,38 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
if (~this.previewTypeList.indexOf(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
getDetailPreview: function (name, type, id) {
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var preview = name;
|
||||
|
||||
if (this.isTypeIsImage(type)) {
|
||||
preview = '<a data-action="showImagePreview" data-id="' + id + '" href="' + this.getImageUrl(id) + '"><img src="'+this.getImageUrl(id, this.previewSize)+'" class="image-preview"></a>';
|
||||
preview = '<a data-action="showImagePreview" data-id="' + id + '" href="' +
|
||||
this.getImageUrl(id) + '"><img src="'+
|
||||
this.getImageUrl(id, this.previewSize)+'" class="image-preview"></a>';
|
||||
}
|
||||
|
||||
return preview;
|
||||
},
|
||||
|
||||
getValueForDisplay: function () {
|
||||
if (this.mode == 'detail' || this.mode == 'list') {
|
||||
if (this.mode === 'detail' || this.mode === 'list') {
|
||||
var nameHash = this.nameHash;
|
||||
|
||||
var typeHash = this.model.get(this.typeHashName) || {};
|
||||
|
||||
var previews = [];
|
||||
var names = [];
|
||||
|
||||
for (var id in nameHash) {
|
||||
var type = typeHash[id] || false;
|
||||
var name = nameHash[id];
|
||||
|
||||
if (
|
||||
this.showPreviews
|
||||
&&
|
||||
@@ -547,12 +647,22 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
&&
|
||||
(this.mode === 'detail' || this.mode === 'list' && this.showPreviewsInListMode)
|
||||
) {
|
||||
previews.push('<div class="attachment-preview">' + this.getDetailPreview(name, type, id) + '</div>');
|
||||
previews.push(
|
||||
'<div class="attachment-preview">' +
|
||||
this.getDetailPreview(name, type, id) + '</div>'
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
var line = '<div class="attachment-block"><span class="fas fa-paperclip text-soft small"></span> <a href="' + this.getDownloadUrl(id) + '" target="_BLANK">' + Handlebars.Utils.escapeExpression(name) + '</a></div>';
|
||||
|
||||
var line = '<div class="attachment-block">' +
|
||||
'<span class="fas fa-paperclip text-soft small"></span> ' +
|
||||
'<a href="' + this.getDownloadUrl(id) + '" target="_BLANK">' +
|
||||
Handlebars.Utils.escapeExpression(name) + '</a></div>';
|
||||
|
||||
names.push(line);
|
||||
}
|
||||
|
||||
var string = previews.join('') + names.join('');
|
||||
|
||||
return string;
|
||||
@@ -569,6 +679,7 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
this.notify('Loading...');
|
||||
|
||||
var filters = null;
|
||||
|
||||
if (('getSelectFilters' + source) in this) {
|
||||
filters = this['getSelectFilters' + source]();
|
||||
|
||||
@@ -585,38 +696,51 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
}
|
||||
}
|
||||
}
|
||||
var boolFilterList = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs', source, 'boolFilterList']);
|
||||
var boolFilterList = this.getMetadata().get(
|
||||
['clientDefs', 'Attachment', 'sourceDefs', source, 'boolFilterList']
|
||||
);
|
||||
|
||||
if (('getSelectBoolFilterList' + source) in this) {
|
||||
boolFilterList = this['getSelectBoolFilterList' + source]();
|
||||
}
|
||||
var primaryFilterName = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs', source, 'primaryFilter']);
|
||||
|
||||
var primaryFilterName = this.getMetadata().get(
|
||||
['clientDefs', 'Attachment', 'sourceDefs', source, 'primaryFilter']
|
||||
);
|
||||
|
||||
if (('getSelectPrimaryFilterName' + source) in this) {
|
||||
primaryFilterName = this['getSelectPrimaryFilterName' + source]();
|
||||
}
|
||||
|
||||
this.createView('insertFromSource', viewName, {
|
||||
scope: source,
|
||||
createButton: false,
|
||||
filters: filters,
|
||||
boolFilterList: boolFilterList,
|
||||
primaryFilterName: primaryFilterName,
|
||||
multiple: true
|
||||
multiple: true,
|
||||
}, function (view) {
|
||||
view.render();
|
||||
|
||||
this.notify(false);
|
||||
|
||||
this.listenToOnce(view, 'select', function (modelList) {
|
||||
if (Object.prototype.toString.call(modelList) !== '[object Array]') {
|
||||
modelList = [modelList];
|
||||
}
|
||||
|
||||
modelList.forEach(function (model) {
|
||||
if (model.name === 'Attachment') {
|
||||
this.pushAttachment(model);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.ajaxPostRequest(source + '/action/getAttachmentList', {
|
||||
id: model.id
|
||||
id: model.id,
|
||||
}).done(function (attachmentList) {
|
||||
attachmentList.forEach(function (item) {
|
||||
this.getModelFactory().create('Attachment', function (attachment) {
|
||||
attachment.set(item);
|
||||
|
||||
this.pushAttachment(attachment, true);
|
||||
}, this);
|
||||
}, this);
|
||||
@@ -625,6 +749,7 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
}, this);
|
||||
});
|
||||
}, this);
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
@@ -633,7 +758,9 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
if (this.isRequired()) {
|
||||
if ((this.model.get(this.idsName) || []).length == 0) {
|
||||
var msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg, 'label');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -642,14 +769,18 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
validateReady: function () {
|
||||
if (this.isUploading) {
|
||||
var msg = this.translate('fieldIsUploading', 'messages').replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg, 'label');
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var data = {};
|
||||
|
||||
data[this.idsName] = this.model.get(this.idsName) || [];
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -664,20 +795,23 @@ define('views/fields/attachment-multiple', 'views/fields/base', function (Dep) {
|
||||
var data = {
|
||||
type: 'isNotLinked',
|
||||
data: {
|
||||
type: type
|
||||
}
|
||||
type: type,
|
||||
},
|
||||
};
|
||||
|
||||
return data;
|
||||
} else if (type === 'isNotEmpty') {
|
||||
}
|
||||
else if (type === 'isNotEmpty') {
|
||||
var data = {
|
||||
type: 'isLinked',
|
||||
data: {
|
||||
type: type
|
||||
}
|
||||
type: type,
|
||||
},
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -220,22 +220,31 @@ define('views/fields/enum', ['views/fields/base', 'lib!Selectize'], function (De
|
||||
var $element = this.$element = this.$el.find('.main-element');
|
||||
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
|
||||
this.handleSearchType(type);
|
||||
|
||||
var valueList = this.getSearchParamsData().valueList || this.searchParams.value || [];
|
||||
|
||||
this.$element.val(valueList.join(':,:'));
|
||||
|
||||
var data = [];
|
||||
|
||||
(this.params.options || []).forEach(function (value) {
|
||||
var label = this.getLanguage().translateOption(value, this.name, this.scope);
|
||||
|
||||
if (this.translatedOptions) {
|
||||
if (value in this.translatedOptions) {
|
||||
label = this.translatedOptions[value];
|
||||
}
|
||||
}
|
||||
|
||||
if (label === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: value,
|
||||
label: label
|
||||
label: label,
|
||||
});
|
||||
}, this);
|
||||
|
||||
@@ -249,14 +258,17 @@ define('views/fields/enum', ['views/fields/base', 'lib!Selectize'], function (De
|
||||
plugins: ['remove_button'],
|
||||
score: function (search) {
|
||||
var score = this.getScoreFunction(search);
|
||||
|
||||
search = search.toLowerCase();
|
||||
|
||||
return function (item) {
|
||||
if (item.label.toLowerCase().indexOf(search) === 0) {
|
||||
return score(item);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.$el.find('.selectize-dropdown-content').addClass('small');
|
||||
|
||||
@@ -62,15 +62,21 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
events: {
|
||||
'click a.remove-attachment': function (e) {
|
||||
var $div = $(e.currentTarget).parent();
|
||||
|
||||
this.deleteAttachment();
|
||||
|
||||
$div.parent().remove();
|
||||
|
||||
this.$el.find('input.file').val(null);
|
||||
},
|
||||
'change input.file': function (e) {
|
||||
var $file = $(e.currentTarget);
|
||||
|
||||
var files = e.currentTarget.files;
|
||||
|
||||
if (files.length) {
|
||||
this.uploadFile(files[0]);
|
||||
|
||||
$file.replaceWith($file.clone(true));
|
||||
}
|
||||
},
|
||||
@@ -78,16 +84,18 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
e.preventDefault();
|
||||
|
||||
var id = this.model.get(this.idName);
|
||||
|
||||
this.createView('preview', 'views/modals/image-preview', {
|
||||
id: id,
|
||||
model: this.model,
|
||||
name: this.model.get(this.nameName)
|
||||
name: this.model.get(this.nameName),
|
||||
}, function (view) {
|
||||
view.render();
|
||||
});
|
||||
},
|
||||
'click a.action[data-action="insertFromSource"]': function (e) {
|
||||
var name = $(e.currentTarget).data('name');
|
||||
|
||||
this.insertFromSource(name);
|
||||
}
|
||||
},
|
||||
@@ -109,9 +117,13 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
showValidationMessage: function (msg, selector) {
|
||||
var $label = this.$el.find('label');
|
||||
|
||||
var title = $label.attr('title');
|
||||
|
||||
$label.attr('title', '');
|
||||
|
||||
Dep.prototype.showValidationMessage.call(this, msg, selector);
|
||||
|
||||
$label.attr('title', title);
|
||||
},
|
||||
|
||||
@@ -119,7 +131,9 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
if (this.isRequired()) {
|
||||
if (this.model.get(this.idName) == null) {
|
||||
var msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
|
||||
|
||||
var $target;
|
||||
|
||||
if (this.isUploading) {
|
||||
$target = this.$el.find('.gray-box');
|
||||
} else {
|
||||
@@ -127,6 +141,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
}
|
||||
|
||||
this.showValidationMessage(msg, $target);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -135,7 +150,9 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
validateReady: function () {
|
||||
if (this.isUploading) {
|
||||
var $target = this.$el.find('.gray-box');
|
||||
var msg = this.translate('fieldIsUploading', 'messages').replace('{field}', this.getLabelText());
|
||||
|
||||
var msg = this.translate('fieldIsUploading', 'messages')
|
||||
.replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg, $target);
|
||||
|
||||
@@ -153,25 +170,43 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
var sourceDefs = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs']) || {};
|
||||
|
||||
this.sourceList = Espo.Utils.clone(this.params.sourceList || []).filter(function (item) {
|
||||
if (!(item in sourceDefs)) {
|
||||
return true;
|
||||
}
|
||||
this.sourceList = Espo.Utils.clone(this.params.sourceList || []);
|
||||
|
||||
var defs = sourceDefs[item];
|
||||
|
||||
if (defs.configCheck) {
|
||||
var configCheck = defs.configCheck;
|
||||
|
||||
if (configCheck) {
|
||||
var arr = configCheck.split('.');
|
||||
|
||||
if (this.getConfig().getByPath(arr)) {
|
||||
return true;
|
||||
}
|
||||
this.sourceList = this.sourceList
|
||||
.concat(
|
||||
this.getMetadata().get(['clientDefs', 'Attachment', 'generalSourceList']) || []
|
||||
)
|
||||
.filter(
|
||||
function (item, i, self) {
|
||||
return self.indexOf(item) === i;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
)
|
||||
.filter(
|
||||
function (item) {
|
||||
var defs = sourceDefs[item] || {};
|
||||
|
||||
if (defs.accessDataList) {
|
||||
if (
|
||||
!Espo.Utils.checkAccessDataList(
|
||||
defs.accessDataList, this.getAcl(), this.getUser()
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (defs.configCheck) {
|
||||
var arr = defs.configCheck.split('.');
|
||||
|
||||
if (!this.getConfig().getByPath(arr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
if ('showPreview' in this.params) {
|
||||
this.showPreview = this.params.showPreview;
|
||||
@@ -215,7 +250,9 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
this.$el.on('drop', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
var e = e.originalEvent;
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||
this.uploadFile(e.dataTransfer.files[0]);
|
||||
}
|
||||
@@ -224,6 +261,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
this.$el.on('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
|
||||
this.$el.on('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
}.bind(this));
|
||||
@@ -231,6 +269,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
if (this.mode == 'search') {
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
|
||||
this.handleSearchType(type);
|
||||
}
|
||||
|
||||
@@ -248,6 +287,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
handleResize: function () {
|
||||
var width = this.$el.width();
|
||||
|
||||
this.$el.find('img.image-preview').css('maxWidth', width + 'px');
|
||||
},
|
||||
|
||||
@@ -268,6 +308,8 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
}
|
||||
}
|
||||
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var src = this.getBasePath() + '?entryPoint=image&size=' + previewSize + '&id=' + id;
|
||||
|
||||
var img = '<img src="' + src + '" class="image-preview">';
|
||||
@@ -288,6 +330,8 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
getEditPreview: function (name, type, id) {
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var preview = name;
|
||||
|
||||
if (~this.previewTypeList.indexOf(type)) {
|
||||
@@ -329,6 +373,8 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
},
|
||||
|
||||
getImageUrl: function (id, size) {
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var url = this.getBasePath() + '?entryPoint=image&id=' + id;
|
||||
|
||||
if (size) {
|
||||
@@ -375,7 +421,6 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
},
|
||||
|
||||
setAttachment: function (attachment) {
|
||||
var arr = _.clone(this.model.get(this.idsName));
|
||||
var o = {};
|
||||
|
||||
o[this.idName] = attachment.id;
|
||||
@@ -391,6 +436,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
var maxFileSize = this.params.maxFileSize || 0;
|
||||
var appMaxUploadSize = this.getHelper().getAppParam('maxUploadSize') || 0;
|
||||
|
||||
if (!maxFileSize || maxFileSize > appMaxUploadSize) {
|
||||
maxFileSize = appMaxUploadSize;
|
||||
}
|
||||
@@ -405,6 +451,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
var msg = this.translate('fieldMaxFileSizeError', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{max}', maxFileSize);
|
||||
|
||||
this.showValidationMessage(msg, '.attachment-button label');
|
||||
|
||||
return;
|
||||
@@ -419,12 +466,16 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
$attachmentBox.find('.remove-attachment').on('click.uploading', function () {
|
||||
isCanceled = true;
|
||||
|
||||
this.$el.find('.attachment-button').removeClass('hidden');
|
||||
|
||||
this.isUploading = false;
|
||||
|
||||
this.$el.find('input.file').val(null);
|
||||
}.bind(this));
|
||||
|
||||
var fileReader = new FileReader();
|
||||
|
||||
fileReader.onload = function (e) {
|
||||
this.handleFileUpload(file, e.target.result, function (result, fileParams) {
|
||||
attachment.set('name', fileParams.name);
|
||||
@@ -456,6 +507,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
);
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
fileReader.readAsDataURL(file);
|
||||
}, this);
|
||||
},
|
||||
@@ -464,8 +516,9 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
var params = {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size
|
||||
size: file.size,
|
||||
};
|
||||
|
||||
callback(contents, params);
|
||||
},
|
||||
|
||||
@@ -474,7 +527,8 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var removeLink = '<a href="javascript:" class="remove-attachment pull-right"><span class="fas fa-times"></span></a>';
|
||||
var removeLink = '<a href="javascript:" class="remove-attachment pull-right">' +
|
||||
'<span class="fas fa-times"></span></a>';
|
||||
|
||||
var preview = name;
|
||||
if (this.showPreview && id) {
|
||||
@@ -484,15 +538,20 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
}
|
||||
|
||||
var $att = $('<div>').append(removeLink)
|
||||
.append($('<span class="preview">' + preview + '</span>').css('width', 'cacl(100% - 30px)'))
|
||||
.append($('<span class="preview">' + preview + '</span>')
|
||||
.css('width', 'cacl(100% - 30px)'))
|
||||
.addClass('gray-box');
|
||||
|
||||
var $container = $('<div>').append($att);
|
||||
|
||||
this.$attachment.append($container);
|
||||
|
||||
if (!id) {
|
||||
var $loading = $('<span class="small uploading-message">' + this.translate('Uploading...') + '</span>');
|
||||
var $loading = $('<span class="small uploading-message">' +
|
||||
this.translate('Uploading...') + '</span>');
|
||||
|
||||
$container.append($loading);
|
||||
|
||||
$att.on('ready', function () {
|
||||
$loading.html(self.translate('Ready'));
|
||||
});
|
||||
@@ -511,6 +570,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
this.notify('Loading...');
|
||||
|
||||
var filters = null;
|
||||
|
||||
if (('getSelectFilters' + source) in this) {
|
||||
filters = this['getSelectFilters' + source]();
|
||||
|
||||
@@ -521,44 +581,57 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
type: 'equals',
|
||||
field: 'accountId',
|
||||
value: this.model.get('parentId'),
|
||||
valueName: this.model.get('parentName')
|
||||
valueName: this.model.get('parentName'),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
var boolFilterList = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs', source, 'boolFilterList']);
|
||||
var boolFilterList = this.getMetadata().get(
|
||||
['clientDefs', 'Attachment', 'sourceDefs', source, 'boolFilterList']
|
||||
);
|
||||
|
||||
if (('getSelectBoolFilterList' + source) in this) {
|
||||
boolFilterList = this['getSelectBoolFilterList' + source]();
|
||||
}
|
||||
var primaryFilterName = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs', source, 'primaryFilter']);
|
||||
|
||||
var primaryFilterName = this.getMetadata().get(
|
||||
['clientDefs', 'Attachment', 'sourceDefs', source, 'primaryFilter']
|
||||
);
|
||||
|
||||
if (('getSelectPrimaryFilterName' + source) in this) {
|
||||
primaryFilterName = this['getSelectPrimaryFilterName' + source]();
|
||||
}
|
||||
|
||||
this.createView('insertFromSource', viewName, {
|
||||
scope: source,
|
||||
createButton: false,
|
||||
filters: filters,
|
||||
boolFilterList: boolFilterList,
|
||||
primaryFilterName: primaryFilterName,
|
||||
multiple: false
|
||||
multiple: false,
|
||||
}, function (view) {
|
||||
view.render();
|
||||
|
||||
this.notify(false);
|
||||
|
||||
this.listenToOnce(view, 'select', function (modelList) {
|
||||
if (Object.prototype.toString.call(modelList) !== '[object Array]') {
|
||||
modelList = [modelList];
|
||||
}
|
||||
|
||||
modelList.forEach(function (model) {
|
||||
if (model.name === 'Attachment') {
|
||||
this.setAttachment(model);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.ajaxPostRequest(source + '/action/getAttachmentList', {
|
||||
id: model.id
|
||||
id: model.id,
|
||||
}).done(function (attachmentList) {
|
||||
attachmentList.forEach(function (item) {
|
||||
this.getModelFactory().create('Attachment', function (attachment) {
|
||||
attachment.set(item);
|
||||
|
||||
this.setAttachment(attachment, true);
|
||||
}, this);
|
||||
}, this);
|
||||
@@ -567,15 +640,18 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
}, this);
|
||||
});
|
||||
}, this);
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var data = {};
|
||||
|
||||
data[this.idName] = this.model.get(this.idName);
|
||||
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,6 +125,9 @@ Espo.define('views/fields/link-multiple-with-primary', 'views/fields/link-multip
|
||||
addLinkHtml: function (id, name) {
|
||||
name = name || id;
|
||||
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
if (this.mode == 'search') {
|
||||
return Dep.prototype.addLinkHtml.call(this, id, name);
|
||||
}
|
||||
@@ -132,7 +135,7 @@ Espo.define('views/fields/link-multiple-with-primary', 'views/fields/link-multip
|
||||
var $container = this.$el.find('.link-container');
|
||||
var $el = $('<div class="form-inline list-group-item link-with-role clearfix link-group-item-with-primary">').addClass('link-' + id).attr('data-id', id);
|
||||
|
||||
var nameHtml = '<div>' + this.getHelper().escapeString(name) || id + ' ' + '</div>';
|
||||
var nameHtml = '<div>' + name + ' ' + '</div>';
|
||||
var removeHtml = '<a href="javascript:" class="pull-right" data-id="' + id + '" data-action="clearLink"><span class="fas fa-times"></a>';
|
||||
|
||||
$left = $('<div>');
|
||||
|
||||
@@ -79,6 +79,9 @@ define('views/fields/link-multiple-with-role', 'views/fields/link-multiple', fun
|
||||
name = this.translate(this.foreignScope, 'scopeNames');
|
||||
}
|
||||
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
var role = (this.columns[id] || {})[this.columnName] || '';
|
||||
var roleHtml = '';
|
||||
|
||||
@@ -107,7 +110,7 @@ define('views/fields/link-multiple-with-role', 'views/fields/link-multiple', fun
|
||||
if (this.mode == 'detail') {
|
||||
iconHtml = this.getIconHtml(id);
|
||||
}
|
||||
var lineHtml = '<div>' + iconHtml + '<a href="#' + this.foreignScope + '/view/' + id + '">' + this.getHelper().escapeString(name) + '</a> ' + roleHtml + '</div>';
|
||||
var lineHtml = '<div>' + iconHtml + '<a href="#' + this.foreignScope + '/view/' + id + '">' + name + '</a> ' + roleHtml + '</div>';
|
||||
return lineHtml;
|
||||
},
|
||||
|
||||
@@ -161,6 +164,8 @@ define('views/fields/link-multiple-with-role', 'views/fields/link-multiple', fun
|
||||
},
|
||||
|
||||
getJQSelect: function (id, roleValue) {
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
|
||||
var $role = $('<select class="role form-control input-sm pull-right" data-id="'+id+'">');
|
||||
this.roleList.forEach(function (role) {
|
||||
var selectedHtml = (role == roleValue) ? 'selected': '';
|
||||
@@ -178,10 +183,13 @@ define('views/fields/link-multiple-with-role', 'views/fields/link-multiple', fun
|
||||
return Dep.prototype.addLinkHtml.call(this, id, name);
|
||||
}
|
||||
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
var $container = this.$el.find('.link-container');
|
||||
var $el = $('<div class="form-inline list-group-item link-with-role link-group-item-with-columns clearfix">').addClass('link-' + id);
|
||||
|
||||
var nameHtml = '<div>' + this.getHelper().escapeString(name) + ' ' + '</div>';
|
||||
var nameHtml = '<div>' + name + ' ' + '</div>';
|
||||
|
||||
var removeHtml = '<a href="javascript:" class="pull-right" data-id="' + id + '" data-action="clearLink"><span class="fas fa-times"></a>';
|
||||
|
||||
|
||||
@@ -328,9 +328,12 @@ define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
addLinkHtml: function (id, name) {
|
||||
name = name || id;
|
||||
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
var $container = this.$el.find('.link-container');
|
||||
var $el = $('<div />').addClass('link-' + id).addClass('list-group-item').attr('data-id', id);
|
||||
$el.html(this.getHelper().escapeString(name || id) + ' ');
|
||||
$el.html(name + ' ');
|
||||
$el.prepend('<a href="javascript:" class="pull-right" data-id="' + id + '" data-action="clearLink"><span class="fas fa-times"></a>');
|
||||
$container.append($el);
|
||||
|
||||
@@ -343,6 +346,10 @@ define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
getDetailLinkHtml: function (id) {
|
||||
var name = this.nameHash[id] || id;
|
||||
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
if (!name && id) {
|
||||
name = this.translate(this.foreignScope, 'scopeNames');
|
||||
}
|
||||
@@ -350,7 +357,7 @@ define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
if (this.mode == 'detail') {
|
||||
iconHtml = this.getIconHtml(id);
|
||||
}
|
||||
return '<a href="#' + this.foreignScope + '/view/' + id + '">' + iconHtml + this.getHelper().escapeString(name) + '</a>';
|
||||
return '<a href="#' + this.foreignScope + '/view/' + id + '">' + iconHtml + name + '</a>';
|
||||
},
|
||||
|
||||
getValueForDisplay: function () {
|
||||
|
||||
@@ -56,6 +56,8 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
|
||||
|
||||
searchTypeList: ['is', 'isEmpty', 'isNotEmpty'],
|
||||
|
||||
initialSearchIsNotIdle: true,
|
||||
|
||||
data: function () {
|
||||
var nameValue = this.model.get(this.nameName);
|
||||
if (!nameValue && this.model.get(this.idName) && this.model.get(this.typeName)) {
|
||||
|
||||
@@ -406,6 +406,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
this.searchData.oneOfIdList.splice(index, 1);
|
||||
}
|
||||
delete this.searchData.oneOfNameHash[id];
|
||||
|
||||
this.trigger('change');
|
||||
},
|
||||
|
||||
addLinkOneOf: function (id, name) {
|
||||
@@ -413,6 +415,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
this.searchData.oneOfIdList.push(id);
|
||||
this.searchData.oneOfNameHash[id] = name;
|
||||
this.addLinkOneOfHtml(id, name);
|
||||
|
||||
this.trigger('change');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -421,10 +425,17 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
},
|
||||
|
||||
addLinkOneOfHtml: function (id, name) {
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = this.getHelper().escapeString(name);
|
||||
|
||||
var $container = this.$el.find('.link-one-of-container');
|
||||
|
||||
var $el = $('<div />').addClass('link-' + id).addClass('list-group-item');
|
||||
$el.html(this.getHelper().escapeString(name) + ' ');
|
||||
|
||||
$el.html(name + ' ');
|
||||
|
||||
$el.prepend('<a href="javascript:" class="pull-right" data-id="' + id + '" data-action="clearLinkOneOf"><span class="fas fa-times"></a>');
|
||||
|
||||
$container.append($el);
|
||||
|
||||
return $el;
|
||||
|
||||
@@ -150,6 +150,8 @@ Espo.define('views/fields/user', 'views/fields/link', function (Dep) {
|
||||
this.searchData.teamIdList.splice(index, 1);
|
||||
}
|
||||
delete this.searchData.teamNameHash[id];
|
||||
|
||||
this.trigger('change');
|
||||
},
|
||||
|
||||
addLinkTeams: function (id, name) {
|
||||
@@ -159,6 +161,8 @@ Espo.define('views/fields/user', 'views/fields/link', function (Dep) {
|
||||
this.searchData.teamIdList.push(id);
|
||||
this.searchData.teamNameHash[id] = name;
|
||||
this.addLinkTeamsHtml(id, name);
|
||||
|
||||
this.trigger('change');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -167,6 +171,9 @@ Espo.define('views/fields/user', 'views/fields/link', function (Dep) {
|
||||
},
|
||||
|
||||
addLinkTeamsHtml: function (id, name) {
|
||||
id = Handlebars.Utils.escapeExpression(id);
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
|
||||
var $container = this.$el.find('.link-teams-container');
|
||||
var $el = $('<div />').addClass('link-' + id).addClass('list-group-item');
|
||||
$el.html(name + ' ');
|
||||
|
||||
@@ -152,7 +152,11 @@ define('views/notification/badge', 'view', function (Dep) {
|
||||
checkBypass: function () {
|
||||
var last = this.getRouter().getLast() || {};
|
||||
|
||||
if (last.controller == 'Admin' && last.action == 'upgrade') {
|
||||
if (
|
||||
last.controller == 'Admin'
|
||||
&&
|
||||
~['upgrade', 'extensions'].indexOf(last.action)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -86,7 +86,9 @@ define('views/record/list', 'view', function (Dep) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
var id = $(e.currentTarget).data('id');
|
||||
|
||||
var id = $(e.currentTarget).attr('data-id');
|
||||
|
||||
var model = this.collection.get(id);
|
||||
|
||||
var scope = this.getModelScope(id);
|
||||
@@ -130,7 +132,9 @@ define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
'click .record-checkbox': function (e) {
|
||||
var $target = $(e.currentTarget);
|
||||
var id = $target.data('id');
|
||||
|
||||
var id = $target.attr('data-id');
|
||||
|
||||
if (e.currentTarget.checked) {
|
||||
this.checkRecord(id, $target);
|
||||
} else {
|
||||
@@ -1080,7 +1084,9 @@ define('views/record/list', 'view', function (Dep) {
|
||||
if (this.selectable) {
|
||||
this.events['click .list a.link'] = function (e) {
|
||||
e.preventDefault();
|
||||
var id = $(e.target).data('id');
|
||||
|
||||
var id = $(e.target).attr('data-id');
|
||||
|
||||
if (id) {
|
||||
var model = this.collection.get(id);
|
||||
if (this.checkboxes) {
|
||||
@@ -1407,7 +1413,9 @@ define('views/record/list', 'view', function (Dep) {
|
||||
getSelected: function () {
|
||||
var list = [];
|
||||
this.$el.find('input.record-checkbox:checked').each(function (i, el) {
|
||||
var id = $(el).data('id');
|
||||
|
||||
var id = $(el).attr('data-id');
|
||||
|
||||
var model = this.collection.get(id);
|
||||
list.push(model);
|
||||
}.bind(this));
|
||||
|
||||
@@ -30,6 +30,8 @@ define('views/template/record/detail', 'views/record/detail', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
saveAndContinueEditingAction: true,
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ define('views/template/record/edit', 'views/record/edit', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
saveAndContinueEditingAction: true,
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
|
||||
@@ -30,8 +30,6 @@ define('views/wysiwyg/modals/insert-image', 'views/modal', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
backdrop: true,
|
||||
|
||||
template: 'wysiwyg/modals/insert-image',
|
||||
|
||||
events: {
|
||||
|
||||
@@ -30,8 +30,6 @@ define('views/wysiwyg/modals/insert-link', 'views/modal', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
backdrop: true,
|
||||
|
||||
template: 'wysiwyg/modals/insert-link',
|
||||
|
||||
events: {
|
||||
|
||||
@@ -25,11 +25,10 @@
|
||||
"laminas/laminas-servicemanager": "^3.3.1",
|
||||
"monolog/monolog": "2.*",
|
||||
"yzalis/identicon": "*",
|
||||
"zordius/lightncandy": "1.*",
|
||||
"zordius/lightncandy": "dev-espo#v1.2.5e",
|
||||
"composer/semver": "^3",
|
||||
"tecnickcom/tcpdf": "^6.3",
|
||||
"phpoffice/phpspreadsheet": "^1.10",
|
||||
"spatie/async": "dev-for-espocrm",
|
||||
"tecnickcom/tcpdf": "6.3.5",
|
||||
"spatie/async": "0.0.4",
|
||||
"symfony/process": "4.1.7",
|
||||
"symfony/http-foundation": "4.4.7",
|
||||
"cboden/ratchet": "^0.4.1",
|
||||
@@ -39,6 +38,7 @@
|
||||
"robthree/twofactorauth": "^1.6",
|
||||
"nesbot/carbon": "^2.26",
|
||||
"zbateson/mail-mime-parser": "1.3.*",
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"doctrine/dbal": "^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -54,7 +54,7 @@
|
||||
"repositories": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/async.git"
|
||||
"url": "https://github.com/yurikuzn/lightncandy.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
531
composer.lock
generated
531
composer.lock
generated
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "80dd458b8718443e013ae4d448e4a732",
|
||||
"content-hash": "a32e7a77ab0ae1e795186b26455beb14",
|
||||
"packages": [
|
||||
{
|
||||
"name": "cboden/ratchet",
|
||||
@@ -112,20 +112,6 @@
|
||||
}
|
||||
],
|
||||
"description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-11-11T10:22:58+00:00"
|
||||
},
|
||||
{
|
||||
@@ -188,20 +174,6 @@
|
||||
"validation",
|
||||
"versioning"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://packagist.com",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-05-26T18:22:04+00:00"
|
||||
},
|
||||
{
|
||||
@@ -316,20 +288,6 @@
|
||||
"redis",
|
||||
"xcache"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-07T18:54:01+00:00"
|
||||
},
|
||||
{
|
||||
@@ -423,20 +381,6 @@
|
||||
"sqlserver",
|
||||
"sqlsrv"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-11-15T18:20:41+00:00"
|
||||
},
|
||||
{
|
||||
@@ -513,20 +457,6 @@
|
||||
"event system",
|
||||
"events"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-05-29T18:28:51+00:00"
|
||||
},
|
||||
{
|
||||
@@ -575,12 +505,6 @@
|
||||
"cron",
|
||||
"schedule"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/dragonmantank",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-08-21T02:30:13+00:00"
|
||||
},
|
||||
{
|
||||
@@ -626,6 +550,56 @@
|
||||
],
|
||||
"time": "2017-07-23T21:35:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ezyang/htmlpurifier",
|
||||
"version": "v4.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ezyang/htmlpurifier.git",
|
||||
"reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/08e27c97e4c6ed02f37c5b2b20488046c8d90d75",
|
||||
"reference": "08e27c97e4c6ed02f37c5b2b20488046c8d90d75",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"HTMLPurifier": "library/"
|
||||
},
|
||||
"files": [
|
||||
"library/HTMLPurifier.composer.php"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
"/library/HTMLPurifier/Language/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Edward Z. Yang",
|
||||
"email": "admin@htmlpurifier.org",
|
||||
"homepage": "http://ezyang.com"
|
||||
}
|
||||
],
|
||||
"description": "Standards compliant HTML filter written in PHP",
|
||||
"homepage": "http://htmlpurifier.org/",
|
||||
"keywords": [
|
||||
"html"
|
||||
],
|
||||
"time": "2020-06-29T00:56:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fig/http-message-util",
|
||||
"version": "1.1.4",
|
||||
@@ -1045,12 +1019,6 @@
|
||||
"service-manager",
|
||||
"servicemanager"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://funding.communitybridge.org/projects/laminas-project",
|
||||
"type": "community_bridge"
|
||||
}
|
||||
],
|
||||
"time": "2020-05-11T14:43:22+00:00"
|
||||
},
|
||||
{
|
||||
@@ -1231,31 +1199,92 @@
|
||||
"time": "2020-09-14T14:23:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "markbaker/complex",
|
||||
"version": "1.4.7",
|
||||
"name": "maennchen/zipstream-php",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MarkBaker/PHPComplex.git",
|
||||
"reference": "1ea674a8308baf547cbcbd30c5fcd6d301b7c000"
|
||||
"url": "https://github.com/maennchen/ZipStream-PHP.git",
|
||||
"reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/1ea674a8308baf547cbcbd30c5fcd6d301b7c000",
|
||||
"reference": "1ea674a8308baf547cbcbd30c5fcd6d301b7c000",
|
||||
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58",
|
||||
"reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.6.0|^7.0.0"
|
||||
"myclabs/php-enum": "^1.5",
|
||||
"php": ">= 7.1",
|
||||
"psr/http-message": "^1.0",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.4.3",
|
||||
"phpcompatibility/php-compatibility": "^8.0",
|
||||
"ext-zip": "*",
|
||||
"guzzlehttp/guzzle": ">= 6.3",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"phpunit/phpunit": ">= 7.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ZipStream\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paul Duncan",
|
||||
"email": "pabs@pablotron.org"
|
||||
},
|
||||
{
|
||||
"name": "Jonatan Männchen",
|
||||
"email": "jonatan@maennchen.ch"
|
||||
},
|
||||
{
|
||||
"name": "Jesse Donat",
|
||||
"email": "donatj@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "András Kolesár",
|
||||
"email": "kolesar@kolesar.hu"
|
||||
}
|
||||
],
|
||||
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
|
||||
"keywords": [
|
||||
"stream",
|
||||
"zip"
|
||||
],
|
||||
"time": "2020-05-30T13:11:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "markbaker/complex",
|
||||
"version": "2.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MarkBaker/PHPComplex.git",
|
||||
"reference": "9999f1432fae467bc93c53f357105b4c31bb994c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/9999f1432fae467bc93c53f357105b4c31bb994c",
|
||||
"reference": "9999f1432fae467bc93c53f357105b4c31bb994c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
|
||||
"phpcompatibility/php-compatibility": "^9.0",
|
||||
"phpdocumentor/phpdocumentor": "2.*",
|
||||
"phploc/phploc": "2.*",
|
||||
"phploc/phploc": "^4.0",
|
||||
"phpmd/phpmd": "2.*",
|
||||
"phpunit/phpunit": "^4.8.35|^5.4.0",
|
||||
"sebastian/phpcpd": "2.*",
|
||||
"squizlabs/php_codesniffer": "^3.3.0"
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.3",
|
||||
"sebastian/phpcpd": "^4.0",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1323,33 +1352,34 @@
|
||||
"complex",
|
||||
"mathematics"
|
||||
],
|
||||
"time": "2018-10-13T23:28:42+00:00"
|
||||
"time": "2020-08-26T10:42:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "markbaker/matrix",
|
||||
"version": "1.2.0",
|
||||
"version": "2.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MarkBaker/PHPMatrix.git",
|
||||
"reference": "5348c5a67e3b75cd209d70103f916a93b1f1ed21"
|
||||
"reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/5348c5a67e3b75cd209d70103f916a93b1f1ed21",
|
||||
"reference": "5348c5a67e3b75cd209d70103f916a93b1f1ed21",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/361c0f545c3172ee26c3d596a0aa03f0cef65e6a",
|
||||
"reference": "361c0f545c3172ee26c3d596a0aa03f0cef65e6a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.6.0|^7.0.0"
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||
"phpcompatibility/php-compatibility": "dev-master",
|
||||
"phploc/phploc": "^4",
|
||||
"phpmd/phpmd": "dev-master",
|
||||
"phpunit/phpunit": "^5.7",
|
||||
"sebastian/phpcpd": "^3.0",
|
||||
"squizlabs/php_codesniffer": "^3.0@dev"
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
|
||||
"phpcompatibility/php-compatibility": "^9.0",
|
||||
"phpdocumentor/phpdocumentor": "2.*",
|
||||
"phploc/phploc": "^4.0",
|
||||
"phpmd/phpmd": "2.*",
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.3",
|
||||
"sebastian/phpcpd": "^4.0",
|
||||
"squizlabs/php_codesniffer": "^3.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1357,22 +1387,22 @@
|
||||
"Matrix\\": "classes/src/"
|
||||
},
|
||||
"files": [
|
||||
"classes/src/functions/adjoint.php",
|
||||
"classes/src/functions/antidiagonal.php",
|
||||
"classes/src/functions/cofactors.php",
|
||||
"classes/src/functions/determinant.php",
|
||||
"classes/src/functions/diagonal.php",
|
||||
"classes/src/functions/identity.php",
|
||||
"classes/src/functions/inverse.php",
|
||||
"classes/src/functions/minors.php",
|
||||
"classes/src/functions/trace.php",
|
||||
"classes/src/functions/transpose.php",
|
||||
"classes/src/operations/add.php",
|
||||
"classes/src/operations/directsum.php",
|
||||
"classes/src/operations/subtract.php",
|
||||
"classes/src/operations/multiply.php",
|
||||
"classes/src/operations/divideby.php",
|
||||
"classes/src/operations/divideinto.php"
|
||||
"classes/src/Functions/adjoint.php",
|
||||
"classes/src/Functions/antidiagonal.php",
|
||||
"classes/src/Functions/cofactors.php",
|
||||
"classes/src/Functions/determinant.php",
|
||||
"classes/src/Functions/diagonal.php",
|
||||
"classes/src/Functions/identity.php",
|
||||
"classes/src/Functions/inverse.php",
|
||||
"classes/src/Functions/minors.php",
|
||||
"classes/src/Functions/trace.php",
|
||||
"classes/src/Functions/transpose.php",
|
||||
"classes/src/Operations/add.php",
|
||||
"classes/src/Operations/directsum.php",
|
||||
"classes/src/Operations/subtract.php",
|
||||
"classes/src/Operations/multiply.php",
|
||||
"classes/src/Operations/divideby.php",
|
||||
"classes/src/Operations/divideinto.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@@ -1382,7 +1412,7 @@
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Baker",
|
||||
"email": "mark@lange.demon.co.uk"
|
||||
"email": "mark@demon-angel.eu"
|
||||
}
|
||||
],
|
||||
"description": "PHP Class for working with matrices",
|
||||
@@ -1392,7 +1422,7 @@
|
||||
"matrix",
|
||||
"vector"
|
||||
],
|
||||
"time": "2019-10-06T11:29:25+00:00"
|
||||
"time": "2021-01-23T16:37:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "michelf/php-markdown",
|
||||
@@ -1519,17 +1549,53 @@
|
||||
"logging",
|
||||
"psr-3"
|
||||
],
|
||||
"funding": [
|
||||
"time": "2020-07-23T08:41:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "myclabs/php-enum",
|
||||
"version": "1.7.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/myclabs/php-enum.git",
|
||||
"reference": "d178027d1e679832db9f38248fcc7200647dc2b7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/myclabs/php-enum/zipball/d178027d1e679832db9f38248fcc7200647dc2b7",
|
||||
"reference": "d178027d1e679832db9f38248fcc7200647dc2b7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7",
|
||||
"squizlabs/php_codesniffer": "1.*",
|
||||
"vimeo/psalm": "^3.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MyCLabs\\Enum\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"url": "https://github.com/Seldaek",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
|
||||
"type": "tidelift"
|
||||
"name": "PHP Enum contributors",
|
||||
"homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"time": "2020-07-23T08:41:23+00:00"
|
||||
"description": "PHP Enum implementation",
|
||||
"homepage": "http://github.com/myclabs/php-enum",
|
||||
"keywords": [
|
||||
"enum"
|
||||
],
|
||||
"time": "2020-11-14T18:14:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
@@ -1707,16 +1773,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"version": "1.10.1",
|
||||
"version": "1.16.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||
"reference": "1648dc9ebef6ebe0c5a172e16cf66732918416e0"
|
||||
"reference": "76d4323b85129d0c368149c831a07a3e258b2b50"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/1648dc9ebef6ebe0c5a172e16cf66732918416e0",
|
||||
"reference": "1648dc9ebef6ebe0c5a172e16cf66732918416e0",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/76d4323b85129d0c368149c831a07a3e258b2b50",
|
||||
"reference": "76d4323b85129d0c368149c831a07a3e258b2b50",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1733,26 +1799,30 @@
|
||||
"ext-xmlwriter": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": "*",
|
||||
"markbaker/complex": "^1.4",
|
||||
"markbaker/matrix": "^1.2",
|
||||
"php": "^7.1",
|
||||
"ezyang/htmlpurifier": "^4.13",
|
||||
"maennchen/zipstream-php": "^2.1",
|
||||
"markbaker/complex": "^1.5||^2.0",
|
||||
"markbaker/matrix": "^1.2||^2.0",
|
||||
"php": "^7.2||^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/simple-cache": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dompdf/dompdf": "^0.8.3",
|
||||
"dompdf/dompdf": "^0.8.5",
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"jpgraph/jpgraph": "^4.0",
|
||||
"mpdf/mpdf": "^8.0",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpunit/phpunit": "^7.5",
|
||||
"phpunit/phpunit": "^8.5||^9.3",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"tecnickcom/tcpdf": "^6.3"
|
||||
},
|
||||
"suggest": {
|
||||
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
|
||||
"dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
|
||||
"jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
|
||||
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
|
||||
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
|
||||
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1796,7 +1866,7 @@
|
||||
"xls",
|
||||
"xlsx"
|
||||
],
|
||||
"time": "2019-12-01T23:13:51+00:00"
|
||||
"time": "2020-12-31T18:03:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1847,6 +1917,55 @@
|
||||
],
|
||||
"time": "2017-02-14T16:28:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
"homepage": "https://github.com/php-fig/http-client",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-client",
|
||||
"psr",
|
||||
"psr-18"
|
||||
],
|
||||
"time": "2020-06-29T06:28:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.0.1",
|
||||
@@ -2104,16 +2223,16 @@
|
||||
},
|
||||
{
|
||||
"name": "psr/simple-cache",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/simple-cache.git",
|
||||
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24"
|
||||
"reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/753fa598e8f3b9966c886fe13f370baa45ef0e24",
|
||||
"reference": "753fa598e8f3b9966c886fe13f370baa45ef0e24",
|
||||
"url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
|
||||
"reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2148,7 +2267,7 @@
|
||||
"psr-16",
|
||||
"simple-cache"
|
||||
],
|
||||
"time": "2017-01-02T13:31:39+00:00"
|
||||
"time": "2017-10-23T01:57:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
@@ -2815,25 +2934,21 @@
|
||||
"micro",
|
||||
"router"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/slimphp",
|
||||
"type": "open_collective"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/slim/slim",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-04-14T20:49:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "spatie/async",
|
||||
"version": "dev-for-espocrm",
|
||||
"version": "0.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/yurikuzn/async.git",
|
||||
"reference": "9e0f665e4cf0cb4f4d3d78c78ab567b2b539415f"
|
||||
"url": "https://github.com/spatie/async.git",
|
||||
"reference": "8b76df4ab77dcf7680eee4b83353d038e28f92f7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/spatie/async/zipball/8b76df4ab77dcf7680eee4b83353d038e28f92f7",
|
||||
"reference": "8b76df4ab77dcf7680eee4b83353d038e28f92f7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"opis/closure": "^3.0",
|
||||
@@ -2847,21 +2962,14 @@
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [],
|
||||
"files": [
|
||||
"src/helpers.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Spatie\\Async\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Spatie\\Async\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"vendor/bin/phpunit"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
@@ -2879,7 +2987,7 @@
|
||||
"async",
|
||||
"spatie"
|
||||
],
|
||||
"time": "2018-12-11T08:49:14+00:00"
|
||||
"time": "2018-01-29T15:09:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-foundation",
|
||||
@@ -2934,20 +3042,6 @@
|
||||
],
|
||||
"description": "Symfony HttpFoundation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-03-30T14:07:33+00:00"
|
||||
},
|
||||
{
|
||||
@@ -3668,6 +3762,7 @@
|
||||
"identicon",
|
||||
"image"
|
||||
],
|
||||
"abandoned": true,
|
||||
"time": "2014-07-13T09:19:12+00:00"
|
||||
},
|
||||
{
|
||||
@@ -3844,20 +3939,14 @@
|
||||
},
|
||||
{
|
||||
"name": "zordius/lightncandy",
|
||||
"version": "v1.2.4",
|
||||
"version": "dev-espo",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/zordius/lightncandy.git",
|
||||
"reference": "dfdb910ae7b59e274f1ff97d29b724871f01b4cc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/zordius/lightncandy/zipball/dfdb910ae7b59e274f1ff97d29b724871f01b4cc",
|
||||
"reference": "dfdb910ae7b59e274f1ff97d29b724871f01b4cc",
|
||||
"shasum": ""
|
||||
"url": "https://github.com/yurikuzn/lightncandy.git",
|
||||
"reference": "207d424c4ced34644663fdc0758c19d5b6974adc"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
"php": ">=7.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7"
|
||||
@@ -3873,7 +3962,6 @@
|
||||
"LightnCandy\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
@@ -3886,13 +3974,13 @@
|
||||
"description": "An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).",
|
||||
"homepage": "https://github.com/zordius/lightncandy",
|
||||
"keywords": [
|
||||
"PHP",
|
||||
"handlebars",
|
||||
"logicless",
|
||||
"mustache",
|
||||
"php",
|
||||
"template"
|
||||
],
|
||||
"time": "2019-06-09T04:10:55+00:00"
|
||||
"time": "2021-02-18T13:06:28+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
@@ -4650,16 +4738,6 @@
|
||||
"testing",
|
||||
"xunit"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://phpunit.de/donate.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/sebastianbergmann",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-06-22T07:06:58+00:00"
|
||||
},
|
||||
{
|
||||
@@ -5333,20 +5411,6 @@
|
||||
"polyfill",
|
||||
"portable"
|
||||
],
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2020-02-27T09:26:54+00:00"
|
||||
},
|
||||
{
|
||||
@@ -5394,12 +5458,12 @@
|
||||
"version": "1.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webmozart/assert.git",
|
||||
"url": "https://github.com/webmozarts/assert.git",
|
||||
"reference": "aed98a490f9a8f78468232db345ab9cf606cf598"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598",
|
||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598",
|
||||
"reference": "aed98a490f9a8f78468232db345ab9cf606cf598",
|
||||
"shasum": ""
|
||||
},
|
||||
@@ -5441,7 +5505,7 @@
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"spatie/async": 20
|
||||
"zordius/lightncandy": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
@@ -5457,6 +5521,5 @@
|
||||
"ext-curl": "*",
|
||||
"ext-exif": "*"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "1.1.0"
|
||||
"platform-dev": []
|
||||
}
|
||||
|
||||
@@ -999,6 +999,10 @@ optgroup {
|
||||
}
|
||||
}
|
||||
|
||||
.input-group .input-group-btn .btn-sm {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.panel-body,
|
||||
section {
|
||||
> p:first-child,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "misc/selectize/selectize.less";
|
||||
@import "misc/selectize/custom.less";
|
||||
@import "misc/selectize/selectize.bootstrap3.less";
|
||||
@import "misc/summernote/summernote.less";
|
||||
@import "misc/summernote/icons.less";
|
||||
|
||||
8
frontend/less/espo/misc/selectize/custom.less
Normal file
8
frontend/less/espo/misc/selectize/custom.less
Normal file
@@ -0,0 +1,8 @@
|
||||
.filter > .form-group .field {
|
||||
.selectize-control {
|
||||
.selectize-input {
|
||||
font-size: @font-size-small;
|
||||
padding: 4px 5px 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,10 @@
|
||||
.disabled [data-value] .remove {
|
||||
border-left-color: lighten(desaturate(@selectize-color-item-border, 100%), @selectize-lighten-disabled-item-border);
|
||||
}
|
||||
}
|
||||
.remove-single {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-size: 23px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* selectize.bootstrap3.css (v0.12.1) - Bootstrap 3 Theme
|
||||
* selectize.css (v0.13.3)
|
||||
* Copyright (c) 2013–2015 Brian Reavis & contributors
|
||||
* Copyright (c) 2020 Selectize Team & contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
||||
* file except in compliance with the License. You may obtain a copy of the License at:
|
||||
@@ -12,11 +13,12 @@
|
||||
* governing permissions and limitations under the License.
|
||||
*
|
||||
* @author Brian Reavis <brian@thirdroute.com>
|
||||
* @author Ris Adams <selectize@risadams.com>
|
||||
*/
|
||||
|
||||
@import "selectize";
|
||||
|
||||
@selectize-fonts-family: inherit;
|
||||
@selectize-font-family: inherit;
|
||||
@selectize-font-size: inherit;
|
||||
@selectize-line-height: @line-height-computed;
|
||||
|
||||
@@ -147,4 +149,4 @@
|
||||
background: none;
|
||||
.selectize-box-shadow(none);
|
||||
.selectize-border-radius(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,9 @@
|
||||
-webkit-user-select: auto !important;
|
||||
.selectize-box-shadow(none) !important;
|
||||
&:focus { outline: none !important; }
|
||||
&[placeholder] {
|
||||
box-sizing: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,9 +229,16 @@
|
||||
.selectize-border-radius(1px);
|
||||
}
|
||||
}
|
||||
[data-selectable], .optgroup-header {
|
||||
.option, .optgroup-header {
|
||||
padding: @selectize-padding-dropdown-item-y @selectize-padding-dropdown-item-x;
|
||||
}
|
||||
.option, [data-disabled], [data-disabled] [data-selectable].option {
|
||||
cursor: inherit;
|
||||
opacity: 0.5;
|
||||
}
|
||||
[data-selectable].option {
|
||||
opacity: 1;
|
||||
}
|
||||
.optgroup:first-child .optgroup-header {
|
||||
border-top: 0 none;
|
||||
}
|
||||
@@ -253,6 +263,7 @@
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: @selectize-max-height-dropdown;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.selectize-control.single .selectize-input {
|
||||
@@ -293,12 +304,3 @@
|
||||
opacity: @selectize-opacity-disabled;
|
||||
background-color: @selectize-color-disabled;
|
||||
}
|
||||
|
||||
.filter > .form-group .field {
|
||||
.selectize-control {
|
||||
.selectize-input {
|
||||
font-size: @font-size-small;
|
||||
padding: 4px 5px 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ class Language{
|
||||
|
||||
private $data = array();
|
||||
|
||||
protected $defaultLabels = [
|
||||
'nginx' => 'linux',
|
||||
'apache' => 'linux',
|
||||
'microsoft-iis' => 'windows',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
require_once 'SystemHelper.php';
|
||||
@@ -118,19 +124,35 @@ class Language{
|
||||
$serverOs = $this->getSystemHelper()->getOs();
|
||||
|
||||
$rewriteRules = $this->getSystemHelper()->getRewriteRules();
|
||||
if (isset($i18n['options']['modRewriteInstruction'][$serverType][$serverOs])) {
|
||||
$modRewriteInstruction = $i18n['options']['modRewriteInstruction'][$serverType][$serverOs];
|
||||
|
||||
preg_match_all('/\{(.*?)\}/', $modRewriteInstruction, $match);
|
||||
if (isset($match[1])) {
|
||||
foreach ($match[1] as $varName) {
|
||||
if (isset($rewriteRules[$varName])) {
|
||||
$modRewriteInstruction = str_replace('{'.$varName.'}', $rewriteRules[$varName], $modRewriteInstruction);
|
||||
}
|
||||
}
|
||||
$label = $i18n['options']['modRewriteInstruction'][$serverType][$serverOs] ?? null;
|
||||
|
||||
if (!isset($label) && isset($this->defaultLabels[$serverType])) {
|
||||
$defaultLabel = $this->defaultLabels[$serverType];
|
||||
|
||||
if (!isset($i18n['options']['modRewriteInstruction'][$serverType][$defaultLabel])) {
|
||||
$defaultLangFile = 'install/core/i18n/' . $this->defaultLanguage . '/install.json';
|
||||
$defaultData = $this->getLangData($defaultLangFile);
|
||||
|
||||
$i18n['options']['modRewriteInstruction'][$serverType][$defaultLabel] = $defaultData['options']['modRewriteInstruction'][$serverType][$defaultLabel];
|
||||
}
|
||||
|
||||
$i18n['options']['modRewriteInstruction'][$serverType][$serverOs] = $modRewriteInstruction;
|
||||
$label = $i18n['options']['modRewriteInstruction'][$serverType][$defaultLabel];
|
||||
}
|
||||
|
||||
if (!$label) {
|
||||
return;
|
||||
}
|
||||
|
||||
preg_match_all('/\{(.*?)\}/', $label, $match);
|
||||
if (isset($match[1])) {
|
||||
foreach ($match[1] as $varName) {
|
||||
if (isset($rewriteRules[$varName])) {
|
||||
$label = str_replace('{'.$varName.'}', $rewriteRules[$varName], $label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$i18n['options']['modRewriteInstruction'][$serverType][$serverOs] = $label;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class SystemHelper extends \Espo\Core\Utils\System
|
||||
|
||||
protected $apiPath;
|
||||
|
||||
protected $modRewriteUrl = '/Metadata';
|
||||
protected $modRewriteUrl = '/';
|
||||
|
||||
protected $writableDir = 'data';
|
||||
|
||||
|
||||
@@ -27,6 +27,20 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
$clearedCookieList = [
|
||||
'auth-token-secret',
|
||||
'auth-username',
|
||||
'auth-token',
|
||||
];
|
||||
|
||||
foreach ($clearedCookieList as $cookieName) {
|
||||
if (!isset($_COOKIE[$cookieName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setcookie($cookieName, null, -1, '/');
|
||||
}
|
||||
|
||||
$config = $installer->getConfig();
|
||||
|
||||
$fields = array(
|
||||
|
||||
@@ -30,14 +30,19 @@
|
||||
return [
|
||||
'apiPath' => '/api/v1',
|
||||
'rewriteRules' => [
|
||||
'APACHE1' => 'a2enmod rewrite
|
||||
service apache2 restart',
|
||||
'APACHE1' => 'sudo a2enmod rewrite
|
||||
sudo service apache2 restart',
|
||||
'APACHE2' => '<Directory /PATH_TO_ESPO/>
|
||||
AllowOverride <b>All</b>
|
||||
</Directory>',
|
||||
'APACHE3' => 'service apache2 restart',
|
||||
'APACHE2_PATH1' => '/etc/apache2/sites-available/ESPO_VIRTUAL_HOST.conf',
|
||||
'APACHE2_PATH2' => '/etc/apache2/apache2.conf',
|
||||
'APACHE2_PATH3' => '/etc/httpd/conf/httpd.conf',
|
||||
'APACHE3' => 'sudo service apache2 restart',
|
||||
'APACHE4' => '# RewriteBase /',
|
||||
'APACHE5' => 'RewriteBase {ESPO_PATH}{API_PATH}',
|
||||
'WINDOWS_APACHE1' => 'LoadModule rewrite_module modules/mod_rewrite.so',
|
||||
'NGINX_PATH' => '/etc/nginx/sites-available/YOUR_SITE',
|
||||
'NGINX' => 'server {
|
||||
# ...
|
||||
|
||||
@@ -88,5 +93,7 @@ service apache2 restart',
|
||||
deny all;
|
||||
}
|
||||
}',
|
||||
'APACHE_LINK' => 'https://www.espocrm.com/documentation/administration/apache-server-configuration/',
|
||||
'NGINX_LINK' => 'https://www.espocrm.com/documentation/administration/nginx-server-configuration/',
|
||||
],
|
||||
];
|
||||
];
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
"mysqlSettingError": "EspoCRM изисква настройка MySQL \"{NAME}\", за да се настрои да {VALUE}",
|
||||
"requiredMariadbVersion": "Вашият MariaDB версия не се поддържа от EspoCRM, моля обновете до MariaDB {} minVersion най-малко",
|
||||
"Ajax failed": "Възникна неочаквана грешка",
|
||||
"Bad init Permission": "Разрешението е отказано за \"{*}\" директория. Моля, избран 775 за \"{*}\" или просто изпълни тази команда в терминала <предварително> <б> {C} </ B> </ предварително> операция не е позволено? Предложения това: {ХСС}",
|
||||
"permissionInstruction": "<br> Run тази команда в терминал: <предварително> <б> \"{C}\" </ B> </ предварително>",
|
||||
"Bad init Permission": "Разрешението е отказано за \"{*}\" директория. Моля, избран 775 за \"{*}\" или просто изпълни тази команда в терминала <pre> <b> {C} </b> </pre> операция не е позволено? Предложения това: {ХСС}",
|
||||
"permissionInstruction": "<br> Run тази команда в терминал: <pre> <b>\"{C}\"</b> </pre>",
|
||||
"operationNotPermitted": "Операция не е позволено? Предложения това: <br> <br> {ХСС}"
|
||||
},
|
||||
"options": {
|
||||
@@ -118,19 +118,18 @@
|
||||
},
|
||||
"modRewriteInstruction": {
|
||||
"apache": {
|
||||
"windows": "<br> <предварително> 1. Намерете httpd.conf файл (обикновено вие ще го намерите в папка, наречена конф, довереник или нещо в този дух) <br> 2. Вътре в httpd.conf файла разкоментирайте модули линия LoadModule rewrite_module / mod_rewrite.so (премахване на лирата \"#\" знак от в предната част на линията) <br> 3. Също така да намерите линията ClearModuleList се некоментирана след това намерете и се уверете, че mod_rewrite.c линията AddModule не е коментиран. </ Предварително>",
|
||||
"linux": "<br> <br> 1. Активиране на \"mod_rewrite\". За това тече тези команди в терминал: <предварително> {APACHE1} </ предварително> <br> 2. Ако предишната стъпка не помогне, опитайте се да се даде възможност на .htaccess подкрепа. Добавяне / редактиране на настройките за конфигурация сървър (/etc/apache/apache2.conf, /etc/httpd/conf/httpd.conf): <предварително> {} apache2 </ предварително> След това стартирате тази команда в терминал: <предварително> {APACHE3} </ предварително> <br> 3. Ако предишната стъпка не помогне, опитайте да добавите пътя на RewriteBase. Отваряне на файл {} API_PATH .htaccess и да се замени следния ред: <предварително> {} APACHE4 </ предварително> Да <предварително> {} APACHE5 </ предварително> <br> За повече информация, моля посетете насоките <един HREF = \" https://www.espocrm.com/documentation/administration/apache-server-configuration/ \"целева =\" _ празно \">"
|
||||
"windows": "<br> <pre> 1. Намерете httpd.conf файл (обикновено вие ще го намерите в папка, наречена конф, довереник или нещо в този дух) <br> 2. Вътре в httpd.conf файла разкоментирайте модули линия LoadModule rewrite_module / mod_rewrite.so (премахване на лирата \"#\" знак от в предната част на линията) <br> 3. Също така да намерите линията ClearModuleList се некоментирана след това намерете и се уверете, че mod_rewrite.c линията AddModule не е коментиран. </pre>",
|
||||
"linux": "<br> <br> 1. Активиране на \"mod_rewrite\". За това тече тези команди в терминал: <pre>{APACHE1}</pre> <br> 2. Ако предишната стъпка не помогне, опитайте се да се даде възможност на .htaccess подкрепа. Добавяне / редактиране на настройките за конфигурация сървър (<code>{APACHE2_PATH1}</code>, <code>{APACHE2_PATH2}</code>, <code>{APACHE2_PATH3}</code>): <pre>{APACHE2}</pre> След това стартирате тази команда в терминал: <pre>{APACHE3}</pre> <br> 3. Ако предишната стъпка не помогне, опитайте да добавите пътя на RewriteBase. Отваряне на файл {API_PATH} .htaccess и да се замени следния ред: <pre>{APACHE4}</pre> Да <pre>{APACHE5}</pre> <br> За повече информация, моля посетете насоките <a href=\"{APACHE_LINK}\" target=\"_blank\">Apache server configuration for EspoCRM</a>.<br><br>"
|
||||
},
|
||||
"nginx": {
|
||||
"linux": "<br> <предварително> {Nginx} </ предварително> <br> За повече информация посети насоките <един HREF = \"https://www.espocrm.com/documentation/administration/nginx-server-configuration/\" целева = \"_ празно\"> конфигурацията на сървъра Nginx за EspoCRM </a>. <br> <br>",
|
||||
"windows": "<br> <предварително> {Nginx} </ предварително> <br> За повече информация посети насоките <един HREF = \"https://www.espocrm.com/documentation/administration/nginx-server-configuration/\" целева = \"_ празно\"> конфигурацията на сървъра Nginx за EspoCRM </a>. <br> <br>"
|
||||
"linux": "<br> Добавете този код в Nginx сървър блок конфигурационния файл <code>{NGINX_PATH}</code> вътре в раздел \"сървър\": <pre>{NGINX}</pre> <br> За повече информация посети насоките <a href=\"{NGINX_LINK}\" target=\"_blank\">конфигурацията на сървъра Nginx за EspoCRM</a>. <br> <br>"
|
||||
}
|
||||
},
|
||||
"modRewriteTitle": {
|
||||
"apache": "API. Грешка: EspoCRM API е недостъпна <br> Възможни проблеми:. Увреждания \"mod_rewrite\" в Apache сървъра, инвалиди .htaccess подкрепа или RewriteBase въпрос <br> ли само необходимите стъпки. След всяка проверка стъпка, ако проблемът не бъде решен.",
|
||||
"nginx": "API Грешка: EspoCRM API е недостъпна <br> Добавете този код в Nginx сървър блок конфигурационния файл (/ и т.н. / Nginx / сайтове-достъпни / YOUR_SITE) вътре в раздел \"сървър\".:",
|
||||
"microsoft-iis": "API. Грешка: EspoCRM API е недостъпна <br> Възможен проблем: увреждания \"URL Rewrite\". Моля, проверете и се даде възможност на \"URL Rewrite\" Модул в IIS сървър",
|
||||
"default": "API. Грешка: EspoCRM API е недостъпна <br> Възможен проблем: инвалиди Rewrite модул. Моля, проверете и се даде възможност на Rewrite модул в сървъра си (напр mod_rewrite в Apache) и .htaccess подкрепа."
|
||||
"apache": "<h3>API. Грешка: EspoCRM API е недостъпна</h3> <br> Ли само необходимите стъпки. След всяка проверка стъпка, ако проблемът не бъде решен.",
|
||||
"nginx": "<h3>API Грешка: EspoCRM API е недостъпна</h3>",
|
||||
"microsoft-iis": "<h3>API. Грешка: EspoCRM API е недостъпна</h3> <br> Възможен проблем: увреждания \"URL Rewrite\". Моля, проверете и се даде възможност на \"URL Rewrite\" Модул в IIS сървър",
|
||||
"default": "<h3>API. Грешка: EspoCRM API е недостъпна</h3> <br> Възможен проблем: инвалиди Rewrite модул. Моля, проверете и се даде възможност на Rewrite модул в сървъра си (напр mod_rewrite в Apache) и .htaccess подкрепа."
|
||||
}
|
||||
},
|
||||
"systemRequirements": {
|
||||
@@ -141,4 +140,4 @@
|
||||
"readable": "четлив",
|
||||
"requiredMariadbVersion": "MariaDB версия"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user