Merge branch 'hotfix/5.0.4'

This commit is contained in:
yuri
2018-02-01 13:15:41 +02:00
134 changed files with 3548 additions and 515 deletions
+17 -1
View File
@@ -30,6 +30,7 @@
namespace Espo\Acl;
use \Espo\ORM\Entity;
use \Espo\Entities\User as EntityUser;
class User extends \Espo\Core\Acl\Base
{
@@ -37,5 +38,20 @@ class User extends \Espo\Core\Acl\Base
{
return $user->id === $entity->id;
}
}
public function checkEntityDelete(EntityUser $user, Entity $entity, $data)
{
if ($entity->id === 'system') {
return false;
}
return parent::checkEntityDelete($user, $entity, $data);
}
public function checkEntityEdit(EntityUser $user, Entity $entity, $data)
{
if ($entity->id === 'system') {
return false;
}
return $this->checkEntity($user, $entity, $data, 'edit');
}
}
+15 -4
View File
@@ -73,6 +73,8 @@ class Table
protected $forbiddenFieldsCache = array();
protected $isStrictModeForced = false;
protected $isStrictMode = false;
public function __construct(User $user, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManagerUtil $fieldManager = null)
@@ -83,7 +85,11 @@ class Table
'fieldTableQuickAccess' => (object) [],
];
$this->isStrictMode = $config->get('aclStrictMode', false);
if ($this->isStrictModeForced) {
$this->isStrictMode = true;
} else {
$this->isStrictMode = $config->get('aclStrictMode', false);
}
$this->user = $user;
@@ -405,7 +411,12 @@ class Table
return;
}
$data = $this->metadata->get('app.'.$this->type.'.default.scopeLevel', array());
$defaultsGroupName = 'default';
if ($this->isStrictMode) {
$defaultsGroupName = 'strictDefault';
}
$data = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'scopeLevel'], []);
foreach ($data as $scope => $item) {
if (isset($table->$scope)) continue;
@@ -416,7 +427,7 @@ class Table
$table->$scope = $value;
}
$defaultFieldData = $this->metadata->get('app.'.$this->type.'.default.fieldLevel', array());
$defaultFieldData = $this->metadata->get(['app', $this->type, $defaultsGroupName, 'fieldLevel'], []);
foreach ($this->getScopeList() as $scope) {
if (isset($table->$scope) && $table->$scope === false) continue;
@@ -424,7 +435,7 @@ class Table
$fieldList = array_keys($this->getMetadata()->get("entityDefs.{$scope}.fields", []));
$defaultScopeFieldData = $this->metadata->get('app.'.$this->type.'.default.scopeFieldLevel.' . $scope, array());
$defaultScopeFieldData = $this->metadata->get('app.'.$this->type.'.'.$defaultsGroupName.'.scopeFieldLevel.' . $scope, []);
foreach (array_merge($defaultFieldData, $defaultScopeFieldData) as $field => $f) {
if (!in_array($field, $fieldList)) continue;
@@ -50,6 +50,8 @@ class Table extends \Espo\Core\Acl\Table
protected $levelList = ['yes', 'all', 'account', 'contact', 'own', 'no'];
protected $isStrictModeForced = true;
public function __construct(User $user, Portal $portal, Config $config = null, FileManager $fileManager = null, Metadata $metadata = null, FieldManagerUtil $fieldManager = null)
{
if (empty($portal)) {
+23 -3
View File
@@ -116,9 +116,10 @@ class CronManager
{
$lastRunData = $this->getFileManager()->getPhpContents($this->lastRunTime);
$lastRunTime = time() - intval($this->getConfig()->get('cron.minExecutionTime')) - 1;
if (is_array($lastRunData) && !empty($lastRunData['time'])) {
$lastRunTime = $lastRunData['time'];
} else {
$lastRunTime = time() - intval($this->getConfig()->get('cronMinInterval', 0)) - 1;
}
return $lastRunTime;
@@ -136,9 +137,9 @@ class CronManager
{
$currentTime = time();
$lastRunTime = $this->getLastRunTime();
$minTime = $this->getConfig()->get('cron.minExecutionTime');
$cronMinInterval = $this->getConfig()->get('cronMinInterval', 0);
if ($currentTime > ($lastRunTime + $minTime) ) {
if ($currentTime > ($lastRunTime + $cronMinInterval)) {
return true;
}
@@ -167,8 +168,27 @@ class CronManager
$pendingJobList = $this->getCronJobUtil()->getPendingJobList();
foreach ($pendingJobList as $job) {
$skip = false;
$this->getEntityManager()->getPdo()->query('LOCK TABLES `job` WRITE');
if ($this->getCronJobUtil()->isJobPending($job->id)) {
if ($job->get('scheduledJobId')) {
if ($this->getCronJobUtil()->isScheduledJobRunning($job->get('scheduledJobId'), $job->get('targetId'), $job->get('targetType'))) {
$skip = true;
}
}
} else {
$skip = true;
}
if ($skip) {
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
continue;
}
$job->set('status', self::RUNNING);
$job->set('pid', $this->getCronJobUtil()->getPid());
$this->getEntityManager()->saveEntity($job);
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
$isSuccess = true;
$skipLog = false;
+5 -1
View File
@@ -143,13 +143,17 @@ class DataManager
if ($job) {
$entityManager->removeEntity($job);
}
$name = $jobName;
if (!empty($defs['name'])) {
$name = $defs['name'];
}
$job = $entityManager->getEntity('ScheduledJob');
$job->set(array(
'job' => $jobName,
'status' => 'Active',
'scheduling' => $defs['scheduling'],
'isInternal' => true,
'name' => $jobName
'name' => $name
));
$entityManager->saveEntity($job);
}
+34 -1
View File
@@ -31,19 +31,35 @@ namespace Espo\Core\Export;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\ORM\Entity;
class Csv extends \Espo\Core\Injectable
{
protected $dependencyList = [
'config',
'preferences'
'preferences',
'metadata'
];
public function loadAdditionalFields(Entity $entity, $fieldList)
{
foreach ($fieldList as $field) {
if ($this->getInjection('metadata')->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
}
}
}
public function process($entityType, $params, $dataList)
{
if (!is_array($params['attributeList'])) {
throw new Error();
}
$dataList = $this->prepareDataList($dataList);
$attributeList = $params['attributeList'];
$delimiter = $this->getInjection('preferences')->get('exportDelimiter');
@@ -62,4 +78,21 @@ class Csv extends \Espo\Core\Injectable
return $csv;
}
protected function prepareDataList($dataList)
{
$prepareDataList = [];
foreach ($dataList as $row) {
$preparedRow = [];
foreach ($row as $item) {
if (is_array($item) || is_object($item)) {
$item = \Espo\Core\Utils\Json::encode($item);
}
$preparedRow[] = $item;
}
$prepareDataList[] = $preparedRow;
}
return $prepareDataList;
}
}
+53
View File
@@ -78,6 +78,13 @@ class Xlsx extends \Espo\Core\Injectable
}
}
}
foreach ($fieldList as $field) {
if ($this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
}
}
}
public function addAdditionalAttributes($entityType, &$attributeList, $fieldList)
@@ -367,6 +374,52 @@ class Xlsx extends \Espo\Core\Injectable
}
$sheet->setCellValue("$col$rowNumber", $value);
}
} else if ($type == 'linkMultiple') {
if (array_key_exists($name . 'Ids', $row) && array_key_exists($name . 'Names', $row)) {
$nameList = [];
foreach ($row[$name . 'Ids'] as $relatedId) {
$relatedName = $relatedId;
if (property_exists($row[$name . 'Names'], $relatedId)) {
$relatedName = $row[$name . 'Names']->$relatedId;
}
$nameList[] = $relatedName;
}
$sheet->setCellValue("$col$rowNumber", implode(', ', $nameList));
}
} else if ($type == 'address') {
$value = '';
if (!empty($row[$name . 'Street'])) {
$value = $value .= $row[$name.'Street'];
}
if (!empty($row[$name.'City']) || !empty($row[$name.'State']) || !empty($row[$name.'PostalCode'])) {
if ($value) {
$value .= "\n";
}
if (!empty($row[$name.'City'])) {
$value .= $row[$name.'City'];
if (
!empty($row[$name.'State']) || !empty($row[$name.'PostalCode'])
) {
$value .= ', ';
}
}
if (!empty($row[$name.'State'])) {
$value .= $row[$name.'State'];
if (!empty($row[$name.'PostalCode'])) {
$value .= ' ';
}
}
if (!empty($row[$name.'PostalCode'])) {
$value .= $row[$name.'PostalCode'];
}
}
if (!empty($row[$name.'Country'])) {
if ($value) {
$value .= "\n";
}
$value .= $row[$name.'Country'];
}
$sheet->setCellValue("$col$rowNumber", $value);
} else {
if (array_key_exists($name, $row)) {
$sheet->setCellValue("$col$rowNumber", $row[$name]);
+52 -26
View File
@@ -154,31 +154,8 @@ class Importer
}
if ($duplicate = $this->findDuplicate($email)) {
if ($assignedUserId) {
$duplicate->addLinkMultipleId('users', $assignedUserId);
$duplicate->addLinkMultipleId('assignedUsers', $assignedUserId);
}
if (!empty($userIdList)) {
foreach ($userIdList as $uId) {
$duplicate->addLinkMultipleId('users', $uId);
}
}
if ($folderData) {
foreach ($folderData as $uId => $folderId) {
$email->setLinkMultipleColumn('users', 'folderId', $uId, $folderId);
}
}
$duplicate->set('isBeingImported', true);
$this->getEntityManager()->saveEntity($duplicate);
if (!empty($teamsIdList)) {
foreach ($teamsIdList as $teamId) {
$this->getEntityManager()->getRepository('Email')->relate($duplicate, 'teams', $teamId);
}
}
$duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id);
$this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList);
return $duplicate;
}
@@ -299,6 +276,26 @@ class Importer
}
}
$this->getEntityManager()->getPdo()->query('LOCK TABLES `email` WRITE');
if ($duplicate = $this->findDuplicate($email)) {
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
$duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id);
$this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList);
return $duplicate;
}
if (!$email->get('messageId')) {
$email->setDummyMessageId();
}
$this->getEntityManager()->saveEntity($email, [
'skipAll' => true,
'keepNew' => true
]);
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
$this->getEntityManager()->saveEntity($email);
foreach ($inlineAttachmentList as $attachment) {
@@ -352,7 +349,7 @@ class Importer
protected function findDuplicate(Entity $email)
{
if ($email->get('messageId')) {
$duplicate = $this->getEntityManager()->getRepository('Email')->where(array(
$duplicate = $this->getEntityManager()->getRepository('Email')->select(['id'])->where(array(
'messageId' => $email->get('messageId')
))->findOne();
if ($duplicate) {
@@ -360,4 +357,33 @@ class Importer
}
}
}
protected function processDuplicate(Entity $duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList)
{
if ($assignedUserId) {
$duplicate->addLinkMultipleId('users', $assignedUserId);
$duplicate->addLinkMultipleId('assignedUsers', $assignedUserId);
}
if (!empty($userIdList)) {
foreach ($userIdList as $uId) {
$duplicate->addLinkMultipleId('users', $uId);
}
}
if ($folderData) {
foreach ($folderData as $uId => $folderId) {
$duplicate->setLinkMultipleColumn('users', 'folderId', $uId, $folderId);
}
}
$duplicate->set('isBeingImported', true);
$this->getEntityManager()->saveEntity($duplicate);
if (!empty($teamsIdList)) {
foreach ($teamsIdList as $teamId) {
$this->getEntityManager()->getRepository('Email')->relate($duplicate, 'teams', $teamId);
}
}
}
}
@@ -33,7 +33,7 @@ use \PhpMimeMailParser\Attachment;
class Parser extends \PhpMimeMailParser\Parser
{
public function getAttachments()
public function getAttachments($include_inline = true)
{
$attachments = [];
$dispositions = ['attachment', 'inline'];
+1 -1
View File
@@ -349,7 +349,7 @@ class Sender
try {
$messageId = $email->get('messageId');
if (empty($messageId) || !is_string($messageId) || strlen($messageId) < 4) {
if (empty($messageId) || !is_string($messageId) || strlen($messageId) < 4 || strpos($messageId, 'dummy:') === 0) {
$messageId = $this->generateMessageId($email);
$email->set('messageId', '<' . $messageId . '>');
} else {
+36 -6
View File
@@ -41,6 +41,24 @@ class Entity extends \Espo\ORM\Entity
return $this->hasAttribute($field . 'Id');
}
public function loadParentNameField($field)
{
if (!$this->hasAttribute($field. 'Id') || !$this->hasAttribute($field . 'Type')) return;
$parentId = $this->get($field . 'Id');
$parentType = $this->get($field . 'Type');
if ($parentId && $parentType) {
if (!$this->entityManager->hasRepository($parentType)) return;
$repository = $this->entityManager->getRepository($parentType);
$foreignEntity = $repository->select(['id', 'name'])->where(['id' => $parentId])->findOne();
if ($foreignEntity) {
$this->set($field . 'Name', $foreignEntity->get('name'));
}
}
}
public function loadLinkMultipleField($field, $columns = null)
{
if (!$this->hasRelation($field) || !$this->hasAttribute($field . 'Ids')) return;
@@ -69,12 +87,20 @@ class Entity extends \Espo\ORM\Entity
}
}
$defs['select'] = ['id', 'name'];
$hasType = false;
if ($this->hasField($field . 'Types')) {
$hasType = true;
$defs['select'][] = 'type';
}
$collection = $this->get($field, $defs);
$ids = array();
$names = new \stdClass();
$types = new \stdClass();
$ids = [];
$names = (object) [];
$types = (object) [];
if (!empty($columns)) {
$columnsData = new \stdClass();
$columnsData = (object) [];
}
if ($collection) {
@@ -82,7 +108,9 @@ class Entity extends \Espo\ORM\Entity
$id = $e->id;
$ids[] = $id;
$names->$id = $e->get('name');
$types->$id = $e->get('type');
if ($hasType) {
$types->$id = $e->get('type');
}
if (!empty($columns)) {
$columnsData->$id = new \stdClass();
foreach ($columns as $column => $f) {
@@ -94,7 +122,9 @@ class Entity extends \Espo\ORM\Entity
$this->set($field . 'Ids', $ids);
$this->set($field . 'Names', $names);
$this->set($field . 'Types', $types);
if ($hasType) {
$this->set($field . 'Types', $types);
}
if (!empty($columns)) {
$this->set($field . 'Columns', $columnsData);
}
+31 -26
View File
@@ -176,7 +176,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function beforeRemove(Entity $entity, array $options = array())
{
parent::beforeRemove($entity, $options);
if (!$this->hooksDisabled) {
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
$this->getEntityManager()->getHookManager()->process($this->entityType, 'beforeRemove', $entity, $options);
}
@@ -192,14 +192,14 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
protected function afterRemove(Entity $entity, array $options = array())
{
parent::afterRemove($entity, $options);
if (!$this->hooksDisabled) {
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterRemove', $entity, $options);
}
}
protected function afterMassRelate(Entity $entity, $relationName, array $params = array(), array $options = array())
{
if (!$this->hooksDisabled) {
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
$hookData = array(
'relationName' => $relationName,
'relationParams' => $params
@@ -220,7 +220,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
if ($foreign instanceof Entity) {
$foreignEntity = $foreign;
if (!$this->hooksDisabled) {
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
$hookData = array(
'relationName' => $relationName,
'relationData' => $data,
@@ -237,7 +237,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
if ($foreign instanceof Entity) {
$foreignEntity = $foreign;
if (!$this->hooksDisabled) {
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
$hookData = array(
'relationName' => $relationName,
'foreignEntity' => $foreignEntity
@@ -251,7 +251,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
{
parent::beforeSave($entity, $options);
if (!$this->hooksDisabled) {
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
$this->getEntityManager()->getHookManager()->process($this->entityType, 'beforeSave', $entity, $options);
}
}
@@ -271,7 +271,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$this->processFileFieldsSave($entity);
}
if (!$this->hooksDisabled) {
if (!$this->hooksDisabled && empty($options['skipHooks'])) {
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterSave', $entity, $options);
}
}
@@ -285,35 +285,40 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
if (!$entity->has('id')) {
$entity->set('id', Util::generateId());
}
}
if ($entity->hasAttribute('createdAt')) {
if (empty($options['import']) || !$entity->has('createdAt')) {
$entity->set('createdAt', $nowString);
}
}
if ($entity->hasAttribute('modifiedAt')) {
$entity->set('modifiedAt', $nowString);
}
if ($entity->hasAttribute('createdById')) {
if (empty($options['skipCreatedBy']) && (empty($options['import']) || !$entity->has('createdById'))) {
if ($this->getEntityManager()->getUser()) {
$entity->set('createdById', $this->getEntityManager()->getUser()->id);
if (empty($options['skipAll'])) {
if ($entity->isNew()) {
if ($entity->hasAttribute('createdAt')) {
if (empty($options['import']) || !$entity->has('createdAt')) {
$entity->set('createdAt', $nowString);
}
}
}
} else {
if (empty($options['silent']) && empty($options['skipModifiedBy'])) {
if ($entity->hasAttribute('modifiedAt')) {
$entity->set('modifiedAt', $nowString);
}
if ($entity->hasAttribute('modifiedById')) {
if ($this->getEntityManager()->getUser()) {
$entity->set('modifiedById', $this->getEntityManager()->getUser()->id);
$entity->set('modifiedByName', $this->getEntityManager()->getUser()->get('name'));
if ($entity->hasAttribute('createdById')) {
if (empty($options['skipCreatedBy']) && (empty($options['import']) || !$entity->has('createdById'))) {
if ($this->getEntityManager()->getUser()) {
$entity->set('createdById', $this->getEntityManager()->getUser()->id);
}
}
}
} else {
if (empty($options['silent']) && empty($options['skipModifiedBy'])) {
if ($entity->hasAttribute('modifiedAt')) {
$entity->set('modifiedAt', $nowString);
}
if ($entity->hasAttribute('modifiedById')) {
if ($this->getEntityManager()->getUser()) {
$entity->set('modifiedById', $this->getEntityManager()->getUser()->id);
$entity->set('modifiedByName', $this->getEntityManager()->getUser()->get('name'));
}
}
}
}
}
$this->restoreData = $restoreData;
$result = parent::save($entity, $options);
@@ -1,6 +1,6 @@
{
"links": {
"meetings": "Susirinkimai",
"meetings": "Susitikimai",
"calls": "Skambučiai",
"tasks": "Užduotys"
},
@@ -2,10 +2,10 @@
"fields": {
"billingAddress": "Mokėtojo adresas",
"shippingAddress": "Siuntimo adresas",
"website": "Interntinė Svetainė"
"website": "Internetinė Svetainė"
},
"links": {
"meetings": "Susirinkimai",
"meetings": "Susitikimai",
"calls": "Skambučiai",
"tasks": "Užduotys"
},
@@ -3,7 +3,7 @@
"address": "Adresas"
},
"links": {
"meetings": "Susirinkimai",
"meetings": "Susitikimai",
"calls": "Skambučiai",
"tasks": "Užduotys"
},
@@ -186,11 +186,10 @@ class AdminNotificationManager
$notification->set(array(
'type' => 'message',
'data' => array(
'userId' => $this->getUser()->id,
'userName' => $this->getUser()->get('name')
'userId' => $userId,
),
'userId' => $user->id,
'message' => $actionData['messageTemplate']
'userId' => $userId,
'message' => $message
));
$this->getEntityManager()->saveEntity($notification);
}
+91 -46
View File
@@ -28,10 +28,12 @@
************************************************************************/
namespace Espo\Core\Utils\Cron;
use \PDO;
use \Espo\Core\CronManager;
use \Espo\Core\Utils\Config;
use \Espo\Core\ORM\EntityManager;
use PDO;
use Espo\Core\CronManager;
use Espo\Core\Utils\Config;
use Espo\Core\ORM\EntityManager;
use Espo\Core\Utils\System;
class Job
{
@@ -64,15 +66,17 @@ class Job
return $this->cronScheduledJob;
}
/**
* Get Pending Jobs
*
* @return array
*/
public function isJobPending($id)
{
return !!$this->getEntityManager()->getRepository('Job')->select(['id'])->where([
'id' => $id,
'status' => CronManager::PENDING
])->findOne();
}
public function getPendingJobList()
{
$jobConfigs = $this->getConfig()->get('cron');
$limit = !empty($jobConfigs['maxJobNumber']) ? intval($jobConfigs['maxJobNumber']) : 0;
$limit = intval($this->getConfig()->get('jobMaxPortion', 0));
$selectParams = [
'select' => [
@@ -88,7 +92,7 @@ class Job
'data'
],
'whereClause' => [
'status' => 'Pending',
'status' => CronManager::PENDING,
'executeTime<=' => date('Y-m-d H:i:s')
],
'orderBy' => 'executeTime'
@@ -97,17 +101,21 @@ class Job
$selectParams['offset'] = 0;
$selectParams['limit'] = $limit;
}
$jobList = $this->getEntityManager()->getRepository('Job')->find($selectParams);
$runningScheduledJobIdList = $this->getRunningScheduledJobIdList();
return $this->getEntityManager()->getRepository('Job')->find($selectParams);
}
$actualJobList = [];
foreach ($jobList as $job) {
if ($job->get('scheduledJobId') && in_array($job->get('scheduledJobId'), $runningScheduledJobIdList)) continue;
$actualJobList[] = $job;
public function isScheduledJobRunning($scheduledJobId, $targetId = null, $targetType = null)
{
$where = [
'scheduledJobId' => $scheduledJobId,
'status' => CronManager::RUNNING
];
if ($targetId && $targetType) {
$where['targetId'] = $targetId;
$where['targetType'] = $targetType;
}
return $actualJobList;
return !!$this->getEntityManager()->getRepository('Job')->select(['id'])->where($where)->findOne();
}
public function getRunningScheduledJobIdList()
@@ -175,39 +183,62 @@ class Job
*/
public function markFailedJobs()
{
$jobConfigs = $this->getConfig()->get('cron');
$this->markFailedJobsByPeriod('jobPeriodForActiveProcess');
$this->markFailedJobsByPeriod('jobPeriod');
}
$currentTime = time();
$periodTime = $currentTime - intval($jobConfigs['jobPeriod']);
protected function markFailedJobsByPeriod($period)
{
$time = time() - $this->getConfig()->get($period);
$pdo = $this->getEntityManager()->getPDO();
$select = "
SELECT id, scheduled_job_id, execute_time, target_id, target_type FROM `job`
SELECT id, scheduled_job_id, execute_time, target_id, target_type, pid FROM `job`
WHERE
`status` = '" . CronManager::RUNNING ."' AND execute_time < '".date('Y-m-d H:i:s', $periodTime)."'
`status` = '" . CronManager::RUNNING ."' AND execute_time < '".date('Y-m-d H:i:s', $time)."'
";
$sth = $pdo->prepare($select);
$sth->execute();
$jobData = array();
while ($row = $sth->fetch(PDO::FETCH_ASSOC)){
$jobData[$row['id']] = $row;
switch ($period) {
case 'jobPeriod':
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
if (empty($row['pid']) || !System::isProcessActive($row['pid'])) {
$jobData[$row['id']] = $row;
}
}
break;
case 'jobPeriodForActiveProcess':
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
$jobData[$row['id']] = $row;
}
break;
}
$update = "
UPDATE job
SET `status` = '". CronManager::FAILED ."'
WHERE id IN ('".implode("', '", array_keys($jobData))."')
";
$sth = $pdo->prepare($update);
$sth->execute();
if (!empty($jobData)) {
$jobQuotedIdList = [];
foreach ($jobData as $jobId => $job) {
$jobQuotedIdList[] = $pdo->quote($jobId);
}
//add status 'Failed' to SchediledJobLog
$cronScheduledJob = $this->getCronScheduledJob();
foreach ($jobData as $jobId => $job) {
if (!empty($job['scheduled_job_id'])) {
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time'], $job['target_id'], $job['target_type']);
$update = "
UPDATE job
SET `status` = '" . CronManager::FAILED . "', attempts = 0
WHERE id IN (".implode(", ", $jobQuotedIdList).")
";
$sth = $pdo->prepare($update);
$sth->execute();
$cronScheduledJob = $this->getCronScheduledJob();
foreach ($jobData as $jobId => $job) {
if (!empty($job['scheduled_job_id'])) {
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time'], $job['target_id'], $job['target_type']);
}
}
}
}
@@ -246,20 +277,29 @@ class Job
$query = "
SELECT id FROM `job`
WHERE
scheduled_job_id = '" . $row['scheduled_job_id'] . "' AND
`status` = '" . CronManager::PENDING ."'
scheduled_job_id = ".$pdo->quote($row['scheduled_job_id'])."
AND `status` = '" . CronManager::PENDING ."'
ORDER BY execute_time
DESC LIMIT 1, 100000
";
$sth = $pdo->prepare($query);
$sth->execute();
$jobIds = $sth->fetchAll(PDO::FETCH_COLUMN);
$jobIdList = $sth->fetchAll(PDO::FETCH_COLUMN);
if (empty($jobIdList)) {
continue;
}
$quotedJobIdList = [];
foreach ($jobIdList as $jobId) {
$quotedJobIdList[] = $pdo->quote($jobId);
}
$update = "
UPDATE job
SET deleted = 1
WHERE
id IN ('". implode("', '", $jobIds)."')
id IN (".implode(", ", $quotedJobIdList).")
";
$sth = $pdo->prepare($update);
@@ -300,13 +340,18 @@ class Job
UPDATE job
SET
`status` = '" . CronManager::PENDING ."',
attempts = '".$attempts."',
failed_attempts = '".$failedAttempts."'
attempts = ".$pdo->quote($attempts).",
failed_attempts = ".$pdo->quote($failedAttempts)."
WHERE
id = '".$row['id']."'
id = ".$pdo->quote($row['id'])."
";
$pdo->prepare($update)->execute();
}
}
}
public function getPid()
{
return System::getPid();
}
}
@@ -91,7 +91,7 @@ class ScheduledJob
}
$scheduledJob->set('lastRun', $runTime);
$entityManager->saveEntity($scheduledJob);
$entityManager->saveEntity($scheduledJob, ['silent' => true]);
$scheduledJobLog = $entityManager->getEntity('ScheduledJobLogRecord');
$scheduledJobLog->set(array(
+36
View File
@@ -134,4 +134,40 @@ class System
return $version;
}
/**
* Pet process ID
*
* @return integer
*/
public static function getPid()
{
if (function_exists('getmypid')) {
return getmypid();
}
}
/**
* Check if process is active
*
* @param integer $pid
*
* @return boolean
*/
public static function isProcessActive($pid)
{
if (empty($pid)) {
return false;
}
if (!function_exists('posix_getsid')) {
return false;
}
if (posix_getsid($pid) === false) {
return false;
}
return true;
}
}
+2 -2
View File
@@ -167,8 +167,8 @@ return array (
'cleanupJobPeriod' => '1 month',
'cleanupActionHistoryPeriod' => '15 days',
'cleanupAuthTokenPeriod' => '1 month',
'currencyFormat' => 1,
'currencyDecimalPlaces' => null,
'currencyFormat' => 2,
'currencyDecimalPlaces' => 2,
'aclStrictMode' => false,
'aclAllowDeleteCreated' => false,
'inlineAttachmentUploadMaxSize' => 20,
@@ -56,16 +56,11 @@ return array ( 'defaultPermissions' =>
'reset.html',
),
),
'cron' => array(
/** Max number of jobs per one execution. */
'maxJobNumber' => 15,
/** Max execution time (in seconds) allocated for a sinle job. If exceeded then set to Failed.*/
'jobPeriod' => 7800,
/** Min time (in seconds) between two cron runs. */
'minExecutionTime' => 50,
/** Attempts to re-run failed jobs. */
'attempts' => 2
),
'jobMaxPortion' => 15, /** Max number of jobs per one execution. */
'jobPeriod' => 7800, /** Max execution time (in seconds) allocated for a sinle job. If exceeded then set to Failed.*/
'jobPeriodForActiveProcess' => 36000, /** Max execution time (in seconds) allocated for a sinle job with active process. If exceeded then set to Failed.*/
'jobRerunAttemptNumber' => 1, /** Number of attempts to re-run failed jobs. */
'cronMinInterval' => 4, /** Min interval (in seconds) between two cron runs. */
'crud' => array(
'get' => 'read',
'post' => 'create',
@@ -117,7 +112,10 @@ return array ( 'defaultPermissions' =>
'smtpSecurity',
'smtpUsername',
'smtpPassword',
'cron',
'jobMaxPortion',
'jobPeriod',
'jobRerunAttemptNumber',
'cronMinInterval',
'authenticationMethod',
'adminPanelIframeUrl',
'ldapHost',
+5 -1
View File
@@ -174,5 +174,9 @@ class Email extends \Espo\Core\ORM\Entity
}
return [];
}
}
public function setDummyMessageId()
{
$this->set('messageId', 'dummy:' . \Espo\Core\Utils\Util::generateId());
}
}
@@ -31,7 +31,7 @@ namespace Espo\Jobs;
use Espo\Core\Exceptions;
class NewVersionChecker extends \Espo\Core\Jobs\Base
class CheckNewVersion extends \Espo\Core\Jobs\Base
{
public function run()
{
@@ -41,11 +41,10 @@ class NewVersionChecker extends \Espo\Core\Jobs\Base
$job = $this->getEntityManager()->getEntity('Job');
$job->set(array(
'name' => 'NewVersionChecker',
'name' => 'Check for New Version (job)',
'serviceName' => 'AdminNotifications',
'method' => 'newVersionChecker',
'methodName' => 'newVersionChecker',
'executeTime' => $this->getRunTime(),
'methodName' => 'jobCheckNewVersion',
'executeTime' => $this->getRunTime()
));
$this->getEntityManager()->saveEntity($job);
@@ -43,8 +43,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
$dateFilter = $request->get('dateFilter');
return $this->getService('Opportunity')->reportByLeadSource($dateFrom, $dateTo);
return $this->getService('Opportunity')->reportByLeadSource($dateFilter, $dateFrom, $dateTo);
}
public function actionReportByStage($params, $data, $request)
@@ -56,8 +57,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
$dateFilter = $request->get('dateFilter');
return $this->getService('Opportunity')->reportByStage($dateFrom, $dateTo);
return $this->getService('Opportunity')->reportByStage($dateFilter, $dateFrom, $dateTo);
}
public function actionReportSalesByMonth($params, $data, $request)
@@ -69,8 +71,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
$dateFilter = $request->get('dateFilter');
return $this->getService('Opportunity')->reportSalesByMonth($dateFrom, $dateTo);
return $this->getService('Opportunity')->reportSalesByMonth($dateFilter, $dateFrom, $dateTo);
}
public function actionReportSalesPipeline($params, $data, $request)
@@ -82,8 +85,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
$dateFrom = $request->get('dateFrom');
$dateTo = $request->get('dateTo');
$dateFilter = $request->get('dateFilter');
return $this->getService('Opportunity')->reportSalesPipeline($dateFrom, $dateTo);
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo);
}
}
@@ -2,7 +2,7 @@
"fields": {
"name": "Vardas",
"emailAddress": "El. paštas",
"website": "Interntinė Svetainė",
"website": "Internetinė Svetainė",
"phoneNumber": "Telefonas",
"billingAddress": "Mokėtojo adresas",
"shippingAddress": "Siuntimo adresas",
@@ -12,6 +12,7 @@
"openedCount": "Atverta",
"clickedCount": "Paspausta",
"optedOutCount": "Pasirinkta",
"bouncedCount": "Atmesta",
"leadCreatedCount": "Potencialūs klientai sukurti",
"revenue": "Pajamos",
"revenueConverted": "Pajamos (konvertuotos)",
@@ -18,7 +18,7 @@
"account": "Įmonė",
"contact": "Kontaktas (pirminis)",
"Contacts": "Kontaktai",
"meetings": "Susirinkimai",
"meetings": "Susitikimai",
"calls": "Skambučiai",
"tasks": "Užduotys",
"emails": "El. laiška",
@@ -4,7 +4,7 @@
"contacts": "Kontaktai",
"opportunities": "Galimybės",
"leads": "Potencialūs klientai",
"meetings": "Susirinkimai",
"meetings": "Susitikimai",
"calls": "Skambučiai",
"tasks": "Užduotys",
"emails": "El. laiška",
@@ -21,7 +21,7 @@
"Lead": "Potencialus klientas",
"Target": "Adresatas",
"Opportunity": "Galimybė",
"Meeting": "Susirinkimas",
"Meeting": "Susitikimas",
"Calendar": "Kalendorius",
"Call": "Skambutis",
"Task": "Užduotis",
@@ -42,7 +42,7 @@
"Lead": "Potencialūs klientai",
"Target": "Adresatai",
"Opportunity": "Galimybės",
"Meeting": "Susirinkimai",
"Meeting": "Susitikimai",
"Calendar": "Kalendorius",
"Call": "Skambučiai",
"Task": "Užduotys",
@@ -9,7 +9,7 @@
"name": "Vardas",
"emailAddress": "El. paštas",
"title": "Pavadinimas",
"website": "Interntinė Svetainė",
"website": "Internetinė Svetainė",
"phoneNumber": "Telefonas",
"accountName": "Įmonės pavadinimas",
"doNotCall": "Neskambinti",
@@ -2,6 +2,7 @@
"view":"crm:views/dashlets/opportunities-by-lead-source",
"aclScope": "Opportunity",
"options": {
"view": "crm:views/dashlets/options/chart",
"fields": {
"title": {
"type": "varchar",
@@ -14,6 +15,12 @@
"dateTo": {
"type": "date",
"required": true
},
"dateFilter": {
"type": "enum",
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
"default": "currentYear",
"translation": "Global.options.dateSearchRanges"
}
},
"layout": [
@@ -22,12 +29,19 @@
[
{"name": "title"}
],
[
{"name": "dateFilter"},
false
],
[
{"name": "dateFrom"},
{"name": "dateTo"}
]
]
}
]
],
"defaults": {
"dateFilter": "currentYear"
}
}
}
@@ -2,6 +2,7 @@
"view":"crm:views/dashlets/opportunities-by-stage",
"aclScope": "Opportunity",
"options": {
"view": "crm:views/dashlets/options/chart",
"fields": {
"title": {
"type": "varchar",
@@ -14,6 +15,12 @@
"dateTo": {
"type": "date",
"required": true
},
"dateFilter": {
"type": "enum",
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
"default": "currentYear",
"translation": "Global.options.dateSearchRanges"
}
},
"layout": [
@@ -22,12 +29,19 @@
[
{"name": "title"}
],
[
{"name": "dateFilter"},
false
],
[
{"name": "dateFrom"},
{"name": "dateTo"}
]
]
}
]
],
"defaults": {
"dateFilter": "currentYear"
}
}
}
@@ -2,6 +2,7 @@
"view":"crm:views/dashlets/sales-by-month",
"aclScope": "Opportunity",
"options": {
"view": "crm:views/dashlets/options/chart",
"fields": {
"title": {
"type": "varchar",
@@ -14,6 +15,12 @@
"dateTo": {
"type": "date",
"required": true
},
"dateFilter": {
"type": "enum",
"options": ["currentYear", "currentQuarter", "between"],
"default": "currentYear",
"translation": "Global.options.dateSearchRanges"
}
},
"layout": [
@@ -22,12 +29,19 @@
[
{"name": "title"}
],
[
{"name": "dateFilter"},
false
],
[
{"name": "dateFrom"},
{"name": "dateTo"}
]
]
}
]
],
"defaults": {
"dateFilter": "currentYear"
}
}
}
@@ -2,6 +2,7 @@
"view":"crm:views/dashlets/sales-pipeline",
"aclScope": "Opportunity",
"options": {
"view": "crm:views/dashlets/options/chart",
"fields": {
"title": {
"type": "varchar",
@@ -14,13 +15,23 @@
"dateTo": {
"type": "date",
"required": true
},
"dateFilter": {
"type": "enum",
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
"default": "currentYear",
"translation": "Global.options.dateSearchRanges"
}
},
"layout": [
{
"rows": [
[
{"name": "title"}
{"name": "title", "span": 2}
],
[
{"name": "dateFilter"},
false
],
[
{"name": "dateFrom"},
@@ -28,6 +39,9 @@
]
]
}
]
],
"defaults": {
"dateFilter": "currentYear"
}
}
}
@@ -195,7 +195,6 @@
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"layoutFiltersDisabled": true,
"exportDisabled": true,
"entity": "TargetList"
},
"originalLead": {
@@ -253,7 +253,6 @@
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"importDisabled": true,
"exportDisabled": true,
"noLoad": true
},
"targetList": {
@@ -36,8 +36,12 @@ use \Espo\Core\Exceptions\Forbidden;
class Opportunity extends \Espo\Services\Record
{
public function reportSalesPipeline($dateFrom, $dateTo)
public function reportSalesPipeline($dateFilter, $dateFrom = null, $dateTo = null)
{
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options');
@@ -48,8 +52,16 @@ class Opportunity extends \Espo\Services\Record
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
";
}
$sql .= "
opportunity.stage <> 'Closed Lost'
GROUP BY opportunity.stage
ORDER BY FIELD(opportunity.stage, '".implode("','", $options)."')
@@ -68,8 +80,12 @@ class Opportunity extends \Espo\Services\Record
return $result;
}
public function reportByLeadSource($dateFrom, $dateTo)
public function reportByLeadSource($dateFilter, $dateFrom = null, $dateTo = null)
{
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$sql = "
@@ -78,8 +94,15 @@ class Opportunity extends \Espo\Services\Record
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
";
}
$sql .= "
opportunity.stage <> 'Closed Lost' AND
opportunity.lead_source <> ''
GROUP BY opportunity.lead_source
@@ -98,8 +121,12 @@ class Opportunity extends \Espo\Services\Record
return $result;
}
public function reportByStage($dateFrom, $dateTo)
public function reportByStage($dateFilter, $dateFrom = null, $dateTo = null)
{
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options');
@@ -110,8 +137,16 @@ class Opportunity extends \Espo\Services\Record
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
";
}
$sql .= "
opportunity.stage <> 'Closed Lost' AND
opportunity.stage <> 'Closed Won'
GROUP BY opportunity.stage
@@ -131,8 +166,12 @@ class Opportunity extends \Espo\Services\Record
return $result;
}
public function reportSalesByMonth($dateFrom, $dateTo)
public function reportSalesByMonth($dateFilter, $dateFrom = null, $dateTo = null)
{
if ($dateFilter !== 'between' && $dateFilter !== 'ever') {
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
}
$pdo = $this->getEntityManager()->getPDO();
$sql = "
@@ -141,10 +180,17 @@ class Opportunity extends \Espo\Services\Record
JOIN currency ON currency.id = opportunity.amount_currency
WHERE
opportunity.deleted = 0 AND
";
if ($dateFilter !== 'ever') {
$sql .= "
opportunity.close_date >= ".$pdo->quote($dateFrom)." AND
opportunity.close_date < ".$pdo->quote($dateTo)." AND
opportunity.stage = 'Closed Won'
";
}
$sql .= "
opportunity.stage = 'Closed Won'
GROUP BY DATE_FORMAT(opportunity.close_date, '%Y-%m')
ORDER BY `month`
";
@@ -159,8 +205,73 @@ class Opportunity extends \Espo\Services\Record
$result[$row['month']] = floatval($row['amount']);
}
return $result;
$dt = new \DateTime($dateFrom);
$dtTo = new \DateTime($dateTo);
$dtTo->setDate($dtTo->format('Y'), $dtTo->format('m'), 1);
if ($dt && $dtTo) {
$interval = new \DateInterval('P1M');
while ($dt->getTimestamp() <= $dtTo->getTimestamp()) {
$month = $dt->format('Y-m');
if (!array_key_exists($month, $result)) {
$result[$month] = 0;
}
$dt->add($interval);
}
}
$keyList = array_keys($result);
sort($keyList);
$today = new \DateTime();
$endPosition = count($keyList) - 1;
for ($i = count($keyList) - 1; $i >= 0; $i--) {
$key = $keyList[$i];
$dt = new \DateTime($key . '-01');
if ($dt->getTimestamp() < $today->getTimestamp()) {
break;
}
if (empty($result[$key])) {
$endPosition = $i;
} else {
break;
}
}
$keyList = array_slice($keyList, 0, $endPosition);
return (object) [
'keyList' => $keyList,
'dataMap' => $result
];
}
protected function getDateRangeByFilter($dateFilter)
{
switch ($dateFilter) {
case 'currentYear':
$dt = new \DateTime();
return [
$dt->modify('first day of January this year')->format('Y-m-d'),
$dt->add(new \DateInterval('P1Y'))->format('Y-m-d')
];
case 'currentQuarter':
$dt = new \DateTime();
$quarter = ceil($dt->format('m') / 3);
$dt->modify('first day of January this year');
return [
$dt->add(new \DateInterval('P'.(($quarter - 1) * 3).'M'))->format('Y-m-d'),
$dt->add(new \DateInterval('P3M'))->format('Y-m-d')
];
case 'currentMonth':
$dt = new \DateTime();
return [
$dt->modify('first day of this month')->format('Y-m-d'),
$dt->add(new \DateInterval('P1M'))->format('Y-m-d')
];
}
return [0, 0];
}
}
+2 -2
View File
@@ -250,7 +250,7 @@ abstract class Base
case 'WEEK_0':
return "CONCAT(YEAR({$part}), '/', WEEK({$part}, 0))";
case 'WEEK_1':
return "CONCAT(YEAR({$part}), '/', WEEK({$part}, 1))";
return "CONCAT(YEAR({$part}), '/', WEEK({$part}, 5))";
case 'MONTH_NUMBER':
$function = 'MONTH';
break;
@@ -266,7 +266,7 @@ abstract class Base
case 'WEEK_NUMBER_0':
return "WEEK({$part}, 0)";
case 'WEEK_NUMBER_1':
return "WEEK({$part}, 1)";
return "WEEK({$part}, 5)";
case 'HOUR_NUMBER':
$function = 'HOUR';
break;
+9 -4
View File
@@ -163,14 +163,14 @@ class EntityManager
public function saveEntity(Entity $entity, array $options = array())
{
$entityName = $entity->getEntityName();
return $this->getRepository($entityName)->save($entity, $options);
$entityType = $entity->getEntityType();
return $this->getRepository($entityType)->save($entity, $options);
}
public function removeEntity(Entity $entity, array $options = array())
{
$entityName = $entity->getEntityName();
return $this->getRepository($entityName)->remove($entity, $options);
$entityType = $entity->getEntityType();
return $this->getRepository($entityType)->remove($entity, $options);
}
public function getRepository($name)
@@ -186,6 +186,11 @@ class EntityManager
$this->metadata->setData($data);
}
public function hasRepository($name)
{
return $this->getMetadata()->has($name);
}
public function getMetadata()
{
return $this->metadata;
+8
View File
@@ -45,4 +45,12 @@ class Metadata
}
return $this->data[$entityType];
}
public function has($entityType)
{
if (!array_key_exists($entityType, $this->data)) {
return null;
}
return true;
}
}
+9 -3
View File
@@ -136,7 +136,9 @@ class RDB extends \Espo\ORM\Repository
public function save(Entity $entity, array $options = array())
{
$this->beforeSave($entity, $options);
if (empty($options['skipBeforeSave']) && empty($options['skipAll'])) {
$this->beforeSave($entity, $options);
}
if ($entity->isNew() && !$entity->isSaved()) {
$result = $this->getMapper()->insert($entity);
} else {
@@ -144,9 +146,13 @@ class RDB extends \Espo\ORM\Repository
}
if ($result) {
$entity->setIsSaved(true);
$this->afterSave($entity, $options);
if (empty($options['skipAfterSave']) && empty($options['skipAll'])) {
$this->afterSave($entity, $options);
}
if ($entity->isNew()) {
$entity->setIsNew(false);
if (empty($options['keepNew'])) {
$entity->setIsNew(false);
}
} else {
if ($entity->isFetched()) {
$entity->updateFetchedValues();
+6 -2
View File
@@ -185,6 +185,10 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
protected function beforeSave(Entity $entity, array $options = array())
{
if ($entity->isNew() && !$entity->get('messageId')) {
$entity->setDummyMessageId();
}
$eaRepository = $this->getEntityManager()->getRepository('EmailAddress');
if ($entity->has('attachmentsIds')) {
@@ -322,8 +326,8 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
}
}
if ($entity->get('isBeingImportered')) {
$entity->set('isBeingImportered', false);
if ($entity->get('isBeingImported')) {
$entity->set('isBeingImported', false);
}
}
+6 -3
View File
@@ -33,6 +33,10 @@ use Espo\ORM\Entity;
class Job extends \Espo\Core\ORM\Repositories\RDB
{
protected $hooksDisabled = true;
protected $processFieldsAfterSaveDisabled = true;
protected function init()
{
parent::init();
@@ -46,14 +50,13 @@ class Job extends \Espo\Core\ORM\Repositories\RDB
public function beforeSave(Entity $entity, array $options = array())
{
if (!$entity->has('executeTime')) {
if (!$entity->has('executeTime') && $entity->isNew()) {
$entity->set('executeTime', date('Y-m-d H:i:s'));
}
if (!$entity->has('attempts') && $entity->isNew()) {
$attempts = $this->getConfig()->get('cron.attempts', 0);
$attempts = $this->getConfig()->get('jobRerunAttemptNumber', 0);
$entity->set('attempts', $attempts);
}
}
}
@@ -0,0 +1,39 @@
<?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\Repositories;
use Espo\ORM\Entity;
class ScheduledJob extends \Espo\Core\ORM\Repositories\RDB
{
protected $hooksDisabled = true;
protected $processFieldsAfterSaveDisabled = true;
}
+3 -3
View File
@@ -32,6 +32,7 @@ namespace Espo\Repositories;
use \Espo\ORM\Entity;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Conflict;
class User extends \Espo\Core\ORM\Repositories\RDB
{
@@ -50,7 +51,7 @@ class User extends \Espo\Core\ORM\Repositories\RDB
))->findOne();
if ($user) {
throw new Error();
throw new Conflict(json_encode(['reason' => 'userNameExists']));
}
} else {
if ($entity->isFieldChanged('userName')) {
@@ -64,7 +65,7 @@ class User extends \Espo\Core\ORM\Repositories\RDB
'id!=' => $entity->id
))->findOne();
if ($user) {
throw new Error();
throw new Conflict(json_encode(['reason' => 'userNameExists']));
}
}
}
@@ -113,4 +114,3 @@ class User extends \Espo\Core\ORM\Repositories\RDB
return false;
}
}
@@ -14,7 +14,8 @@
"boolFilterList": "Additional Filters",
"sortBy": "Order (field)",
"sortDirection": "Order (direction)",
"expandedLayout": "Layout"
"expandedLayout": "Layout",
"dateFilter": "Date Filter"
},
"options": {
"mode": {
@@ -37,7 +37,15 @@
"emailAccounts": "Personal Accounts",
"hasAttachment": "Has Attachment",
"assignedUsers": "Assigned Users",
"sentBy": "Sent By"
"sentBy": "Sent By",
"ccEmailAddresses": "CC Email Addresses",
"messageId": "Message Id",
"messageIdInternal": "Message Id (Internal)",
"folderId": "Folder Id",
"fromName": "From Name",
"fromString": "From String",
"isSystem": "Is System"
},
"links": {
"replied": "Replied",
@@ -18,7 +18,8 @@
"CheckEmailAccounts": "Check Personal Email Accounts",
"SendEmailReminders": "Send Email Reminders",
"AuthTokenControl": "Auth Token Control",
"SendEmailNotifications": "Send Email Notifications"
"SendEmailNotifications": "Send Email Notifications",
"CheckNewVersion": "Check for New Version"
},
"cronSetup": {
"linux": "Note: Add this line to the crontab file to run Espo Scheduled Jobs:",
@@ -68,10 +68,10 @@
"theme": "Theme",
"userThemesDisabled": "Disable User Themes",
"emailMessageMaxSize": "Email Max Size (Mb)",
"massEmailMaxPerHourCount": "Max count of emails sent per hour",
"massEmailMaxPerHourCount": "Max number of emails sent per hour",
"personalEmailMaxPortionSize": "Max email portion size for personal account fetching",
"inboundEmailMaxPortionSize": "Max email portion size for group account fetching",
"maxEmailAccountCount": "Max count of personal email accounts per user",
"maxEmailAccountCount": "Max number of personal email accounts per user",
"authTokenLifetime": "Auth Token Lifetime (hours)",
"authTokenMaxIdleTime": "Auth Token Max Idle Time (hours)",
"dashboardLayout": "Dashboard Layout (default)",
@@ -81,7 +81,8 @@
"forbidden": "Forbidden, please try later",
"uniqueLinkHasBeenSent": "The unique URL has been sent to the specified email address.",
"passwordChangedByRequest": "Password has been changed.",
"setupSmtpBefore": "You need to setup <a href=\"{url}\">SMTP settings</a> to make the system be able to send password in email."
"setupSmtpBefore": "You need to setup <a href=\"{url}\">SMTP settings</a> to make the system be able to send password in email.",
"userNameExists": "User Name already exists"
},
"options": {
"gender": {
@@ -46,6 +46,9 @@
"desc": "Mažėjančia tvarka"
}
},
"messages": {
"linkAlreadyExists": "Sąsajos pavadinimo konfliktas."
},
"tooltips": {
"statusField": "Šio lauko atnaujinimai įrašomi sraute.",
"textFilterFields": "Laukeliai, naudojami teksto paieškoje.",
@@ -42,7 +42,7 @@
"PhoneNumber": "Telefono numeris"
},
"scopeNamesPlural": {
"Email": "El. laiška",
"Email": "El. laiškai",
"User": "Vartotojai",
"Team": "Komandos",
"Role": "Rolės",
@@ -54,7 +54,7 @@
"ExternalAccount": "Išorinės paskyros",
"Extension": "Plėtiniai",
"Dashboard": "Prietaisų skydelis",
"InboundEmail": "Grupės el. pašto paskyros ",
"InboundEmail": "Grupės el. pašto paskyros",
"Stream": "Srautas",
"Import": "Importuoti rezultatus",
"Template": "Šablonai",
@@ -196,6 +196,7 @@
"List View": "Rodyti kaip sąrašą\n",
"Tree View": "Rodyti kaip medį",
"Unlink All": "Atsieti visus",
"Total": "Iš viso",
"Print to PDF": "Spausdinti į PDF",
"Default": "Numatytas",
"Number": "Numeris",
@@ -291,7 +292,7 @@
"firstName": "Vardas",
"lastName": "Pavardė",
"salutationName": "Sveikinimas",
"assignedUser": "Priskirta vartotojas",
"assignedUser": "Priskirtas darbuotojas",
"assignedUsers": "Priskirti vartotojai",
"emailAddress": "El. paštas",
"assignedUserName": "Priskirto vartotojo vardas",
@@ -313,7 +314,7 @@
"children": "Antrinis"
},
"links": {
"assignedUser": "Priskirta vartotojas",
"assignedUser": "Priskirtas darbuotojas",
"createdBy": "Sukurta",
"modifiedBy": "Koreguota",
"team": "Komanda",
@@ -26,6 +26,7 @@
"Header Row Value": "Antraštinės eilutės vertė",
"Field": "Laukelis",
"What to Import?": "Ką importuoti?",
"Entity Type": "Vedinio tipas",
"What to do?": "Ką daryti?",
"Properties": "Savybės",
"Header Row": "Antraštinė eilutė",
@@ -56,6 +57,7 @@
},
"fields": {
"file": "Failas",
"entityType": "Vedinio tipas",
"imported": "Importuoti įrašai",
"duplicates": "Dublikuoti įrašai",
"updated": "Atnaujinti įrašai",
@@ -1,5 +1,6 @@
{
"fields": {
"enabled": "Įjungta",
"clientId": "Kliento ID",
"redirectUri": "Nukreipti URL",
"apiKey": "API raktas"
@@ -41,13 +41,12 @@
"exportDisabled": "Išjungti eksportavimą (leidžiama tik admionistratoriui)",
"b2cMode": "B2C režimas",
"avatarsDisabled": "Išjungti piktogramas",
"displayListViewRecordCount": "Rodyti visus (sąrašo rodmenyje)",
"theme": "Tema",
"userThemesDisabled": "Išjungti naudotojo temas",
"emailMessageMaxSize": "Maksimalus el. laiško dydis (Mb)",
"massEmailMaxPerHourCount": "Maksimalus per valandą siunčiamų el. laiškų skaičius",
"personalEmailMaxPortionSize": "Maksimalus el. pašto siuntimo dydis asmeninei paskyrai",
"inboundEmailMaxPortionSize": "Maksimalus el. laiškų kiekis grupės paskyrai laikomas",
"maxEmailAccountCount": "Maksimalus el. pašto paskyrų kiekis vienam vartotojui",
"authTokenLifetime": "Autentikavimo žymės gyvavimas (valandomis)",
"authTokenMaxIdleTime": "Autentikavimo žymės maksimalus laikas (valandomis)",
"dashboardLayout": "Prietaisų skydelio išdėstymas (numatytas)",
@@ -3,13 +3,11 @@
"rows": [
[
{"name": "name"},
{"name": "exportPermission"}
],
[
{"name": "assignmentPermission"},
{"name": "exportPermission"},
{"name": "userPermission"}
],
[
{"name": "assignmentPermission"},
{"name": "portalPermission"},
{"name": "groupEmailAccountPermission"}
]
@@ -2,7 +2,8 @@
{
"label": "Currency Settings",
"rows": [
[{"name": "defaultCurrency"}, {"name": "currencyList"}]
[{"name": "defaultCurrency"}, {"name": "currencyFormat"}],
[{"name": "currencyList"}, {"name": "currencyDecimalPlaces"}]
]
},
{
@@ -11,12 +11,10 @@
{
"label": "Locale",
"rows": [
[{"name": "dateFormat"}, {"name": "timeZone"}],
[{"name": "timeFormat"}, {"name": "weekStart"}],
[{"name": "language"}, false],
[{"name": "currencyFormat"}, {"name": "thousandSeparator"}],
[{"name": "currencyDecimalPlaces"}, {"name": "decimalMark"}],
[{"name": "addressFormat"}, false],
[{"name": "language"}, {"name": "timeZone"}],
[{"name": "dateFormat"}, {"name": "weekStart"}],
[{"name": "timeFormat"}, {"name": "thousandSeparator"}],
[{"name": "addressFormat"}, {"name": "decimalMark"}],
[{"name": "addressPreview"}, false]
]
@@ -64,9 +64,6 @@
"Attachment": {
"parent": false
},
"User": {
"gender": false
},
"EmailFolder": {
"assignedUser": false
},
@@ -78,8 +75,30 @@
},
"default": {
"scopeLevel": {
"User": {
"read": "all"
}
},
"fieldLevel": {
},
"scopeFieldLevel": {
"User": {
"gender": false
}
}
},
"strictDefault": {
"scopeLevel": {
"User": {
"read": "own"
}
},
"fieldLevel": {
},
"scopeFieldLevel": {
"User": {
"gender": false
}
}
},
"valuePermissionList": [
@@ -80,12 +80,13 @@
"isInternal": false,
"isGlobal": false
},
"User": {
"gender": false
"Email": {
"inboundEmails": false,
"emailAccounts": false
}
}
},
"default": {
"strictDefault": {
"scopeLevel": {
},
"fieldLevel": {
@@ -113,22 +114,21 @@
"edit": "no"
},
"leads": false
},
"KnowledgeBaseArticle": {
"assignedUser": false
},
"User": {
"gender": false
}
}
},
"valuePermissionList": [
"exportPermission"
],
"permissionsDefaults": {
"exportPermission": "no"
},
"permissionsStrictDefaults": {
"exportPermission": "no"
},
"scopeLevelTypesDefaults": {
"boolean": false,
"record": false
},
"scopeLevelTypesStrictDefaults": {
"boolean": false,
"record": false
@@ -131,7 +131,8 @@
"messageId": {
"type": "varchar",
"maxLength": 255,
"readOnly": true
"readOnly": true,
"index": true
},
"messageIdInternal": {
"type": "varchar",
@@ -416,9 +417,6 @@
"textFilterFields": ["name", "bodyPlain", "body"]
},
"indexes": {
"dateSentAssignedUser": {
"columns": ["dateSent", "assignedUserId"]
},
"dateSent": {
"columns": ["dateSent", "deleted"]
},
@@ -39,6 +39,9 @@
"link": "scheduledJob",
"field": "job"
},
"pid": {
"type": "int"
},
"attempts": {
"type": "int"
},
@@ -66,8 +66,8 @@
"asc": true
},
"jobSchedulingMap": {
"CheckInboundEmails": "*/4 * * * *",
"CheckEmailAccounts": "*/5 * * * *",
"CheckInboundEmails": "*/2 * * * *",
"CheckEmailAccounts": "*/1 * * * *",
"SendEmailReminders": "*/2 * * * *",
"Cleanup": "1 1 * * 0",
"AuthTokenControl": "*/6 * * * *",
@@ -78,7 +78,8 @@
"isSystem": true,
"scheduling": "1 */12 * * *"
},
"NewVersionChecker": {
"CheckNewVersion": {
"name": "Check for New Version",
"isSystem": true,
"scheduling": "15 5 * * *"
}
@@ -337,10 +337,14 @@
"tooltip": true
},
"inboundEmailMaxPortionSize": {
"type": "int"
"type": "int",
"min": 1,
"max": 500
},
"personalEmailMaxPortionSize": {
"type": "int"
"type": "int",
"min": 1,
"max": 500
},
"maxEmailAccountCount": {
"type": "int"
@@ -5,5 +5,12 @@
"dashboardCellMargin": 19,
"navbarHeight": 44,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartSuccessColor": "#87C956",
"chartTickColor": "#e8eced",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -5,5 +5,12 @@
"dashboardCellMargin": 19,
"navbarHeight": 44,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#87C956",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -8,5 +8,12 @@
"dashboardCellHeight": 155,
"dashboardCellMargin": 19,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#87C956",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -5,5 +5,12 @@
"dashboardCellMargin": 19,
"navbarHeight": 44,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#85b75f",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -8,5 +8,12 @@
"dashboardCellHeight": 155,
"dashboardCellMargin": 19,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#85b75f",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -5,5 +5,12 @@
"dashboardCellMargin": 19,
"navbarHeight": 44,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#83CD77",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -8,5 +8,12 @@
"dashboardCellHeight": 155,
"dashboardCellMargin": 19,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#83CD77",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -5,5 +5,12 @@
"dashboardCellMargin": 19,
"navbarHeight": 44,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"textColor": "#333",
"fontSize": 14,
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#7BC169",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -8,5 +8,12 @@
"dashboardCellHeight": 155,
"dashboardCellMargin": 19,
"modalFooterAtTheTop": true,
"modalFullHeight": true
"modalFullHeight": true,
"fontSize": 14,
"textColor": "#333",
"chartGridColor": "#ddd",
"chartTickColor": "#e8eced",
"chartSuccessColor": "#7BC169",
"chartColorList": ["#6FA8D6", "#4E6CAD", "#EDC555", "#ED8F42", "#DE6666", "#7CC4A4", "#8A7CC2", "#D4729B"],
"chartColorAlternativeList": ["#6FA8D6", "#EDC555", "#ED8F42", "#7CC4A4", "#D4729B"]
}
@@ -31,7 +31,7 @@ namespace Espo\Services;
class AdminNotifications extends \Espo\Core\Services\Base
{
public function newVersionChecker($data)
public function jobCheckNewVersion($data)
{
$config = $this->getConfig();
+10
View File
@@ -209,4 +209,14 @@ class App extends \Espo\Core\Services\Base
}
return $value;
}
public function jobClearCache()
{
$this->getInjection('container')->get('dataManager')->clearCache();
}
public function jobRebuild()
{
$this->getInjection('container')->get('dataManager')->rebuild();
}
}
+4 -2
View File
@@ -119,9 +119,11 @@ class Email extends Record
}
}
}
$emailAccountService = $this->getServiceFactory()->create('EmailAccount');
$emailAccount = $emailAccountService->findAccountForUser($this->getUser(), $fromAddress);
if (!$smtpParams) {
$emailAccountService = $this->getServiceFactory()->create('EmailAccount');
$emailAccount = $emailAccountService->findAccountForUser($this->getUser(), $fromAddress);
if ($emailAccount) {
$smtpParams = $emailAccountService->getSmtpParamsFromAccount($emailAccount);
if ($smtpParams) {
+4 -1
View File
@@ -320,8 +320,8 @@ class EmailAccount extends Record
}
if (!empty($email)) {
$this->getEntityManager()->getRepository('EmailAccount')->relate($emailAccount, 'emails', $email);
if (!$email->isFetched()) {
$this->getEntityManager()->getRepository('EmailAccount')->relate($emailAccount, 'emails', $email);
$this->noteAboutEmail($email);
}
}
@@ -368,6 +368,9 @@ class EmailAccount extends Record
$email = $importer->importMessage($parserName, $message, $userId, $teamIdList, $userIdList, $filterCollection, $fetchOnlyHeader, $folderData);
} catch (\Exception $e) {
$GLOBALS['log']->error('EmailAccount '.$emailAccount->id.' (Import Message w/ '.$parserName.'): [' . $e->getCode() . '] ' .$e->getMessage());
if ($e->getCode() === 'HY000' && strpos($e->getMessage(), '1100') !== false) {
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
}
}
return $email;
}
@@ -394,6 +394,9 @@ class InboundEmail extends \Espo\Services\Record
$email = $importer->importMessage($parserName, $message, $userId, $teamIdList, $userIdList, $filterCollection, $fetchOnlyHeader, $folderData);
} catch (\Exception $e) {
$GLOBALS['log']->error('InboundEmail '.$emailAccount->id.' (Import Message w/ '.$parserName.'): [' . $e->getCode() . '] ' .$e->getMessage());
if ($e->getCode() === 'HY000' && strpos($e->getMessage(), '1100') !== false) {
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
}
}
return $email;
}
+16 -12
View File
@@ -282,13 +282,10 @@ class Record extends \Espo\Core\Services\Base
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', array());
foreach ($fieldDefs as $field => $defs) {
if (isset($defs['type']) && $defs['type'] == 'linkParent') {
$id = $entity->get($field . 'Id');
$scope = $entity->get($field . 'Type');
if ($scope) {
if ($foreignEntity = $this->getEntityManager()->getEntity($scope, $id)) {
$entity->set($field . 'Name', $foreignEntity->get('name'));
}
$parentId = $entity->get($field . 'Id');
$parentType = $entity->get($field . 'Type');
if ($parentId && $parentType) {
$entity->loadParentNameField($field);
}
}
}
@@ -1065,7 +1062,7 @@ class Record extends \Espo\Core\Services\Base
$ids = $params['ids'];
foreach ($ids as $id) {
$entity = $this->getEntity($id);
if ($this->getAcl()->check($entity, 'edit')) {
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
$entity->set($data);
if ($this->checkAssignment($entity)) {
if ($repository->save($entity)) {
@@ -1095,7 +1092,7 @@ class Record extends \Espo\Core\Services\Base
$collection = $repository->find($selectParams);
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'edit')) {
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
$entity->set($data);
if ($this->checkAssignment($entity)) {
if ($repository->save($entity)) {
@@ -1128,6 +1125,11 @@ class Record extends \Espo\Core\Services\Base
return true;
}
protected function checkEntityForMassUpdate(Entity $entity, $data)
{
return true;
}
public function massRemove(array $params)
{
$idsRemoved = array();
@@ -1306,14 +1308,16 @@ class Record extends \Espo\Core\Services\Base
return false;
}
public function checkAttributeIsAllowedForExport($entity, $attribute)
public function checkAttributeIsAllowedForExport($entity, $attribute, $isExportAllFields = false)
{
$entity = $this->getEntityManager()->getEntity($this->getEntityType());
if (in_array($attribute, $this->internalAttributeList)) {
return false;
}
if (!$isExportAllFields) {
return true;
}
$isNotStorable = $entity->getAttributeParam($attribute, 'notStorable');
if (!$isNotStorable) {
return true;
@@ -1441,7 +1445,7 @@ class Record extends \Espo\Core\Services\Base
if (in_array($attribute, $attributeListToSkip)) {
continue;
}
if ($this->checkAttributeIsAllowedForExport($seed, $attribute)) {
if ($this->checkAttributeIsAllowedForExport($seed, $attribute, true)) {
$attributeList[] = $attribute;
}
}
+28 -6
View File
@@ -212,6 +212,15 @@ class User extends Record
return $passwordHash->hash($password);
}
protected function filterInput($data)
{
parent::filterInput($data);
if (!$this->getUser()->get('isSuperAdmin')) {
unset($data->isSuperAdmin);
}
}
public function createEntity($data)
{
$newPassword = null;
@@ -219,9 +228,6 @@ class User extends Record
$newPassword = $data->password;
$data->password = $this->hashPassword($data->password);
}
if (!$this->getUser()->get('isSuperAdmin')) {
unset($data->isSuperAdmin);
}
$user = parent::createEntity($data);
@@ -251,9 +257,6 @@ class User extends Record
unset($data->isActive);
unset($data->isPortalUser);
}
if (!$this->getUser()->get('isSuperAdmin')) {
unset($data->isSuperAdmin);
}
$user = parent::updateEntity($id, $data);
@@ -470,6 +473,25 @@ class User extends Record
return true;
}
protected function checkEntityForMassUpdate(Entity $entity, $data)
{
if ($entity->id == 'system') {
return false;
}
if ($entity->id == $this->getUser()->id) {
if (property_exists($data, 'isActive')) {
return false;
}
if (property_exists($data, 'isPortalUser')) {
return false;
}
if (property_exists($data, 'isSuperAdmin')) {
return false;
}
}
return true;
}
public function afterUpdate(Entity $entity, array $data = array())
{
parent::afterUpdate($entity, $data);
+1920 -2
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -1158,7 +1158,11 @@ var Bull = Bull || {};
var proceed = function (layoutTemplate) {
var injection = _.extend(layoutDefs, data || {});
callback(_.template(layoutTemplate, injection));
var template = _.template(layoutTemplate, injection);
if (typeof template === 'function') {
template = template(injection);
}
callback(template);
}.bind(this);
var layoutTemplate = this._getCachedLayoutTemplate(layoutType);
+3 -3
View File
File diff suppressed because one or more lines are too long
@@ -158,6 +158,10 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
if (Object.prototype.toString.call(this.enabledScopeList) !== '[object Array]') {
this.enabledScopeList = [];
}
this.once('remove', function () {
this.isRemoved = true;
}, this);
},
toggleScopeFilter: function (name) {
@@ -376,6 +380,7 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
},
adjustSize: function () {
if (this.isRemoved) return;
var height = this.getCalculatedHeight();
this.$calendar.fullCalendar('option', 'contentHeight', height);
},
@@ -36,17 +36,38 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
thousandSeparator: ',',
colors: ['#6FA8D6', '#4E6CAD', '#EDC555', '#ED8F42', '#DE6666', '#7CC4A4', '#8A7CC2', '#D4729B'],
defaultColorList: ['#6FA8D6', '#4E6CAD', '#EDC555', '#ED8F42', '#DE6666', '#7CC4A4', '#8A7CC2', '#D4729B'],
successColor: '#85b75f',
outlineColor: '#333',
gridColor: '#ddd',
tickColor: '#e8eced',
textColor: '#333',
hoverColor: '#FF3F19',
legendColumnWidth: 90,
legendColumnNumber: 6,
labelFormatter: function (v) {
return '<span style="color:'+this.textColor+'">' + v + '</span>';
},
init: function () {
Dep.prototype.init.call(this);
this.flotr = Flotr;
this.successColor = this.getThemeManager().getParam('chartSuccessColor') || this.successColor;
this.colorList = this.getThemeManager().getParam('chartColorList') || this.defaultColorList;
this.tickColor = this.getThemeManager().getParam('chartTickColor') || this.tickColor;
this.gridColor = this.getThemeManager().getParam('chartGridColor') || this.gridColor;
this.textColor = this.getThemeManager().getParam('textColor') || this.textColor;
this.hoverColor = this.getThemeManager().getParam('hoverColor') || this.hoverColor;
if (this.getPreferences().has('decimalMark')) {
this.decimalMark = this.getPreferences().get('decimalMark')
} else {
@@ -64,7 +85,7 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
this.once('after:render', function () {
$(window).on('resize.chart' + this.name, function () {
this.drow();
this.draw();
}.bind(this));
}, this);
@@ -73,34 +94,86 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
}, this);
},
formatNumber: function (value) {
formatNumber: function (value, isCurrency) {
if (value !== null) {
var maxDecimalPlaces = 2;
var currencyDecimalPlaces = this.getConfig().get('currencyDecimalPlaces');
if (isCurrency) {
if (currencyDecimalPlaces === 0) {
value = Math.round(value);
} else if (currencyDecimalPlaces) {
value = Math.round(value * Math.pow(10, currencyDecimalPlaces)) / (Math.pow(10, currencyDecimalPlaces));
} else {
value = Math.round(value * Math.pow(10, maxDecimalPlaces)) / (Math.pow(10, maxDecimalPlaces));
}
} else {
var maxDecimalPlaces = 4;
value = Math.round(value * Math.pow(10, maxDecimalPlaces)) / (Math.pow(10, maxDecimalPlaces));
}
var parts = value.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
if (parts[1] == 0) {
parts.splice(1, 1);
if (isCurrency) {
if (currencyDecimalPlaces === 0) {
delete parts[1];
} else if (currencyDecimalPlaces) {
var decimalPartLength = 0;
if (parts.length > 1) {
decimalPartLength = parts[1].length;
} else {
parts[1] = '';
}
if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) {
var limit = currencyDecimalPlaces - decimalPartLength;
for (var i = 0; i < limit; i++) {
parts[1] += '0';
}
}
}
}
return parts.join(this.decimalMark);
var value = parts.join(this.decimalMark);
return value;
}
return '';
},
getLegentColumnNumber: function () {
var width = this.$el.closest('.panel-body').width();
var legendColumnNumber = Math.floor(width / this.legendColumnWidth);
return legendColumnNumber || this.legendColumnNumber;
},
getLegentHeight: function () {
var lineNumber = Math.ceil(this.chartData.length / this.getLegentColumnNumber());
var legendHeight = 0;
var lineHeight = this.getThemeManager().getParam('dashletChartLegentRowHeight') || 22;
if (lineNumber > 0) {
legendHeight = lineHeight * lineNumber;
}
return legendHeight;
},
afterRender: function () {
this.$el.closest('.panel-body').css({
'overflow-y': 'visible',
'overflow-x': 'visible'
});
this.fetch(function (data) {
this.chartData = this.prepareData(data);
var $container = this.$container = this.$el.find('.chart-container');
var height = 'calc(100% - 22px)';
if (this.chartData.length > 5) {
height = 'calc(100% - 42px)';
}
$container.css('height', height);
var legendHeight = this.getLegentHeight();
var heightCss = 'calc(100% - '+legendHeight.toString()+'px)';
$container.css('height', heightCss);
setTimeout(function () {
if (!$container.size() || !$container.is(":visible")) return;
this.drow();
this.draw();
}.bind(this), 1);
});
},
@@ -121,6 +194,9 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
});
},
getDateFilter: function () {
return this.getOption('dateFilter') || 'currentYear';
}
});
});
@@ -33,7 +33,12 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
name: 'OpportunitiesByLeadSource',
url: function () {
return 'Opportunity/action/reportByLeadSource?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
var url = 'Opportunity/action/reportByLeadSource?dateFilter='+ this.getDateFilter();
if (this.getDateFilter() === 'between') {
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
}
return url;
},
prepareData: function (response) {
@@ -55,13 +60,13 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
setup: function () {
this.currency = this.getConfig().get('defaultCurrency');
this.currencySymbol = '';
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
},
drow: function () {
draw: function () {
var self = this;
this.flotr.draw(this.$container.get(0), this.chartData, {
colors: this.colors,
colors: this.colorList,
shadowSize: false,
pie: {
show: true,
@@ -69,18 +74,25 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
lineWidth: 1,
fillOpacity: 1,
sizeRatio: 0.8,
labelFormatter: function (total, value) {
var percentage = (100 * value / total).toFixed(2);
if (percentage < 7) return '';
return '<span class="small" style="font-size: 0.8em;color:'+this.textColor+'">'+ percentage.toString() +'%' + '</span>';
}
},
grid: {
horizontalLines: false,
verticalLines: false,
outline: '',
color: this.outlineColor
tickColor: this.tickColor
},
yaxis: {
showLabels: false,
color: this.textColor
},
xaxis: {
showLabels: false,
color: this.textColor
},
legend: {
show: false,
@@ -88,20 +100,26 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
mouse: {
track: true,
relative: true,
lineColor: this.hoverColor,
trackFormatter: function (obj) {
return self.formatNumber(obj.y) + ' ' + self.currency;
},
var value = self.currencySymbol + self.formatNumber(obj.y, true);
var fraction = obj.fraction || 0;
var percentage = (100 * fraction).toFixed(2).toString();
return (obj.series.label || self.translate('None')) + ':<br>' + value + ' / ' + percentage + '%';
}
},
legend: {
show: true,
noColumns: 8,
noColumns: this.getLegentColumnNumber(),
container: this.$el.find('.legend-container'),
labelBoxMargin: 0
},
labelBoxMargin: 0,
labelFormatter: self.labelFormatter.bind(self),
labelBoxBorderColor: 'transparent',
backgroundOpacity: 0
}
});
},
}
});
});
@@ -38,7 +38,12 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
},
url: function () {
return 'Opportunity/action/reportByStage?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
var url = 'Opportunity/action/reportByStage?dateFilter='+ this.getDateFilter();
if (this.getDateFilter() === 'between') {
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
}
return url;
},
prepareData: function (response) {
@@ -60,7 +65,7 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
data: [[item.value, d.length - i]],
label: this.getLanguage().translateOption(item.stage, 'stage', 'Opportunity'),
}
if (item.stage == 'Closed Won') {
if (item.stagsuccessColore == 'Closed Won') {
o.color = this.successColor;
}
data.push(o);
@@ -73,13 +78,13 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
setup: function () {
this.currency = this.getConfig().get('defaultCurrency');
this.currencySymbol = '';
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
},
drow: function () {
draw: function () {
var self = this;
this.flotr.draw(this.$container.get(0), this.chartData, {
colors: this.colors,
colors: this.colorList,
shadowSize: false,
bars: {
show: true,
@@ -87,44 +92,53 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
shadowSize: 0,
lineWidth: 1,
fillOpacity: 1,
barWidth: 0.5,
barWidth: 0.5
},
grid: {
horizontalLines: false,
outline: 'sw',
color: this.outlineColor
color: this.gridColor,
tickColor: this.tickColor
},
yaxis: {
min: 0,
showLabels: false,
color: this.textColor
},
xaxis: {
min: 0,
color: this.textColor,
tickFormatter: function (value) {
if (value != 0) {
return self.formatNumber(value) + ' ' + self.currency;
if (value == 0) {
return '';
}
if (value % 1 == 0) {
return self.currencySymbol + self.formatNumber(Math.floor(value)).toString();
}
return '';
},
}
},
mouse: {
track: true,
relative: true,
position: 's',
lineColor: this.hoverColor,
trackFormatter: function (obj) {
return self.formatNumber(obj.x) + ' ' + self.currency;
},
var label = (obj.series.label || self.translate('None'));
var value = label + ':<br>' + self.currencySymbol + self.formatNumber(obj.x, true);
return value;
}
},
legend: {
show: true,
noColumns: 5,
noColumns: this.getLegentColumnNumber(),
container: this.$el.find('.legend-container'),
labelBoxMargin: 0
},
labelBoxMargin: 0,
labelFormatter: self.labelFormatter.bind(self),
labelBoxBorderColor: 'transparent',
backgroundOpacity: 0
}
});
},
}
});
});
@@ -0,0 +1,49 @@
/************************************************************************
* 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('crm:views/dashlets/options/chart', 'views/dashlets/options/base', function (Dep) {
return Dep.extend({
setupBeforeFinal: function () {
this.listenTo(this.model, 'change:dateFilter', this.controlDateFilter);
this.controlDateFilter();
},
controlDateFilter: function () {
if (this.model.get('dateFilter') === 'between') {
this.showField('dateFrom');
this.showField('dateTo');
} else {
this.hideField('dateFrom');
this.hideField('dateTo');
}
}
});
});
@@ -38,17 +38,28 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
},
url: function () {
return 'Opportunity/action/reportSalesByMonth?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
var url = 'Opportunity/action/reportSalesByMonth?dateFilter='+ this.getDateFilter();
if (this.getDateFilter() === 'between') {
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
}
return url;
},
getLegentHeight: function () {
return 0;
},
prepareData: function (response) {
var months = this.months = Object.keys(response).sort();
var monthList = this.monthList = response.keyList;
var dataMap = response.dataMap || {};
var values = [];
for (var month in response) {
values.push(response[month]);
}
monthList.forEach(function (month) {
values.push(dataMap[month]);
}, this);
this.chartData = [];
@@ -71,12 +82,12 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
setup: function () {
this.currency = this.getConfig().get('defaultCurrency');
this.currencySymbol = '';
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
this.colorBad = this.successColor;
},
drow: function () {
draw: function () {
var self = this;
this.flotr.draw(this.$container.get(0), this.chartData, {
shadowSize: false,
@@ -86,31 +97,37 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
shadowSize: 0,
lineWidth: 1,
fillOpacity: 1,
barWidth: 0.5,
barWidth: 0.5
},
grid: {
horizontalLines: true,
verticalLines: false,
outline: 'sw',
color: this.outlineColor
color: this.gridColor,
tickColor: this.tickColor
},
yaxis: {
min: 0,
showLabels: true,
color: this.textColor,
tickFormatter: function (value) {
if (value == 0) {
return '';
}
return self.formatNumber(value) + ' ' + self.currency;
},
if (value % 1 == 0) {
return self.currencySymbol + self.formatNumber(Math.floor(value)).toString();
}
return '';
}
},
xaxis: {
min: 0,
color: this.textColor,
tickFormatter: function (value) {
if (value % 1 == 0) {
var i = parseInt(value);
if (i in self.months) {
return moment(self.months[i] + '-01').format('MMM YYYY');
if (i in self.monthList) {
return moment(self.monthList[i] + '-01').format('MMM YYYY');
}
}
return '';
@@ -119,13 +136,17 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
mouse: {
track: true,
relative: true,
lineColor: this.hoverColor,
trackFormatter: function (obj) {
return self.formatNumber(obj.y) + ' ' + self.currency;
},
var i = parseInt(obj.x);
var value = '';
if (i in self.monthList) {
value += moment(self.monthList[i] + '-01').format('MMM YYYY') + ':<br>';
}
return value + self.currencySymbol + self.formatNumber(obj.y, true);
}
}
});
},
})
}
});
});
@@ -38,7 +38,12 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
},
url: function () {
return 'Opportunity/action/reportSalesPipeline?dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
var url = 'Opportunity/action/reportSalesPipeline?dateFilter='+ this.getDateFilter();
if (this.getDateFilter() === 'between') {
url += '&dateFrom=' + this.getOption('dateFrom') + '&dateTo=' + this.getOption('dateTo');
}
return url;
},
prepareData: function (response) {
@@ -80,63 +85,15 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
setup: function () {
this.currency = this.getConfig().get('defaultCurrency');
this.currencySymbol = '';
var data = [
{
value: 12000,
stage: 'Prospecting'
},
{
value: 5050,
stage: 'Qualification'
},
{
value: 4050,
stage: 'Needs Analysis'
},
{
value: 3230,
stage: 'Value Proposition'
},
{
value: 2000,
stage: 'Proposal/Price Quote'
},
{
value: 1200.5,
stage: 'Negotiation/Review'
},
{
value: 700,
stage: 'Closed Won'
},
];
this.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', this.currency]) || '';
this.chartData = [];
for (var i = 0; i < data.length; i++) {
var item = data[i];
var value = item.value;
var nextValue = ((i + 1) < data.length) ? data[i + 1].value : value;
var o = {
data: [[i, value], [i + 1, nextValue]],
label: item.stage
};
this.chartData.push(o);
}
this.maxY = 1000;
if (data.length) {
this.maxY = data[0].value + (data[0].value / 20);
}
},
drow: function () {
draw: function () {
var self = this;
var colors = Espo.Utils.clone(this.colors);
var colors = Espo.Utils.clone(this.colorList);
this.chartData.forEach(function (item, i) {
if (i + 1 > colors.length) {
@@ -154,46 +111,51 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
lines: {
show: true,
fill: true,
fillOpacity: 1,
fillOpacity: 1
},
points: {
show: true,
show: true
},
grid: {
horizontalLines: false,
outline: 'sw',
color: this.outlineColor
color: this.tickColor,
verticalLines: false,
outline: 'ew',
tickColor: this.tickColor
},
yaxis: {
min: 0,
max: this.maxY,
showLabels: false,
showLabels: false
},
xaxis: {
min: 0,
showLabels: false,
showLabels: false
},
mouse: {
track: true,
relative: true,
position: 'ne',
lineColor: this.hoverColor,
trackFormatter: function (obj) {
if (obj.x >= self.chartData.length) {
return null;
}
return self.formatNumber(obj.y) + ' ' + self.currency;
},
var label = self.chartData[parseInt(obj.x)].label;
var label = (label || self.translate('None'));
return label + ':<br>' + self.currencySymbol + self.formatNumber(obj.y, true);
}
},
legend: {
show: true,
noColumns: 5,
noColumns: this.getLegentColumnNumber(),
container: this.$el.find('.legend-container'),
labelBoxMargin: 0
},
labelBoxMargin: 0,
labelFormatter: self.labelFormatter.bind(self),
labelBoxBorderColor: 'transparent',
backgroundOpacity: 0
}
});
},
}
});
});
+49 -2
View File
@@ -19,8 +19,55 @@
<% _.each(panel.rows, function (row, rowNumber) { %>
<div class="row">
<% _.each(row, function (cell, cellNumber) { %>
<%
var spanClassBase;
if (columnCount === 1) {
spanClassBase = 'col-sm-12';
} else if (columnCount === 2) {
spanClassBase = 'col-sm-6';
} else if (columnCount === 3) {
spanClassBase = 'col-sm-4';
} else if (columnCount === 4) {
spanClassBase = 'col-md-3 col-sm-6';
} else {
spanClass = 'col-sm-12';
}
%>
<% if (cell != false) { %>
<div class="cell<% if (columnCount == 1 || cell.fullWidth) { %> col-sm-12<% } else {%> col-sm-6<% } %> form-group<% if (cell.name) { %>{{#if hiddenFields.<%= cell.name %>}} hidden-cell{{/if}}<% } %>" data-name="<%= cell.name %>">
<%
var spanClass;
if (columnCount === 1 || cell.fullWidth) {
spanClass = 'col-sm-12';
} else if (columnCount === 2) {
if (cell.span === 2) {
spanClass = 'col-sm-12';
} else {
spanClass = 'col-sm-6';
}
} else if (columnCount === 3) {
if (cell.span === 2) {
spanClass = 'col-sm-8';
} else if (cell.span === 3) {
spanClass = 'col-sm-12';
} else {
spanClass = 'col-sm-4';
}
} else if (columnCount === 4) {
if (cell.span === 2) {
spanClass = 'col-sm-6';
} else if (cell.span === 3) {
spanClass = 'col-sm-9';
} else if (cell.span === 4) {
spanClass = 'col-sm-12';
} else {
spanClass = 'col-md-3 col-sm-6';
}
} else {
spanClass = 'col-sm-12';
}
%>
<div class="cell <%= spanClass %> form-group<% if (cell.name) { %>{{#if hiddenFields.<%= cell.name %>}} hidden-cell{{/if}}<% } %>" data-name="<%= cell.name %>">
<% if (!cell.noLabel) { %><label class="control-label<% if (cell.name) { %>{{#if hiddenFields.<%= cell.name %>}} hidden{{/if}}<% } %>" data-name="<%= cell.name %>"><span class="label-text"><%
if ('customLabel' in cell) {
print (cell.customLabel);
@@ -37,7 +84,7 @@
%></div>
</div>
<% } else { %>
<div class="col-sm-6"></div>
<div class="<%= spanClassBase %>"></div>
<% } %>
<% }); %>
</div>
-2
View File
@@ -6,7 +6,6 @@
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-body">
<section>
<h5>Version {{version}}</h5>
<p>
Copyright &copy; 2014-2018 EspoCRM: Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko.
@@ -39,7 +38,6 @@
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.
</p>
</section>
</div>
</div>
@@ -6,7 +6,7 @@
<div class="row">
<div class="col-sm-6">
<div class="panel">
<div class="panel-body">
<div class="panel-body panel-body-form">
<div class="cell form-group" data-name="enabled">
<label class="control-label" data-name="enabled">{{translate 'enabled' scope='Integration' category='fields'}}</label>
<div class="field" data-name="enabled">{{{enabled}}}</div>
@@ -6,7 +6,7 @@
<div class="row">
<div class="col-sm-6">
<div class="panel">
<div class="panel-body">
<div class="panel-body panel-body-form">
<div class="cell form-group" data-name="enabled">
<label class="control-label" data-name="enabled">{{translate 'enabled' scope='Integration' category='fields'}}</label>
<div class="field" data-name="enabled">{{{enabled}}}</div>
@@ -1,4 +1,4 @@
<header data-style="{{style}}">
<header data-style="{{style}}" data-name="{{name}}">
<label data-is-custom="{{#if isCustomLabel}}true{{/if}}">{{label}}</label>&nbsp;
<a href="javascript:" data-action="edit-panel-label" class="edit-panel-label"><i class="glyphicon glyphicon-pencil"></i></a>
<a href="javascript:" style="float: right;" data-action="removePanel" class="remove-panel" data-number="{{number}}"><i class="glyphicon glyphicon-remove"></i></a>
+1 -1
View File
@@ -142,7 +142,7 @@
<div id="layout-panel-tpl" style="display: none;">
<li>
<header data-style="<%= style %>">
<header data-style="<%= style %>" data-name="<%= name %>">
<label data-is-custom="<%= isCustomLabel ? 'true' : '' %>"><%= label %></label>&nbsp;
<a href="javascript:" data-action="edit-panel-label" class="edit-panel-label"><i class="glyphicon glyphicon-pencil"></i></a>
<a href="javascript:" style="float: right;" data-action="removePanel" class="remove-panel"><i class="glyphicon glyphicon-remove"></i></a>
+2 -2
View File
@@ -12,8 +12,8 @@
</div>
{{/if}}
<div style="margin-bottom: 10px;" class="hidden message-container"><span class="text-success"></span></div>
<div class="button-container">
<div class="hidden message-container margin-bottom"><span class="text-success"></span></div>
<div>
<button class="btn btn-default action {{#if cacheIsEnabled}}hidden{{/if}}" data-action="returnToApplication">{{translate 'Return to Application'}}</button>
</div>
</div>
@@ -0,0 +1,5 @@
{{#if value}}
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
{{else}}
{{translate 'None'}}
{{/if}}
@@ -0,0 +1,5 @@
{{#if value}}
<span title="{{currencySymbol}}{{value}}">{{currencySymbol}}{{value}}</span>
{{else}}
{{translate 'None'}}
{{/if}}
@@ -0,0 +1,5 @@
{{#if value}}
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
{{else}}
{{translate 'None'}}
{{/if}}

Some files were not shown because too many files have changed in this diff Show More