Compare commits

...

30 Commits
3.2.0 ... 3.2.2

Author SHA1 Message Date
yuri
6f45e26028 version 2015-04-24 10:04:19 +03:00
Taras Machyshyn
f6892877c1 Extension can be installed with the same version 2015-04-23 15:44:31 +03:00
yuri
c7cc44edf6 Merge branch 'hotfix/3.2.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/3.2.2 2015-04-23 11:56:41 +03:00
yuri
e958d7c488 fix inbound email 2015-04-23 11:55:25 +03:00
Taras Machyshyn
77ff11990b Installer: disable prompt password saving while installing 2015-04-23 11:48:23 +03:00
Taras Machyshyn
994e0c1eda Extensions: improved restore functionality of install script 2015-04-23 10:48:42 +03:00
Taras Machyshyn
d51e88be60 Minor bug fixes for fileManager 2015-04-23 10:42:24 +03:00
yuri
9595d528a8 wysywyg strip base tag 2015-04-17 15:22:29 +03:00
yuri
9bfb27e10a layout pretty print 2015-04-17 11:57:00 +03:00
yuri
be50630d2f fix primary address 2015-04-17 11:51:16 +03:00
yuri
d8b629a5a0 fix email note if sent is fail 2015-04-16 10:31:03 +03:00
yuri
6d0b39f2b6 fix email note 2015-04-15 16:43:06 +03:00
yuri
b778f74628 Test Connection for email account 2015-04-15 12:10:59 +03:00
yuri
6db658c0b3 fix follow button 2015-04-15 10:47:03 +03:00
yuri
6fb88665bb messageId fix 2015-04-15 10:05:08 +03:00
yuri
f20a47542a disable escape for quick edit 2015-04-14 14:30:55 +03:00
yuri
c77e3b7e92 link field placeholder 2015-04-14 12:28:50 +03:00
yuri
e7b033304c clear_cache.php 2015-04-14 12:11:45 +03:00
yuri
2b8a8d23eb fix notifications hook 2015-04-14 11:51:34 +03:00
yuri
0f59f79a9e fix email search 2015-04-14 11:50:05 +03:00
yuri
2904cd1f53 performance tweeks 2015-04-14 11:43:29 +03:00
yuri
d87ae60fea version 2015-04-13 12:16:30 +03:00
yuri
0d17cf6e78 lang cache issue 2015-04-13 11:53:22 +03:00
yuri
81739d59f5 quickDetailDisabled 2015-04-13 11:07:16 +03:00
yuri
7704d5afd0 layout change 2015-04-13 10:43:18 +03:00
yuri
da268f2b29 note email from === parent 2015-04-13 10:30:57 +03:00
yuri
01dc251ef2 fix email RE 2015-04-13 10:17:20 +03:00
yuri
9054d70f4c fix email from 2015-04-13 10:14:05 +03:00
yuri
788b265cd4 email fixes 2015-04-13 10:00:48 +03:00
yuri
c96640bcb1 fix orm double join 2015-04-10 15:41:45 +03:00
63 changed files with 627 additions and 182 deletions

View File

@@ -155,6 +155,7 @@ module.exports = function (grunt) {
'bootstrap.php',
'cron.php',
'rebuild.php',
'clear_cache.php',
'upgrade.php',
'index.php',
'LICENSE.txt',

View File

@@ -41,7 +41,7 @@ class Email extends \Espo\Core\Controllers\Record
throw new BadRequest();
}
if (empty($data['password'])) {
if (is_null($data['password'])) {
if ($data['type'] == 'preferences') {
if (!$this->getUser()->isAdmin() && $data['id'] != $this->getUser()->id) {
throw new Forbidden();

View File

@@ -44,5 +44,27 @@ class EmailAccount extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
}
public function actionTestConnection($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (is_null($data['password'])) {
$emailAccount = $this->getEntityManager()->getEntity('EmailAccount', $data['id']);
if (!$emailAccount) {
throw new Error();
}
if ($emailAccount->get('assignedUserId') != $this->getUser()->id && !$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$data['password'] = $this->getContainer()->get('crypt')->decrypt($emailAccount->get('password'));
}
return $this->getRecordService()->testConnection($data);
}
}

View File

@@ -138,6 +138,12 @@ class Application
$dataManager->rebuild();
}
public function runClearCache()
{
$dataManager = $this->getContainer()->get('dataManager');
$dataManager->clearCache();
}
public function isInstalled()
{
$config = $this->getContainer()->get('config');

View File

@@ -110,7 +110,9 @@ class Importer
$dateSent = $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s');
$email->set('dateSent', $dateSent);
}
}
} else {
$email->set('dateSent', date('Y-m-d H:i:s'));
}
if (isset($message->deliveryDate)) {
$dt = new \DateTime($message->deliveryDate);
if ($dt) {

View File

@@ -307,7 +307,7 @@ class Sender
$this->transport->send($message);
$email->set('messageId', $messageId);
$email->set('messageId', '<' . $messageId . '>');
$email->set('status', 'Sent');
$email->set('dateSent', date("Y-m-d H:i:s"));
} catch (\Exception $e) {

View File

@@ -27,13 +27,6 @@ use Espo\Core\Utils\Util;
class Install extends \Espo\Core\Upgrades\Actions\Base
{
/**
* Is copied extension files to Espo
*
* @var [type]
*/
protected $isCopied = null;
/**
* Main installation process
*
@@ -54,8 +47,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
$this->initialize();
$this->isCopied = false;
/** check if an archive is unzipped, if no then unzip */
$packagePath = $this->getPackagePath();
if (!file_exists($packagePath)) {
@@ -77,7 +68,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
if (!$this->copyFiles()) {
$this->throwErrorAndRemovePackage('Cannot copy files.');
}
$this->isCopied = true;
/* remove files defined in a manifest */
$this->deleteFiles(true);
@@ -103,10 +93,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
protected function restoreFiles()
{
if (!$this->isCopied) {
return;
}
$GLOBALS['log']->info('Installer: Restore previous files.');
$backupPath = $this->getPath('backupPath');

View File

@@ -160,7 +160,7 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
$extensionEntity = $this->getExtensionEntity();
if (isset($extensionEntity)) {
$comparedVersion = version_compare($manifest['version'], $extensionEntity->get('version'));
$comparedVersion = version_compare($manifest['version'], $extensionEntity->get('version'), '>=');
if ($comparedVersion <= 0) {
$this->throwErrorAndRemovePackage('You cannot install an older version of this extension.');
}

View File

@@ -472,7 +472,7 @@ class Manager
$sourceFile = is_file($sourcePath) ? $sourcePath : $this->concatPaths(array($sourcePath, $file));
$destFile = $this->concatPaths(array($destPath, $file));
if (file_exists($sourceFile)) {
if (file_exists($sourceFile) && is_file($sourceFile)) {
$res &= copy($sourceFile, $destFile);
}
}

View File

@@ -136,7 +136,7 @@ class Layout
}
$layoutPath = $this->getLayoutPath($controllerName, true);
$data = Json::encode($layoutData);
$data = Json::encode($layoutData, \JSON_PRETTY_PRINT);
$result &= $this->getFileManager()->putContents(array($layoutPath, $layoutName.'.json'), $data);
}

View File

@@ -64,7 +64,7 @@ class Notifications extends \Espo\Core\Hooks\Base
$className = '\\Espo\\Custom\\Notificators\\' . $normalizedName;
if (!class_exists($className)) {
$moduleName = $this->getMetadata()->getScopeModuleName($entityName);
$moduleName = $this->getMetadata()->getScopeModuleName($entityType);
if ($moduleName) {
$className = '\\Espo\\Modules\\' . $moduleName . '\\Notificators\\' . $normalizedName;
} else {

View File

@@ -18,7 +18,7 @@
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
************************************************************************/
namespace Espo\Modules\Crm\Controllers;
@@ -30,9 +30,9 @@ class InboundEmail extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
}
public function actionGetFolders($params, $data, $request)
{
{
return $this->getRecordService()->getFolders(array(
'host' => $request->get('host'),
'port' => $request->get('port'),
@@ -41,7 +41,23 @@ class InboundEmail extends \Espo\Core\Controllers\Record
'password' => $request->get('password'),
'id' => $request->get('id')
));
}
public function actionTestConnection($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (is_null($data['password'])) {
$inboundEmail = $this->getEntityManager()->getEntity('InboundEmail', $data['id']);
if (!$inboundEmail) {
throw new Error();
}
$data['password'] = $this->getContainer()->get('crypt')->decrypt($inboundEmail->get('password'));
}
return $this->getRecordService()->testConnection($data);
}
}

View File

@@ -1 +1,14 @@
[{"label":"","rows":[[{"name":"name"}],[{"name":"status"}],[{"name":"direction"}],[{"name":"dateStart"}],[{"name":"duration"}],[{"name":"parent"}]]}]
[
{
"label":"",
"rows":[
[{"name":"name"}],
[{"name":"status"}],
[{"name":"direction"}],
[{"name":"dateStart"}],
[{"name":"duration"}],
[{"name":"parent"}],
[{"name":"description"}]
]
}
]

View File

@@ -1 +1,13 @@
[{"label":"","rows":[[{"name":"name"}],[{"name":"status"}],[{"name":"priority"}],[{"name":"type"}],[{"name":"account"}]]}]
[
{
"label":"",
"rows":[
[{"name":"name"}],
[{"name":"status"}],
[{"name":"priority"}],
[{"name":"type"}],
[{"name":"account"}],
[{"name":"description"}]
]
}
]

View File

@@ -26,7 +26,8 @@
],
[
{"name":"monitoredFolders"},{"name":"password"}
]
],
[{"name": "testConnection", "customLabel": null, "view": "Crm:InboundEmail.Fields.TestConnection"}]
]
},
{

View File

@@ -1 +1,14 @@
[{"label":"","rows":[[{"name":"name"}],[{"name":"status"}],[{"name":"dateStart"}],[{"name":"duration"}],[{"name":"dateEnd"}],[{"name":"parent"}]]}]
[
{
"label":"",
"rows":[
[{"name":"name"}],
[{"name":"status"}],
[{"name":"dateStart"}],
[{"name":"duration"}],
[{"name":"dateEnd"}],
[{"name":"parent"}],
[{"name":"description"}]
]
}
]

View File

@@ -1 +1,14 @@
[{"label":"","rows":[[{"name":"name"}],[{"name":"status"}],[{"name":"priority"}],[{"name":"dateStart"}],[{"name":"dateEnd"}],[{"name":"parent"}]]}]
[
{
"label":"",
"rows":[
[{"name":"name"}],
[{"name":"status"}],
[{"name":"priority"}],
[{"name":"dateStart"}],
[{"name":"dateEnd"}],
[{"name":"parent"}],
[{"name":"description"}]
]
}
]

View File

@@ -266,6 +266,28 @@ class Activities extends \Espo\Core\Services\Base
entity_email_address_2.entity_type = " . $this->getPDO()->quote($scope) . " AND
entity_email_address_2.deleted = 0
";
$sql .= "
WHERE
email.deleted = 0 AND
(
email.parent_type <> ".$this->getPDO()->quote($scope)." OR
email.parent_id <> ".$this->getPDO()->quote($id)." OR
email.parent_type IS NULL OR
email.parent_id IS NULL
) AND
(entity_email_address_2.entity_id = ".$this->getPDO()->quote($id).")
";
if (!empty($notIn)) {
$sql .= "
AND email.status {$op} ('". implode("', '", $notIn) . "')
";
}
$sql = $sql . "
UNION
" . $baseSql;
$sql .= "
LEFT JOIN email_email_address ON
email_email_address.email_id = email.id AND
email_email_address.deleted = 0
@@ -284,7 +306,7 @@ class Activities extends \Espo\Core\Services\Base
email.parent_type IS NULL OR
email.parent_id IS NULL
) AND
(entity_email_address_1.entity_id = ".$this->getPDO()->quote($id)." OR entity_email_address_2.entity_id = ".$this->getPDO()->quote($id).")
(entity_email_address_1.entity_id = ".$this->getPDO()->quote($id).")
";
if (!empty($notIn)) {
$sql .= "

View File

@@ -121,6 +121,27 @@ class InboundEmail extends \Espo\Services\Record
return $foldersArr;
}
public function testConnection(array $params)
{
$imapParams = array(
'host' => $params['host'],
'port' => $params['port'],
'user' => $params['username'],
'password' => $params['password']
);
if (!empty($params['ssl'])) {
$imapParams['ssl'] = 'SSL';
}
$storage = new \Espo\Core\Mail\Mail\Storage\Imap($imapParams);
if ($storage->getFolders()) {
return true;
}
throw new Error();
}
public function fetchFromMailServer(Entity $inboundEmail)
{
if ($inboundEmail->get('status') != 'Active') {
@@ -230,7 +251,7 @@ class InboundEmail extends \Espo\Services\Record
}
if ($k == count($ids) - 1) {
if ($message) {
if ($message && isset($message->date)) {
$dt = new \DateTime($message->date);
if ($dt) {
$dateSent = $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s');
@@ -385,7 +406,7 @@ class InboundEmail extends \Espo\Services\Record
}
$contact = $this->getEntityManager()->getRepository('Contact')->where(array(
'EmailAddress.id' => $email->get('fromEmailAddressId')
'emailAddresses.id' => $email->get('fromEmailAddressId')
))->findOne();
if ($contact) {
$case->set('contactId', $contact->id);

View File

@@ -60,7 +60,7 @@ class Email extends \Espo\Core\Notificators\Base
$userIdList = [];
foreach ($emailUserIdList as $userId) {
if (!in_array($userId, $userIdList) && !in_array() && $userId != $this->getUser()->id) {
if (!in_array($userId, $userIdList) && $userId != $this->getUser()->id) {
$userIdList[] = $userId;
}
}
@@ -80,6 +80,13 @@ class Email extends \Espo\Core\Notificators\Base
}
}
if (empty($data['personEntityId'])) {
$data['fromString'] = \Espo\Services\Email::parseFromName($entity->get('fromString'));
if (empty($data['fromString']) && $from) {
$data['fromString'] = $from;
}
}
$parent = null;
if ($entity->get('parentId') && $entity->get('parentType')) {
$parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId'));

View File

@@ -128,7 +128,7 @@ abstract class Base
$params['leftJoins'] = array();
}
$joinsPart = $this->getBelongsToJoins($entity, $params['select'], $params['joins'] + $params['leftJoins']);
$joinsPart = $this->getBelongsToJoins($entity, $params['select'], array_merge($params['joins'], $params['leftJoins']));
$wherePart = $this->getWhere($entity, $whereClause);

View File

@@ -21,9 +21,11 @@
"labels": {
"Create EmailAccount": "Create Email Account",
"IMAP": "IMAP",
"Main": "Main"
"Main": "Main",
"Test Connection": "Test Connection"
},
"messages": {
"couldNotConnectToImap": "Could not connect to IMAP server"
"couldNotConnectToImap": "Could not connect to IMAP server",
"connectionIsOk": "Connection is Ok"
}
}

View File

@@ -22,7 +22,8 @@
],
[
{"name":"monitoredFolders"},{"name":"password"}
]
],
[{"name": "testConnection", "customLabel": null, "view": "EmailAccount.Fields.TestConnection"}]
]
}
]

View File

@@ -105,12 +105,12 @@ class Email extends \Espo\Core\SelectManagers\Base
if (empty($result['customJoin'])) {
$result['customJoin'] = '';
}
if (stripos($result['customJoin'], 'email_email_address') === false) {
if (stripos($result['customJoin'], 'emailEmailAddress') === false) {
$result['customJoin'] .= "
LEFT JOIN email_email_address
LEFT JOIN email_email_address AS `emailEmailAddress`
ON
email_email_address.email_id = email.id AND
email_email_address.deleted = 0
emailEmailAddress.email_id = email.id AND
emailEmailAddress.deleted = 0
";
}
}

View File

@@ -104,12 +104,15 @@ class Email extends Record
}
}
}
$this->getStreamService()->noteEmailSent($parent, $entity);
}
}
$emailSender->send($entity, $params);
if ($parent) {
$this->getStreamService()->noteEmailSent($parent, $entity);
}
$this->getEntityManager()->saveEntity($entity);
}

View File

@@ -92,6 +92,27 @@ class EmailAccount extends Record
return $foldersArr;
}
public function testConnection(array $params)
{
$imapParams = array(
'host' => $params['host'],
'port' => $params['port'],
'user' => $params['username'],
'password' => $params['password']
);
if (!empty($params['ssl'])) {
$imapParams['ssl'] = 'SSL';
}
$storage = new \Espo\Core\Mail\Mail\Storage\Imap($imapParams);
if ($storage->getFolders()) {
return true;
}
throw new Error();
}
public function createEntity($data)
{
if (!$this->getUser()->isAdmin()) {
@@ -224,7 +245,7 @@ class EmailAccount extends Record
if ($k == count($ids) - 1) {
$lastUID = $storage->getUniqueId($id);
if ($message) {
if ($message && isset($message->date)) {
$dt = new \DateTime($message->date);
if ($dt) {
$dateSent = $dt->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s');

View File

@@ -257,35 +257,83 @@ class Stream extends \Espo\Core\Services\Base
$offset = intval($params['offset']);
$maxSize = intval($params['maxSize']);
$selectParams = array(
'offset' => $offset,
'limit' => $maxSize + 1,
'orderBy' => 'number',
'order' => 'DESC',
'distinct' => true,
'customJoin' => "
JOIN subscription ON
$pdo = $this->getEntityManager()->getPDO();
$sql = "
(
SELECT
note.id AS 'id',
note.number AS 'number',
note.type AS 'type',
note.post AS 'post',
note.data AS 'data',
note.parent_type AS 'parentType',
note.parent_id AS 'parentId',
note.created_at AS 'createdAt',
note.created_by_id AS 'createdById',
TRIM(CONCAT(createdBy.first_name, ' ', createdBy.last_name)) AS `createdByName`
FROM `note` AS `note`
JOIN subscription AS `subscription` ON
(
(
note.parent_type = subscription.entity_type AND
note.parent_id = subscription.entity_id
)
) AND
subscription.user_id = ".$pdo->quote($this->getUser()->id)."
LEFT JOIN `user` AS `createdBy` ON note.created_by_id = createdBy.id
WHERE note.deleted = 0 {where}
ORDER BY number DESC
)
UNION
(
SELECT
note.id AS 'id',
note.number AS 'number',
note.type AS 'type',
note.post AS 'post',
note.data AS 'data',
note.parent_type AS 'parentType',
note.parent_id AS 'parentId',
note.created_at AS 'createdAt',
note.created_by_id AS 'createdById',
TRIM(CONCAT(createdBy.first_name, ' ', createdBy.last_name)) AS `createdByName`
FROM `note` AS `note`
JOIN subscription AS `subscription` ON
(
(
note.super_parent_type = subscription.entity_type AND
note.super_parent_id = subscription.entity_id
)
) AND
subscription.user_id = ".$pdo->quote($this->getUser()->id)."
LEFT JOIN `user` AS `createdBy` ON note.created_by_id = createdBy.id
WHERE note.deleted = 0 AND
(
(
note.parent_type = subscription.entity_type AND
note.parent_id = subscription.entity_id
) OR
(
note.super_parent_type = subscription.entity_type AND
note.super_parent_id = subscription.entity_id
)
) AND
subscription.user_id = '" . $this->getUser()->id . "'
"
);
note.parent_id <> note.super_parent_id
OR
note.parent_type <> note.super_parent_type
)
{where}
ORDER BY number DESC
)
ORDER BY number DESC
";
if (!empty($params['after'])) {
$where = array();
$where['createdAt>'] = $params['after'];
$selectParams['whereClause'] = $where;
$sql = str_replace('{where}', "AND note.created_at > ".$pdo->quote($params['after']), $sql);
} else {
$sql = str_replace('{where}', '', $sql);
}
$collection = $this->getEntityManager()->getRepository('Note')->find($selectParams);
$sql = $this->getEntityManager()->getQuery()->limit($sql, $offset, $maxSize + 1);
$collection = $this->getEntityManager()->getRepository('Note')->findByQuery($sql);
foreach ($collection as $e) {
if ($e->get('type') == 'Post' || $e->get('type') == 'EmailReceived') {

33
clear_cache.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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/.
************************************************************************/
$sapiName = php_sapi_name();
if (substr($sapiName, 0, 3) != 'cli') {
die("Rebuild can be run only via CLI");
}
include "bootstrap.php";
$app = new \Espo\Core\Application();
$app->runClearCache();

View File

@@ -0,0 +1,31 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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/.
************************************************************************/
Espo.define('Crm:Views.InboundEmail.Fields.TestConnection', 'Views.EmailAccount.Fields.TestConnection', function (Dep) {
return Dep.extend({
url: 'InboundEmail/action/testConnection',
});
});

View File

@@ -23,7 +23,9 @@ Espo.define('Crm:Views.InboundEmail.Record.List', 'Views.Record.List', function
return Dep.extend({
allowQuickEdit: false,
quickDetailDisabled: true,
quickEditDisabled: true,
massActionList: ['remove'],

View File

@@ -2,7 +2,7 @@
<div class="input-group add-team">
<input class="main-element form-control" type="text" name="" value="" autocomplete="off" placeholder="{{translate 'Select'}}">
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1"><span class="glyphicon glyphicon-arrow-up"></span></button>
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1" title="{{translate 'Select'}}"><span class="glyphicon glyphicon-arrow-up"></span></button>
</span>
</div>

View File

@@ -3,8 +3,8 @@
<div class="input-group add-team">
<input class="main-element form-control" type="text" name="" value="" autocomplete="off" placeholder="{{translate 'Select'}}">
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1"><span class="glyphicon glyphicon-arrow-up"></span></button>
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1" title="{{translate 'Select'}}"><span class="glyphicon glyphicon-arrow-up"></span></button>
</span>
</div>

View File

@@ -4,9 +4,9 @@
{{options foreignScopeList foreignScope category='scopeNames'}}
</select>
</span>
<input class="main-element form-control" type="text" name="{{nameName}}" value="{{nameValue}}" autocomplete="off">
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1"><i class="glyphicon glyphicon-arrow-up"></i></button>
<input class="main-element form-control" type="text" name="{{nameName}}" value="{{nameValue}}" autocomplete="off" placeholder="{{translate 'Select'}}">
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1" title="{{translate 'Select'}}"><i class="glyphicon glyphicon-arrow-up"></i></button>
<button data-action="clearLink" class="btn btn-default" type="button" tabindex="-1"><i class="glyphicon glyphicon-remove"></i></button>
</span>
</div>

View File

@@ -2,10 +2,10 @@
{{options foreignScopeList searchParams.valueType category='scopeNames'}}
</select>
<div class="input-group">
<input class="form-control input-sm" type="text" name="{{nameName}}" value="{{searchParams.valueName}}" autocomplete="off">
<span class="input-group-btn">
<button type="button" class="btn btn-sm btn-default" data-action="selectLink" tabindex="-1"><i class="glyphicon glyphicon-arrow-up"></i></button>
<button type="button" class="btn btn-sm btn-default" data-action="clearLink" tabindex="-1"><i class="glyphicon glyphicon-remove"></i></button>
<input class="form-control input-sm" type="text" name="{{nameName}}" value="{{searchParams.valueName}}" autocomplete="off" placeholder="{{translate 'Select'}}">
<span class="input-group-btn">
<button type="button" class="btn btn-sm btn-default" data-action="selectLink" tabindex="-1" title="{{translate 'Select'}}"><i class="glyphicon glyphicon-arrow-up"></i></button>
<button type="button" class="btn btn-sm btn-default" data-action="clearLink" tabindex="-1"><i class="glyphicon glyphicon-remove"></i></button>
</span>
</div>
<input type="hidden" name="{{idName}}" value="{{searchParams.valueId}}">

View File

@@ -1,7 +1,7 @@
<div class="input-group">
<input class="main-element form-control" type="text" name="{{nameName}}" value="{{nameValue}}" autocomplete="off">
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1"><i class="glyphicon glyphicon-arrow-up"></i></button>
<input class="main-element form-control" type="text" name="{{nameName}}" value="{{nameValue}}" autocomplete="off" placeholder="{{translate 'Select'}}">
<span class="input-group-btn">
<button data-action="selectLink" class="btn btn-default" type="button" tabindex="-1" title="{{translate 'Select'}}"><i class="glyphicon glyphicon-arrow-up"></i></button>
<button data-action="clearLink" class="btn btn-default" type="button" tabindex="-1"><i class="glyphicon glyphicon-remove"></i></button>
</span>
</div>

View File

@@ -1,8 +1,8 @@
<div class="input-group">
<input class="form-control input-sm" type="text" name="{{nameName}}" value="{{searchParams.valueName}}" autocomplete="off">
<span class="input-group-btn">
<button type="button" class="btn btn-sm btn-default" data-action="selectLink" tabindex="-1"><i class="glyphicon glyphicon-arrow-up"></i></button>
<button type="button" class="btn btn-sm btn-default" data-action="clearLink" tabindex="-1"><i class="glyphicon glyphicon-remove"></i></button>
<input class="form-control input-sm" type="text" name="{{nameName}}" value="{{searchParams.valueName}}" autocomplete="off" placeholder="{{translate 'Select'}}">
<span class="input-group-btn">
<button type="button" class="btn btn-sm btn-default" data-action="selectLink" tabindex="-1" title="{{translate 'Select'}}"><i class="glyphicon glyphicon-arrow-up"></i></button>
<button type="button" class="btn btn-sm btn-default" data-action="clearLink" tabindex="-1"><i class="glyphicon glyphicon-remove"></i></button>
</span>
</div>
<input type="hidden" name="{{idName}}" value="{{searchParams.value}}">

View File

@@ -2,4 +2,4 @@
<link href="client/css/font-awesome.min.css" rel="stylesheet">
<link href="client/css/summernote.css" rel="stylesheet">
<textarea class="main-element form-control summernote" name="{{name}}" {{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}} {{#if params.rows}} rows="{{params.rows}}"{{/if}}>{{value}}</textarea>
<textarea class="main-element form-control summernote" name="{{name}}" {{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}} {{#if params.rows}} rows="{{params.rows}}"{{/if}}></textarea>

View File

@@ -414,7 +414,7 @@ _.extend(Espo.App.prototype, {
this.language.load(function () {
langIsLoaded = true;
handleProcess();
}.bind(this));
}.bind(this), true);
if (!userIsLoaded) {

View File

@@ -29,9 +29,9 @@ _.extend(Espo.Language.prototype, {
data: null,
cache: null,
url: 'I18n',
has: function (name, category, scope) {
if (scope in this.data) {
if (category in this.data[scope]) {
@@ -65,7 +65,7 @@ _.extend(Espo.Language.prototype, {
}
return res;
},
translateOption: function (value, field, scope) {
var translation = this.translate(field, 'options', scope);
if (typeof translation != 'object') {
@@ -73,9 +73,9 @@ _.extend(Espo.Language.prototype, {
}
return translation[value] || value;
},
loadFromCache: function () {
if (this.cache) {
var cached = this.cache.get('app', 'language');
if (cached) {
@@ -85,19 +85,19 @@ _.extend(Espo.Language.prototype, {
}
return null;
},
clearCache: function () {
if (this.cache) {
this.cache.clear('app', 'language');
}
},
storeToCache: function () {
if (this.cache) {
this.cache.set('app', 'language', this.data);
}
},
load: function (callback, disableCache) {
this.once('sync', callback);
@@ -125,7 +125,7 @@ _.extend(Espo.Language.prototype, {
}
});
},
}, Backbone.Events);

View File

@@ -36,7 +36,7 @@
this.container = 'body'
this.onRemove = function () {};
var params = ['className', 'backdrop', 'closeButton', 'header', 'body', 'width', 'height', 'buttons', 'removeOnClose', 'graggable', 'container', 'onRemove'];
var params = ['className', 'backdrop', 'keyboard', 'closeButton', 'header', 'body', 'width', 'height', 'buttons', 'removeOnClose', 'graggable', 'container', 'onRemove'];
params.forEach(function (param) {
if (param in options) {
this[param] = options[param];
@@ -124,6 +124,7 @@
Dialog.prototype.show = function () {
this.$el.modal({
backdrop: this.backdrop,
keyboard: this.keyboard
});
};
Dialog.prototype.hide = function () {

View File

@@ -45,26 +45,37 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
},
addUnfollowButtonToMenu: function () {
this.menu.buttons.unshift({
name: 'unfollow',
label: 'Followed',
style: 'success',
action: 'unfollow'
});
var index = -1;
this.menu.buttons.forEach(function (data, i) {
if (data.name == 'follow') {
var index = i;
index = i;
return;
}
}, this);
if (~index) {
this.menu.buttons.splice(index, 1);
}
this.menu.buttons.unshift({
name: 'unfollow',
label: 'Followed',
style: 'success',
action: 'unfollow'
});
},
addFollowButtonToMenu: function () {
var index = -1;
this.menu.buttons.forEach(function (data, i) {
if (data.name == 'unfollow') {
index = i;
return;
}
}, this);
if (~index) {
this.menu.buttons.splice(index, 1);
}
this.menu.buttons.unshift({
name: 'follow',
label: 'Follow',
@@ -72,17 +83,6 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
icon: 'glyphicon glyphicon-share-alt',
action: 'follow'
});
var index = -1;
this.menu.buttons.forEach(function (data, i) {
if (data.name == 'unfollow') {
var index = i;
return;
}
}, this);
if (~index) {
this.menu.buttons.splice(index, 1);
}
},
setup: function () {
@@ -90,39 +90,37 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
if (this.getMetadata().get('scopes.' + this.scope + '.stream')) {
if (this.model.has('isFollowed')) {
if (this.model.get('isFollowed')) {
this.addUnfollowButtonToMenu();
} else {
this.addFollowButtonToMenu();
}
this.handleFollowButton();
} else {
this.once('after:render', function () {
var proceed = function () {
if (this.model.get('isFollowed')) {
this.addUnfollowButton();
this.addUnfollowButtonToMenu();
} else {
this.addFollowButton();
this.addFollowButtonToMenu();
}
}.bind(this);
if (this.model.has('isFollowed')) {
proceed();
this.handleFollowButton();
} else {
this.listenToOnce(this.model, 'sync', function () {
if (this.model.has('isFollowed')) {
proceed();
this.handleFollowButton();
}
}.bind(this));
}, this);
}
}, this);
}
}
},
handleFollowButton: function () {
if (this.model.get('isFollowed')) {
if (this.isRendered()) {
this.addUnfollowButton();
}
this.addUnfollowButtonToMenu();
} else {
if (this.isRendered()) {
this.addFollowButton();
}
this.addFollowButtonToMenu();
}
},
addFollowButton: function () {
$el = $('<button>').addClass('btn btn-default action')
.attr('data-action', 'follow')
@@ -145,7 +143,11 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
type: 'PUT',
success: function () {
$el.remove();
this.addUnfollowButton();
this.model.set('isFollowed', true);
this.handleFollowButton();
}.bind(this),
error: function () {
$el.removeClass('disabled');
}.bind(this)
});
},
@@ -158,7 +160,11 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
type: 'DELETE',
success: function () {
$el.remove();
this.addFollowButton();
this.model.set('isFollowed', false);
this.handleFollowButton();
}.bind(this),
error: function () {
$el.removeClass('disabled');
}.bind(this)
});

View File

@@ -0,0 +1,109 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 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/.
************************************************************************/
Espo.define('Views.EmailAccount.Fields.TestConnection', 'Views.Fields.Base', function (Dep) {
return Dep.extend({
readOnly: true,
_template: '<button class="btn btn-default disabled" data-action="testConnection">{{translate \'Test Connection\' scope=\'EmailAccount\'}}</button>',
url: 'EmailAccount/action/testConnection',
events: {
'click [data-action="testConnection"]': function () {
this.test();
},
},
fetch: function () {
return {};
},
checkAvailability: function () {
if (this.model.get('host')) {
this.$el.find('button').removeClass('disabled');
} else {
this.$el.find('button').addClass('disabled');
}
},
afterRender: function () {
this.checkAvailability();
this.stopListening(this.model, 'change:host');
this.listenTo(this.model, 'change:host', function () {
this.checkAvailability();
}, this);
},
getData: function () {
var data = {
'host': this.model.get('host'),
'port': this.model.get('port'),
'ssl': this.model.get('ssl'),
'username': this.model.get('username'),
'password': this.model.get('password') || null,
'id': this.model.id
};
return data;
},
test: function () {
var data = this.getData();
var $btn = this.$el.find('button');
$btn.addClass('disabled');
Espo.Ui.notify(this.translate('pleaseWait', 'messages'));
$.ajax({
url: this.url,
type: 'POST',
data: JSON.stringify(data),
error: function (xhr, status) {
var statusReason = xhr.getResponseHeader('X-Status-Reason') || '';
statusReason = statusReason.replace(/ $/, '');
statusReason = statusReason.replace(/,$/, '');
var msg = this.translate('Error') + ' ' + xhr.status;
if (statusReason) {
msg += ': ' + statusReason;
}
Espo.Ui.error(msg);
console.error(msg);
xhr.errorIsHandled = true;
$btn.removeClass('disabled');
}.bind(this)
}).done(function () {
$btn.removeClass('disabled');
Espo.Ui.success(this.translate('connectionIsOk', 'messages', 'EmailAccount'));
}.bind(this));
},
});
});

View File

@@ -23,7 +23,9 @@ Espo.define('Views.EmailAccount.Record.List', 'Views.Record.List', function (Dep
return Dep.extend({
allowQuickEdit: false,
quickDetailDisabled: true,
quickEditDisabled: true,
checkAllResultDisabled: true,

View File

@@ -189,7 +189,7 @@ Espo.define('Views.Email.Detail', 'Views.Detail', function (Dep) {
};
var subject = this.model.get('name');
if (subject.indexOf('Re:') !== 0) {
if (subject.toUpperCase().indexOf('RE:') !== 0) {
attributes['name'] = 'Re: ' + subject;
} else {
attributes['name'] = subject;

View File

@@ -27,7 +27,9 @@ Espo.define('Views.Extension.Record.List', 'Views.Record.List', function (Dep) {
checkboxes: false,
allowQuickEdit: false,
quickDetailDisabled: true,
quickEditDisabled: true,
massActionList: []

View File

@@ -287,20 +287,26 @@ Espo.define('Views.Fields.Email', 'Views.Fields.Base', function (Dep) {
fetch: function () {
var data = {};
var adderssData = this.fetchEmailAddressData();
var adderssData = this.fetchEmailAddressData() || [];
data[this.dataFieldName] = adderssData;
data[this.name] = null;
var primaryIndex = 0;
(adderssData || []).forEach(function (item, i) {
adderssData.forEach(function (item, i) {
if (item.primary) {
primaryIndex = i;
return;
}
});
if (adderssData.length && primaryIndex > 0) {
var t = adderssData[0];
adderssData[0] = adderssData[primaryIndex];
adderssData[primaryIndex] = t;
}
if (adderssData.length) {
data[this.name] = adderssData[primaryIndex].emailAddress;
data[this.name] = adderssData[0].emailAddress;
}
return data;

View File

@@ -225,19 +225,26 @@ Espo.define('Views.Fields.Phone', 'Views.Fields.Base', function (Dep) {
fetch: function () {
var data = {};
var adderssData = this.fetchPhoneNumberData();
var adderssData = this.fetchPhoneNumberData() || [];
data[this.dataFieldName] = adderssData;
data[this.name] = null;
var primaryIndex = 0;
(adderssData || []).forEach(function (item, i) {
adderssData.forEach(function (item, i) {
if (item.primary) {
primaryIndex = i;
return;
}
});
if (adderssData.length && primaryIndex > 0) {
var t = adderssData[0];
adderssData[0] = adderssData[primaryIndex];
adderssData[primaryIndex] = t;
}
if (adderssData.length) {
data[this.name] = adderssData[primaryIndex].phoneNumber;
data[this.name] = adderssData[0].phoneNumber;
}
return data;

View File

@@ -80,8 +80,8 @@ Espo.define('Views.Fields.Text', 'Views.Fields.Base', function (Dep) {
afterRender: function () {
Dep.prototype.afterRender.call(this);
var text = this.model.get(this.name);
if (this.mode == 'edit') {
var text = this.getValueForDisplay();
if (text) {
this.$element.val(text);
}

View File

@@ -62,6 +62,14 @@ Espo.define('Views.Fields.Wysiwyg', ['Views.Fields.Text', 'lib!Summernote'], fun
});
},
getValueForDisplay: function () {
var value = Dep.prototype.getValueForDisplay.call(this);
if (this.mode == 'edit' && value) {
value = value.replace(/<[\/]{0,1}(base|BASE)[^><]*>/g, '');
}
return value;
},
afterRender: function () {
Dep.prototype.afterRender.call(this);

View File

@@ -23,7 +23,9 @@ Espo.define('Views.Import.Record.List', 'Views.Record.List', function (Dep) {
return Dep.extend({
allowQuickEdit: false,
quickDetailDisabled: true,
quickEditDisabled: true,
checkAllResultDisabled: true,

View File

@@ -45,6 +45,8 @@ Espo.define('Views.Modal', 'View', function (Dep) {
width: false,
escapeDisabled: false,
init: function () {
var id = this.cssName + '-container-' + Math.floor((Math.random() * 10000) + 1).toString();
var containerSelector = this.containerSelector = '#' + id;
@@ -71,6 +73,7 @@ Espo.define('Views.Modal', 'View', function (Dep) {
body: '',
buttons: buttons,
width: this.width,
keyboard: !this.escapeDisabled,
onRemove: function () {
this.remove();
}.bind(this)

View File

@@ -37,6 +37,8 @@ Espo.define('Views.Modals.Edit', 'Views.Modal', function (Dep) {
columnCount: 1,
escapeDisabled: true,
setup: function () {
var self = this;

View File

@@ -40,7 +40,11 @@ Espo.define('Views.Notifications.Items.EmailReceived', 'Views.Notifications.Noti
this.userId = data.userId;
this.messageData['entityType'] = Espo.Utils.upperCaseFirst((this.translate(data.entityType, 'scopeNames') || '').toLowerCase());
this.messageData['from'] = '<a href="#' + data.personEntityType + '/view/' + data.personEntityId + '">' + data.personEntityName + '</a>';
if (data.personEntityId) {
this.messageData['from'] = '<a href="#' + data.personEntityType + '/view/' + data.personEntityId + '">' + data.personEntityName + '</a>';
} else {
this.messageData['from'] = data.fromString || this.translate('empty address');
}
this.emailId = data.emailId;
this.emailName = data.emailName;

View File

@@ -90,7 +90,7 @@ Espo.define('Views.OutboundEmail.Fields.TestSend', 'Views.Fields.Base', function
type: 'POST',
data: JSON.stringify(data),
error: function (xhr, status) {
var statusReason = xhr.getResponseHeader('X-Status-Reason');
var statusReason = xhr.getResponseHeader('X-Status-Reason') || '';
statusReason = statusReason.replace(/ $/, '');
statusReason = statusReason.replace(/,$/, '');

View File

@@ -184,7 +184,9 @@ Espo.define('Views.Record.List', 'View', function (Dep) {
exportAction: true,
allowQuickEdit: true,
quickDetailDisabled: false,
quickEditDisabled: false,
/**
* @param {array} Columns layout. Will be convered in 'Bull' typed layout for a fields rendering.
@@ -847,23 +849,27 @@ Espo.define('Views.Record.List', 'View', function (Dep) {
var id = data.id;
if (!id) return;
this.notify('Loading...');
this.createView('quickDetail', 'Modals.Detail', {
scope: this.scope,
id: id
}, function (view) {
view.once('after:render', function () {
Espo.Ui.notify(false);
});
view.render();
view.once('after:save', function () {
console.log(2);
var model = this.collection.get(id);
if (model) {
model.fetch();
}
}, this);
}.bind(this));
if (!this.quickDetailDisabled) {
this.notify('Loading...');
this.createView('quickDetail', 'Modals.Detail', {
scope: this.scope,
id: id
}, function (view) {
view.once('after:render', function () {
Espo.Ui.notify(false);
});
view.render();
view.once('after:save', function () {
console.log(2);
var model = this.collection.get(id);
if (model) {
model.fetch();
}
}, this);
}.bind(this));
} else {
this.getRouter().navigate('#' + this.scope + '/view/' + id, {trigger: true});
}
},
actionQuickEdit: function (d) {
@@ -871,7 +877,7 @@ Espo.define('Views.Record.List', 'View', function (Dep) {
var id = d.id;
if (!id) return;
if (this.allowQuickEdit) {
if (!this.quickEditDisabled) {
this.notify('Loading...');
this.createView('quickEdit', 'Modals.Edit', {
scope: this.scope,

View File

@@ -23,7 +23,9 @@ Espo.define('Views.Role.Record.List', 'Views.Record.List', function (Dep) {
return Dep.extend({
allowQuickEdit: false,
quickDetailDisabled: true,
quickEditDisabled: true,
massActionList: ['remove'],

View File

@@ -17,15 +17,17 @@
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
Espo.define('Views.ScheduledJob.Record.List', 'Views.Record.List', function (Dep) {
************************************************************************/
Espo.define('Views.ScheduledJob.Record.List', 'Views.Record.List', function (Dep) {
return Dep.extend({
allowQuickEdit: false,
});
quickDetailDisabled: true,
quickEditDisabled: true,
});
});

View File

@@ -61,6 +61,11 @@ Espo.define('Views.Stream.Notes.EmailReceived', 'Views.Stream.Note', function (D
this.messageName += 'From';
this.messageData['from'] = '<a href="#'+data.personEntityType+'/view/' + data.personEntityId + '">' + data.personEntityName + '</a>';
}
if (this.model.get('parentType') === data.personEntityType && this.model.get('parentId') == data.personEntityId) {
this.isThis = true;
}
if (this.isThis) {
this.messageName += 'This';
}

View File

@@ -23,7 +23,9 @@ Espo.define('Views.Team.Record.List', 'Views.Record.List', function (Dep) {
return Dep.extend({
allowQuickEdit: false,
quickDetailDisabled: true,
quickEditDisabled: true,
massActionList: ['remove'],

View File

@@ -23,7 +23,7 @@ Espo.define('Views.User.Record.List', 'Views.Record.List', function (Dep) {
return Dep.extend({
allowQuickEdit: false,
quickEditDisabled: true,
massActionList: ['remove', 'massUpdate', 'export'],

View File

@@ -2,7 +2,7 @@
<div id="msg-box" class="alert hide"></div>
<form id="nav">
<form id="nav" autocomplete="off">
<div class="row">
<div class=" col-md-6">
<div class="row">

View File

@@ -1,7 +1,7 @@
<div class="panel-body body">
<div id="msg-box" class="alert hide"></div>
<div class="loading-icon hide"></div>
<form id="nav">
<form id="nav" autocomplete="off">
<div class="row">
<div class="col-md-8" style="width:100%" >

View File

@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "3.2.0",
"version": "3.2.2",
"description": "",
"main": "index.php",
"repository": {