Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94c00e2901 | |||
| 7de988104b | |||
| 03bc90968c | |||
| a5401a22b9 | |||
| 4c1375d8b2 | |||
| bab4a3b0d6 | |||
| 33e135f227 | |||
| 56b9d8d5c1 | |||
| 38ec88e302 | |||
| 10be208d8d | |||
| 38d227a948 | |||
| 1a8e4435e2 | |||
| 12febbdff5 | |||
| ebea168350 | |||
| 704519f80b | |||
| 5f1000ddd3 | |||
| 2ee7e6cf4a | |||
| 2989975829 | |||
| a4c068427d | |||
| e1a76b9924 | |||
| 1f2efcd716 | |||
| 576dfe068f | |||
| 56f8e68599 | |||
| e8d75808ef | |||
| bc8bf89d6e | |||
| 91385cc3f0 | |||
| ad21511c72 | |||
| 938a24f102 | |||
| 43b041ed6c | |||
| 5e3f048795 | |||
| 3aec3ef6d2 | |||
| 712a450734 | |||
| 5675b6721b | |||
| a4e23b8adb | |||
| 9d49f418c8 | |||
| ea9f70dffa | |||
| b624accbe8 | |||
| 12f974635e | |||
| cfce68eb7e | |||
| db8fbd1ed3 | |||
| 06395f3ff5 | |||
| 4065ec5477 | |||
| 31d45a0583 | |||
| 696faa4468 | |||
| 625876f123 | |||
| 974cd05276 | |||
| fb5db991a3 | |||
| 3dafb7e922 | |||
| ee640273a1 | |||
| b92de17f72 | |||
| abc48ca76f | |||
| 46443aae7e | |||
| a88c3283d6 | |||
| 2b9653ee0b | |||
| 2cf79abdb1 | |||
| 20a000a46c | |||
| 95bfd5bace | |||
| da736008f1 | |||
| 1de98d9616 | |||
| 31a6143cc3 | |||
| 8502a84a3f | |||
| 2234678728 |
@@ -48,6 +48,6 @@ class ContainsType extends \Espo\Core\Formula\Functions\Base
|
||||
return false;
|
||||
}
|
||||
|
||||
return strpos($string, $needle) !== false;
|
||||
return mb_strpos($string, $needle) !== false;
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,6 @@ class LowerCaseType extends \Espo\Core\Formula\Functions\Base
|
||||
$value = strval($value);
|
||||
}
|
||||
|
||||
return strtolower($value);
|
||||
return mb_strtolower($value);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
@@ -52,9 +52,9 @@ class SubstringType extends \Espo\Core\Formula\Functions\Base
|
||||
|
||||
if (count($item->value) > 2) {
|
||||
$length = $this->evaluate($item->value[2]);
|
||||
return substr($string, $start, $length);
|
||||
return mb_substr($string, $start, $length);
|
||||
} else {
|
||||
return substr($string, $start);
|
||||
return mb_substr($string, $start);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,6 @@ class UpperCaseType extends \Espo\Core\Formula\Functions\Base
|
||||
$value = strval($value);
|
||||
}
|
||||
|
||||
return strtoupper($value);
|
||||
return mb_strtoupper($value);
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,8 @@ class Parser
|
||||
|
||||
$this->processStrings($expression, $modifiedExpression, $splitterIndexList, true);
|
||||
|
||||
$expressionOutOfBraceList = [];
|
||||
|
||||
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
|
||||
if ($modifiedExpression[$i] === '(') {
|
||||
$braceCounter++;
|
||||
@@ -186,6 +188,11 @@ class Parser
|
||||
if ($braceCounter === 0 && $i < strlen($modifiedExpression) - 1) {
|
||||
$hasExcessBraces = false;
|
||||
}
|
||||
if ($braceCounter === 0) {
|
||||
$expressionOutOfBraceList[] = true;
|
||||
} else {
|
||||
$expressionOutOfBraceList[] = false;
|
||||
}
|
||||
}
|
||||
if ($braceCounter !== 0) {
|
||||
throw new Error('Incorrect round brackets in expression ' . $expression . '.');
|
||||
@@ -226,7 +233,13 @@ class Parser
|
||||
|
||||
foreach ($this->priorityList as $operationList) {
|
||||
foreach ($operationList as $operator) {
|
||||
$index = strpos($expression, $operator, 1);
|
||||
$startFrom = 1;
|
||||
while (true) {
|
||||
$index = strpos($expression, $operator, $startFrom);
|
||||
if ($index === false) break;
|
||||
if ($expressionOutOfBraceList[$index]) break;
|
||||
$startFrom = $index + 1;
|
||||
}
|
||||
if ($index !== false) {
|
||||
$possibleRightOperator = null;
|
||||
if (strlen($operator) === 1) {
|
||||
|
||||
@@ -137,7 +137,7 @@ class Entity extends \Espo\ORM\Entity
|
||||
}
|
||||
|
||||
$this->set($idsAttribute, $ids);
|
||||
if (!$this->hasFetched($idsAttribute)) {
|
||||
if (!$this->isNew() && !$this->hasFetched($idsAttribute)) {
|
||||
$this->setFetched($idsAttribute, $ids);
|
||||
}
|
||||
|
||||
@@ -170,7 +170,13 @@ class Entity extends \Espo\ORM\Entity
|
||||
$entityName = $entity->get('name');
|
||||
}
|
||||
|
||||
$this->set($field . 'Id', $entityId);
|
||||
$idAttribute = $field . 'Id';
|
||||
|
||||
if (!$this->isNew() && !$this->hasFetched($idAttribute)) {
|
||||
$this->setFetched($idAttribute, $entityId);
|
||||
}
|
||||
|
||||
$this->set($idAttribute, $entityId);
|
||||
$this->set($field . 'Name', $entityName);
|
||||
}
|
||||
|
||||
|
||||
@@ -465,17 +465,21 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$data->$columnName = $foreignEntity->get($columnField);
|
||||
}
|
||||
$existingColumnsData->$foreignId = $data;
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->has($fieldName)) {
|
||||
$entity->setFetched($fieldName, $existingIds);
|
||||
}
|
||||
if ($entity->has($columnsFieldsName) && !empty($columns)) {
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
if (!$entity->isNew()) {
|
||||
if ($entity->has($fieldName)) {
|
||||
$entity->setFetched($fieldName, $existingIds);
|
||||
}
|
||||
if ($entity->has($columnsFieldsName) && !empty($columns)) {
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($existingIds as $id) {
|
||||
@@ -540,13 +544,17 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$where[$foreignKey] = $entity->id;
|
||||
$previousForeignEntity = $this->getEntityManager()->getRepository($foreignEntityType)->where($where)->findOne();
|
||||
if ($previousForeignEntity) {
|
||||
$entity->setFetched($idFieldName, $previousForeignEntity->id);
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($idFieldName, $previousForeignEntity->id);
|
||||
}
|
||||
if ($previousForeignEntity->id !== $entity->get($idFieldName)) {
|
||||
$previousForeignEntity->set($foreignKey, null);
|
||||
$this->getEntityManager()->saveEntity($previousForeignEntity);
|
||||
}
|
||||
} else {
|
||||
$entity->setFetched($idFieldName, null);
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($idFieldName, null);
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->get($idFieldName)) {
|
||||
|
||||
@@ -161,6 +161,9 @@ class Base
|
||||
if ($desc) {
|
||||
$list = array_reverse($list);
|
||||
}
|
||||
foreach ($list as $i => $listItem) {
|
||||
$list[$i] = str_replace(',', '_COMMA_', $listItem);
|
||||
}
|
||||
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
|
||||
return;
|
||||
}
|
||||
@@ -392,7 +395,8 @@ class Base
|
||||
protected function q($params, &$result)
|
||||
{
|
||||
if (isset($params['q']) && $params['q'] !== '') {
|
||||
$this->textFilter($params['q'], $result);
|
||||
$textFilter = $params['q'];
|
||||
$this->textFilter($textFilter, $result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1251,7 +1255,7 @@ class Base
|
||||
|
||||
$value = $item['value'];
|
||||
|
||||
if (is_null($value)) break;
|
||||
if (is_null($value) || !$value && !is_array($value)) break;
|
||||
|
||||
$relationType = $seed->getRelationType($link);
|
||||
|
||||
@@ -1535,7 +1539,8 @@ class Base
|
||||
if (!$fullTextSearchMinLength) {
|
||||
$fullTextSearchMinLength = 0;
|
||||
}
|
||||
if (mb_strlen($textFilter) >= $fullTextSearchMinLength) {
|
||||
$textFilterWoWildcards = str_replace('*', '', $textFilter);
|
||||
if (mb_strlen($textFilterWoWildcards) >= $fullTextSearchMinLength) {
|
||||
$useFullTextSearch = true;
|
||||
}
|
||||
}
|
||||
@@ -1560,9 +1565,17 @@ class Base
|
||||
$useFullTextSearch = false;
|
||||
}
|
||||
|
||||
if ($isAuxiliaryUse) {
|
||||
if (mb_strpos($textFilter, '@') !== false) {
|
||||
$useFullTextSearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($useFullTextSearch) {
|
||||
$textFilter = str_replace(['(', ')'], '', $textFilter);
|
||||
|
||||
if (
|
||||
$isAuxiliaryUse
|
||||
$isAuxiliaryUse && mb_strpos($textFilter, '*') === false
|
||||
||
|
||||
mb_strpos($textFilter, ' ') === false
|
||||
&&
|
||||
@@ -1577,6 +1590,19 @@ class Base
|
||||
$function = 'MATCH_BOOLEAN';
|
||||
}
|
||||
|
||||
$textFilter = str_replace('"*', '"', $textFilter);
|
||||
$textFilter = str_replace('*"', '"', $textFilter);
|
||||
|
||||
while (strpos($textFilter, '**')) {
|
||||
$textFilter = str_replace('**', '*', $textFilter);
|
||||
$textFilter = trim($textFilter);
|
||||
}
|
||||
|
||||
while (mb_substr($textFilter, -2) === ' *') {
|
||||
$textFilter = mb_substr($textFilter, 0, mb_strlen($textFilter) - 2);
|
||||
$textFilter = trim($textFilter);
|
||||
}
|
||||
|
||||
$fullTextSearchColumnSanitizedList = [];
|
||||
$query = $this->getEntityManager()->getQuery();
|
||||
foreach ($fullTextSearchColumnList as $i => $field) {
|
||||
@@ -1617,15 +1643,34 @@ class Base
|
||||
$forceFullTextSearch = true;
|
||||
}
|
||||
|
||||
$textFilterForFullTextSearch = $textFilter;
|
||||
|
||||
$skipWidlcards = false;
|
||||
if (!$useFullTextSearch) {
|
||||
if (mb_strpos($textFilter, '*') !== false) {
|
||||
$skipWidlcards = true;
|
||||
$textFilter = str_replace('*', '%', $textFilter);
|
||||
|
||||
if (mb_strpos($textFilter, '*') !== false) {
|
||||
$skipWidlcards = true;
|
||||
$textFilter = str_replace('*', '%', $textFilter);
|
||||
} else {
|
||||
if (!$useFullTextSearch) {
|
||||
$textFilterForFullTextSearch .= '*';
|
||||
}
|
||||
}
|
||||
|
||||
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilter, !$useFullTextSearch);
|
||||
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
|
||||
|
||||
$skipFullTextSearch = false;
|
||||
if (!$forceFullTextSearch) {
|
||||
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
|
||||
$skipFullTextSearch = true;
|
||||
} else if (mb_strpos($textFilterForFullTextSearch, ' *') !== false) {
|
||||
$skipFullTextSearch = true;
|
||||
}
|
||||
}
|
||||
|
||||
$fullTextSearchData = null;
|
||||
if (!$skipFullTextSearch) {
|
||||
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
|
||||
}
|
||||
|
||||
$fullTextGroup = [];
|
||||
|
||||
|
||||
@@ -25,16 +25,19 @@
|
||||
*
|
||||
* 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\Utils\Database\DBAL\Driver\PDOMySql;
|
||||
|
||||
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
|
||||
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
|
||||
{
|
||||
|
||||
public function getDatabasePlatform()
|
||||
{
|
||||
return new \Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform();
|
||||
}
|
||||
|
||||
|
||||
public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
|
||||
{
|
||||
return new \Espo\Core\Utils\Database\DBAL\Schema\MySqlSchemaManager($conn);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Index as DBALIndex;
|
||||
|
||||
class Index extends \Doctrine\DBAL\Schema\Index
|
||||
{
|
||||
public function addFlag($flag)
|
||||
{
|
||||
$this->_flags[strtolower($flag)] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasFlag($flag)
|
||||
{
|
||||
return isset($this->_flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
public function removeFlag($flag)
|
||||
{
|
||||
unset($this->_flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
public function isFullfilledBy(DBALIndex $other)
|
||||
{
|
||||
if (count($other->getColumns()) != count($this->getColumns())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sameColumns = $this->spansColumns($other->getColumns());
|
||||
|
||||
if ($sameColumns) {
|
||||
$flags = $this->getFlags();
|
||||
$otherFlags = $other->getFlags();
|
||||
|
||||
if ( ! $this->isUnique() && !$this->isPrimary() && $flags === $otherFlags) {
|
||||
return true;
|
||||
} else if ($other->isPrimary() != $this->isPrimary()) {
|
||||
return false;
|
||||
} else if ($other->isUnique() != $this->isUnique()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($flags) != count($otherFlags) || array_diff($flags, $otherFlags) !== array_diff($otherFlags, $flags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Events;
|
||||
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
|
||||
|
||||
class MySqlSchemaManager extends \Doctrine\DBAL\Schema\MySqlSchemaManager
|
||||
{
|
||||
public function createSchema()
|
||||
{
|
||||
$sequences = array();
|
||||
if ($this->_platform->supportsSequences()) {
|
||||
$sequences = $this->listSequences();
|
||||
}
|
||||
$tables = $this->listTables();
|
||||
|
||||
return new Schema($tables, $sequences, $this->createSchemaConfig());
|
||||
}
|
||||
|
||||
public function listTables()
|
||||
{
|
||||
$tableNames = $this->listTableNames();
|
||||
|
||||
$tables = array();
|
||||
foreach ($tableNames as $tableName) {
|
||||
$tables[] = $this->listTableDetails($tableName);
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
public function listTableDetails($tableName)
|
||||
{
|
||||
$columns = $this->listTableColumns($tableName);
|
||||
$foreignKeys = array();
|
||||
if ($this->_platform->supportsForeignKeyConstraints()) {
|
||||
$foreignKeys = $this->listTableForeignKeys($tableName);
|
||||
}
|
||||
$indexes = $this->listTableIndexes($tableName);
|
||||
|
||||
return new Table($tableName, $columns, $indexes, $foreignKeys, false, array());
|
||||
}
|
||||
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
$sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
|
||||
|
||||
$tableIndexes = $this->_conn->fetchAll($sql);
|
||||
|
||||
return $this->_getPortableTableIndexesList($tableIndexes, $table);
|
||||
}
|
||||
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
|
||||
{
|
||||
foreach($tableIndexes as $k => $v) {
|
||||
$v = array_change_key_case($v, CASE_LOWER);
|
||||
if($v['key_name'] == 'PRIMARY') {
|
||||
$v['primary'] = true;
|
||||
} else {
|
||||
$v['primary'] = false;
|
||||
}
|
||||
if (strpos($v['index_type'], 'FULLTEXT') !== false) {
|
||||
$v['flags'] = array('FULLTEXT');
|
||||
}
|
||||
$tableIndexes[$k] = $v;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach($tableIndexes as $tableIndex) {
|
||||
|
||||
$indexName = $keyName = $tableIndex['key_name'];
|
||||
if ($tableIndex['primary']) {
|
||||
$keyName = 'primary';
|
||||
}
|
||||
$keyName = strtolower($keyName);
|
||||
|
||||
if (!isset($result[$keyName])) {
|
||||
$result[$keyName] = array(
|
||||
'name' => $indexName,
|
||||
'columns' => array($tableIndex['column_name']),
|
||||
'unique' => $tableIndex['non_unique'] ? false : true,
|
||||
'primary' => $tableIndex['primary'],
|
||||
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
|
||||
);
|
||||
} else {
|
||||
$result[$keyName]['columns'][] = $tableIndex['column_name'];
|
||||
}
|
||||
}
|
||||
|
||||
$eventManager = $this->_platform->getEventManager();
|
||||
|
||||
$indexes = array();
|
||||
foreach($result as $indexKey => $data) {
|
||||
$index = null;
|
||||
$defaultPrevented = false;
|
||||
|
||||
if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) {
|
||||
$eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn);
|
||||
$eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs);
|
||||
|
||||
$defaultPrevented = $eventArgs->isDefaultPrevented();
|
||||
$index = $eventArgs->getIndex();
|
||||
}
|
||||
|
||||
if ( ! $defaultPrevented) {
|
||||
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags']);
|
||||
}
|
||||
|
||||
if ($index) {
|
||||
$indexes[$indexKey] = $index;
|
||||
}
|
||||
}
|
||||
|
||||
return $indexes;
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,9 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
class Schema extends \Doctrine\DBAL\Schema\Schema
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a new table.
|
||||
*
|
||||
|
||||
@@ -28,11 +28,12 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
class Table extends \Doctrine\DBAL\Schema\Table
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
* @param string $typeName
|
||||
@@ -49,4 +50,35 @@ class Table extends \Doctrine\DBAL\Schema\Table
|
||||
return $column;
|
||||
}
|
||||
|
||||
public function addIndex(array $columnNames, $indexName = null, array $flags = array())
|
||||
{
|
||||
if($indexName == null) {
|
||||
$indexName = $this->_generateIdentifierName(
|
||||
array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->_createIndex($columnNames, $indexName, false, false, $flags);
|
||||
}
|
||||
|
||||
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array())
|
||||
{
|
||||
if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
|
||||
throw SchemaException::indexNameInvalid($indexName);
|
||||
}
|
||||
|
||||
foreach ($columnNames as $columnName => $indexColOptions) {
|
||||
if (is_numeric($columnName) && is_string($indexColOptions)) {
|
||||
$columnName = $indexColOptions;
|
||||
}
|
||||
|
||||
if ( ! $this->hasColumn($columnName)) {
|
||||
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
|
||||
}
|
||||
}
|
||||
|
||||
$this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -33,28 +33,29 @@ class LinkMultiple extends Base
|
||||
{
|
||||
protected function load($fieldName, $entityName)
|
||||
{
|
||||
$data = array(
|
||||
$entityName => array (
|
||||
'fields' => array(
|
||||
$fieldName.'Ids' => array(
|
||||
$data = [
|
||||
$entityName => [
|
||||
'fields' => [
|
||||
$fieldName.'Ids' => [
|
||||
'type' => 'jsonArray',
|
||||
'notStorable' => true,
|
||||
'isLinkMultipleIdList' => true,
|
||||
'relation' => $fieldName
|
||||
),
|
||||
$fieldName.'Names' => array(
|
||||
'relation' => $fieldName,
|
||||
'isUnordered' => true
|
||||
],
|
||||
$fieldName.'Names' => [
|
||||
'type' => 'jsonObject',
|
||||
'notStorable' => true,
|
||||
'isLinkMultipleNameMap' => true
|
||||
)
|
||||
)
|
||||
),
|
||||
'unset' => array(
|
||||
$entityName => array(
|
||||
'fields.'.$fieldName
|
||||
)
|
||||
)
|
||||
);
|
||||
]
|
||||
]
|
||||
],
|
||||
'unset' => [
|
||||
$entityName => [
|
||||
'fields.' . $fieldName
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$fieldParams = $this->getFieldParams();
|
||||
|
||||
@@ -67,10 +68,10 @@ class LinkMultiple extends Base
|
||||
|
||||
$columns = $this->getMetadata()->get("entityDefs.{$entityName}.fields.{$fieldName}.columns");
|
||||
if (!empty($columns)) {
|
||||
$data[$entityName]['fields'][$fieldName . 'Columns'] = array(
|
||||
$data[$entityName]['fields'][$fieldName . 'Columns'] = [
|
||||
'type' => 'jsonObject',
|
||||
'notStorable' => true,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
||||
@@ -662,7 +662,6 @@ class EntityManager
|
||||
$link => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleField,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleField,
|
||||
"noLoad" => !$linkMultipleField,
|
||||
"importDisabled" => !$linkMultipleField,
|
||||
@@ -727,7 +726,6 @@ class EntityManager
|
||||
$linkForeign => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
|
||||
"noLoad" => !$linkMultipleFieldForeign,
|
||||
"importDisabled" => !$linkMultipleFieldForeign,
|
||||
@@ -751,7 +749,6 @@ class EntityManager
|
||||
$link => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleField,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleField,
|
||||
"importDisabled" => !$linkMultipleField,
|
||||
"noLoad" => !$linkMultipleField,
|
||||
@@ -774,7 +771,6 @@ class EntityManager
|
||||
$linkForeign => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
|
||||
"importDisabled" => !$linkMultipleFieldForeign,
|
||||
"noLoad" => !$linkMultipleFieldForeign,
|
||||
@@ -848,7 +844,6 @@ class EntityManager
|
||||
$link => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleField,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleField,
|
||||
"noLoad" => !$linkMultipleField,
|
||||
"importDisabled" => !$linkMultipleField,
|
||||
@@ -873,7 +868,6 @@ class EntityManager
|
||||
$linkForeign => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
|
||||
"noLoad" => !$linkMultipleFieldForeign,
|
||||
"importDisabled" => !$linkMultipleFieldForeign,
|
||||
|
||||
@@ -58,18 +58,32 @@ class AssignmentEmailNotification extends \Espo\Core\Hooks\Base
|
||||
|
||||
foreach ($userIdList as $userId) {
|
||||
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
|
||||
if ($this->getUser()->id === $userId) continue;
|
||||
if (!$this->isNotSelfAssignment($entity, $userId)) continue;
|
||||
$this->createJob($entity, $userId);
|
||||
}
|
||||
} else {
|
||||
$userId = $entity->get('assignedUserId');
|
||||
if (!empty($userId) && $userId != $this->getUser()->id && $entity->isAttributeChanged('assignedUserId')) {
|
||||
if (!empty($userId) && $entity->isAttributeChanged('assignedUserId') && $this->isNotSelfAssignment($entity, $userId)) {
|
||||
$this->createJob($entity, $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function isNotSelfAssignment(Entity $entity, $assignedUserId)
|
||||
{
|
||||
if ($entity->hasAttribute('createdById') && $entity->hasAttribute('modifiedById')) {
|
||||
if ($entity->isNew()) {
|
||||
$isNotSelfAssignment = $assignedUserId !== $entity->get('createdById');
|
||||
} else {
|
||||
$isNotSelfAssignment = $assignedUserId !== $entity->get('modifiedById');
|
||||
}
|
||||
} else {
|
||||
$isNotSelfAssignment = $assignedUserId !== $this->getUser()->id;
|
||||
}
|
||||
return $isNotSelfAssignment;
|
||||
}
|
||||
|
||||
protected function createJob(Entity $entity, $userId)
|
||||
{
|
||||
$job = $this->getEntityManager()->getEntity('Job');
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
[
|
||||
{
|
||||
"route":"/Activities/:scope/:id/:name",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Activities",
|
||||
"action":"list",
|
||||
"scope":":scope",
|
||||
"id":":id",
|
||||
"name":":name"
|
||||
{
|
||||
"route": "/Activities/:scope/:id/:name",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Activities",
|
||||
"action": "list",
|
||||
"scope": ":scope",
|
||||
"id": ":id",
|
||||
"name": ":name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route": "/Activities",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Activities",
|
||||
"action": "listCalendarEvents"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"route":"/Activities",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Activities",
|
||||
"action":"listCalendarEvents"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -33,19 +33,23 @@ class CampaignLogRecord extends \Espo\Core\SelectManagers\Base
|
||||
{
|
||||
protected function accessOnlyOwn(&$result)
|
||||
{
|
||||
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
|
||||
|
||||
$result['whereClause'][] = array(
|
||||
'campaign.assignedUserId' => $this->getUser()->id
|
||||
'campaignAccess.assignedUserId' => $this->getUser()->id
|
||||
);
|
||||
}
|
||||
|
||||
protected function accessOnlyTeam(&$result)
|
||||
{
|
||||
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
|
||||
|
||||
$teamIdList = $this->user->get('teamsIds');
|
||||
if (empty($result['customWhere'])) {
|
||||
$result['customWhere'] = '';
|
||||
}
|
||||
if (empty($teamIdList)) {
|
||||
$result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
|
||||
$result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
|
||||
return;
|
||||
}
|
||||
$arr = [];
|
||||
@@ -55,12 +59,12 @@ class CampaignLogRecord extends \Espo\Core\SelectManagers\Base
|
||||
}
|
||||
}
|
||||
|
||||
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
|
||||
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
|
||||
$result['customWhere'] .= "
|
||||
AND (
|
||||
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
|
||||
OR
|
||||
campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
|
||||
campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
|
||||
)
|
||||
";
|
||||
$result['whereClause'][] = array(
|
||||
|
||||
@@ -33,19 +33,23 @@ class CampaignTrackingUrl extends \Espo\Core\SelectManagers\Base
|
||||
{
|
||||
protected function accessOnlyOwn(&$result)
|
||||
{
|
||||
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
|
||||
|
||||
$result['whereClause'][] = array(
|
||||
'campaign.assignedUserId' => $this->getUser()->id
|
||||
'campaignAccess.assignedUserId' => $this->getUser()->id
|
||||
);
|
||||
}
|
||||
|
||||
protected function accessOnlyTeam(&$result)
|
||||
{
|
||||
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
|
||||
|
||||
$teamIdList = $this->user->get('teamsIds');
|
||||
if (empty($result['customWhere'])) {
|
||||
$result['customWhere'] = '';
|
||||
}
|
||||
if (empty($teamIdList)) {
|
||||
$result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
|
||||
$result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
|
||||
return;
|
||||
}
|
||||
$arr = [];
|
||||
@@ -55,12 +59,12 @@ class CampaignTrackingUrl extends \Espo\Core\SelectManagers\Base
|
||||
}
|
||||
}
|
||||
|
||||
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
|
||||
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
|
||||
$result['customWhere'] .= "
|
||||
AND (
|
||||
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
|
||||
OR
|
||||
campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
|
||||
campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
|
||||
)
|
||||
";
|
||||
$result['whereClause'][] = array(
|
||||
|
||||
@@ -45,21 +45,25 @@ class MassEmail extends \Espo\Core\SelectManagers\Base
|
||||
);
|
||||
}
|
||||
|
||||
protected function acessOnlyOwn(&$result)
|
||||
protected function accessOnlyOwn(&$result)
|
||||
{
|
||||
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
|
||||
|
||||
$result['whereClause'][] = array(
|
||||
'campaign.assignedUserId' => $this->getUser()->id
|
||||
'campaignAccess.assignedUserId' => $this->getUser()->id
|
||||
);
|
||||
}
|
||||
|
||||
protected function accessOnlyTeam(&$result)
|
||||
{
|
||||
$teamIdList = $this->user->get('teamsIds');
|
||||
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
|
||||
|
||||
$teamIdList = $this->user->getLinkMultipleIdList('teams');
|
||||
if (empty($result['customWhere'])) {
|
||||
$result['customWhere'] = '';
|
||||
}
|
||||
if (empty($teamIdList)) {
|
||||
$result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
|
||||
$result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
|
||||
return;
|
||||
}
|
||||
$arr = [];
|
||||
@@ -69,12 +73,12 @@ class MassEmail extends \Espo\Core\SelectManagers\Base
|
||||
}
|
||||
}
|
||||
|
||||
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
|
||||
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
|
||||
$result['customWhere'] .= "
|
||||
AND (
|
||||
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
|
||||
OR
|
||||
campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
|
||||
campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
|
||||
)
|
||||
";
|
||||
$result['whereClause'][] = array(
|
||||
|
||||
@@ -718,7 +718,7 @@ abstract class Mapper implements IMapper
|
||||
|
||||
public function insert(IEntity $entity)
|
||||
{
|
||||
$dataArr = $this->toArray($entity);
|
||||
$dataArr = $this->toValueMap($entity);
|
||||
|
||||
$fieldArr = array();
|
||||
$valArr = array();
|
||||
@@ -745,33 +745,35 @@ abstract class Mapper implements IMapper
|
||||
|
||||
public function update(IEntity $entity)
|
||||
{
|
||||
$dataArr = $this->toArray($entity);
|
||||
$valueMap = $this->toValueMap($entity);
|
||||
|
||||
$setArr = array();
|
||||
foreach ($dataArr as $field => $value) {
|
||||
if ($field == 'id') {
|
||||
$setArr = [];
|
||||
|
||||
foreach ($valueMap as $attribute => $value) {
|
||||
if ($attribute == 'id') {
|
||||
continue;
|
||||
}
|
||||
$type = $entity->fields[$field]['type'];
|
||||
$type = $entity->getAttributeType($attribute);
|
||||
|
||||
if ($type == IEntity::FOREIGN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entity->getFetched($field) === $value && $type != IEntity::JSON_ARRAY && $type != IEntity::JSON_OBJECT) {
|
||||
if (!$entity->isAttributeChanged($attribute) && $type !== IEntity::JSON_OBJECT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->prepareValueForInsert($type, $value);
|
||||
|
||||
$setArr[] = "`" . $this->toDb($field) . "` = " . $this->quote($value);
|
||||
$setArr[] = "`" . $this->toDb($attribute) . "` = " . $this->quote($value);
|
||||
}
|
||||
|
||||
if (count($setArr) == 0) {
|
||||
return $entity->id;
|
||||
}
|
||||
|
||||
$setPart = implode(', ', $setArr);
|
||||
$wherePart = $this->query->getWhere($entity, array('id' => $entity->id, 'deleted' => 0));
|
||||
$wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]);
|
||||
|
||||
$sql = $this->composeUpdateQuery($this->toDb($entity->getEntityType()), $setPart, $wherePart);
|
||||
|
||||
@@ -815,21 +817,25 @@ abstract class Mapper implements IMapper
|
||||
return $this->update($entity);
|
||||
}
|
||||
|
||||
protected function toArray(IEntity $entity, $onlyStorable = true)
|
||||
protected function toValueMap(IEntity $entity, $onlyStorable = true)
|
||||
{
|
||||
$arr = array();
|
||||
foreach ($entity->fields as $field => $fieldDefs) {
|
||||
if ($entity->has($field)) {
|
||||
$data = [];
|
||||
foreach ($entity->getAttributes() as $attribute => $defs) {
|
||||
if ($entity->has($attribute)) {
|
||||
if ($onlyStorable) {
|
||||
if (!empty($fieldDefs['notStorable']) || !empty($fieldDefs['autoincrement']) || isset($fieldDefs['source']) && $fieldDefs['source'] != 'db')
|
||||
continue;
|
||||
if ($fieldDefs['type'] == IEntity::FOREIGN)
|
||||
continue;
|
||||
if (
|
||||
!empty($defs['notStorable'])
|
||||
||
|
||||
!empty($defs['autoincrement'])
|
||||
||
|
||||
isset($defs['source']) && $defs['source'] != 'db'
|
||||
) continue;
|
||||
if ($defs['type'] == IEntity::FOREIGN) continue;
|
||||
}
|
||||
$arr[$field] = $entity->get($field);
|
||||
$data[$attribute] = $entity->get($attribute);
|
||||
}
|
||||
}
|
||||
return $arr;
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function fromRow(IEntity $entity, $data)
|
||||
|
||||
@@ -576,7 +576,13 @@ abstract class Base
|
||||
if (strpos($orderBy, 'LIST:') === 0) {
|
||||
list($l, $field, $list) = explode(':', $orderBy);
|
||||
$fieldPath = $this->getFieldPathForOrderBy($entity, $field);
|
||||
$part = "FIELD(" . $fieldPath . ", '" . implode("', '", array_reverse(explode(",", $list))) . "') DESC";
|
||||
$listQuoted = [];
|
||||
$list = array_reverse(explode(',', $list));
|
||||
foreach ($list as $i => $listItem) {
|
||||
$listItem = str_replace('_COMMA_', ',', $listItem);
|
||||
$listQuoted[] = $this->quote($listItem);
|
||||
}
|
||||
$part = "FIELD(" . $fieldPath . ", " . implode(", ", $listQuoted) . ") DESC";
|
||||
return $part;
|
||||
}
|
||||
|
||||
|
||||
@@ -369,25 +369,92 @@ abstract class Entity implements IEntity
|
||||
return $this->isFetched;
|
||||
}
|
||||
|
||||
public function isFieldChanged($fieldName)
|
||||
public function isFieldChanged($name)
|
||||
{
|
||||
return $this->has($fieldName) && ($this->get($fieldName) != $this->getFetched($fieldName));
|
||||
return $this->has($name) && ($this->get($name) != $this->getFetched($name));
|
||||
}
|
||||
|
||||
public function isAttributeChanged($fieldName)
|
||||
public function isAttributeChanged($name)
|
||||
{
|
||||
return $this->has($fieldName) && ($this->get($fieldName) != $this->getFetched($fieldName));
|
||||
if (!$this->has($name)) return false;
|
||||
|
||||
if (!$this->hasFetched($name)) {
|
||||
return true;
|
||||
}
|
||||
return !self::areValuesEqual(
|
||||
$this->getAttributeType($name),
|
||||
$this->get($name),
|
||||
$this->getFetched($name),
|
||||
$this->getAttributeParam($name, 'isUnordered')
|
||||
);
|
||||
|
||||
return $this->get($name) != $this->getFetched($name);
|
||||
}
|
||||
|
||||
public function setFetched($fieldName, $value)
|
||||
public static function areValuesEqual($type, $v1, $v2, $isUnordered = false)
|
||||
{
|
||||
$this->fetchedValuesContainer[$fieldName] = $value;
|
||||
if ($type === self::JSON_ARRAY) {
|
||||
if (is_array($v1) && is_array($v2)) {
|
||||
if ($isUnordered) {
|
||||
sort($v1);
|
||||
sort($v2);
|
||||
}
|
||||
if ($v1 != $v2) {
|
||||
return false;
|
||||
}
|
||||
foreach ($v1 as $i => $itemValue) {
|
||||
if (is_object($v1[$i]) && is_object($v2[$i])) {
|
||||
if (!self::areValuesEqual(self::JSON_OBJECT, $v1[$i], $v2[$i])) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($v1[$i] !== $v2[$i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if ($type === self::JSON_OBJECT) {
|
||||
if (is_object($v1) && is_object($v2)) {
|
||||
if ($v1 != $v2) {
|
||||
return false;
|
||||
}
|
||||
$a1 = get_object_vars($v1);
|
||||
$a2 = get_object_vars($v2);
|
||||
foreach ($v1 as $key => $itemValue) {
|
||||
if (is_object($a1[$key]) && is_object($a2[$key])) {
|
||||
if (!self::areValuesEqual(self::JSON_OBJECT, $a1[$key], $a2[$key])) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (is_array($a1[$key]) && is_array($a2[$key])) {
|
||||
if (!self::areValuesEqual(self::JSON_ARRAY, $a1[$key], $a2[$key])) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($a1[$key] !== $a2[$key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $v1 === $v2;
|
||||
}
|
||||
|
||||
public function getFetched($fieldName)
|
||||
public function setFetched($name, $value)
|
||||
{
|
||||
if (isset($this->fetchedValuesContainer[$fieldName])) {
|
||||
return $this->fetchedValuesContainer[$fieldName];
|
||||
$this->fetchedValuesContainer[$name] = $value;
|
||||
}
|
||||
|
||||
public function getFetched($name)
|
||||
{
|
||||
if (isset($this->fetchedValuesContainer[$name])) {
|
||||
return $this->fetchedValuesContainer[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,8 @@
|
||||
"personStringData": {
|
||||
"type": "varchar",
|
||||
"notStorable": true,
|
||||
"disabled": true
|
||||
"disabled": true,
|
||||
"view": "views/email/fields/person-string-data"
|
||||
},
|
||||
"isRead": {
|
||||
"type": "bool",
|
||||
|
||||
@@ -85,6 +85,7 @@
|
||||
},
|
||||
"collection": {
|
||||
"sortBy": "createdAt",
|
||||
"asc": false
|
||||
"asc": false,
|
||||
"textFilterFields": ["name", "bodyPlain", "body", "subject"]
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,319 +1,326 @@
|
||||
[
|
||||
{
|
||||
"route":"/",
|
||||
"method":"get",
|
||||
"params":"<h1>EspoCRM REST API<\/h1>"
|
||||
"route": "/",
|
||||
"method": "get",
|
||||
"params": "<h1>EspoCRM REST API<\/h1>"
|
||||
},
|
||||
{
|
||||
"route":"/App/user",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"App",
|
||||
"action":"user"
|
||||
"route": "/App/user",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "App",
|
||||
"action": "user"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Metadata",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Metadata"
|
||||
"route": "/Metadata",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Metadata"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"I18n",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"I18n"
|
||||
"route": "I18n",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "I18n"
|
||||
},
|
||||
"conditions":{
|
||||
"auth":false
|
||||
"conditions": {
|
||||
"auth": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Settings",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Settings"
|
||||
"route": "/Settings",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Settings"
|
||||
},
|
||||
"conditions":{
|
||||
"auth":false
|
||||
"conditions": {
|
||||
"auth": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Settings",
|
||||
"method":"patch",
|
||||
"params":{
|
||||
"controller":"Settings"
|
||||
"route": "/Settings",
|
||||
"method": "patch",
|
||||
"params": {
|
||||
"controller": "Settings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"User/passwordChangeRequest",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":"User",
|
||||
"action":"passwordChangeRequest"
|
||||
"route": "/Settings",
|
||||
"method": "put",
|
||||
"params": {
|
||||
"controller": "Settings"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route": "User/passwordChangeRequest",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": "User",
|
||||
"action": "passwordChangeRequest"
|
||||
},
|
||||
"conditions":{
|
||||
"auth":false
|
||||
"conditions": {
|
||||
"auth": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"User/changePasswordByRequest",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":"User",
|
||||
"action":"changePasswordByRequest"
|
||||
"route": "User/changePasswordByRequest",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": "User",
|
||||
"action": "changePasswordByRequest"
|
||||
},
|
||||
"conditions":{
|
||||
"auth":false
|
||||
"conditions": {
|
||||
"auth": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Stream",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Stream",
|
||||
"action":"list",
|
||||
"scope":"User"
|
||||
"route": "/Stream",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Stream",
|
||||
"action": "list",
|
||||
"scope": "User"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/GlobalSearch",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"GlobalSearch",
|
||||
"action":"search"
|
||||
"route": "/GlobalSearch",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "GlobalSearch",
|
||||
"action": "search"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/action/:action",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":":action"
|
||||
"route": "/:controller/action/:action",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": ":action"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/action/:action",
|
||||
"method":"put",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":":action"
|
||||
"route": "/:controller/action/:action",
|
||||
"method": "put",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": ":action"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/action/:action",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":":action"
|
||||
"route": "/:controller/action/:action",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": ":action"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/layout/:name",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Layout",
|
||||
"scope":":controller"
|
||||
"route": "/:controller/layout/:name",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Layout",
|
||||
"scope": ":controller"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/layout/:name",
|
||||
"method":"put",
|
||||
"params":{
|
||||
"controller":"Layout",
|
||||
"scope":":controller"
|
||||
"route": "/:controller/layout/:name",
|
||||
"method": "put",
|
||||
"params": {
|
||||
"controller": "Layout",
|
||||
"scope": ":controller"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/layout/:name",
|
||||
"method":"patch",
|
||||
"params":{
|
||||
"controller":"Layout",
|
||||
"scope":":controller"
|
||||
"route": "/:controller/layout/:name",
|
||||
"method": "patch",
|
||||
"params": {
|
||||
"controller": "Layout",
|
||||
"scope": ":controller"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/rebuild",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":"Admin",
|
||||
"action":"rebuild"
|
||||
"route": "/Admin/rebuild",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": "Admin",
|
||||
"action": "rebuild"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/clearCache",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":"Admin",
|
||||
"action":"clearCache"
|
||||
"route": "/Admin/clearCache",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": "Admin",
|
||||
"action": "clearCache"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/jobs",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Admin",
|
||||
"action":"jobs"
|
||||
"route": "/Admin/jobs",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Admin",
|
||||
"action": "jobs"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/fieldManager/:scope/:name",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"FieldManager",
|
||||
"action":"read",
|
||||
"scope":":scope",
|
||||
"name":":name"
|
||||
"route": "/Admin/fieldManager/:scope/:name",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "FieldManager",
|
||||
"action": "read",
|
||||
"scope": ":scope",
|
||||
"name": ":name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/fieldManager/:scope",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":"FieldManager",
|
||||
"action":"create",
|
||||
"scope":":scope"
|
||||
"route": "/Admin/fieldManager/:scope",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": "FieldManager",
|
||||
"action": "create",
|
||||
"scope": ":scope"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/fieldManager/:scope/:name",
|
||||
"method":"put",
|
||||
"params":{
|
||||
"controller":"FieldManager",
|
||||
"action":"update",
|
||||
"scope":":scope",
|
||||
"name":":name"
|
||||
"route": "/Admin/fieldManager/:scope/:name",
|
||||
"method": "put",
|
||||
"params": {
|
||||
"controller": "FieldManager",
|
||||
"action": "update",
|
||||
"scope": ":scope",
|
||||
"name": ":name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/fieldManager/:scope/:name",
|
||||
"method":"patch",
|
||||
"params":{
|
||||
"controller":"FieldManager",
|
||||
"action":"update",
|
||||
"scope":":scope",
|
||||
"name":":name"
|
||||
"route": "/Admin/fieldManager/:scope/:name",
|
||||
"method": "patch",
|
||||
"params": {
|
||||
"controller": "FieldManager",
|
||||
"action": "update",
|
||||
"scope": ":scope",
|
||||
"name": ":name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/Admin/fieldManager/:scope/:name",
|
||||
"method":"delete",
|
||||
"params":{
|
||||
"controller":"FieldManager",
|
||||
"action":"delete",
|
||||
"scope":":scope",
|
||||
"name":":name"
|
||||
"route": "/Admin/fieldManager/:scope/:name",
|
||||
"method": "delete",
|
||||
"params": {
|
||||
"controller": "FieldManager",
|
||||
"action": "delete",
|
||||
"scope": ":scope",
|
||||
"name": ":name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"read",
|
||||
"id":":id"
|
||||
"route": "/:controller/:id",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "read",
|
||||
"id": ":id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"index"
|
||||
"route": "/:controller",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "index"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"create"
|
||||
"route": "/:controller",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "create"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id",
|
||||
"method":"put",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"update",
|
||||
"id":":id"
|
||||
"route": "/:controller/:id",
|
||||
"method": "put",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "update",
|
||||
"id": ":id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id",
|
||||
"method":"patch",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"patch",
|
||||
"id":":id"
|
||||
"route": "/:controller/:id",
|
||||
"method": "patch",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "patch",
|
||||
"id": ":id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id",
|
||||
"method":"delete",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"delete",
|
||||
"id":":id"
|
||||
"route": "/:controller/:id",
|
||||
"method": "delete",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "delete",
|
||||
"id": ":id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id/stream",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":"Stream",
|
||||
"action":"list",
|
||||
"id":":id",
|
||||
"scope":":controller"
|
||||
"route": "/:controller/:id/stream",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": "Stream",
|
||||
"action": "list",
|
||||
"id": ":id",
|
||||
"scope": ":controller"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id/subscription",
|
||||
"method":"put",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"id":":id",
|
||||
"action":"follow"
|
||||
"route": "/:controller/:id/subscription",
|
||||
"method": "put",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"id": ":id",
|
||||
"action": "follow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id/subscription",
|
||||
"method":"delete",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"id":":id",
|
||||
"action":"unfollow"
|
||||
"route": "/:controller/:id/subscription",
|
||||
"method": "delete",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"id": ":id",
|
||||
"action": "unfollow"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id/:link",
|
||||
"method":"get",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"listLinked",
|
||||
"id":":id",
|
||||
"link":":link"
|
||||
"route": "/:controller/:id/:link",
|
||||
"method": "get",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "listLinked",
|
||||
"id": ":id",
|
||||
"link": ":link"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id/:link",
|
||||
"method":"post",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"createLink",
|
||||
"id":":id",
|
||||
"link":":link"
|
||||
"route": "/:controller/:id/:link",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "createLink",
|
||||
"id": ":id",
|
||||
"link": ":link"
|
||||
}
|
||||
},
|
||||
{
|
||||
"route":"/:controller/:id/:link",
|
||||
"method":"delete",
|
||||
"params":{
|
||||
"controller":":controller",
|
||||
"action":"removeLink",
|
||||
"id":":id",
|
||||
"link":":link"
|
||||
"route": "/:controller/:id/:link",
|
||||
"method": "delete",
|
||||
"params": {
|
||||
"controller": ":controller",
|
||||
"action": "removeLink",
|
||||
"id": ":id",
|
||||
"link": ":link"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -31,24 +31,6 @@ namespace Espo\SelectManagers;
|
||||
|
||||
class EmailTemplate extends \Espo\Core\SelectManagers\Base
|
||||
{
|
||||
|
||||
protected function textFilter($textFilter, &$result)
|
||||
{
|
||||
$d = array();
|
||||
|
||||
$d['name*'] = '' . $textFilter . '%';
|
||||
$d['subject*'] = '%' . $textFilter . '%';
|
||||
|
||||
if (strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH) {
|
||||
$d['bodyPlain*'] = '%' . $textFilter . '%';
|
||||
$d['body*'] = '%' . $textFilter . '%';
|
||||
}
|
||||
|
||||
$result['whereClause'][] = array(
|
||||
'OR' => $d
|
||||
);
|
||||
}
|
||||
|
||||
protected function filterActual(&$result)
|
||||
{
|
||||
|
||||
@@ -58,4 +40,3 @@ class EmailTemplate extends \Espo\Core\SelectManagers\Base
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -173,14 +173,26 @@ class Email extends Record
|
||||
|
||||
if (!$smtpParams && $fromAddress === strtolower($this->getConfig()->get('outboundEmailFromAddress'))) {
|
||||
if (!$this->getConfig()->get('outboundEmailIsShared')) {
|
||||
throw new Error('Can not use system smtp. System SMTP is not shared.');
|
||||
throw new Error('Can not use system SMTP. System account is not shared.');
|
||||
}
|
||||
$emailSender->setParams(array(
|
||||
$emailSender->setParams([
|
||||
'fromName' => $this->getConfig()->get('outboundEmailFromName')
|
||||
));
|
||||
]);
|
||||
}
|
||||
|
||||
$params = array();
|
||||
if (!$smtpParams && !$this->getConfig()->get('outboundEmailIsShared')) {
|
||||
throw new Error('No SMTP params found for '.$fromAddress.'.');
|
||||
}
|
||||
|
||||
if (!$smtpParams) {
|
||||
if (in_array($fromAddress, $userAddressList)) {
|
||||
$emailSender->setParams([
|
||||
'fromName' => $this->getUser()->get('name')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$params = [];
|
||||
|
||||
$parent = null;
|
||||
if ($entity->get('parentType') && $entity->get('parentId')) {
|
||||
|
||||
@@ -209,12 +209,15 @@ class EmailAccount extends Record
|
||||
if (empty($fetchData)) {
|
||||
$fetchData = new \StdClass();
|
||||
}
|
||||
$fetchData = clone $fetchData;
|
||||
if (!property_exists($fetchData, 'lastUID')) {
|
||||
$fetchData->lastUID = new \StdClass();;
|
||||
$fetchData->lastUID = new \StdClass();
|
||||
}
|
||||
if (!property_exists($fetchData, 'lastDate')) {
|
||||
$fetchData->lastDate = new \StdClass();;
|
||||
$fetchData->lastDate = new \StdClass();
|
||||
}
|
||||
$fetchData->lastUID = clone $fetchData->lastUID;
|
||||
$fetchData->lastDate = clone $fetchData->lastDate;
|
||||
|
||||
$storage = $this->getStorage($emailAccount);
|
||||
|
||||
@@ -385,7 +388,7 @@ class EmailAccount extends Record
|
||||
$emailAccount = $this->getEntityManager()->getRepository('EmailAccount')->where([
|
||||
'emailAddress' => $address,
|
||||
'assignedUserId' => $user->id,
|
||||
'active' => true
|
||||
'status' => 'Active'
|
||||
])->findOne();
|
||||
|
||||
return $emailAccount;
|
||||
|
||||
@@ -210,12 +210,15 @@ class InboundEmail extends \Espo\Services\Record
|
||||
if (empty($fetchData)) {
|
||||
$fetchData = new \StdClass();
|
||||
}
|
||||
$fetchData = clone $fetchData;
|
||||
if (!property_exists($fetchData, 'lastUID')) {
|
||||
$fetchData->lastUID = new \StdClass();;
|
||||
}
|
||||
if (!property_exists($fetchData, 'lastDate')) {
|
||||
$fetchData->lastDate = new \StdClass();;
|
||||
$fetchData->lastDate = new \StdClass();
|
||||
}
|
||||
$fetchData->lastUID = clone $fetchData->lastUID;
|
||||
$fetchData->lastDate = clone $fetchData->lastDate;
|
||||
|
||||
$imapParams = array(
|
||||
'host' => $emailAccount->get('host'),
|
||||
|
||||
@@ -2191,7 +2191,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
}
|
||||
|
||||
// TODO remove in 5.5.0
|
||||
if (in_array($this->getEntityType(), ['Report', 'Workflow'])) {
|
||||
if (in_array($this->getEntityType(), ['Report', 'Workflow', 'ReportPanel'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -972,7 +972,7 @@ class Stream extends \Espo\Core\Services\Base
|
||||
foreach ($auditedFields as $field => $item) {
|
||||
$updated = false;
|
||||
foreach ($item['actualList'] as $attribute) {
|
||||
if ($entity->get($attribute) !== $entity->getFetched($attribute)) {
|
||||
if ($entity->hasFetched($attribute) && $entity->isAttributeChanged($attribute)) {
|
||||
$updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
|
||||
mouse: {
|
||||
track: true,
|
||||
relative: true,
|
||||
position: 's',
|
||||
position: 'w',
|
||||
lineColor: this.hoverColor,
|
||||
trackFormatter: function (obj) {
|
||||
var label = (obj.series.label || self.translate('None'));
|
||||
|
||||
@@ -160,6 +160,7 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
|
||||
track: true,
|
||||
relative: true,
|
||||
lineColor: this.hoverColor,
|
||||
position: 's',
|
||||
trackFormatter: function (obj) {
|
||||
var i = parseInt(obj.x);
|
||||
var value = '';
|
||||
|
||||
@@ -137,7 +137,7 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
|
||||
mouse: {
|
||||
track: true,
|
||||
relative: true,
|
||||
position: 'ne',
|
||||
position: 'n',
|
||||
lineColor: this.hoverColor,
|
||||
trackFormatter: function (obj) {
|
||||
if (obj.x >= self.chartData.length) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<span class="list-icon-container pull-right"{{#unless isReplied}} style="visibility: hidden;"{{/unless}}>
|
||||
<span class="glyphicon glyphicon-share-alt small text-muted icon-flip-horizontal" title="{{translate 'isReplied' category='fields' scope='Email'}}"></span>
|
||||
</span>
|
||||
<span title="{{value}}">{{value}}</span>
|
||||
@@ -1,5 +1,7 @@
|
||||
{{#if hasAttachment}}
|
||||
<span class="glyphicon glyphicon-paperclip small text-muted pull-right"></span>
|
||||
<span class="list-icon-container pull-right">
|
||||
<span class="glyphicon glyphicon-paperclip small text-muted" title="{{translate 'hasAttachment' category='fields' scope='Email'}}"></span>
|
||||
</span>
|
||||
{{/if}}
|
||||
{{#unless isRead}}<strong>{{/unless}}
|
||||
<a href="#{{model.name}}/view/{{model.id}}" class="link{{#if isImportant}} text-warning{{/if}}" data-id="{{model.id}}" title="{{value}}">{{value}}</a>
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
{{#unless isEmpty}}{{{value}}}{{else}}{{translate 'None'}}{{/unless}}
|
||||
{{#unless isEmpty}}{{{value}}}{{else}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{#unless isEmpty}}{{{value}}}{{/unless}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{#if value}}
|
||||
{{{value}}}
|
||||
{{/if}}
|
||||
@@ -1,5 +1,5 @@
|
||||
{{#if value}}
|
||||
{{#if isNotEmpty}}
|
||||
{{value}} {{currencyValue}}
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{#if value}}
|
||||
{{#if isNotEmpty}}
|
||||
{{currencySymbol}}{{value}}
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{#if value}}
|
||||
{{#if isNotEmpty}}
|
||||
{{value}}
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{#if value}}
|
||||
{{#if isNotEmpty}}
|
||||
{{value}} {{currencyValue}}
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{{#if value}}
|
||||
{{#if isNotEmpty}}
|
||||
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{{#if value}}
|
||||
<span title="{{currencySymbol}}{{value}}">{{currencySymbol}}{{value}}</span>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if isNotEmpty}}
|
||||
<span title="{{currencySymbol}}{{value}}">{{currencySymbol}}{{value}}</span>
|
||||
{{/if}}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
{{#if value}}
|
||||
{{#if isNotEmpty}}
|
||||
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{{#if isNotEmpty}}{{value}}{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1,4 @@
|
||||
{{#if value}}
|
||||
{{{value}}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{{#if isNotEmpty}}{{translateOption salutationValue field='salutationName' scope=scope}} {{firstValue}} {{lastValue}}{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
@@ -1,5 +1,5 @@
|
||||
{{#if value}}
|
||||
<a href="{{url}}" target="_blank">{{value}}</a>
|
||||
{{else}}
|
||||
{{translate 'None'}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
{{#if isNotEmpty}}{{value}}{{else}}{{translate 'None'}}{{/if}}
|
||||
{{#if isNotEmpty}}{{value}}{{else}}
|
||||
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
|
||||
{{/if}}
|
||||
@@ -1,6 +1,6 @@
|
||||
{{#unless isPlain}}
|
||||
{{#if useIframe}}
|
||||
<iframe frameborder="0" style="width: 100%;" class="hidden" scrolling="no"></iframe>
|
||||
<iframe frameborder="0" style="width: 100%; overflow-x: hidden; overflow-y: hidden;" class="hidden"></iframe>
|
||||
{{else}}
|
||||
<div class="html-container">{{{value}}}</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="search-container">{{{search}}}</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row row-list-container">
|
||||
{{#unless categoriesDisabled}}
|
||||
<div class="categories-container{{#unless categoriesDisabled}} col-md-3 col-sm-4{{else}} col-md-12{{/unless}}">{{{categories}}}</div>
|
||||
{{/unless}}
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
{{#if createButton}}
|
||||
<div class="button-container">
|
||||
<button class="btn btn-default" data-action="create">{{translate 'Create'}}</button>
|
||||
<button class="btn btn-default" data-action="create">{{createText}}</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<input type="checkbox" class="record-checkbox" data-id="{{model.id}}">
|
||||
<span class="record-checkbox-container"><input type="checkbox" class="record-checkbox" data-id="{{model.id}}"></span>
|
||||
@@ -1,3 +1,4 @@
|
||||
{{#if fieldList.length}}
|
||||
<div class="row">
|
||||
{{#each fieldList}}
|
||||
<div class="cell form-group col-sm-6 col-md-12{{#if hidden}} hidden-cell{{/if}}" data-name="{{name}}">
|
||||
@@ -8,21 +9,26 @@
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#unless complexDateFieldsDisabled}}
|
||||
<div class="row">
|
||||
{{#if hasComplexCreated}}
|
||||
<div class="cell form-group col-sm-6 col-md-12" data-name="complexCreated">
|
||||
<label class="control-label" data-name="complexCreated"><span class="label-text">{{translate 'Created'}}</span></label>
|
||||
<div class="field" data-name="complexCreated">
|
||||
<span data-name="createdAt" class="field">{{{createdAtField}}}</span> <span class="text-muted">»</span> <span data-name="createdBy" class="field">{{{createdByField}}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if hasComplexModified}}
|
||||
<div class="cell form-group col-sm-6 col-md-12" data-name="complexModified">
|
||||
<label class="control-label" data-name="complexModified"><span class="label-text">{{translate 'Modified'}}</span></label>
|
||||
<div class="field" data-name="complexModified">
|
||||
<span data-name="modifiedAt" class="field">{{{modifiedAtField}}}</span> <span class="text-muted">»</span> <span data-name="modifiedBy" class="field">{{{modifiedByField}}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{{#if fieldList.length}}
|
||||
<div class="row">
|
||||
{{#each fieldList}}
|
||||
<div class="cell form-group col-sm-6 col-md-12{{#if hidden}} hidden-cell{{/if}}" data-name="{{name}}">
|
||||
@@ -8,3 +9,4 @@
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -50,6 +50,11 @@ Espo.define('model', [], function () {
|
||||
Dep.prototype.initialize.call(this);
|
||||
},
|
||||
|
||||
sync: function (method, model, options) {
|
||||
if (method === 'patch') options.type = 'PUT';
|
||||
return Dep.prototype.sync.call(this, method, model, options);
|
||||
},
|
||||
|
||||
set: function (key, val, options) {
|
||||
if (typeof key === 'object') {
|
||||
var o = key;
|
||||
|
||||
@@ -35,7 +35,8 @@ Espo.define('views/admin/entity-manager/record/edit-formula', 'views/record/base
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
this.createField('beforeSaveCustomScript', 'views/fields/formula', {
|
||||
targetEntityType: this.options.targetEntityType
|
||||
targetEntityType: this.options.targetEntityType,
|
||||
height: 500
|
||||
}, 'edit');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
Espo.define('views/email/fields/person-string-data', 'views/fields/varchar', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
listTemplate: 'email/fields/person-string-data/list',
|
||||
|
||||
data: function () {
|
||||
var data = Dep.prototype.data.call(this);
|
||||
data.isReplied = this.model.get('isReplied');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,7 @@ Espo.define('views/email/fields/subject', 'views/fields/varchar', function (Dep)
|
||||
data.isRead = (this.model.get('sentById') === this.getUser().id) || this.model.get('isRead');
|
||||
data.isImportant = this.model.get('isImportant');
|
||||
data.hasAttachment = this.model.get('hasAttachment');
|
||||
data.isReplied = this.model.get('isReplied');
|
||||
|
||||
if (!data.isNotEmpty) {
|
||||
if (
|
||||
|
||||
@@ -52,45 +52,41 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) {
|
||||
var data = Dep.prototype.data.call(this);
|
||||
data.ucName = Espo.Utils.upperCaseFirst(this.name);
|
||||
|
||||
data.postalCodeValue = this.model.get(this.postalCodeField);
|
||||
data.streetValue = this.model.get(this.streetField);
|
||||
data.cityValue = this.model.get(this.cityField);
|
||||
data.stateValue = this.model.get(this.stateField);
|
||||
data.countryValue = this.model.get(this.countryField);
|
||||
this.addressPartList.forEach(function (item) {
|
||||
var value = this.model.get(this[item + 'Field']);
|
||||
data[item + 'Value'] = value;
|
||||
}, this);
|
||||
|
||||
if (this.mode == 'detail' || this.mode == 'list') {
|
||||
data.formattedAddress = this.getFormattedAddress();
|
||||
}
|
||||
|
||||
data.isEmpty = !(data.postalCodeValue ||
|
||||
data.streetValue ||
|
||||
data.cityValue ||
|
||||
data.stateValue ||
|
||||
data.countryValue);
|
||||
var isNotEmpty = false;
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
setupSearch: function () {
|
||||
this.searchData.value = this.getSearchParamsData().value || this.searchParams.additionalValue;
|
||||
this.searchData.value = this.getSearchParamsData().value || this.searchParams.additionalValue;
|
||||
},
|
||||
|
||||
getFormattedAddress: function () {
|
||||
var postalCodeValue = this.model.get(this.postalCodeField);
|
||||
var streetValue = this.model.get(this.streetField);
|
||||
var cityValue = this.model.get(this.cityField);
|
||||
var stateValue = this.model.get(this.stateField);
|
||||
var countryValue = this.model.get(this.countryField);
|
||||
var isNotEmpty = false;
|
||||
var isSet = false;
|
||||
this.addressAttributeList.forEach(function (attribute) {
|
||||
isNotEmpty = isNotEmpty || this.model.get(attribute);
|
||||
isSet = isSet || this.model.has(attribute);
|
||||
}, this);
|
||||
|
||||
var isEmpty = !(
|
||||
postalCodeValue ||
|
||||
streetValue ||
|
||||
cityValue ||
|
||||
stateValue ||
|
||||
countryValue
|
||||
);
|
||||
var isEmpty = !isNotEmpty;
|
||||
|
||||
if (isEmpty) {
|
||||
if (this.mode === 'list') {
|
||||
return '';
|
||||
}
|
||||
if (!isSet) {
|
||||
return this.translate('...');
|
||||
}
|
||||
return this.translate('None');
|
||||
}
|
||||
|
||||
@@ -320,13 +316,18 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) {
|
||||
}
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this.postalCodeField = this.options.defs.name + 'PostalCode';
|
||||
this.streetField = this.options.defs.name + 'Street';
|
||||
this.stateField = this.options.defs.name + 'State';
|
||||
this.cityField = this.options.defs.name + 'City';
|
||||
this.countryField = this.options.defs.name + 'Country';
|
||||
Dep.prototype.init.call(this);
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
var actualAttributePartList = this.getMetadata().get(['fields', this.type, 'actualFields']) || [];
|
||||
this.addressAttributeList = [];
|
||||
this.addressPartList = [];
|
||||
actualAttributePartList.forEach(function (item) {
|
||||
var attribute = this.name + Espo.Utils.upperCaseFirst(item);
|
||||
this.addressAttributeList.push(attribute);
|
||||
this.addressPartList.push(item);
|
||||
this[item + 'Field'] = attribute;
|
||||
}, this);
|
||||
},
|
||||
|
||||
validateRequired: function () {
|
||||
@@ -397,7 +398,7 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) {
|
||||
type: 'like',
|
||||
field: this.countryField,
|
||||
value: value + '%'
|
||||
},
|
||||
}
|
||||
],
|
||||
data: {
|
||||
value: value
|
||||
|
||||
@@ -32,7 +32,7 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
|
||||
|
||||
type: 'array',
|
||||
|
||||
listTemplate: 'fields/array/detail',
|
||||
listTemplate: 'fields/array/list',
|
||||
|
||||
detailTemplate: 'fields/array/detail',
|
||||
|
||||
@@ -51,7 +51,8 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
|
||||
translatedOptions: this.translatedOptions,
|
||||
hasOptions: this.params.options ? true : false,
|
||||
itemHtmlList: itemHtmlList,
|
||||
isEmpty: (this.selected || []).length === 0
|
||||
isEmpty: (this.selected || []).length === 0,
|
||||
valueIsSet: this.model.has(this.name)
|
||||
}, Dep.prototype.data.call(this));
|
||||
},
|
||||
|
||||
@@ -109,7 +110,7 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
|
||||
}
|
||||
|
||||
if (this.options.customOptionList) {
|
||||
this.setOptionList(this.options.customOptionList);
|
||||
this.setOptionList(this.options.customOptionList, true);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -146,13 +147,21 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
|
||||
|
||||
},
|
||||
|
||||
setOptionList: function (optionList) {
|
||||
setOptionList: function (optionList, silent) {
|
||||
if (!this.originalOptionList) {
|
||||
this.originalOptionList = this.params.options;
|
||||
}
|
||||
this.params.options = Espo.Utils.clone(optionList);
|
||||
|
||||
if (this.mode == 'edit') {
|
||||
if (this.mode == 'edit' && !silent) {
|
||||
var selectedOptionList = [];
|
||||
this.selected.forEach(function (option) {
|
||||
if (~optionList.indexOf(option)) {
|
||||
selectedOptionList.push(option);
|
||||
}
|
||||
}, this);
|
||||
this.selected = selectedOptionList;
|
||||
|
||||
if (this.isRendered()) {
|
||||
this.reRender();
|
||||
this.trigger('change');
|
||||
|
||||
@@ -32,7 +32,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
|
||||
type: 'attachmentMultiple',
|
||||
|
||||
listTemplate: 'fields/attachments-multiple/detail',
|
||||
listTemplate: 'fields/attachments-multiple/list',
|
||||
|
||||
detailTemplate: 'fields/attachments-multiple/detail',
|
||||
|
||||
@@ -495,7 +495,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
for (var id in nameHash) {
|
||||
var type = typeHash[id] || false;
|
||||
var name = nameHash[id];
|
||||
if (this.showPreviews && ~this.previewTypeList.indexOf(type)) {
|
||||
if (this.showPreviews && ~this.previewTypeList.indexOf(type) && this.mode !== 'list') {
|
||||
previews.push('<div class="attachment-preview">' + this.getDetailPreview(name, type, id) + '</div>');
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ Espo.define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
if (this.model.get(this.name) !== null && typeof this.model.get(this.name) !== 'undefined') {
|
||||
data.isNotEmpty = true;
|
||||
}
|
||||
data.valueIsSet = this.model.has(this.name);
|
||||
return data;
|
||||
},
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Espo.define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
type: 'linkMultiple',
|
||||
|
||||
listTemplate: 'fields/link-multiple/detail',
|
||||
listTemplate: 'fields/link-multiple/list',
|
||||
|
||||
detailTemplate: 'fields/link-multiple/detail',
|
||||
|
||||
@@ -359,16 +359,19 @@ Espo.define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
|
||||
if (type === 'anyOf') {
|
||||
var values = this.ids || [];
|
||||
var idList = this.ids || [];
|
||||
|
||||
var data = {
|
||||
type: 'linkedWith',
|
||||
value: this.ids || [],
|
||||
value: idList,
|
||||
nameHash: this.nameHash,
|
||||
data: {
|
||||
type: type
|
||||
}
|
||||
};
|
||||
if (!idList.length) {
|
||||
data.value = null;
|
||||
}
|
||||
return data;
|
||||
} else if (type === 'noneOf') {
|
||||
var values = this.ids || [];
|
||||
|
||||
@@ -32,7 +32,7 @@ Espo.define('views/fields/multi-enum', ['views/fields/array', 'lib!Selectize'],
|
||||
|
||||
type: 'multiEnum',
|
||||
|
||||
listTemplate: 'fields//array/detail',
|
||||
listTemplate: 'fields//array/list',
|
||||
|
||||
detailTemplate: 'fields/array/detail',
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ Espo.define('views/fields/person-name', 'views/fields/varchar', function (Dep) {
|
||||
data.firstMaxLength = this.model.getFieldParam(this.firstField, 'maxLength');
|
||||
data.lastMaxLength = this.model.getFieldParam(this.lastField, 'maxLength');
|
||||
|
||||
data.valueIsSet = this.model.has(this.firstField) || this.model.has(this.lastField);
|
||||
|
||||
if (this.mode === 'detail') {
|
||||
data.isNotEmpty = !!data.firstValue || !!data.lastValue || !!data.salutationValue;
|
||||
} else if (this.mode === 'list' || this.mode === 'listLink') {
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('Views.Fields.Url', 'Views.Fields.Varchar', function (Dep) {
|
||||
Espo.define('views/fields/url', 'views/fields/varchar', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
type: 'url',
|
||||
|
||||
listTemplate: 'fields.url.list',
|
||||
listTemplate: 'fields/url/list',
|
||||
|
||||
detailTemplate: 'fields.url.detail',
|
||||
detailTemplate: 'fields/url/detail',
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
@@ -58,6 +58,8 @@ Espo.define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
) {
|
||||
data.isNotEmpty = true;
|
||||
}
|
||||
data.valueIsSet = this.model.has(this.name);
|
||||
|
||||
if (this.mode === 'search') {
|
||||
if (typeof this.searchParams.value === 'string') {
|
||||
this.searchData.value = this.searchParams.value;
|
||||
|
||||
@@ -208,10 +208,23 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
documentElement.write(body);
|
||||
documentElement.close();
|
||||
|
||||
var $body = $iframe.contents().find('html body');
|
||||
|
||||
var $document = $(documentElement);
|
||||
|
||||
var processWidth = function () {
|
||||
var bodyElement = $body.get(0);
|
||||
if (bodyElement) {
|
||||
if (bodyElement.clientWidth !== iframeElement.scrollWidth) {
|
||||
iframeElement.style.height = (iframeElement.scrollHeight + 20) + 'px';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var increaseHeightStep = 10;
|
||||
var processIncreaseHeight = function (iteration, previousDiff) {
|
||||
$body.css('height', '');
|
||||
|
||||
iteration = iteration || 0;
|
||||
|
||||
if (iteration > 200) {
|
||||
@@ -224,6 +237,8 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
|
||||
if (typeof previousDiff !== 'undefined') {
|
||||
if (diff === previousDiff) {
|
||||
$body.css('height', (iframeElement.clientHeight - increaseHeightStep) + 'px');
|
||||
processWidth();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -232,6 +247,8 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
var height = iframeElement.scrollHeight + increaseHeightStep;
|
||||
iframeElement.style.height = height + 'px';
|
||||
processIncreaseHeight(iteration, diff);
|
||||
} else {
|
||||
processWidth();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,7 +258,6 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
overflowY: 'hidden',
|
||||
overflowX: 'hidden'
|
||||
});
|
||||
$iframe.attr('scrolling', 'no');
|
||||
|
||||
iframeElement.style.height = '0px';
|
||||
} else {
|
||||
@@ -265,7 +281,6 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
overflowY: 'hidden',
|
||||
overflowX: 'scroll'
|
||||
});
|
||||
$iframe.attr('scrolling', 'yes');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -423,19 +438,21 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
onScrollEdit: function (e) {
|
||||
var $target = $(e.target);
|
||||
var toolbarHeight = this.$toolbar.height();
|
||||
var top;
|
||||
var toolbarWidth = this.$toolbar.parent().width();
|
||||
var edgeTop, edgeTopAbsolute;
|
||||
|
||||
if ($target.get(0) === window.document) {
|
||||
var $buttonContainer = $target.find('.detail-button-container:not(.hidden)');
|
||||
var offset = $buttonContainer.offset();
|
||||
if (offset) {
|
||||
var edgeTop = offset.top + $buttonContainer.height();
|
||||
var edgeTopAbsolute = edgeTop - $(window).scrollTop();
|
||||
edgeTop = offset.top + $buttonContainer.height();
|
||||
edgeTopAbsolute = edgeTop - $(window).scrollTop();
|
||||
}
|
||||
} else {
|
||||
var offset = $target.offset();
|
||||
if (offset) {
|
||||
var edgeTop = offset.top;
|
||||
var edgeTopAbsolute = edgeTop;
|
||||
edgeTop = offset.top;
|
||||
edgeTopAbsolute = edgeTop - $(window).scrollTop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +466,8 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
|
||||
if (toStick) {
|
||||
this.$toolbar.css({
|
||||
top: edgeTopAbsolute + 'px'
|
||||
top: edgeTopAbsolute + 'px',
|
||||
width: toolbarWidth + 'px'
|
||||
});
|
||||
this.$toolbar.addClass('sticked');
|
||||
this.$area.css({
|
||||
@@ -458,7 +476,8 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun
|
||||
});
|
||||
} else {
|
||||
this.$toolbar.css({
|
||||
top: ''
|
||||
top: '',
|
||||
width: ''
|
||||
});
|
||||
this.$toolbar.removeClass('sticked');
|
||||
this.$area.css({
|
||||
|
||||
@@ -92,15 +92,17 @@ Espo.define('views/global-search/global-search', 'view', function (Dep) {
|
||||
$document = $(document);
|
||||
$document.on('mouseup.global-search', function (e) {
|
||||
if (e.which !== 1) return;
|
||||
if (!$container.is(e.target) && $container.has(e.target).length === 0) {
|
||||
this.closePanel();
|
||||
}
|
||||
}.bind(this));
|
||||
$document.on('click.global-search', function (e) {
|
||||
if (e.target.tagName == 'A' && $(e.target).data('action') != 'showMore') {
|
||||
setTimeout(function () {
|
||||
this.closePanel();
|
||||
}.bind(this), 100);
|
||||
return;
|
||||
}
|
||||
if (!$container.is(e.target) && $container.has(e.target).length === 0) {
|
||||
this.closePanel();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
@@ -112,11 +114,10 @@ Espo.define('views/global-search/global-search', 'view', function (Dep) {
|
||||
if (this.hasView('panel')) {
|
||||
this.getView('panel').remove();
|
||||
};
|
||||
$document.off('mouseup.global-search');
|
||||
$container.remove();
|
||||
},
|
||||
$document.off('mouseup.global-search');
|
||||
$document.off('click.global-search');
|
||||
$container.remove();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -50,15 +50,19 @@ Espo.define('views/modals/select-records-with-categories', ['views/modals/select
|
||||
this.scope = this.entityType = this.options.scope || this.scope;
|
||||
this.categoryScope = this.categoryScope || this.scope + 'Category';
|
||||
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
this.categoriesDisabled = this.categoriesDisabled ||
|
||||
this.getMetadata().get('scopes.' + this.categoryScope + '.disabled') ||
|
||||
!this.getAcl().checkScope(this.categoryScope);
|
||||
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
|
||||
},
|
||||
|
||||
loadList: function () {
|
||||
this.loadCategories();
|
||||
if (!this.categoriesDisabled) {
|
||||
this.loadCategories();
|
||||
}
|
||||
Dep.prototype.loadList.call(this);
|
||||
},
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
var method = 'massAction' + Espo.Utils.upperCaseFirst(action);
|
||||
if (method in this) {
|
||||
this[method]();
|
||||
this[method]();
|
||||
} else {
|
||||
this.massAction(action);
|
||||
}
|
||||
@@ -311,9 +311,9 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
this.$el.find('input.select-all').prop('checked', true);
|
||||
|
||||
this.massActionList.forEach(function(item) {
|
||||
if (!~this.checkAllResultMassActionList.indexOf(item)) {
|
||||
this.$el.find('div.list-buttons-container .actions li a.mass-action[data-action="'+item+'"]').parent().addClass('hidden');
|
||||
}
|
||||
if (!~this.checkAllResultMassActionList.indexOf(item)) {
|
||||
this.$el.find('div.list-buttons-container .actions li a.mass-action[data-action="'+item+'"]').parent().addClass('hidden');
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (this.checkAllResultMassActionList.length) {
|
||||
@@ -333,9 +333,9 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
|
||||
this.massActionList.forEach(function(item) {
|
||||
if (!~this.checkAllResultMassActionList.indexOf(item)) {
|
||||
this.$el.find('div.list-buttons-container .actions li a.mass-action[data-action="'+item+'"]').parent().removeClass('hidden');
|
||||
}
|
||||
if (!~this.checkAllResultMassActionList.indexOf(item)) {
|
||||
this.$el.find('div.list-buttons-container .actions li a.mass-action[data-action="'+item+'"]').parent().removeClass('hidden');
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
@@ -469,11 +469,11 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
var ids = [];
|
||||
var data = {};
|
||||
if (this.allResultIsChecked) {
|
||||
data.where = this.collection.getWhere();
|
||||
data.where = this.collection.getWhere();
|
||||
data.selectData = this.collection.data || {};
|
||||
data.byWhere = true;
|
||||
data.byWhere = true;
|
||||
} else {
|
||||
data.ids = ids;
|
||||
data.ids = ids;
|
||||
}
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
@@ -485,45 +485,45 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data)
|
||||
}).done(function (result) {
|
||||
result = result || {};
|
||||
var count = result.count;
|
||||
if (this.allResultIsChecked) {
|
||||
if (count) {
|
||||
this.unselectAllResult();
|
||||
this.listenToOnce(this.collection, 'sync', function () {
|
||||
var msg = 'massRemoveResult';
|
||||
if (count == 1) {
|
||||
msg = 'massRemoveResultSingle'
|
||||
}
|
||||
Espo.Ui.success(this.translate(msg, 'messages').replace('{count}', count));
|
||||
}, this);
|
||||
this.collection.fetch();
|
||||
Espo.Ui.notify(false);
|
||||
} else {
|
||||
Espo.Ui.warning(self.translate('noRecordsRemoved', 'messages'));
|
||||
}
|
||||
} else {
|
||||
var idsRemoved = result.ids || [];
|
||||
if (count) {
|
||||
idsRemoved.forEach(function (id) {
|
||||
Espo.Ui.notify(false);
|
||||
this.checkedList = [];
|
||||
result = result || {};
|
||||
var count = result.count;
|
||||
if (this.allResultIsChecked) {
|
||||
if (count) {
|
||||
this.unselectAllResult();
|
||||
this.listenToOnce(this.collection, 'sync', function () {
|
||||
var msg = 'massRemoveResult';
|
||||
if (count == 1) {
|
||||
msg = 'massRemoveResultSingle'
|
||||
}
|
||||
Espo.Ui.success(this.translate(msg, 'messages').replace('{count}', count));
|
||||
}, this);
|
||||
this.collection.fetch();
|
||||
Espo.Ui.notify(false);
|
||||
} else {
|
||||
Espo.Ui.warning(self.translate('noRecordsRemoved', 'messages'));
|
||||
}
|
||||
} else {
|
||||
var idsRemoved = result.ids || [];
|
||||
if (count) {
|
||||
idsRemoved.forEach(function (id) {
|
||||
Espo.Ui.notify(false);
|
||||
|
||||
this.collection.trigger('model-removing', id);
|
||||
this.removeRecordFromList(id);
|
||||
this.uncheckRecord(id, null, true);
|
||||
|
||||
}, this);
|
||||
var msg = 'massRemoveResult';
|
||||
if (count == 1) {
|
||||
msg = 'massRemoveResultSingle'
|
||||
}
|
||||
Espo.Ui.success(self.translate(msg, 'messages').replace('{count}', count));
|
||||
} else {
|
||||
Espo.Ui.warning(self.translate('noRecordsRemoved', 'messages'));
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
var msg = 'massRemoveResult';
|
||||
if (count == 1) {
|
||||
msg = 'massRemoveResultSingle'
|
||||
}
|
||||
Espo.Ui.success(self.translate(msg, 'messages').replace('{count}', count));
|
||||
} else {
|
||||
Espo.Ui.warning(self.translate('noRecordsRemoved', 'messages'));
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
massActionPrintPdf: function () {
|
||||
@@ -681,12 +681,12 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
Espo.Ui.warning(this.translate('noRecordsUpdated', 'messages'));
|
||||
}
|
||||
if (allResultIsChecked) {
|
||||
this.selectAllResult();
|
||||
} else {
|
||||
ids.forEach(function (id) {
|
||||
this.checkRecord(id);
|
||||
}, this);
|
||||
}
|
||||
this.selectAllResult();
|
||||
} else {
|
||||
ids.forEach(function (id) {
|
||||
this.checkRecord(id);
|
||||
}, this);
|
||||
}
|
||||
}.bind(this));
|
||||
this.collection.fetch();
|
||||
}, this);
|
||||
@@ -694,16 +694,16 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
},
|
||||
|
||||
massActionExport: function () {
|
||||
if (!this.getConfig().get('exportDisabled') || this.getUser().get('isAdmin')) {
|
||||
this.export();
|
||||
}
|
||||
if (!this.getConfig().get('exportDisabled') || this.getUser().get('isAdmin')) {
|
||||
this.export();
|
||||
}
|
||||
},
|
||||
|
||||
removeMassAction: function (item) {
|
||||
var index = this.massActionList.indexOf(item);
|
||||
if (~index) {
|
||||
this.massActionList.splice(index, 1);
|
||||
}
|
||||
var index = this.massActionList.indexOf(item);
|
||||
if (~index) {
|
||||
this.massActionList.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
addMassAction: function (item, allResult) {
|
||||
@@ -758,9 +758,9 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
var checkAllResultMassActionList = [];
|
||||
this.checkAllResultMassActionList.forEach(function (item) {
|
||||
if (~this.massActionList.indexOf(item)) {
|
||||
checkAllResultMassActionList.push(item);
|
||||
}
|
||||
if (~this.massActionList.indexOf(item)) {
|
||||
checkAllResultMassActionList.push(item);
|
||||
}
|
||||
}, this);
|
||||
this.checkAllResultMassActionList = checkAllResultMassActionList;
|
||||
|
||||
@@ -790,7 +790,7 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
||
|
||||
this.getAcl().get('exportPermission') === 'no'
|
||||
) {
|
||||
this.removeMassAction('export');
|
||||
this.removeMassAction('export');
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -970,13 +970,13 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
var defs = [];
|
||||
|
||||
for (var i in this.listLayout) {
|
||||
var width = false;
|
||||
var width = false;
|
||||
|
||||
if ('width' in this.listLayout[i] && this.listLayout[i].width !== null) {
|
||||
width = this.listLayout[i].width + '%';
|
||||
} else if ('widthPx' in this.listLayout[i]) {
|
||||
width = this.listLayout[i].widthPx;
|
||||
}
|
||||
} else if ('widthPx' in this.listLayout[i]) {
|
||||
width = this.listLayout[i].widthPx;
|
||||
}
|
||||
|
||||
var item = {
|
||||
name: this.listLayout[i].name,
|
||||
@@ -1013,7 +1013,7 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
layout.push({
|
||||
name: 'r-checkboxField',
|
||||
columnName: 'r-checkbox',
|
||||
template: 'record.list-checkbox'
|
||||
template: 'record/list-checkbox'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1057,7 +1057,7 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
return layout;
|
||||
},
|
||||
|
||||
checkRecord: function (id, $target) {
|
||||
checkRecord: function (id, $target, isSilent) {
|
||||
$target = $target || this.$el.find('.record-checkbox[data-id="' + id + '"]');
|
||||
|
||||
if (!$target.size()) return;
|
||||
@@ -1071,24 +1071,28 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
$target.closest('tr').addClass('active');
|
||||
|
||||
this.handleAfterCheck();
|
||||
this.handleAfterCheck(isSilent);
|
||||
},
|
||||
|
||||
uncheckRecord: function (id, $target) {
|
||||
uncheckRecord: function (id, $target, isSilent) {
|
||||
$target = $target || this.$el.find('.record-checkbox[data-id="' + id + '"]');
|
||||
$target.get(0).checked = false;
|
||||
if ($target.get(0)) {
|
||||
$target.get(0).checked = false;
|
||||
}
|
||||
|
||||
var index = this.checkedList.indexOf(id);
|
||||
if (index != -1) {
|
||||
this.checkedList.splice(index, 1);
|
||||
}
|
||||
|
||||
$target.closest('tr').removeClass('active');
|
||||
if ($target.get(0)) {
|
||||
$target.closest('tr').removeClass('active');
|
||||
}
|
||||
|
||||
this.handleAfterCheck();
|
||||
this.handleAfterCheck(isSilent);
|
||||
},
|
||||
|
||||
handleAfterCheck: function () {
|
||||
handleAfterCheck: function (isSilent) {
|
||||
if (this.checkedList.length) {
|
||||
this.$el.find('.actions-button').removeAttr('disabled');
|
||||
} else {
|
||||
@@ -1101,7 +1105,9 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
this.$el.find('.select-all').prop('checked', false);
|
||||
}
|
||||
|
||||
this.trigger('check');
|
||||
if (!isSilent) {
|
||||
this.trigger('check');
|
||||
}
|
||||
},
|
||||
|
||||
getRowActionsDefs: function () {
|
||||
|
||||
@@ -34,28 +34,44 @@ Espo.define('views/record/panels/default-side', 'views/record/panels/side', func
|
||||
|
||||
data: function () {
|
||||
var data = Dep.prototype.data.call(this);
|
||||
if (this.complexCreatedDisabled && this.complexModifiedDisabled) {
|
||||
if (this.complexCreatedDisabled && this.complexModifiedDisabled || (!this.hasComplexCreated && !this.hasComplexModified)) {
|
||||
data.complexDateFieldsDisabled = true;
|
||||
}
|
||||
data.hasComplexCreated = this.hasComplexCreated;
|
||||
data.hasComplexModified = this.hasComplexModified;
|
||||
return data;
|
||||
},
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
this.hasComplexCreated =
|
||||
!!this.getMetadata().get(['entityDefs', this.model.name, 'fields', 'createdAt'])
|
||||
&&
|
||||
!!this.getMetadata().get(['entityDefs', this.model.name, 'fields', 'createdBy']);
|
||||
|
||||
this.hasComplexModified =
|
||||
!!this.getMetadata().get(['entityDefs', this.model.name, 'fields', 'modifiedAt'])
|
||||
&&
|
||||
!!this.getMetadata().get(['entityDefs', this.model.name, 'fields', 'modifiedBy']);
|
||||
|
||||
if (!this.complexCreatedDisabled) {
|
||||
this.createField('createdBy', null, null, null, true);
|
||||
this.createField('createdAt', null, null, null, true);
|
||||
if (!this.model.get('createdById')) {
|
||||
this.recordViewObject.hideField('complexCreated');
|
||||
if (this.hasComplexCreated) {
|
||||
this.createField('createdBy', null, null, null, true);
|
||||
this.createField('createdAt', null, null, null, true);
|
||||
if (!this.model.get('createdById')) {
|
||||
this.recordViewObject.hideField('complexCreated');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.recordViewObject.hideField('complexCreated');
|
||||
}
|
||||
|
||||
if (!this.complexModifiedDisabled) {
|
||||
this.createField('modifiedBy', null, null, null, true);
|
||||
this.createField('modifiedAt', null, null, null, true);
|
||||
if (this.hasComplexModified) {
|
||||
this.createField('modifiedBy', null, null, null, true);
|
||||
this.createField('modifiedAt', null, null, null, true);
|
||||
}
|
||||
if (!this.model.get('modifiedById')) {
|
||||
this.recordViewObject.hideField('complexModified');
|
||||
}
|
||||
@@ -63,13 +79,13 @@ Espo.define('views/record/panels/default-side', 'views/record/panels/side', func
|
||||
this.recordViewObject.hideField('complexModified');
|
||||
}
|
||||
|
||||
if (!this.complexCreatedDisabled) {
|
||||
if (!this.complexCreatedDisabled && this.hasComplexCreated) {
|
||||
this.listenTo(this.model, 'change:createdById', function () {
|
||||
if (!this.model.get('createdById')) return;
|
||||
this.recordViewObject.showField('complexCreated');
|
||||
}, this);
|
||||
}
|
||||
if (!this.complexModifiedDisabled) {
|
||||
if (!this.complexModifiedDisabled && this.hasComplexModified) {
|
||||
this.listenTo(this.model, 'change:modifiedById', function () {
|
||||
if (!this.model.get('modifiedById')) return;
|
||||
this.recordViewObject.showField('complexModified');
|
||||
|
||||
@@ -120,6 +120,12 @@ Espo.define('views/record/panels/side', 'view', function (Dep) {
|
||||
this.createFields();
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
if (this.$el.children().size() === 0) {
|
||||
this.$el.parent().addClass('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
setupFields: function () {
|
||||
},
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text
|
||||
if ($.contains(this.$postContainer.get(0), e.target)) return;
|
||||
if (this.$textarea.val() !== '') return;
|
||||
|
||||
var attachmentsIds = this.seed.get('attachmentsIds');
|
||||
var attachmentsIds = this.seed.get('attachmentsIds') || [];
|
||||
if (!attachmentsIds.length && !this.getView('attachments').isUploading) {
|
||||
this.disablePostingMode();
|
||||
}
|
||||
@@ -363,7 +363,7 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text
|
||||
return;
|
||||
}
|
||||
|
||||
if (message == '' && this.seed.get('attachmentsIds').length == 0) {
|
||||
if (message == '' && (this.seed.get('attachmentsIds') || []).length == 0) {
|
||||
this.notify('Post cannot be empty', 'error');
|
||||
this.$textarea.prop('disabled', false);
|
||||
return;
|
||||
@@ -386,7 +386,7 @@ Espo.define('views/stream/panel', ['views/record/panels/relationship', 'lib!Text
|
||||
}, this);
|
||||
|
||||
model.set('post', message);
|
||||
model.set('attachmentsIds', Espo.Utils.clone(this.seed.get('attachmentsIds')));
|
||||
model.set('attachmentsIds', Espo.Utils.clone(this.seed.get('attachmentsIds') || []));
|
||||
model.set('type', 'Post');
|
||||
model.set('isInternal', this.isInternalNoteMode);
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ Espo.define('views/user/detail', 'views/detail', function (Dep) {
|
||||
name: 'preferences',
|
||||
label: 'Preferences',
|
||||
style: 'default',
|
||||
action: "preferences"
|
||||
action: "preferences",
|
||||
link: '#Preferences/edit/' + this.getUser().id
|
||||
});
|
||||
|
||||
if (!this.model.get('isPortalUser')) {
|
||||
@@ -47,7 +48,8 @@ Espo.define('views/user/detail', 'views/detail', function (Dep) {
|
||||
name: 'emailAccounts',
|
||||
label: "Email Accounts",
|
||||
style: 'default',
|
||||
action: "emailAccounts"
|
||||
action: "emailAccounts",
|
||||
link: '#EmailAccount/list/userId=' + this.model.id + '&userName=' + encodeURIComponent(this.model.get('name'))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,7 +58,8 @@ Espo.define('views/user/detail', 'views/detail', function (Dep) {
|
||||
name: 'externalAccounts',
|
||||
label: 'External Accounts',
|
||||
style: 'default',
|
||||
action: "externalAccounts"
|
||||
action: "externalAccounts",
|
||||
link: '#ExternalAccount'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,14 @@ div.list-expanded > ul > li > div.expanded-row > .cell:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.list > table.table td > span.glyphicon.pull-right,
|
||||
.list > table.table td > span.list-icon-container.pull-right {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.list > table.table td > span.list-icon-container.pull-left {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.list > ul.list-group > li.list-row .cell > span.glyphicon.pull-right {
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
@@ -375,6 +382,10 @@ ul.dropdown-menu > li.checkbox {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dropdown-menu > li > a {
|
||||
line-height: @line-height-computed;
|
||||
}
|
||||
|
||||
ul.dropdown-menu > li > a.active {
|
||||
background-color: @gray-lighter;
|
||||
text-decoration: none;
|
||||
@@ -961,8 +972,8 @@ table.list th {
|
||||
.list > table th[data-name="r-checkbox"] {
|
||||
white-space: nowrap;
|
||||
.select-all-container {
|
||||
line-height: @line-height-base;
|
||||
height: unit(@line-height-base, em);
|
||||
line-height: @line-height-computed;
|
||||
height: @line-height-computed;
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
@@ -977,6 +988,11 @@ table.list th {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.list .record-checkbox-container {
|
||||
height: @line-height-computed;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.list > table td > div.field {
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -1078,7 +1094,8 @@ table.less-padding td.cell[data-name="buttons"] > .btn-group {
|
||||
right: -11px;
|
||||
}
|
||||
|
||||
.list-container + .button-container {
|
||||
.list-container + .button-container,
|
||||
.row-list-container + .button-container {
|
||||
margin-top: 10px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
@@ -1273,6 +1290,15 @@ label.attach-file-label {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.icon-flip-horizontal {
|
||||
-moz-transform: scaleX(-1);
|
||||
-webkit-transform: scaleX(-1);
|
||||
-o-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
-ms-filter: fliph;
|
||||
filter: fliph;
|
||||
}
|
||||
|
||||
.post-container > textarea.note,
|
||||
textarea.auto-height {
|
||||
overflow-x: hidden;
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
.clearfix();
|
||||
}
|
||||
|
||||
@line-height-small-computed: floor((@font-size-base * @line-height-small));
|
||||
|
||||
@table-cell-padding: 8px;
|
||||
|
||||
@table-cell-less-padding: 3px;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "5.3.2",
|
||||
"version": "5.3.5",
|
||||
"description": "",
|
||||
"main": "index.php",
|
||||
"repository": {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\integration\Espo\Core\FulltextIndex;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
class CheckCreatedIndexTest extends \tests\integration\Core\BaseTestCase
|
||||
{
|
||||
protected $dataFile = 'InitData.php';
|
||||
|
||||
protected $pathToFiles = 'Core/FulltextIndex/customFiles';
|
||||
|
||||
public function entitylist()
|
||||
{
|
||||
return [
|
||||
['Email'],
|
||||
['Account'],
|
||||
['Contact'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider entitylist
|
||||
*/
|
||||
public function testCreatedIndexes($entityName)
|
||||
{
|
||||
$entityManager = $this->getContainer()->get('entityManager');
|
||||
$pdo = $entityManager->getPDO();
|
||||
|
||||
$fulltextFieldList = $entityManager->getOrmMetadata()->get($entityName, 'fullTextSearchColumnList');
|
||||
|
||||
if (!$fulltextFieldList) {
|
||||
$this->assertNull($fulltextFieldList);
|
||||
return;
|
||||
}
|
||||
|
||||
$query = "SHOW INDEX FROM `". Util::toCamelCase($entityName) ."` WHERE Index_type = 'FULLTEXT'";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
|
||||
$rowList = $sth->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$this->assertNotEmpty($rowList);
|
||||
|
||||
$result = [];
|
||||
foreach ($rowList as $row) {
|
||||
$result[] = Util::toCamelCase($row['Column_name']);
|
||||
}
|
||||
|
||||
asort($fulltextFieldList);
|
||||
asort($result);
|
||||
|
||||
$this->assertEquals($fulltextFieldList, $result);
|
||||
}
|
||||
}
|
||||
@@ -56,4 +56,30 @@ class SearchByEmailAddressTest extends \tests\integration\Core\BaseTestCase
|
||||
$this->assertArrayHasKey('collection', $result);
|
||||
$this->assertEquals(1, count($result['collection']));
|
||||
}
|
||||
|
||||
public function testTextSearch()
|
||||
{
|
||||
$entityManager = $this->getContainer()->get('entityManager');
|
||||
|
||||
$email = $entityManager->getEntity('Email');
|
||||
$email->set('from', 'test@test.com');
|
||||
$email->set('status', 'Archived');
|
||||
$email->set('name', 'Improvements to our Privacy Policy');
|
||||
$email->set('body', 'name abc test');
|
||||
$entityManager->saveEntity($email);
|
||||
|
||||
$emailService = $this->getApplication()->getContainer()->get('serviceFactory')->create('Email');
|
||||
|
||||
$result = $emailService->findEntities([
|
||||
'textFilter' => 'name abc'
|
||||
]);
|
||||
$this->assertArrayHasKey('collection', $result);
|
||||
$this->assertEquals(1, count($result['collection']));
|
||||
|
||||
$result = $emailService->findEntities([
|
||||
'textFilter' => 'Improvements to our Privacy Policy'
|
||||
]);
|
||||
$this->assertArrayHasKey('collection', $result);
|
||||
$this->assertEquals(1, count($result['collection']));
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"collection": {
|
||||
"fullTextSearch": true,
|
||||
"sortBy": "createdAt",
|
||||
"asc": false,
|
||||
"textFilterFields": [
|
||||
"name",
|
||||
"emailAddress"
|
||||
]
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"collection": {
|
||||
"sortBy": "createdAt",
|
||||
"asc": false,
|
||||
"textFilterFields": [
|
||||
"name",
|
||||
"emailAddress"
|
||||
],
|
||||
"fullTextSearch": true
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"collection": {
|
||||
"sortBy": "dateSent",
|
||||
"asc": false,
|
||||
"textFilterFields": [
|
||||
"name",
|
||||
"bodyPlain",
|
||||
"body"
|
||||
],
|
||||
"fullTextSearch": true
|
||||
}
|
||||
}
|
||||
@@ -77,4 +77,29 @@ class EvaluatorTest extends \PHPUnit\Framework\TestCase
|
||||
$actual = $this->evaluator->process($expression);
|
||||
$this->assertTrue($actual);
|
||||
}
|
||||
|
||||
function testSummationOfMultipleIfThenElse()
|
||||
{
|
||||
$expression = "
|
||||
ifThenElse(
|
||||
true,
|
||||
(1 + 0 + 1) - 1 * 0.5,
|
||||
0
|
||||
)
|
||||
+
|
||||
ifThenElse(
|
||||
true,
|
||||
(1 - 0) * 0.5,
|
||||
0
|
||||
)
|
||||
+
|
||||
ifThenElse(
|
||||
true,
|
||||
(1 - 0) * 0.5,
|
||||
0
|
||||
)
|
||||
";
|
||||
$actual = $this->evaluator->process($expression);
|
||||
$this->assertEquals(2.5, $actual);
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,7 @@ class ParserTest extends \PHPUnit\Framework\TestCase
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$this->assertEquals($expected, $actual);
|
||||
|
||||
$expression = "!value * 10";
|
||||
|
||||
@@ -35,6 +35,7 @@ use Espo\Entities\Post;
|
||||
use Espo\Entities\Comment;
|
||||
use Espo\Entities\Tag;
|
||||
use Espo\Entities\Note;
|
||||
use Espo\Entities\Job;
|
||||
|
||||
require_once 'tests/unit/testData/DB/Entities.php';
|
||||
require_once 'tests/unit/testData/DB/MockPDO.php';
|
||||
@@ -348,6 +349,34 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
|
||||
$this->db->update($this->post);
|
||||
}
|
||||
|
||||
public function testUpdateArray1()
|
||||
{
|
||||
$query = "UPDATE `job` SET `array` = '[\"2\",\"1\"]' WHERE job.id = '1' AND job.deleted = '0'";
|
||||
|
||||
$this->mockQuery($query, true);
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->id = '1';
|
||||
$job->setFetched('array', ['1', '2']);
|
||||
$job->set('array', ['2', '1']);
|
||||
|
||||
$this->db->update($job);
|
||||
}
|
||||
|
||||
public function testUpdateArray2()
|
||||
{
|
||||
$query = "UPDATE `job` SET `array` = NULL WHERE job.id = '1' AND job.deleted = '0'";
|
||||
|
||||
$this->mockQuery($query, true);
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->id = '1';
|
||||
$job->setFetched('array', ['1', '2']);
|
||||
$job->set('array', null);
|
||||
|
||||
$this->db->update($job);
|
||||
}
|
||||
|
||||
public function testRemoveRelationHasMany()
|
||||
{
|
||||
$query = "UPDATE `comment` SET post_id = NULL WHERE comment.deleted = '0' AND comment.id = '100'";
|
||||
@@ -448,6 +477,5 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
use Espo\ORM\DB\MysqlMapper;
|
||||
use Espo\ORM\DB\Query\Mysql as Query;
|
||||
use Espo\ORM\EntityFactory;
|
||||
|
||||
|
||||
require_once 'tests/unit/testData/DB/Entities.php';
|
||||
|
||||
class EntityTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function testIsAttributeChanged()
|
||||
{
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('string', 'test');
|
||||
$this->assertFalse($job->isAttributeChanged('string'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('string', 'test');
|
||||
$job->set('string', 'hello');
|
||||
$this->assertTrue($job->isAttributeChanged('string'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->set('string', 'hello');
|
||||
$this->assertTrue($job->isAttributeChanged('string'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->set('string', null);
|
||||
$this->assertTrue($job->isAttributeChanged('string'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', ['1', '2']);
|
||||
$job->set('array', ['2', '1']);
|
||||
$this->assertTrue($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', ['1', '2']);
|
||||
$job->set('array', ['1', '2']);
|
||||
$this->assertFalse($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', ['1', '2']);
|
||||
$job->set('array', ['1', 2]);
|
||||
$this->assertTrue($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', [
|
||||
(object) ['1' => 'v1']
|
||||
]);
|
||||
$job->set('array', [
|
||||
(object) ['1' => 'v1']
|
||||
]);
|
||||
$this->assertFalse($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', [
|
||||
(object) ['k1' => 'v1']
|
||||
]);
|
||||
$job->set('array', [
|
||||
(object) ['k1' => 'v2']
|
||||
]);
|
||||
$this->assertTrue($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', [
|
||||
(object) ['k1' => 'v1']
|
||||
]);
|
||||
$job->set('array', [
|
||||
(object) ['k1' => 'v1', 'k2' => 'v2'],
|
||||
]);
|
||||
$this->assertTrue($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', ['1', '2']);
|
||||
$job->set('array', ['1', '2', '3']);
|
||||
$this->assertTrue($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->set('array', ['1', '2', '3']);
|
||||
$this->assertTrue($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->set('array', null);
|
||||
$this->assertTrue($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('array', null);
|
||||
$this->assertFalse($job->isAttributeChanged('array'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('arrayUnordered', ['1', '2']);
|
||||
$job->set('arrayUnordered', ['2', '1']);
|
||||
$this->assertFalse($job->isAttributeChanged('arrayUnordered'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('arrayUnordered', ['1', '2']);
|
||||
$job->set('arrayUnordered', ['1', '2']);
|
||||
$this->assertFalse($job->isAttributeChanged('arrayUnordered'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('arrayUnordered', ['1', '2']);
|
||||
$job->set('arrayUnordered', ['1', '2', '3']);
|
||||
$this->assertTrue($job->isAttributeChanged('arrayUnordered'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('arrayUnordered', ['1', '2']);
|
||||
$job->set('arrayUnordered', null);
|
||||
$this->assertTrue($job->isAttributeChanged('arrayUnordered'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('object', (object) ['1' => 'value-1']);
|
||||
$job->set('object', (object) ['1' => 'value-1']);
|
||||
$this->assertFalse($job->isAttributeChanged('object'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('object', (object) ['1' => 'value-1']);
|
||||
$job->set('object', ['1' => 'value-1']);
|
||||
$this->assertTrue($job->isAttributeChanged('object'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('object', (object) ['1' => '1']);
|
||||
$job->set('object', (object) ['1' => 1]);
|
||||
$this->assertTrue($job->isAttributeChanged('object'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('object', (object) [
|
||||
'k1' => (object) [
|
||||
'k11' => 'v1'
|
||||
]
|
||||
]);
|
||||
$job->set('object', (object) [
|
||||
'k1' => (object) [
|
||||
'k11' => 'v2'
|
||||
]
|
||||
]);
|
||||
$this->assertTrue($job->isAttributeChanged('object'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('object', (object) [
|
||||
'k1' => [
|
||||
'k11' => 'v1'
|
||||
]
|
||||
]);
|
||||
$job->set('object', (object) [
|
||||
'k1' => (object) [
|
||||
'k11' => 'v1'
|
||||
]
|
||||
]);
|
||||
$this->assertTrue($job->isAttributeChanged('object'));
|
||||
|
||||
$job = new \Espo\Entities\Job();
|
||||
$job->setFetched('object', [
|
||||
'k1' => [
|
||||
'k11' => 'v1'
|
||||
]
|
||||
]);
|
||||
$job->set('object', (object) [
|
||||
'k1' => (object) [
|
||||
'k11' => 'v1'
|
||||
]
|
||||
]);
|
||||
$this->assertTrue($job->isAttributeChanged('object'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -242,3 +242,32 @@ class Article extends TEntity
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class Job extends TEntity
|
||||
{
|
||||
public $fields = array(
|
||||
'id' => array(
|
||||
'type' => Entity::ID
|
||||
),
|
||||
'string' => array(
|
||||
'type' => Entity::VARCHAR,
|
||||
'len' => 50
|
||||
),
|
||||
'array' => array(
|
||||
'type' => Entity::JSON_ARRAY
|
||||
),
|
||||
'arrayUnordered' => array(
|
||||
'type' => Entity::JSON_ARRAY,
|
||||
'isUnordered' => true
|
||||
),
|
||||
'object' => array(
|
||||
'type' => Entity::JSON_OBJECT
|
||||
),
|
||||
'deleted' => array(
|
||||
'type' => Entity::BOOL,
|
||||
'default' => 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user