Compare commits

...

18 Commits

Author SHA1 Message Date
Yuri Kuznetsov 9a965bfba4 v 2020-10-05 15:27:48 +03:00
Yuri Kuznetsov 7349f37786 fix admin panel 2020-10-05 12:21:11 +03:00
Yuri Kuznetsov 3d03dfaef7 css fix 2020-10-03 19:01:19 +03:00
Yuri Kuznetsov 5bf88eaff1 categories tree improvements 2020-10-03 18:59:56 +03:00
Yuri Kuznetsov f3c4b2ba39 image preview improvements 2020-10-03 17:24:05 +03:00
Yuri Kuznetsov 92f6fb4588 settings tooltips 2020-10-03 12:27:54 +03:00
Yuri Kuznetsov 2d6d7fd618 list tree css fix 2020-10-02 14:57:26 +03:00
Yuri Kuznetsov 3a1d997d5a orm cleanup 2020-10-01 20:18:35 +03:00
Yuri Kuznetsov 5bbb259c95 orm fix order list 2020-10-01 20:16:32 +03:00
Yuri Kuznetsov b8e44767b3 upgrade real estate compatibility check 2020-10-01 16:27:46 +03:00
Yuri Kuznetsov 6b486beb5c fix autoload 2020-10-01 14:25:12 +03:00
Yuri Kuznetsov aab1265e4e data cache get safe contents 2020-10-01 14:08:03 +03:00
Yuri Kuznetsov 676181a4be fix ldap 2020-10-01 13:32:06 +03:00
Yuri Kuznetsov 839e5b7c74 data cache support onlt array and stdclass 2020-10-01 12:51:25 +03:00
Yuri Kuznetsov aff7003ceb cs fix 2020-10-01 12:01:51 +03:00
Yuri Kuznetsov 66aa60f303 file rename in loop for windows 2020-10-01 11:44:01 +03:00
Yuri Kuznetsov b196811cf9 fix notice 2020-10-01 10:24:36 +03:00
Yuri Kuznetsov 3fdddadd53 cs fix 2020-09-30 17:42:07 +03:00
28 changed files with 573 additions and 173 deletions
@@ -68,14 +68,14 @@ class LDAP extends Espo
Config $config,
EntityManager $entityManager,
PasswordHash $passwordHash,
Language $language,
Language $defaultLanguage,
ApplicationUser $applicationUser,
bool $isPortal = false
) {
$this->config = $config;
$this->entityManager = $entityManager;
$this->passwordHash = $passwordHash;
$this->language = $language;
$this->language = $defaultLanguage;
$this->applicationUser = $applicationUser;
$this->isPortal = $isPortal;
@@ -51,6 +51,8 @@ class ContainerConfiguration
public function getLoaderClassName(string $name) : ?string
{
$className = null;
try {
$className = $this->metadata->get(['app', 'containerServices', $name, 'loaderClassName']);
+6 -3
View File
@@ -33,6 +33,7 @@ use Espo\Core\{
Exceptions\Error,
Utils\Autoload\Loader,
Utils\DataCache,
Utils\File\Manager as FileManager,
};
use Exception;
@@ -52,14 +53,16 @@ class Autoload
protected $config;
protected $metadata;
protected $dataCache;
protected $fileManager;
protected $loader;
public function __construct(Config $config, Metadata $metadata, DataCache $dataCache, Loader $loader)
{
public function __construct(
Config $config, Metadata $metadata, DataCache $dataCache, FileManager $fileManager, Loader $loader
) {
$this->config = $config;
$this->metadata = $metadata;
$this->dataCache = $dataCache;
$this->fileManager = $fileManager;
$this->loader = $loader;
}
+19 -12
View File
@@ -63,35 +63,33 @@ class DataCache
*
* @throws Error if is not cached.
*
* @return ?array|StdClass|int|float|string
* @return ?array|StdClass
*/
public function get(string $key)
{
$cacheFile = $this->getCacheFile($key);
$result = $this->fileManager->getPhpContents($cacheFile);
$data = $this->fileManager->getPhpSafeContents($cacheFile);
if ($result === false) {
if ($data === false) {
throw new Error("Could not get '{$key}'.");
}
return $result;
if (! $this->checkDataIsValid($data)) {
throw new Error("Bad data fetched from cache by key '{$key}'.");
}
return $data;
}
/**
* Store in cache.
*
* @param ?array|StdClass|int|float|string $data
* @param ?array|StdClass $data
*/
public function store(string $key, $data)
{
if (
! is_array($data) &&
! $data instanceof StdClass &&
! is_int($data) &&
! is_float($data) &&
! is_int($data)
) {
if (! $this->checkDataIsValid($data)) {
throw new InvalidArgumentException("Bad cache data type.");
}
@@ -104,6 +102,15 @@ class DataCache
}
}
protected function checkDataIsValid($data)
{
$isInvalid =
! is_array($data) &&
! $data instanceof StdClass;
return ! $isInvalid;
}
protected function getCacheFile(string $key) : string
{
if (
+99 -42
View File
@@ -47,6 +47,14 @@ class Manager
protected $tmpDir = 'data/tmp';
const RENAME_RETRY_NUMBER = 10;
const RENAME_RETRY_INTERVAL = 0.1;
const GET_SAFE_CONTENTS_RETRY_NUMBER = 10;
const GET_SAFE_CONTENTS_RETRY_INTERVAL = 0.1;
public function __construct(?Config $config = null)
{
$params = null;
@@ -175,10 +183,10 @@ class Manager
}
/**
* Get PHP array from PHP file.
* Get data from PHP file.
*
* @param string|array $path
* @return array|object|bool
* @return mixed|bool
*/
public function getPhpContents($path)
{
@@ -193,6 +201,43 @@ class Manager
return false;
}
/**
* Get array or StdClass data from PHP file.
* For Windows: If a file is not yet written, it will wait until it's ready.
*
* @return array|StdClass
*/
public function getPhpSafeContents(string $path)
{
if (!file_exists($path)) {
throw new Error("Can't get contents from non-existing file '{$path}'.");
}
if (!strtolower(substr($path, -4)) == '.php') {
throw new Error("Only PHP file are allowed for getting contents.");
}
$counter = 0;
while ($counter < self::GET_SAFE_CONTENTS_RETRY_NUMBER) {
$data = include($path);
if (is_array($data) || $data instanceof StdClass) {
return $data;
}
if (stripos(\PHP_OS, 'WIN') !== 0) {
break;
}
usleep(self::GET_SAFE_CONTENTS_RETRY_INTERVAL * 1000000);
$counter ++;
}
throw new Error("Bad data stored in file '{$path}'.");
}
/**
* Write data to a file.
*
@@ -266,6 +311,10 @@ class Manager
$result = rename($tmpPath, $path);
if (!$result && stripos(\PHP_OS, 'WIN') === 0) {
$result = $this->renameInLoop($tmpPath, $path);
}
if ($this->isFile($tmpPath)) {
$this->removeFile($tmpPath);
}
@@ -273,6 +322,29 @@ class Manager
return $result;
}
protected function renameInLoop(string $source, string $destination) : bool
{
$counter = 0;
while ($counter < self::RENAME_RETRY_NUMBER) {
if (!$this->isWritable($destination)) {
break;
}
$result = rename($source, $destination);
if ($result !== false) {
return true;
}
usleep(self::RENAME_RETRY_INTERVAL * 1000000);
$counter ++;
}
return false;
}
/**
* Save PHP content to file.
*
@@ -288,8 +360,8 @@ class Manager
*
* @param string | array $path
* @param string $data
* @param integer $flags
* @param resource $context
* @param integer $flags
* @param resource $context
*
* @return bool
*/
@@ -377,8 +449,8 @@ class Manager
/**
* Unset some element of content data.
*
* @param string | array $path
* @param array | string $unsets
* @param string | array $path
* @param array | string $unsets
* @return bool
*/
public function unsetContents($path, $unsets, $isJSON = true)
@@ -412,8 +484,8 @@ class Manager
/**
* Concat paths
* @param string | array $paths Ex. array('pathPart1', 'pathPart2', 'pathPart3')
* Concat paths.
* @param string | array $paths Ex. array('pathPart1', 'pathPart2', 'pathPart3')
* @return string
*/
protected function concatPaths($paths)
@@ -432,11 +504,11 @@ class Manager
}
/**
* Create a new dir
* Create a new dir.
*
* @param string | array $path
* @param int $permission - ex. 0755
* @param bool $recursive
* @param string | array $path
* @param int $permission - ex. 0755
* @param bool $recursive
*
* @return bool
*/
@@ -487,11 +559,11 @@ class Manager
* Ex. $sourcePath = 'data/uploads/extensions/file.json',
* $destPath = 'data/uploads/backup', result will be data/uploads/backup/data/uploads/backup/file.json.
*
* @param string $sourcePath
* @param string $destPath
* @param boolean $recursively
* @param array $fileList - list of files that should be copied
* @param boolean $copyOnlyFiles - copy only files, instead of full path with directories,
* @param string $sourcePath
* @param string $destPath
* @param boolean $recursively
* @param array $fileList - list of files that should be copied
* @param boolean $copyOnlyFiles - copy only files, instead of full path with directories,
* Ex. $sourcePath = 'data/uploads/extensions/file.json',
* $destPath = 'data/uploads/backup', result will be 'data/uploads/backup/file.json'
* @return boolen
@@ -510,7 +582,6 @@ class Manager
$permissionDeniedList = [];
foreach ($fileList as $file) {
if ($copyOnlyFiles) {
$file = pathinfo($file, PATHINFO_BASENAME);
}
@@ -667,7 +738,6 @@ class Manager
*
* @param string $dirPath - directory path.
* @param bool $removeWithDir - if remove with directory.
*
* @return bool
*/
public function removeInDir($dirPath, $removeWithDir = false)
@@ -699,8 +769,8 @@ class Manager
/**
* Remove items (files or directories).
*
* @param string | array $items
* @param string $dirPath
* @param string | array $items
* @param string $dirPath
* @return boolean
*/
public function remove($items, $dirPath = null, $removeEmptyDirs = false)
@@ -774,8 +844,8 @@ class Manager
/**
* Check if $dirname is directory.
*
* @param string $dirname
* @param string $basePath
* @param string $dirname
* @param string $basePath
*
* @return boolean
*/
@@ -893,10 +963,10 @@ class Manager
}
/**
* Get parent dir name/path
* Get parent dir name/path.
*
* @param string $path
* @param boolean $isFullPath
* @param string $path
* @param boolean $isFullPath
* @return string
*/
public function getParentDirName($path, $isFullPath = true)
@@ -907,7 +977,6 @@ class Manager
/**
* Return content of PHP file.
*
* @param string $varName - name of variable which contains the content.
* @param array $content
*
* @return string | false
@@ -961,12 +1030,8 @@ class Manager
/**
* Check if $paths are writable. Permission denied list are defined in getLastPermissionDeniedList().
*
* @param array $paths
*
* @return boolean
*/
public function isWritableList(array $paths)
public function isWritableList(array $paths) : bool
{
$permissionDeniedList = [];
@@ -1002,12 +1067,8 @@ class Manager
/**
* Check if $path is writable.
*
* @param string |array $path
*
* @return boolean
*/
public function isWritable($path)
public function isWritable(string $path) : bool
{
$existFile = $this->getExistsPath($path);
@@ -1016,12 +1077,8 @@ class Manager
/**
* Check if $path is writable.
*
* @param string | array $path
*
* @return boolean
*/
public function isReadable($path)
public function isReadable(string $path) : bool
{
$existFile = $this->getExistsPath($path);
+34 -9
View File
@@ -34,6 +34,8 @@ use Countable;
use ArrayAccess;
use SeekableIterator;
use RuntimeException;
use OutOfBoundsException;
use InvalidArgumentException;
/**
* A standard collection of entities. It allocates a memory for all entities.
@@ -80,7 +82,9 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
{
do {
$this->position ++;
$next = false;
if (!$this->valid() && $this->position <= $this->getLastValidKey()) {
$next = true;
}
@@ -90,13 +94,17 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
private function getLastValidKey()
{
$keys = array_keys($this->dataList);
$i = end($keys);
while ($i > 0) {
if (isset($this->dataList[$i])) {
break;
}
$i--;
}
return $i;
}
@@ -115,20 +123,23 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if (!isset($this->dataList[$offset])) {
return null;
}
return $this->getEntityByOffset($offset);
}
public function offsetSet($offset, $value)
{
if (!($value instanceof Entity)) {
throw new \InvalidArgumentException('Only Entity is allowed to be added to EntityCollection.');
throw new InvalidArgumentException('Only Entity is allowed to be added to EntityCollection.');
}
if (is_null($offset)) {
$this->dataList[] = $value;
} else {
$this->dataList[$offset] = $value;
return;
}
$this->dataList[$offset] = $value;
}
public function offsetUnset($offset)
@@ -144,8 +155,9 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
public function seek($offset)
{
$this->position = $offset;
if (!$this->valid()) {
throw new \OutOfBoundsException("Invalid seek offset ($offset).");
throw new OutOfBoundsException("Invalid seek offset ($offset).");
}
}
@@ -160,13 +172,15 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if ($value instanceof Entity) {
return $value;
} else if (is_array($value)) {
$this->dataList[$offset] = $this->buildEntityFromArray($value);
} else {
return null;
}
return $this->dataList[$offset];
if (is_array($value)) {
$this->dataList[$offset] = $this->buildEntityFromArray($value);
return $this->dataList[$offset];
}
return null;
}
protected function buildEntityFromArray(array $dataArray)
@@ -179,9 +193,11 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if ($entity) {
$entity->set($dataArray);
if ($this->isFetched) {
$entity->setAsFetched();
}
return $entity;
}
}
@@ -213,6 +229,7 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
public function merge(EntityCollection $collection)
{
$newData = $this->dataList;
$incomingDataList = $collection->getDataList();
foreach ($incomingDataList as $v) {
@@ -230,12 +247,14 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
if ($this->indexOf($value) !== false) {
return true;
}
return false;
}
public function indexOf($value)
{
$index = 0;
if (is_array($value)) {
foreach ($this->dataList as $v) {
if (is_array($v)) {
@@ -247,6 +266,7 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
return $index;
}
}
$index ++;
}
} else if ($value instanceof Entity) {
@@ -260,9 +280,11 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
return $index;
}
}
$index ++;
}
}
return false;
}
@@ -272,14 +294,17 @@ class EntityCollection implements Collection, Iterator, Countable, ArrayAccess,
public function toArray(bool $itemsAsObjects = false) : array
{
$arr = [];
foreach ($this as $entity) {
if ($itemsAsObjects) {
$item = $entity->getValueMap();
} else {
$item = $entity->toArray();
}
$arr[] = $item;
}
return $arr;
}
@@ -1296,7 +1296,7 @@ abstract class BaseQueryComposer implements QueryComposer
$modifiedOrder[] = $newItem;
}
$part = $this->getOrderExpressionPart($entity, $modifiedOrder, null, false, $params, true);
$part = $this->getOrderExpressionPart($entity, $modifiedOrder, null, $params, true);
return $part;
}
@@ -1446,21 +1446,32 @@ abstract class BaseQueryComposer implements QueryComposer
}
if (is_string($value)) {
return self::getAllAttributesFromComplexExpression($value);
$value = [[$value]];
}
if (!is_array($value)) {
return [];
}
$list = [];
if (is_array($value)) {
foreach ($value as $item) {
if (!is_array($item) || !isset($item[0])) continue;
$list = array_merge(
$list,
self::getAllAttributesFromComplexExpression($item[0])
);
foreach ($value as $item) {
if (!is_array($item) || !isset($item[0])) {
continue;
}
$expression = $item[0];
if (strpos($expression, 'LIST:') === 0 && substr_count($expression, ':') === 2) {
$expression = explode(':', $expression)[1];
}
$attributeList = self::getAllAttributesFromComplexExpression($expression);
$list = array_merge(
$list,
$attributeList
);
}
return $list;
@@ -1828,7 +1839,6 @@ abstract class BaseQueryComposer implements QueryComposer
Entity $entity,
$orderBy = null,
$order = null,
bool $useColumnAlias = false,
?array &$params = null,
bool $noCustom = false
) : ?string {
@@ -1849,7 +1859,7 @@ abstract class BaseQueryComposer implements QueryComposer
}
$arr[] = $this->getOrderExpressionPart(
$entity, $orderByInternal, $orderInternal, $useColumnAlias, $params, $noCustom
$entity, $orderByInternal, $orderInternal, $params, $noCustom
);
}
}
@@ -1860,13 +1870,8 @@ abstract class BaseQueryComposer implements QueryComposer
if (strpos($orderBy, 'LIST:') === 0) {
list($l, $field, $list) = explode(':', $orderBy);
if ($useColumnAlias) {
$fieldPath = $this->quoteIdentifier(
$this->sanitizeSelectAlias($field)
);
} else {
$fieldPath = $this->getAttributePathForOrderBy($entity, $field, $params);
}
$fieldPath = $this->getAttributePathForOrderBy($entity, $field, $params);
$listQuoted = [];
$list = array_reverse(explode(',', $list));
@@ -1905,13 +1910,7 @@ abstract class BaseQueryComposer implements QueryComposer
return $this->getAttributeOrderSql($entity, $orderBy, $params, $order);
}
if ($useColumnAlias) {
$fieldPath = $this->quoteIdentifier(
$this->sanitizeSelectAlias($orderBy)
);
} else {
$fieldPath = $this->getAttributePathForOrderBy($entity, $orderBy, $params);
}
$fieldPath = $this->getAttributePathForOrderBy($entity, $orderBy, $params);
if (!$fieldPath) {
throw new LogicException("Could not handle 'order' for '".$entity->getEntityType()."'.");
@@ -1922,7 +1921,7 @@ abstract class BaseQueryComposer implements QueryComposer
protected function getOrderPart(Entity $entity, $orderBy = null, $order = null, &$params = null) : ?string
{
return $this->getOrderExpressionPart($entity, $orderBy, $order, false, $params);
return $this->getOrderExpressionPart($entity, $orderBy, $order, $params);
}
protected function getAttributePathForOrderBy(Entity $entity, string $orderBy, array $params) : ?string
@@ -162,6 +162,7 @@
"max": "Max",
"translation": "Translation",
"previewSize": "Preview Size",
"listPreviewSize": "Preview Size in List View",
"noEmptyString": "Empty string value is not allowed",
"defaultType": "Default Type",
"seeMoreDisabled": "Disable Text Cut",
@@ -289,6 +290,7 @@
},
"options": {
"previewSize": {
"": "Default",
"x-small": "X-Small",
"small": "Small",
"medium": "Medium",
@@ -162,6 +162,19 @@
}
},
"tooltips": {
"displayListViewRecordCount": "A total number of records will be shown on the list view.",
"currencyList": "What currencies will be available in the system.",
"activitiesEntityList": "What records will be available in the Activities panel.",
"historyEntityList": "What records will be available in the History panel.",
"calendarEntityList": "What records will be available in the Calendar.",
"addressStateList": "State suggestions for address fields.",
"addressCityList": "City suggestions for address fields.",
"addressCountryList": "Country suggestions for address fields.",
"exportDisabled": "Users won't be able to export records. Only admin will be allowed.",
"globalSearchEntityList": "What records can be searched with Global Search.",
"siteUrl": "A URL of this EspoCRM instance. You need to change it if you move to another domain.",
"useCache": "Not recommended to disable, unless for development purpose.",
"useWebSocket": "WebSocket enables two-way interactive communication between a server and a browser. Requires setting up the WebSocket daemon on your server. Check the documentation for more info.",
"passwordRecoveryForInternalUsersDisabled": "Only portal users will be able to recover password.",
"passwordRecoveryNoExposure": "It won't be possible to determine whether a specific email address is registered in the system.",
"emailAddressLookupEntityTypeList": "For email address autocomplete.",
@@ -2,7 +2,8 @@
"fields": {
"useCache": {
"type": "bool",
"default": true
"default": true,
"tooltip": true
},
"recordsPerPage": {
"type": "int",
@@ -61,7 +62,8 @@
"type": "multiEnum",
"default": ["USD", "EUR"],
"required": true,
"view": "views/settings/fields/currency-list"
"view": "views/settings/fields/currency-list",
"tooltip": true
},
"defaultCurrency": {
"type": "enum",
@@ -143,7 +145,8 @@
"globalSearchEntityList": {
"type": "multiEnum",
"translation": "Global.scopeNames",
"view": "views/settings/fields/global-search-entity-list"
"view": "views/settings/fields/global-search-entity-list",
"tooltip": true
},
"exportDelimiter": {
"type": "varchar",
@@ -326,7 +329,8 @@
},
"exportDisabled": {
"type": "bool",
"default": false
"default": false,
"tooltip": true
},
"emailNotificationsDelay": {
"type": "int",
@@ -399,7 +403,8 @@
"type": "varchar"
},
"displayListViewRecordCount": {
"type": "bool"
"type": "bool",
"tooltip": true
},
"userThemesDisabled": {
"type": "bool",
@@ -461,7 +466,8 @@
"disabled": true
},
"siteUrl": {
"type": "varchar"
"type": "varchar",
"tooltip": true
},
"applicationName": {
"type": "varchar"
@@ -503,15 +509,18 @@
},
"calendarEntityList": {
"type": "multiEnum",
"view": "views/settings/fields/calendar-entity-list"
"view": "views/settings/fields/calendar-entity-list",
"tooltip": true
},
"activitiesEntityList": {
"type": "multiEnum",
"view": "views/settings/fields/activities-entity-list"
"view": "views/settings/fields/activities-entity-list",
"tooltip": true
},
"historyEntityList": {
"type": "multiEnum",
"view": "views/settings/fields/history-entity-list"
"view": "views/settings/fields/history-entity-list",
"tooltip": true
},
"busyRangesEntityList": {
"type": "multiEnum",
@@ -578,13 +587,16 @@
"tooltip": true
},
"addressCountryList": {
"type": "multiEnum"
"type": "multiEnum",
"tooltip": true
},
"addressCityList": {
"type": "multiEnum"
"type": "multiEnum",
"tooltip": true
},
"addressStateList": {
"type": "multiEnum"
"type": "multiEnum",
"tooltip": true
},
"jobRunInParallel": {
"type": "bool",
@@ -621,7 +633,8 @@
"tooltip": true
},
"useWebSocket": {
"type": "bool"
"type": "bool",
"tooltip": true
}
}
}
@@ -11,6 +11,14 @@
"default":"small",
"options": ["x-small", "small", "medium", "large"]
},
{
"name": "listPreviewSize",
"type": "enum",
"default": "small",
"options": ["", "small", "medium"],
"translation": "Admin.options.previewSize",
"default": ""
},
{
"name": "maxFileSize",
"type": "float",
+26 -7
View File
@@ -1,9 +1,28 @@
<div class="cell">
<a href="javascript:"
class="action{{#unless showFold}} hidden{{/unless}} small"
data-action="fold"
data-id="{{model.id}}"><span class="fas fa-chevron-down"></span></a>
<div class="cell">
<a href="javascript:" class="action{{#unless showFold}} hidden{{/unless}} small" data-action="fold" data-id="{{model.id}}"><span class="fas fa-chevron-down"></span></a>
<a href="javascript:" class="action{{#unless showUnfold}} hidden{{/unless}} small" data-action="unfold" data-id="{{model.id}}"><span class="fas fa-chevron-right"></span></a>
<span data-name="white-space" data-id="{{model.id}}" class="empty-icon{{#unless isEnd}} hidden{{/unless}}">&nbsp;</span>
<a href="javascript:"
class="action{{#unless showUnfold}} hidden{{/unless}} small"
data-action="unfold"
data-id="{{model.id}}"><span class="fas fa-chevron-right"></span></a>
<a href="#{{model.name}}/view/{{model.id}}" class="link{{#if isSelected}} text-bold{{/if}}" data-id="{{model.id}}">{{name}}</a>
</div>
<div class="children{{#unless isUnfolded}} hidden{{/unless}}">{{{children}}}</div>
<span data-name="white-space" data-id="{{model.id}}" class="empty-icon{{#unless isEnd}} hidden{{/unless}}">&nbsp;</span>
<a href="#{{model.name}}/view/{{model.id}}"
class="link{{#if isSelected}} text-bold{{/if}}" data-id="{{model.id}}"
>{{name}}</a>
{{#unless readOnly}}
<a href="javascript:"
class="action small remove-link hidden"
data-action="remove" data-id="{{model.id}}"
title="{{translate 'Remove'}}"
>
<span class="fas fa-times"></span>
</a>
{{/unless}}
</div>
<div class="children{{#unless isUnfolded}} hidden{{/unless}}">{{{children}}}</div>
+1 -1
View File
@@ -14,7 +14,7 @@
{{/unless}}
{{/if}}
{{/unless}}
<div class="list list-expanded">
<div class="list list-expanded list-tree">
{{#if showRootMenu}}
<div class="btn-group pull-right">
<a href="javascript:" class="small dropdown-toggle btn-link" data-toggle="dropdown">
+1
View File
@@ -84,6 +84,7 @@ define('views/admin/index', 'view', function (Dep) {
panelItem.items.forEach(function (item) {
item.label = this.translate(item.label, 'labels', 'Admin');
panelItem.itemList.push(item);
item.keywords = [];
}, this);
}
+80 -21
View File
@@ -34,6 +34,8 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
listTemplate: 'fields/file/list',
listLinkTemplate: 'fields/file/list',
detailTemplate: 'fields/file/detail',
editTemplate: 'fields/file/edit',
@@ -134,7 +136,9 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
if (this.isUploading) {
var $target = this.$el.find('.gray-box');
var msg = this.translate('fieldIsUploading', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg, $target);
return true;
}
},
@@ -150,12 +154,18 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
var sourceDefs = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs']) || {};
this.sourceList = Espo.Utils.clone(this.params.sourceList || []).filter(function (item) {
if (!(item in sourceDefs)) return true;
if (!(item in sourceDefs)) {
return true;
}
var defs = sourceDefs[item];
if (defs.configCheck) {
var configCheck = defs.configCheck;
if (configCheck) {
var arr = configCheck.split('.');
if (this.getConfig().getByPath(arr)) {
return true;
}
@@ -193,6 +203,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
var name = this.model.get(this.nameName);
var type = this.model.get(this.typeName) || this.defaultType;
var id = this.model.get(this.idName);
if (id) {
this.addAttachmentBox(name, type, id);
}
@@ -227,6 +238,7 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
if (this.previewSize === 'large') {
this.handleResize();
this.resizeIsBeingListened = true;
$(window).on('resize.' + this.cid, function () {
this.handleResize();
}.bind(this));
@@ -241,11 +253,36 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
getDetailPreview: function (name, type, id) {
name = Handlebars.Utils.escapeExpression(name);
var preview = name;
if (~this.previewTypeList.indexOf(type)) {
preview = '<a data-action="showImagePreview" data-id="' + id + '" href="' + this.getImageUrl(id) + '"><img src="'+this.getBasePath()+'?entryPoint=image&size='+this.previewSize+'&id=' + id + '" class="image-preview"></a>';
if (!~this.previewTypeList.indexOf(type)) {
return name;
}
var previewSize = this.previewSize;
if (this.isListMode()) {
previewSize = 'small';
if (this.params.listPreviewSize) {
previewSize = this.params.listPreviewSize;
}
}
var src = this.getBasePath() + '?entryPoint=image&size=' + previewSize + '&id=' + id;
var img = '<img src="' + src + '" class="image-preview">';
if (this.mode === 'listLink') {
var link = '#' + this.model.entityType + '/view/' + this.model.id;
return '<a href="'+link+'">' + img + '</a>';
}
var preview = '' +
'<a data-action="showImagePreview" data-id="' + id + '" href="' + this.getImageUrl(id) + '">' +
img +
'</a>';
return preview;
},
@@ -261,50 +298,68 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
},
getValueForDisplay: function () {
if (this.mode == 'detail' || this.mode == 'list') {
var name = this.model.get(this.nameName);
var type = this.model.get(this.typeName) || this.defaultType;
var id = this.model.get(this.idName);
if (!id) {
return false;
}
var string = '';
if (this.showPreview && ~this.previewTypeList.indexOf(type)) {
string = '<div class="attachment-preview">' + this.getDetailPreview(name, type, id) + '</div>';
} else {
string = '<span class="fas fa-paperclip text-soft small"></span> <a href="'+ this.getDownloadUrl(id) +'" target="_BLANK">' + Handlebars.Utils.escapeExpression(name) + '</a>';
}
return string;
if (! (this.isDetailMode() || this.isListMode())) {
return '';
}
var name = this.model.get(this.nameName);
var type = this.model.get(this.typeName) || this.defaultType;
var id = this.model.get(this.idName);
if (!id) {
return false;
}
if (this.showPreview && ~this.previewTypeList.indexOf(type)) {
var classNamePart = '';
if (this.isListMode() && this.params.listPreviewSize) {
classNamePart += ' no-shrink';
}
return'<div class="attachment-preview'+classNamePart+'">' +
this.getDetailPreview(name, type, id) +
'</div>';
}
return '<span class="fas fa-paperclip text-soft small"></span> ' +
'<a href="'+ this.getDownloadUrl(id) +'" target="_BLANK">' +
Handlebars.Utils.escapeExpression(name) +
'</a>';
},
getImageUrl: function (id, size) {
var url = this.getBasePath() + '?entryPoint=image&id=' + id;
if (size) {
url += '&size=' + size;
}
if (this.getUser().get('portalId')) {
url += '&portalId=' + this.getUser().get('portalId');
}
return url;
},
getDownloadUrl: function (id) {
var url = this.getBasePath() + '?entryPoint=download&id=' + id;
if (this.getUser().get('portalId')) {
url += '&portalId=' + this.getUser().get('portalId');
}
return url;
},
deleteAttachment: function () {
var id = this.model.get(this.idName);
var o = {};
o[this.idName] = null;
o[this.nameName] = null;
this.model.set(o);
this.$attachment.empty();
@@ -322,8 +377,10 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
setAttachment: function (attachment) {
var arr = _.clone(this.model.get(this.idsName));
var o = {};
o[this.idName] = attachment.id;
o[this.nameName] = attachment.get('name');
this.model.set(o);
},
@@ -343,11 +400,13 @@ define('views/fields/file', 'views/fields/link', function (Dep) {
exceedsMaxFileSize = true;
}
}
if (exceedsMaxFileSize) {
var msg = this.translate('fieldMaxFileSizeError', 'messages')
.replace('{field}', this.getLabelText())
.replace('{max}', maxFileSize);
this.showValidationMessage(msg, '.attachment-button label');
return;
}
+2 -2
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/fields/image', 'views/fields/file', function (Dep) {
define('views/fields/image', 'views/fields/file', function (Dep) {
return Dep.extend({
@@ -38,7 +38,7 @@ Espo.define('views/fields/image', 'views/fields/file', function (Dep) {
defaultType: 'image/jpeg',
previewSize: 'small'
previewSize: 'small',
});
});
+1 -2
View File
@@ -38,8 +38,7 @@ define('views/list-tree', 'views/list', function (Dep) {
getRecordViewName: function () {
return this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.listTree') || 'views/record/list-tree';
}
},
});
});
+2 -2
View File
@@ -366,14 +366,14 @@ define('views/list-with-categories', 'views/list', function (Dep) {
collection: collection,
el: this.options.el + ' .categories-container',
selectable: true,
createDisabled: true,
showRoot: true,
rootName: this.translate(this.scope, 'scopeNamesPlural'),
buttonsDisabled: true,
checkboxes: false,
showEditLink: this.getAcl().check(this.categoryScope, 'edit'),
isExpanded: this.isExpanded,
hasExpandedToggler: this.hasExpandedToggler
hasExpandedToggler: this.hasExpandedToggler,
readOnly: true,
}, function (view) {
if (this.currentCategoryId) {
view.setSelected(this.currentCategoryId);
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/modals/select-category-tree-records', 'views/modals/select-records', function (Dep) {
define('views/modals/select-category-tree-records', 'views/modals/select-records', function (Dep) {
return Dep.extend({
@@ -113,13 +113,13 @@ Espo.define('views/modals/select-category-tree-records', 'views/modals/select-re
this.createView('list', viewName, {
collection: collection,
el: this.containerSelector + ' .list-container',
createDisabled: true,
readOnly: true,
selectable: true,
checkboxes: this.multiple,
massActionsDisabled: true,
searchManager: searchManager,
checkAllResultDisabled: true,
buttonsDisabled: true
buttonsDisabled: true,
}, function (list) {
list.once('select', function (model) {
this.trigger('select', model);
@@ -136,4 +136,3 @@ Espo.define('views/modals/select-category-tree-records', 'views/modals/select-re
});
});
@@ -55,8 +55,6 @@ define('views/modals/select-records-with-categories', ['views/modals/select-reco
!this.getAcl().checkScope(this.categoryScope);
Dep.prototype.setup.call(this);
},
loadList: function () {
@@ -77,12 +75,12 @@ define('views/modals/select-records-with-categories', ['views/modals/select-reco
collection: collection,
el: this.options.el + ' .categories-container',
selectable: true,
createDisabled: true,
readOnly: true,
showRoot: true,
rootName: this.translate(this.scope, 'scopeNamesPlural'),
buttonsDisabled: true,
checkboxes: false,
isExpanded: this.isExpanded
isExpanded: this.isExpanded,
}, function (view) {
if (this.isRendered()) {
view.render();
+105 -4
View File
@@ -45,7 +45,8 @@ define('views/record/list-tree-item', 'view', function (Dep) {
showFold: this.isUnfolded && !this.isEnd,
showUnfold: !this.isUnfolded && !this.isEnd,
isEnd: this.isEnd,
isSelected: this.isSelected
isSelected: this.isSelected,
readOnly: this.readOnly,
};
},
@@ -57,6 +58,10 @@ define('views/record/list-tree-item', 'view', function (Dep) {
'click [data-action="fold"]': function (e) {
this.fold();
e.stopPropagation();
},
'click [data-action="remove"]': function (e) {
this.actionRemove();
e.stopPropagation();
}
},
@@ -67,10 +72,13 @@ define('views/record/list-tree-item', 'view', function (Dep) {
var path = this.selectedData.path;
var names = this.selectedData.names;
path.length = 0;
var view = this;
while (1) {
path.unshift(view.model.id);
names[view.model.id] = view.model.get('name');
if (view.getParentView().level) {
view = view.getParentView().getParentView();
} else {
@@ -83,16 +91,27 @@ define('views/record/list-tree-item', 'view', function (Dep) {
if ('level' in this.options) {
this.level = this.options.level;
}
if ('isSelected' in this.options) {
this.isSelected = this.options.isSelected;
}
if ('selectedData' in this.options) {
this.selectedData = this.options.selectedData;
}
this.readOnly = this.options.readOnly;
if ('createDisabled' in this.options) {
this.createDisabled = this.options.createDisabled;
}
if (this.readOnly) {
this.createDisabled = true;
}
this.rootView = this.options.rootView;
this.scope = this.model.name;
this.isUnfolded = false;
@@ -123,23 +142,29 @@ define('views/record/list-tree-item', 'view', function (Dep) {
createChildren: function () {
var childCollection = this.model.get('childCollection');
var callback = null;
if (this.isRendered()) {
callback = function (view) {
this.listenToOnce(view, 'after:render', function () {
this.trigger('children-created');
}, this);
view.render();
}.bind(this);
}
this.createView('children', this.listViewName, {
collection: childCollection,
el: this.options.el + ' > .children',
createDisabled: this.options.createDisabled,
readOnly: this.options.readOnly,
level: this.level + 1,
selectedData: this.selectedData,
model: this.model,
selectable: this.options.selectable
selectable: this.options.selectable,
rootView: this.rootView,
}, callback);
},
@@ -148,14 +173,22 @@ define('views/record/list-tree-item', 'view', function (Dep) {
parentId: this.model.id
}).then(function (idList) {
var childrenView = this.getView('children');
idList.forEach(function (id) {
var model = this.model.get('childCollection').get(id);
if (model) {
model.isEnd = true;
}
var itemView = childrenView.getView(id);
if (!itemView) return;
if (!itemView) {
return;
}
itemView.isEnd = true;
itemView.afterIsEnd();
}, this);
@@ -167,6 +200,7 @@ define('views/record/list-tree-item', 'view', function (Dep) {
if (this.createDisabled) {
this.once('children-created', function () {
var childrenView = this.getView('children');
if (!this.model.lastAreChecked) {
this.checkLastChildren();
}
@@ -174,10 +208,15 @@ define('views/record/list-tree-item', 'view', function (Dep) {
}
var childCollection = this.model.get('childCollection');
if (childCollection !== null) {
this.createChildren();
this.isUnfolded = true;
this.afterUnfold();
this.trigger('after:unfold');
} else {
this.getCollectionFactory().create(this.scope, function (collection) {
collection.url = this.collection.url;
@@ -185,19 +224,27 @@ define('views/record/list-tree-item', 'view', function (Dep) {
collection.maxDepth = 1;
Espo.Ui.notify(this.translate('loading', 'messages'));
this.listenToOnce(collection, 'sync', function () {
this.notify(false);
this.model.set('childCollection', collection);
this.createChildren();
this.isUnfolded = true;
if (collection.length || !this.createDisabled) {
this.afterUnfold();
this.trigger('after:unfold');
} else {
this.isEnd = true;
this.afterIsEnd();
}
}, this);
collection.fetch();
}, this);
}
@@ -205,7 +252,9 @@ define('views/record/list-tree-item', 'view', function (Dep) {
fold: function () {
this.clearView('children');
this.isUnfolded = false;
this.afterFold();
},
@@ -215,9 +264,22 @@ define('views/record/list-tree-item', 'view', function (Dep) {
} else {
this.afterFold();
}
if (this.isEnd) {
this.afterIsEnd();
}
if (!this.readOnly) {
var $remove = this.$el.find('> .cell [data-action="remove"]');
this.$el.find('> .cell').on('mouseenter', function ($el) {
$remove.removeClass('hidden');
});
this.$el.find('> .cell').on('mouseleave', function ($el) {
$remove.addClass('hidden');
});
}
},
afterFold: function () {
@@ -237,7 +299,46 @@ define('views/record/list-tree-item', 'view', function (Dep) {
this.$el.find('a[data-action="fold"][data-id="'+this.model.id+'"]').addClass('hidden');
this.$el.find('span[data-name="white-space"][data-id="'+this.model.id+'"]').removeClass('hidden');
this.$el.find(' > .children').addClass('hidden');
}
},
getCurrentPath: function () {
var pointer = this;
var path = [];
while (true) {
path.unshift(pointer.model.id);
if (pointer.getParentView() === this.rootView) {
break;
}
pointer = pointer.getParentView().getParentView();
}
return path;
},
actionRemove: function () {
this.confirm({
message: this.translate('removeRecordConfirmation', 'messages', this.scope),
confirmText: this.translate('Remove'),
}, function () {
this.model
.destroy(
{
wait: true,
}
)
.then(
function () {
this.remove();
}
.bind(this)
);
}.bind(this));
},
});
});
+48 -7
View File
@@ -64,8 +64,11 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
data: function () {
var data = Dep.prototype.data.call(this);
data.createDisabled = this.createDisabled;
data.showRoot = this.showRoot;
if (data.showRoot) {
data.rootName = this.rootName || this.translate('Root');
}
@@ -96,12 +99,15 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
this.selectable = this.options.selectable;
}
this.createDisabled = this.options.createDisabled || this.createDisabled;
this.readOnly = this.options.readOnly;
this.createDisabled = this.readOnly || this.options.createDisabled || this.createDisabled;
this.isExpanded = this.options.isExpanded;
if ('showRoot' in this.options) {
this.showRoot = this.options.showRoot;
if ('rootName' in this.options) {
this.rootName = this.options.rootName;
}
@@ -114,16 +120,21 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
if ('level' in this.options) {
this.level = this.options.level;
}
this.rootView = this.options.rootView || this;
if (this.level == 0) {
this.selectedData = {
id: null,
path: [],
names: {}
names: {},
};
}
if ('selectedData' in this.options) {
this.selectedData = this.options.selectedData;
}
Dep.prototype.setup.call(this);
if (this.selectable) {
@@ -139,6 +150,7 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
o.selectedData = this.selectedData;
}
}
if (this.level > 0) {
this.getParentView().trigger('select', o);
}
@@ -152,6 +164,7 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
} else {
this.selectedData.id = id;
}
this.rowList.forEach(function (key) {
var view = this.getView(key);
@@ -160,6 +173,7 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
} else {
view.isSelected = false;
}
if (view.hasView('children')) {
view.getView('children').setSelected(id);
}
@@ -176,25 +190,32 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
var modelList = this.collection.models;
var count = modelList.length;
var built = 0;
modelList.forEach(function (model, i) {
var key = model.id;
this.rowList.push(key);
this.createView(key, this.itemViewName, {
model: model,
collection: this.collection,
el: this.options.el + ' ' + this.getRowSelector(model.id),
createDisabled: this.createDisabled,
readOnly: this.readOnly,
level: this.level,
isSelected: model.id == this.selectedData.id,
selectedData: this.selectedData,
selectable: this.selectable,
setViewBeforeCallback: this.options.skipBuildRows && !this.isRendered()
setViewBeforeCallback: this.options.skipBuildRows && !this.isRendered(),
rootView: this.rootView,
}, function () {
built++;
if (built == count) {
if (typeof callback == 'function') {
callback();
}
this.wait(false);
};
}.bind(this));
@@ -224,7 +245,16 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
var attributes = this.getCreateAttributes();
attributes.order = this.collection.length + 1;
var maxOrder = 0;
this.collection.models.forEach(function (m) {
if (m.get('order') > maxOrder) {
maxOrder = m.get('order');
}
}, this);
attributes.order = maxOrder + 1;
attributes.parentId = null;
attributes.parentName = null;
@@ -235,17 +265,22 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
var scope = this.collection.name;
var viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.edit') || 'Modals.Edit';
var viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.edit') || 'views/modals/edit';
this.createView('quickCreate', viewName, {
scope: scope,
attributes: attributes
attributes: attributes,
}, function (view) {
view.render();
this.listenToOnce(view, 'after:save', function (model) {
view.close();
model.set('childCollection', this.collection.createSeed());
if (model.get('parentId') !== attributes.parentId) {
var v = this;
while (1) {
if (v.level) {
v = v.getParentView().getParentView();
@@ -253,10 +288,14 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
break;
}
}
v.collection.fetch();
return;
}
this.collection.push(model);
this.buildRows(function () {
this.render();
}.bind(this));
@@ -266,12 +305,14 @@ define('views/record/list-tree', 'views/record/list', function (Dep) {
actionSelectRoot: function () {
this.trigger('select', {id: null});
if (this.selectable) {
this.$el.find('a.link').removeClass('text-bold');
this.$el.find('a.link[data-action="selectRoot"]').addClass('text-bold');
this.setSelected(null);
}
}
},
});
});
+15
View File
@@ -1053,6 +1053,13 @@ section {
margin-right: -@container-padding;
}
#main > .list-container > .list-tree > ul,
.modal-body > .list-container > .list-tree > ul {
padding-top: @table-cell-padding;
padding-bottom: @table-cell-padding;
}
@media screen and (min-width: @screen-sm-min) {
#main > .list-container > .list {
@@ -1540,6 +1547,9 @@ label.attach-file-label {
.list-row > .cell > .attachment-preview {
margin-bottom: -@table-cell-padding;
margin-top: -@table-cell-padding;
}
.list-row > .cell > .attachment-preview:not(.no-shrink) {
max-height: @line-height-computed + @table-cell-padding + @table-cell-padding;
a {
@@ -2121,6 +2131,11 @@ table.table td.cell .complex-text {
.list-group-tree > li .cell {
padding: 3px 0;
.remove-link {
margin-left: 4px;
margin-right: 4px;
}
}
.categories-container .root-item {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta2",
"version": "6.0.0-beta3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta2",
"version": "6.0.0-beta3",
"description": "",
"main": "index.php",
"repository": {
+5 -5
View File
@@ -72,17 +72,17 @@ class DataCacheTest extends \PHPUnit\Framework\TestCase
$this->assertFalse($result);
}
public function testGetData()
public function testGetDataInt()
{
$this->expectException(Error::class);
$this->fileManager
->expects($this->once())
->method('getPhpContents')
->method('getPhpSafeContents')
->with('data/cache/application/autoload.php')
->willReturn(1);
$result = $this->dataCache->get('autoload');
$this->assertEquals(1, $result);
}
public function testGetError()
@@ -91,7 +91,7 @@ class DataCacheTest extends \PHPUnit\Framework\TestCase
$this->fileManager
->expects($this->once())
->method('getPhpContents')
->method('getPhpSafeContents')
->with('data/cache/application/autoload.php')
->willReturn(false);
+23 -3
View File
@@ -937,9 +937,9 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase
$sql = $this->query->compose(Select::fromRaw([
'from' => 'Comment',
'select' => array('COUNT:id', 'post.name'),
'leftJoins' => array('post'),
'groupBy' => array('post.name'),
'select' => ['COUNT:id', 'post.name'],
'leftJoins' => ['post'],
'groupBy' => ['post.name'],
'orderBy' => 'LIST:post.name:Test,Hello',
]));
@@ -972,6 +972,26 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($expectedSql, $sql);
}
public function testOrderByList()
{
$sql = $this->query->compose(Select::fromRaw([
'from' => 'Comment',
'select' => ['id'],
'leftJoins' => ['post'],
'groupBy' => ['post.name'],
'orderBy' => 'LIST:post.name:Test,Hello',
'distinct' => true,
]));
$expectedSql =
"SELECT comment.id AS `id`, post.name AS `post.name` FROM `comment` " .
"LEFT JOIN `post` AS `post` ON comment.post_id = post.id " .
"WHERE comment.deleted = 0 " .
"GROUP BY post.name ".
"ORDER BY FIELD(post.name, 'Hello', 'Test') DESC";
$this->assertEquals($expectedSql, $sql);
}
public function testOrderByExpression1()
{
$select = $this->queryBuilder
+19
View File
@@ -75,6 +75,25 @@ class BeforeUpgrade
throw new Error($message);
}
}
$extension = $em->getRepository('Extension')
->where([
'name' => 'Real Estate',
'isInstalled' => true,
])
->findOne();
if ($extension) {
$version = $extension->get('version');
if (version_compare($version, '1.5.0', '<')) {
$message =
"EspoCRM 6.0.0 is not compatible with Real Estate extension of a version lower than 1.5.0. " .
"Please upgrade the extension or uninstall it. Then run the upgrade command again.";
throw new Error($message);
}
}
}
protected function processMyIsamCheck()