Compare commits

...

132 Commits
3.3.0 ... 3.4.0

Author SHA1 Message Date
yuri
7e264780ec fix acl 2015-06-09 16:29:46 +03:00
Taras Machyshyn
0750a89f74 Ukrainian corrections 2015-06-09 16:07:47 +03:00
Taras Machyshyn
5b52668359 Fixed metadata cache bug 2015-06-09 15:51:23 +03:00
Taras Machyshyn
3c91f8450d Bug fixing 2015-06-09 14:05:01 +03:00
yuri
f820168283 Merge branch 'hotfix/3.3.1' 2015-06-09 12:54:37 +03:00
yuri
bdb90a0016 Merge branch 'hotfix/3.3.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/3.3.1 2015-06-09 12:54:20 +03:00
yuri
521fa42bf1 fix layout 2015-06-09 12:25:15 +03:00
yuri
1b272ea0c7 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-06-09 12:22:01 +03:00
Taras Machyshyn
b055adfd87 Updated composer.lock file 2015-06-09 12:21:46 +03:00
yuri
42ea6e0d7c cleanup 2015-06-09 12:11:17 +03:00
yuri
20b21622ed layout manager improvement 2015-06-09 11:57:11 +03:00
yuri
34c61a2fc5 lang 2015-06-09 10:47:43 +03:00
yuri
cf8198dc98 version 2015-06-09 10:31:30 +03:00
yuri
c57fd2c55e Merge branch 'master' of https://github.com/espocrm/espocrm 2015-06-08 17:52:57 +03:00
yuri
3669bf4e11 fix modal 2015-06-08 17:29:58 +03:00
yuri
b6bbdada06 modal fit height 2015-06-08 17:17:14 +03:00
yuri
804a618408 folders width 2015-06-05 15:27:07 +03:00
yuri
a1192474d9 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-06-05 14:53:24 +03:00
yuri
dd28642370 setReadOnly form action 2015-06-05 14:53:11 +03:00
Taras Machyshyn
b92295cbb1 Installer: added check PHP < 5.4.0 2015-06-05 13:08:47 +03:00
Taras Machyshyn
d186cd2b1a Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-06-05 12:23:18 +03:00
Taras Machyshyn
9ebee47d5c Orm converter improvements 2015-06-05 12:23:05 +03:00
yuri
3fdc7c8cf7 create button change 2015-06-05 11:10:07 +03:00
Yuri Kuznetsov
b298b26bab Merge pull request #53 from alasdaircr/attr-not-empty
Fix ifAttrNotEmpty failing with boolean attributes
2015-06-05 10:17:54 +03:00
Alasdair Campbell
0a38857e64 Fix ifAttrNotEmpty failing with boolean attributes 2015-06-05 00:06:31 +01:00
yuri
2497e9bd88 fix modals 2015-06-04 17:52:24 +03:00
yuri
bee44612e5 external account id max length 2015-06-04 11:33:11 +03:00
Yuri Kuznetsov
8946b4ad11 Merge pull request #52 from alasdaircr/post-entrypoints
PATCH: Allow POST entrypoints
2015-06-04 10:10:34 +03:00
Alasdair Campbell
d4b78b9977 PATCH: Allow POST entrypoints 2015-06-03 16:12:17 +01:00
yuri
f9614385df Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-06-03 16:30:22 +03:00
yuri
7352abebe9 activities control 2015-06-03 16:30:12 +03:00
yuri
5c57b34893 user activities 2015-06-03 15:55:13 +03:00
yuri
0d873b7302 fix campaign fields 2015-06-03 12:41:06 +03:00
yuri
ccca6a8f55 fix fullForm link 2015-06-03 11:16:09 +03:00
Taras Machyshyn
bad9cc848f Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-06-02 17:31:18 +03:00
Taras Machyshyn
e3ab16a7bf Merge branch 'hotfix/3.3.1' 2015-06-02 17:31:04 +03:00
Taras Machyshyn
6db068e625 Improve Extension installation 2015-06-02 17:29:58 +03:00
Taras Machyshyn
64b1ac3ccf Rebuild fixes 2015-06-02 17:23:57 +03:00
yuri
ca038970ff folders notify 2015-06-02 17:11:39 +03:00
yuri
4d852c8515 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-06-02 17:09:00 +03:00
yuri
b96ea5100b modals select 2015-06-02 17:05:51 +03:00
yuri
fd86727bfc scopes in entity templates 2015-06-02 15:52:54 +03:00
yuri
8dacf66242 record tree create acl 2015-06-02 15:44:36 +03:00
Taras Machyshyn
bbb4271dd1 Merge branch 'hotfix/3.3.1' 2015-06-02 15:33:45 +03:00
Taras Machyshyn
8b72499c19 Databse rebuild fixes 2015-06-02 15:31:20 +03:00
yuri
0a9a5a47af document folders 3 2015-06-02 12:19:28 +03:00
yuri
4304724315 disable isOverdue field 2015-06-02 11:02:37 +03:00
yuri
8dc3c146e0 fix 2015-05-29 18:14:50 +03:00
yuri
7cefff04a7 document folders 2 2015-05-29 17:18:42 +03:00
yuri
dd6d6995b9 row actions active fix 2015-05-28 16:59:36 +03:00
yuri
3606dc39e3 document folder 1 2015-05-28 16:19:37 +03:00
yuri
03fef2fc8c Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-05-28 11:38:16 +03:00
yuri
704e548654 opp lead source disable customization and none for empty 2015-05-28 11:35:26 +03:00
yuri
6120d5501d fix followers order 2015-05-28 10:47:13 +03:00
Taras Machyshyn
3f8c338265 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-05-27 17:19:18 +03:00
Taras Machyshyn
ec07b23af8 Test corrections 2015-05-27 17:19:04 +03:00
yuri
cc349ca545 tooltip 2015-05-27 16:39:36 +03:00
yuri
b16ec66eb3 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-05-27 16:34:31 +03:00
yuri
22667e17f1 Repository: leftJoin method 2015-05-27 16:34:18 +03:00
yuri
5741834d53 fix test 2015-05-27 16:32:18 +03:00
yuri
0df39cdc4f fix middle tables 2015-05-27 16:30:36 +03:00
yuri
70489e8846 email loop prevention and query middle table change 2015-05-27 16:17:45 +03:00
Taras Machyshyn
c4558ec633 Merge branch 'hotfix/3.3.1' 2015-05-27 16:03:09 +03:00
Taras Machyshyn
5b500ae42a Added support UTF-8 strings for saved JSON data 2015-05-27 16:01:24 +03:00
yuri
cdbe6a2383 active filter 2 2015-05-26 16:32:29 +03:00
yuri
48c650eab2 user active filter 2015-05-26 16:31:33 +03:00
yuri
0c815b5761 followers 3 2015-05-26 16:17:24 +03:00
yuri
9b104b9a16 followers 2 2015-05-26 12:26:30 +03:00
yuri
97248e2b18 fix users panel 2015-05-26 11:41:15 +03:00
yuri
b3aa244f4e fix side panels 2015-05-26 11:40:13 +03:00
yuri
45c59ff242 fix createRelated 2015-05-26 11:30:25 +03:00
yuri
0038c3666d followers 1 2015-05-26 11:25:29 +03:00
yuri
7cfebbf87e fix entryPoint error messages 2015-05-26 10:34:57 +03:00
yuri
0cd3666a96 Merge branch 'hotfix/3.3.1' 2015-05-25 11:15:06 +03:00
yuri
d4a5f68398 fix before remove 2015-05-25 11:14:53 +03:00
yuri
1a30a9c29a pre-loader huck 2015-05-22 16:51:20 +03:00
yuri
cd75ddbbd7 select filter search 2015-05-22 16:46:03 +03:00
yuri
55aedb7194 selectRelated filters 2015-05-22 15:40:37 +03:00
yuri
aa303d164f reply from histpry panel 2015-05-22 15:17:55 +03:00
yuri
1723f63b21 label 2015-05-22 11:47:16 +03:00
yuri
55fef695ec fix panels 2015-05-22 11:42:44 +03:00
yuri
42ee41ae54 cleanup 2015-05-22 11:10:03 +03:00
yuri
676f480db8 full form on modal header 2015-05-21 15:34:07 +03:00
yuri
233408b076 wysiwyg size and scroll 2 2015-05-21 14:55:05 +03:00
yuri
9dfec82f6e fix frontend acl 2015-05-21 12:33:21 +03:00
yuri
3c791b6363 wysiwyg size and scroll 2015-05-21 11:39:51 +03:00
yuri
5a0e5f6804 total count in title 2015-05-21 11:13:25 +03:00
yuri
3376ab06d0 improve reply email 2015-05-21 11:01:42 +03:00
yuri
e8b22f9331 remove email ico 2015-05-21 10:33:59 +03:00
yuri
0b9104f992 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-05-20 17:12:28 +03:00
yuri
566d0e50a6 unlink all 2015-05-20 17:11:55 +03:00
yuri
be5a6a0ee0 fix controller 2015-05-20 16:10:07 +03:00
yuri
4df460d6cb fix paneld 2015-05-20 14:31:20 +03:00
yuri
7443c969b1 refactor panels 2015-05-20 14:27:38 +03:00
Taras Machyshyn
3b5b1fb1b3 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-05-20 13:05:18 +03:00
Taras Machyshyn
5737927e2d Merge branch 'hotfix/3.3.1' 2015-05-20 13:05:01 +03:00
Taras Machyshyn
c59b837315 Fixed a bug with '__APPEND__' merge 2015-05-20 13:03:33 +03:00
yuri
ce75b68df0 Merge branch 'master' of ssh://172.20.0.1/var/git/espo/backend 2015-05-20 12:02:51 +03:00
yuri
d17257fc95 link fields filters fix 2015-05-20 12:02:07 +03:00
Taras Machyshyn
ed996eff43 Merge branch 'hotfix/3.3.1' 2015-05-20 10:57:20 +03:00
Taras Machyshyn
8f70c32687 Correction Ukrainian translation 2015-05-20 10:36:08 +03:00
yuri
d134db65a3 fix varchar searc 2015-05-19 18:07:56 +03:00
yuri
deaa3f3f73 fix varchar searc 2015-05-19 18:05:50 +03:00
yuri
5f5f6dcf38 fix query sanitize 2015-05-19 15:31:06 +03:00
yuri
8e373a9c33 fix warnings 2015-05-19 15:23:44 +03:00
yuri
f7a367d755 email to case 2015-05-19 15:17:11 +03:00
yuri
fea113269b compose email from detail view imporovement 2015-05-19 15:01:36 +03:00
yuri
5299da7a21 contact target list for import 2015-05-19 12:28:39 +03:00
yuri
9a06ee3221 opted out panel 2015-05-19 12:16:40 +03:00
yuri
5b489e2341 target list opted out api 2015-05-19 11:48:43 +03:00
yuri
5b0e4f910d fix target list add 2015-05-18 18:10:43 +03:00
Taras Machyshyn
575a4f12cc Fixed a bug with deleting/adding an autoincrement field 2015-05-18 16:14:55 +03:00
yuri
9b77f74e53 optedOut column 2015-05-18 12:02:34 +03:00
yuri
7be801c647 target list btn style 2015-05-18 11:30:14 +03:00
yuri
a95471a239 fix lang 2015-05-18 11:26:54 +03:00
yuri
8982993813 dropdownItemList 2015-05-18 11:25:17 +03:00
Taras Machyshyn
cc48dc2a65 Fixed a bug with module 'order' option 2015-05-18 10:47:21 +03:00
yuri
9e0d8632f5 email ui improvements 2015-05-15 17:46:17 +03:00
yuri
2f751085e0 bcc 2015-05-15 17:37:06 +03:00
yuri
f5faae13c4 email icon 2 2015-05-15 17:30:04 +03:00
yuri
015fd4014c email ico 2015-05-15 17:22:11 +03:00
yuri
ff9c68d7f6 createdAccount filter 2015-05-15 16:45:28 +03:00
yuri
323138eebf filter email by subject 2015-05-15 16:36:42 +03:00
yuri
43f15252bb relatedAttributeFunctions 2015-05-15 15:26:17 +03:00
yuri
25ad4ffa43 campaign and document small improvements 2015-05-15 15:06:50 +03:00
yuri
0cd3a464d8 acl improvement 2015-05-15 12:15:14 +03:00
yuri
71e9b313bb campaign label 2015-05-15 11:08:12 +03:00
yuri
9b13f82c55 selectize 2015-05-15 11:01:57 +03:00
yuri
20cf214187 stream hover 2015-05-14 12:26:26 +03:00
yuri
2d06568470 hover list row 2015-05-14 12:19:09 +03:00
yuri
59ccd121c5 fix modal in notification 2015-05-14 11:18:47 +03:00
yuri
caa0256f19 fix email notification 2015-05-14 10:54:13 +03:00
271 changed files with 4833 additions and 1295 deletions

View File

@@ -31,8 +31,13 @@ class App extends \Espo\Core\Controllers\Base
$preferences = $this->getPreferences()->toArray();
unset($preferences['smtpPassword']);
$user = $this->getUser();
if (!$user->has('teamsIds')) {
$user->loadLinkMultipleField('teams');
}
return array(
'user' => $this->getUser()->toArray(),
'user' => $user->toArray(),
'acl' => $this->getAcl()->getMap(),
'preferences' => $preferences,
'token' => $this->getUser()->get('token')

View File

@@ -34,9 +34,9 @@ class Table
private $cacheFile;
private $actionList = array('read', 'edit', 'delete');
private $actionList = ['read', 'edit', 'delete'];
private $levelList = array('all', 'team', 'own', 'no');
private $levelList = ['all', 'team', 'own', 'no'];
protected $fileManager;
@@ -112,12 +112,14 @@ class Table
{
$aclTables = [];
$assignmentPermissionList = [];
$userPermissionList = [];
$userRoles = $this->user->get('roles');
foreach ($userRoles as $role) {
$aclTables[] = $role->get('data');
$assignmentPermissionList[] = $role->get('assignmentPermission');
$userPermissionList[] = $role->get('userPermission');
}
$teams = $this->user->get('teams');
@@ -126,11 +128,14 @@ class Table
foreach ($teamRoles as $role) {
$aclTables[] = $role->get('data');
$assignmentPermissionList[] = $role->get('assignmentPermission');
$userPermissionList[] = $role->get('userPermission');
}
}
$this->data['table'] = $this->merge($aclTables);
$this->data['assignmentPermission'] = $this->mergeValues($assignmentPermissionList, 'all');
$this->data['assignmentPermission'] = $this->mergeValues($assignmentPermissionList, $this->metadata->get('app.acl.valueDefaults.assignmentPermission', 'all'));
$this->data['userPermission'] = $this->mergeValues($userPermissionList, $this->metadata->get('app.acl.valueDefaults.userPermission', 'no'));
}
private function initSolid()
@@ -222,8 +227,11 @@ class Table
foreach ($scopeList as $scope) {
if (!array_key_exists($scope, $data)) {
$aclType = $this->metadata->get('scopes.' . $scope . '.acl');
if (!empty($aclType) && ($aclType === true || $aclType === 'record')) {
$data[$scope] = $this->metadata->get('app.acl.recordDefault');
if ($aclType === true) {
$aclType = 'recordAllTeamOwnNo';
}
if (!empty($aclType)) {
$data[$scope] = $this->metadata->get('app.acl.defaults.' . $aclType, true);
}
}
}

View File

@@ -115,7 +115,7 @@ class AclManager
if ($user->isAdmin()) {
return true;
}
$this->getTable($user)->get($permission);
return $this->getTable($user)->get($permission);
}
public function checkReadOnlyTeam(User $user, $scope)

View File

@@ -105,22 +105,23 @@ class Application
$container = $this->getContainer();
$slim->get('/', function() {});
$slim->post('/', function() {});
$entryPointManager = new \Espo\Core\EntryPointManager($container);
$auth = $this->getAuth();
$apiAuth = new \Espo\Core\Utils\Api\Auth($auth, $entryPointManager->checkAuthRequired($entryPoint), true);
$slim->add($apiAuth);
try {
$auth = $this->getAuth();
$apiAuth = new \Espo\Core\Utils\Api\Auth($auth, $entryPointManager->checkAuthRequired($entryPoint), true);
$slim->add($apiAuth);
$slim->hook('slim.before.dispatch', function () use ($entryPoint, $entryPointManager, $container) {
try {
$slim->hook('slim.before.dispatch', function () use ($entryPoint, $entryPointManager, $container) {
$entryPointManager->run($entryPoint);
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode(), true);
}
});
});
$slim->run();
$slim->run();
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode(), true);
}
}
public function runCron()

View File

@@ -236,6 +236,7 @@ class Container
return new \Espo\Core\Utils\Language(
$this->get('fileManager'),
$this->get('config'),
$this->get('metadata'),
$this->get('preferences')
);
}

View File

@@ -51,7 +51,7 @@ class Record extends Base
$service = $this->getServiceFactory()->create($name);
} else {
$service = $this->getServiceFactory()->create($this->defaultRecordServiceName);
$service->setEntityName($name);
$service->setEntityType($name);
}
return $service;
@@ -69,13 +69,17 @@ class Record extends Base
return $entity->toArray();
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
return $this->actionUpdate($params, $data);
return $this->actionUpdate($params, $data, $request);
}
public function actionCreate($params, $data)
public function actionCreate($params, $data, $request)
{
if (!$request->isPost()) {
throw BadRequest();
}
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
}
@@ -89,8 +93,12 @@ class Record extends Base
throw new Error();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
if (!$request->isPut() && !$request->isPatch()) {
throw BadRequest();
}
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
}
@@ -135,7 +143,7 @@ class Record extends Base
return array(
'total' => $result['total'],
'list' => $result['collection']->toArray()
'list' => isset($result['collection']) ? $result['collection']->toArray() : $result['list']
);
}
@@ -170,12 +178,16 @@ class Record extends Base
return array(
'total' => $result['total'],
'list' => $result['collection']->toArray()
'list' => isset($result['collection']) ? $result['collection']->toArray() : $result['list']
);
}
public function actionDelete($params)
public function actionDelete($params, $data, $request)
{
if (!$request->isDelete()) {
throw BadRequest();
}
$id = $params['id'];
if ($this->getRecordService()->deleteEntity($id)) {
@@ -212,6 +224,10 @@ class Record extends Base
public function actionMassUpdate($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'edit')) {
throw new Forbidden();
}
@@ -235,6 +251,9 @@ class Record extends Base
public function actionMassDelete($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'delete')) {
throw new Forbidden();
}
@@ -254,10 +273,14 @@ class Record extends Base
return $idsRemoved;
}
public function actionCreateLink($params, $data)
public function actionCreateLink($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (empty($params['id']) || empty($params['link'])) {
throw BadRequest();
throw new BadRequest();
}
$id = $params['id'];
@@ -294,13 +317,17 @@ class Record extends Base
throw new Error();
}
public function actionRemoveLink($params, $data)
public function actionRemoveLink($params, $data, $request)
{
if (!$request->isDelete()) {
throw new BadRequest();
}
$id = $params['id'];
$link = $params['link'];
if (empty($params['id']) || empty($params['link'])) {
throw BadRequest();
throw new BadRequest();
}
$foreignIds = array();
@@ -326,8 +353,11 @@ class Record extends Base
throw new Error();
}
public function actionFollow($params)
public function actionFollow($params, $data, $request)
{
if (!$request->isPut()) {
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'read')) {
throw new Forbidden();
}
@@ -335,8 +365,11 @@ class Record extends Base
return $this->getRecordService()->follow($id);
}
public function actionUnfollow($params)
public function actionUnfollow($params, $data, $request)
{
if (!$request->isDelete()) {
throw new BadRequest();
}
if (!$this->getAcl()->check($this->name, 'read')) {
throw new Forbidden();
}

View File

@@ -28,9 +28,9 @@ use \Espo\Core\Exceptions\NotFound,
class EntryPointManager
{
private $container;
private $fileManager;
private $container;
private $fileManager;
protected $data = null;
@@ -38,7 +38,7 @@ class EntryPointManager
protected $allowedMethods = array(
'run',
);
);
/**
* @var array - path to entryPoint files
@@ -46,14 +46,14 @@ class EntryPointManager
private $paths = array(
'corePath' => 'application/Espo/EntryPoints',
'modulePath' => 'application/Espo/Modules/{*}/EntryPoints',
'customPath' => 'custom/Espo/Custom/EntryPoints',
'customPath' => 'custom/Espo/Custom/EntryPoints',
);
public function __construct(\Espo\Core\Container $container)
{
$this->container = $container;
$this->fileManager = $container->get('fileManager');
$this->container = $container;
$this->fileManager = $container->get('fileManager');
}
protected function getContainer()
@@ -69,16 +69,16 @@ class EntryPointManager
public function checkAuthRequired($name)
{
$className = $this->getClassName($name);
if ($className === false) {
if (!$className) {
throw new NotFound();
}
return $className::$authRequired;
return $className::$authRequired;
}
public function run($name)
public function run($name)
{
$className = $this->getClassName($name);
if ($className === false) {
if (!$className) {
throw new NotFound();
}
$entryPoint = new $className($this->container);
@@ -89,7 +89,7 @@ class EntryPointManager
protected function getClassName($name)
{
$name = Util::normilizeClassName($name);
if (!isset($this->data)) {
$this->init();
}
@@ -98,8 +98,8 @@ class EntryPointManager
if (isset($this->data[$name])) {
return $this->data[$name];
}
return false;
return false;
}
@@ -108,8 +108,7 @@ class EntryPointManager
$classParser = $this->getContainer()->get('classParser');
$classParser->setAllowedMethods($this->allowedMethods);
$this->data = $classParser->getData($this->paths, $this->cacheFile);
}
}
}

View File

@@ -164,6 +164,10 @@ class Sender
$message->addFrom($fromAddress, $fromName);
}
if (!$email->get('from')) {
$email->set('from', $fromAddress);
}
if (!empty($params['replyToAddress'])) {
$replyToName = null;
if (!empty($params['replyToName'])) {

View File

@@ -162,8 +162,6 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
public function remove(Entity $entity, array $options = array())
{
$this->getEntityManager()->getHookManager()->process($this->entityName, 'beforeRemove', $entity, $options);
$result = parent::remove($entity, $options);
if ($result) {
$this->getEntityManager()->getHookManager()->process($this->entityName, 'afterRemove', $entity, $options);

View File

@@ -209,10 +209,9 @@ class Base
$defs = $relDefs[$link];
if ($defs['type'] == 'manyMany') {
$joins[] = $link;
if (!empty($defs['relationName']) && !empty($defs['midKeys'])) {
if (!empty($defs['midKeys'])) {
$key = $defs['midKeys'][1];
$relationName = lcfirst($defs['relationName']);
$part[$relationName . '.' . $key] = $idsValue;
$part[$link . 'Middle.' . $key] = $idsValue;
}
} else if ($defs['type'] == 'belongsTo') {
if (!empty($defs['key'])) {

View File

@@ -0,0 +1,9 @@
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"customizable": true,
"importable": true,
"notifications": true
}

View File

@@ -0,0 +1,9 @@
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"customizable": true,
"importable": false,
"notifications": false
}

View File

@@ -0,0 +1,9 @@
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"customizable": true,
"importable": true,
"notifications": true
}

View File

@@ -57,10 +57,10 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
//check permissions copied and deleted files
$this->checkIsWritable();
$this->backupExistingFiles();
$this->beforeRunAction();
$this->backupExistingFiles();
/* run before install script */
$this->runScript('before');

View File

@@ -56,17 +56,17 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
/* copy core files */
if (!$this->copyFiles()) {
throw new $this->throwErrorAndRemovePackage('Cannot copy files.');
$this->throwErrorAndRemovePackage('Cannot copy files.');
}
/* remove extension files, saved in fileList */
if (!$this->deleteFiles(true)) {
throw new $this->throwErrorAndRemovePackage('Permission denied to delete files.');
$this->throwErrorAndRemovePackage('Permission denied to delete files.');
}
}
if (!$this->systemRebuild()) {
throw new $this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
}
/* run after uninstall script */

View File

@@ -33,16 +33,8 @@ class Converter
private $schemaConverter;
private $schemaFromMetadata = null;
/**
* @var array $meta - metadata array
*/
//private $meta;
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager)
{
$this->metadata = $metadata;
@@ -53,7 +45,6 @@ class Converter
$this->schemaConverter = new Schema\Converter($this->fileManager);
}
protected function getMetadata()
{
return $this->metadata;
@@ -69,13 +60,11 @@ class Converter
return $this->schemaConverter;
}
public function getSchemaFromMetadata($entityList = null)
{
$ormMeta = $this->getMetadata()->getOrmMetadata();
$entityDefs = $this->getMetadata()->get('entityDefs');
$this->schemaFromMetadata = $this->getSchemaConverter()->process($ormMeta, $entityDefs, $entityList);
$this->schemaFromMetadata = $this->getSchemaConverter()->process($ormMeta, $entityList);
return $this->schemaFromMetadata;
}
@@ -98,11 +87,4 @@ class Converter
return $result;
}
}
?>
}

View File

@@ -22,12 +22,13 @@
namespace Espo\Core\Utils\Database\DBAL\Platforms;
use Doctrine\DBAL\Schema\TableDiff,
Doctrine\DBAL\Schema\Index,
Doctrine\DBAL\Schema\Table,
Doctrine\DBAL\Schema\Constraint,
Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\Constraint;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\ColumnDiff;
use Doctrine\DBAL\Schema\Column;
class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
{
@@ -39,21 +40,52 @@ class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
$queryParts[] = 'RENAME TO ' . $diff->newName;
}
foreach ($diff->addedColumns as $column) {
if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
//espo: It works not correctly. It can rename some existing fields
foreach ($diff->renamedColumns as $oldColumnName => $column) {
if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
continue;
}
//espo: remaned autoincrement field
if ($column->getAutoincrement()) {
$diff->removedColumns[$oldColumnName] = new Column($oldColumnName, $column->getType(), $column->toArray());
$columnName = $column->getQuotedName($this);
$diff->addedColumns[$columnName] = $column;
continue;
}
//END espo
$columnArray = $column->toArray();
$columnArray['comment'] = $this->getColumnComment($column);
$queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
}
/*$queryParts[] = 'CHANGE ' . $oldColumnName . ' '
. $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); */
$queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); //espo: fixed the problem
} //espo: END
foreach ($diff->removedColumns as $column) {
if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
continue;
}
//espo: remove autoincrement option
if ($column->getAutoincrement()) {
$columnName = $column->getQuotedName($this);
$changedColumn = clone $column;
$changedColumn->setNotNull(false);
$changedColumn->setAutoincrement(false);
$changedProperties = array(
'notnull',
'autoincrement',
);
$diff->changedColumns[$columnName] = new ColumnDiff($columnName, $changedColumn, $changedProperties, $column);
}
//END espo
//$queryParts[] = 'DROP ' . $column->getQuotedName($this); //espo: no needs to remove columns
}
@@ -71,19 +103,15 @@ class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
. $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
}
//espo: It works not correctly. It can rename some existing fields
foreach ($diff->renamedColumns as $oldColumnName => $column) {
if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
foreach ($diff->addedColumns as $column) {
if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
continue;
}
$columnArray = $column->toArray();
$columnArray['comment'] = $this->getColumnComment($column);
/*$queryParts[] = 'CHANGE ' . $oldColumnName . ' '
. $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); */
$queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); //espo: fixed the problem
} //espo: END
$queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
}
$sql = array();
$tableSql = array();

View File

@@ -79,7 +79,7 @@ class Converter
protected $idParams = array(
'dbType' => 'varchar',
'len' => '24',
'len' => 24,
);
/**
@@ -107,9 +107,9 @@ class Converter
return $this->metadata;
}
protected function getEntityDefs()
protected function getEntityDefs($reload = false)
{
if (empty($this->entityDefs)) {
if (empty($this->entityDefs) || $reload) {
$this->entityDefs = $this->getMetadata()->get('entityDefs');
}
@@ -131,9 +131,14 @@ class Converter
return $this->metadataHelper;
}
/**
* Orm metadata convertation process
*
* @return array
*/
public function process()
{
$entityDefs = $this->getEntityDefs();
$entityDefs = $this->getEntityDefs(true);
$ormMeta = array();
foreach($entityDefs as $entityName => $entityMeta) {
@@ -185,12 +190,12 @@ class Converter
switch ($fieldParams['type']) {
case 'id':
if ($fieldParams['dbType'] != 'int') {
$fieldParams = array_merge($fieldParams, $this->idParams);
$fieldParams = array_merge($this->idParams, $fieldParams);
}
break;
case 'foreignId':
$fieldParams = array_merge($fieldParams, $this->idParams);
$fieldParams = array_merge($this->idParams, $fieldParams);
$fieldParams['notNull'] = false;
break;
@@ -221,6 +226,11 @@ class Converter
*/
protected function convertFields($entityName, &$entityMeta)
{
//List of unmerged fields with default field defenitions in $outputMeta
$unmergedFields = array(
'name',
);
$outputMeta = array(
'id' => array(
'type' => Entity::ID,
@@ -230,6 +240,10 @@ class Converter
'type' => isset($entityMeta['fields']['name']['type']) ? $entityMeta['fields']['name']['type'] : Entity::VARCHAR,
'notStorable' => true,
),
'deleted' => array(
'type' => Entity::BOOL,
'default' => false,
),
);
foreach($entityMeta['fields'] as $fieldName => $fieldParams) {
@@ -238,8 +252,14 @@ class Converter
$fieldTypeMeta = $this->getMetadataHelper()->getFieldDefsByType($fieldParams);
$fieldDefs = $this->convertField($entityName, $fieldName, $fieldParams, $fieldTypeMeta);
if ($fieldDefs !== false) {
$outputMeta[$fieldName] = $fieldDefs; //push fieldDefs to the main array
//push fieldDefs to the ORM metadata array
if (isset($outputMeta[$fieldName]) && !in_array($fieldName, $unmergedFields)) {
$outputMeta[$fieldName] = array_merge($outputMeta[$fieldName], $fieldDefs);
} else {
$outputMeta[$fieldName] = $fieldDefs;
}
}
/** check and set the linkDefs from 'fields' metadata */
@@ -254,13 +274,6 @@ class Converter
}
}
if (!isset($outputMeta['deleted'])) {
$outputMeta['deleted'] = array(
'type' => Entity::BOOL,
'default' => false,
);
}
return $outputMeta;
}
@@ -308,6 +321,15 @@ class Converter
'type' => 'varchar',
'notStorable' => true,
);
$ormMeta[$entityName]['fields']['followersIds'] = array(
'type' => 'jsonArray',
'notStorable' => true,
);
$ormMeta[$entityName]['fields']['followersNames'] = array(
'type' => 'jsonObject',
'notStorable' => true,
);
}
} //END: add a field 'isFollowed' for stream => true
@@ -316,12 +338,6 @@ class Converter
protected function convertField($entityName, $fieldName, array $fieldParams, $fieldTypeMeta = null)
{
/** set default type if exists */
if (!isset($fieldParams['type']) || empty($fieldParams['type'])) {
$GLOBALS['log']->debug('Field type does not exist for '.$entityName.':'.$fieldName.'. Use default type ['.$this->defaultFieldType.']');
$fieldParams['type'] = $this->defaultFieldType;
} /** END: set default type if exists */
/** merge fieldDefs option from field definition */
if (!isset($fieldTypeMeta)) {
$fieldTypeMeta = $this->getMetadataHelper()->getFieldDefsByType($fieldParams);
@@ -362,8 +378,6 @@ class Converter
return array();
}
$entityDefs = $this->getEntityDefs();
$relationships = array();
foreach($entityMeta['links'] as $linkName => $linkParams) {

View File

@@ -28,8 +28,6 @@ class RelationManager
{
private $metadata;
private $entityDefs;
public function __construct(\Espo\Core\Utils\Metadata $metadata)
{
$this->metadata = $metadata;
@@ -40,16 +38,6 @@ class RelationManager
return $this->metadata;
}
protected function getEntityDefs()
{
if (empty($this->entityDefs)) {
$this->entityDefs = $this->getMetadata()->get('entityDefs');
}
return $this->entityDefs;
}
public function getLinkEntityName($entityName, $linkParams)
{
return isset($linkParams['entity']) ? $linkParams['entity'] : $entityName;
@@ -110,7 +98,7 @@ class RelationManager
public function convert($linkName, $linkParams, $entityName, $ormMeta)
{
$entityDefs = $this->getEntityDefs();
$entityDefs = $this->getMetadata()->get('entityDefs');
$foreignEntityName = $this->getLinkEntityName($entityName, $linkParams);
$foreignLink = $this->getForeignLink($linkName, $linkParams, $entityDefs[$foreignEntityName]);

View File

@@ -47,11 +47,10 @@ class Converter
'unique' => 'unique',
);
//todo: same array in Converters\Orm
protected $idParams = array(
'dbType' => 'varchar',
'len' => '24',
'len' => 24,
);
//todo: same array in Converters\Orm
@@ -68,8 +67,6 @@ class Converter
{
$this->fileManager = $fileManager;
$this->dbalSchema = new \Espo\Core\Utils\Database\DBAL\Schema\Schema();
$this->typeList = array_keys(\Doctrine\DBAL\Types\Type::getTypesMap());
}
@@ -78,13 +75,31 @@ class Converter
return $this->fileManager;
}
protected function getSchema()
/**
* Get schema
*
* @param boolean $reload
*
* @return \Doctrine\DBAL\Schema\Schema
*/
protected function getSchema($reload = false)
{
if (!isset($this->dbalSchema) || $reload) {
$this->dbalSchema = new \Espo\Core\Utils\Database\DBAL\Schema\Schema();
}
return $this->dbalSchema;
}
public function process(array $ormMeta, $entityDefs, $entityList = null)
/**
* Schema convertation process
*
* @param array $ormMeta
* @param array|null $entityList
*
* @return \Doctrine\DBAL\Schema\Schema
*/
public function process(array $ormMeta, $entityList = null)
{
$GLOBALS['log']->debug('Schema\Converter - Start: building schema');
@@ -106,7 +121,7 @@ class Converter
$ormMeta = array_intersect_key($ormMeta, array_flip($dependentEntities));
}
$schema = $this->getSchema();
$schema = $this->getSchema(true);
$tables = array();
foreach ($ormMeta as $entityName => $entityParams) {

View File

@@ -126,20 +126,17 @@ class EntityManager
}
$labelCreate = $this->getLanguage()->translate('Create') . ' ' . $labelSingular;
$scopeData = array(
'entity' => true,
'layouts' => true,
'tab' => true,
'acl' => true,
'module' => 'Custom',
'isCustom' => true,
'customizable' => true,
'importable' => true,
'type' => $type,
'stream' => $stream,
'notifications' => true
);
$this->getMetadata()->set('scopes', $name, $scopeData);
$filePath = "application/Espo/Core/Templates/Metadata/{$type}/scopes.json";
$scopesDataContents = $this->getFileManager()->getContents($filePath);
$scopesDataContents = str_replace('{entityType}', $name, $scopesDataContents);
$scopesData = Json::decode($entityDefsDataContents, true);
$scopesData['stream'] = $stream;
$scopesData['type'] = $type;
$scopesData['module'] = 'Custom';
$scopesData['isCustom'] = true;
$this->getMetadata()->set('scopes', $name, $scopesData);
$filePath = "application/Espo/Core/Templates/Metadata/{$type}/entityDefs.json";
$entityDefsDataContents = $this->getFileManager()->getContents($filePath);

View File

@@ -28,10 +28,12 @@ use Espo\Core\Utils\Json;
class FileUnifier
{
private $fileManager;
private $metadata;
public function __construct(\Espo\Core\Utils\File\Manager $fileManager)
public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Metadata $metadata = null)
{
$this->fileManager = $fileManager;
$this->metadata = $metadata;
}
protected function getFileManager()
@@ -39,6 +41,11 @@ class FileUnifier
return $this->fileManager;
}
protected function getMetadata()
{
return $this->metadata;
}
/**
* Unite files content
*
@@ -53,7 +60,7 @@ class FileUnifier
if (!empty($paths['modulePath'])) {
$moduleDir = strstr($paths['modulePath'], '{*}', true);
$moduleList = $this->getFileManager()->getFileList($moduleDir, false, '', false);
$moduleList = isset($this->metadata) ? $this->getMetadata()->getModuleList() : $this->getFileManager()->getFileList($moduleDir, false, '', false);
foreach ($moduleList as $moduleName) {
$moduleFilePath = str_replace('{*}', $moduleName, $paths['modulePath']);

View File

@@ -242,7 +242,7 @@ class Manager
public function putContentsJson($path, $data)
{
if (!Utils\Json::isJSON($data)) {
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT);
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
return $this->putContents($path, $data, LOCK_EX);
@@ -283,7 +283,7 @@ class Manager
$data = Utils\Util::merge($savedDataArray, $newDataArray);
if ($isReturnJson) {
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT);
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
if ($isPhp) {

View File

@@ -27,15 +27,17 @@ use Espo\Core\Utils;
class Unifier
{
private $fileManager;
private $metadata;
protected $params = array(
'unsetFileName' => 'unset.json',
'defaultsPath' => 'application/Espo/Core/defaults',
);
public function __construct(\Espo\Core\Utils\File\Manager $fileManager)
public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Metadata $metadata = null)
{
$this->fileManager = $fileManager;
$this->metadata = $metadata;
}
protected function getFileManager()
@@ -43,6 +45,11 @@ class Unifier
return $this->fileManager;
}
protected function getMetadata()
{
return $this->metadata;
}
/**
* Unite file content to the file
*
@@ -58,11 +65,12 @@ class Unifier
if (!empty($paths['modulePath'])) {
$customDir = strstr($paths['modulePath'], '{*}', true);
$dirList = $this->getFileManager()->getFileList($customDir, false, '', false);
foreach ($dirList as $dirName) {
$curPath = str_replace('{*}', $dirName, $paths['modulePath']);
$content = Utils\Util::merge($content, $this->unifySingle($curPath, $name, $recursively, $dirName));
$moduleList = isset($this->metadata) ? $this->getMetadata()->getModuleList() : $this->getFileManager()->getFileList($customDir, false, '', false);
foreach ($moduleList as $moduleName) {
$curPath = str_replace('{*}', $moduleName, $paths['modulePath']);
$content = Utils\Util::merge($content, $this->unifySingle($curPath, $name, $recursively, $moduleName));
}
}

View File

@@ -30,6 +30,7 @@ class Language
{
private $fileManager;
private $config;
private $metadata;
private $preferences;
private $unifier;
@@ -62,13 +63,14 @@ class Language
);
public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Config $config, \Espo\Entities\Preferences $preferences = null)
public function __construct(File\Manager $fileManager, Config $config, Metadata $metadata, \Espo\Entities\Preferences $preferences = null)
{
$this->fileManager = $fileManager;
$this->config = $config;
$this->metadata = $metadata;
$this->preferences = $preferences;
$this->unifier = new \Espo\Core\Utils\File\Unifier($this->fileManager);
$this->unifier = new \Espo\Core\Utils\File\Unifier($this->fileManager, $this->metadata);
}
protected function getFileManager()
@@ -81,6 +83,11 @@ class Language
return $this->config;
}
protected function getMetadata()
{
return $this->metadata;
}
protected function getPreferences()
{
return $this->preferences;

View File

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

View File

@@ -28,8 +28,6 @@ class Metadata
{
protected $meta = null;
protected $scopes = array();
private $config;
private $unifier;
private $fileManager;
@@ -92,7 +90,7 @@ class Metadata
protected function getUnifier()
{
if (!isset($this->unifier)) {
$this->unifier = new \Espo\Core\Utils\File\Unifier($this->fileManager);
$this->unifier = new \Espo\Core\Utils\File\Unifier($this->fileManager, $this);
}
return $this->unifier;
@@ -153,6 +151,7 @@ class Metadata
if (file_exists($this->cacheFile) && !$reload) {
$this->meta = $this->getFileManager()->getPhpContents($this->cacheFile);
} else {
$this->clearVars();
$this->meta = $this->getUnifier()->unify($this->name, $this->paths, true);
$this->meta = $this->setLanguageFromConfig($this->meta);
$this->meta = $this->addAdditionalFields($this->meta);
@@ -531,4 +530,16 @@ class Metadata
return $path;
}
/**
* Clear metadata variables when reload meta
*
* @return void
*/
protected function clearVars()
{
$this->meta = null;
$this->moduleList = null;
$this->ormMeta = null;
}
}

View File

@@ -186,19 +186,25 @@ class Util
*/
public static function unsetInArrayByValue($needle, array $haystack, $reIndex = true)
{
$doReindex = false;
foreach($haystack as $key => $value) {
if (is_array($value)) {
$haystack[$key] = static::unsetInArrayByValue($needle, $value);
} else if ($needle === $value) {
unset($haystack[$key]);
if ($reIndex) {
array_splice($haystack, $key, 1);
} else {
unset($haystack[$key]);
$doReindex = true;
}
}
}
if ($doReindex) {
$haystack = array_values($haystack);
}
return $haystack;
}

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;
@@ -29,7 +29,7 @@ use \Espo\Core\Exceptions\Error,
class Activities extends \Espo\Core\Controllers\Base
{
public static $defaultAction = 'index';
public function actionListCalendarEvents($params, $data, $request)
{
if (!$this->getAcl()->check('Calendar')) {
@@ -38,12 +38,12 @@ class Activities extends \Espo\Core\Controllers\Base
$from = $request->get('from');
$to = $request->get('to');
if (empty($from) || empty($to)) {
throw new BadRequest();
}
$service = $this->getService('Activities');
return $service->getEvents($this->getUser()->id, $from, $to);
}
@@ -51,7 +51,7 @@ class Activities extends \Espo\Core\Controllers\Base
public function actionPopupNotifications()
{
$userId = $this->getUser()->id;
return $this->getService('Activities')->getPopupNotifications($userId);
}
@@ -72,30 +72,37 @@ class Activities extends \Espo\Core\Controllers\Base
public function actionList($params, $data, $request)
{
$name = $params['name'];
if (!in_array($name, array('activities', 'history'))) {
if (!in_array($name, ['activities', 'history'])) {
throw new BadRequest();
}
$entityName = $params['scope'];
if (empty($params['scope'])) {
throw new BadRequest();
}
if (empty($params['id'])) {
throw new BadRequest();
}
$entityType = $params['scope'];
$id = $params['id'];
$offset = intval($request->get('offset'));
$maxSize = intval($request->get('maxSize'));
$asc = $request->get('asc') === 'true';
$sortBy = $request->get('sortBy');
$where = $request->get('where');
$scope = null;
if (!empty($where) && !empty($where['scope']) && $where['scope'] !== 'false') {
$scope = $where['scope'];
if (is_array($where) && !empty($where[0]) && $where[0] !== 'false') {
$scope = $where[0];
}
$service = $this->getService('Activities');
$methodName = 'get' . ucfirst($name);
return $service->$methodName($entityName, $id, array(
return $service->$methodName($entityType, $id, array(
'scope' => $scope,
'offset' => $offset,
'maxSize' => $maxSize,

View File

@@ -0,0 +1,28 @@
<?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/.
************************************************************************/
namespace Espo\Modules\Crm\Controllers;
class DocumentFolder extends \Espo\Core\Templates\Controllers\CategoryTree
{
}

View File

@@ -22,7 +22,27 @@
namespace Espo\Modules\Crm\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class TargetList extends \Espo\Core\Controllers\Record
{
public function actionUnlinkAll($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (empty($data['id'])) {
throw new BadRequest();
}
if (empty($data['link'])) {
throw new BadRequest();
}
return $this->getRecordService()->unlinkAll($data['id'], $data['link']);
}
}

View File

@@ -18,11 +18,11 @@
*
* 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\Entities;
class Document extends \Espo\Core\Entities\Person
class Document extends \Espo\Core\ORM\Entity
{
}

View File

@@ -0,0 +1,29 @@
<?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/.
************************************************************************/
namespace Espo\Modules\Crm\Entities;
class DocumentFolder extends \Espo\Core\Templates\Entities\CategoryTree
{
}

View File

@@ -92,6 +92,10 @@ class Contact extends \Espo\Core\ORM\Repositories\RDB
}
}
if ($entity->has('targetListId') && $entity->isNew()) {
$this->relate($entity, 'targetLists', $entity->get('targetListId'));
}
return $result;
}
}

View File

@@ -0,0 +1,27 @@
<?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/.
************************************************************************/
namespace Espo\Modules\Crm\Repositories;
class DocumentFolder extends \Espo\Core\Repositories\CategoryTree
{
}

View File

@@ -23,7 +23,8 @@
"tasksPrimary": "Tasks (expanded)",
"emailsPrimary": "Emails (expanded)",
"targetLists": "Target Lists",
"campaignLogRecords": "Campaign Log"
"campaignLogRecords": "Campaign Log",
"campaign": "Campaign"
},
"options": {
"type": {

View File

@@ -15,7 +15,8 @@
"hardBouncedCount": "Hard Bounced",
"softBouncedCount": "Soft Bounced",
"leadCreatedCount": "Leads Created",
"revenue": "Revenue"
"revenue": "Revenue",
"revenueConverted": "revenue (converted)"
},
"links": {
"targetLists": "Target Lists",

View File

@@ -20,7 +20,8 @@
"opportunities": "Opportunities",
"cases": "Cases",
"targetLists": "Target Lists",
"campaignLogRecords": "Campaign Log"
"campaignLogRecords": "Campaign Log",
"campaign": "Campaign"
},
"labels": {
"Create Contact": "Create Contact"

View File

@@ -11,11 +11,14 @@
"source": "Source",
"publishDate": "Publish Date",
"expirationDate": "Expiration Date",
"description": "Description"
"description": "Description",
"accounts": "Accounts",
"folder": "Folder"
},
"links": {
"accounts": "Accounts",
"opportunities": "Opportunities"
"opportunities": "Opportunities",
"folder": "Folder"
},
"options": {
"status": {
@@ -23,6 +26,13 @@
"Draft": "Draft",
"Expired": "Expired",
"Canceled": "Canceled"
},
"type": {
"": "None",
"Contract": "Contract",
"NDA": "NDA",
"EULA": "EULA",
"License Agreement": "License Agreement"
}
},
"presetFilters": {

View File

@@ -0,0 +1,9 @@
{
"labels": {
"Create DocumentFolder": "Create Document Folder",
"Manage Categories": "Manage Folders"
},
"links": {
"documents": "Documents"
}
}

View File

@@ -2,6 +2,7 @@
"labels": {
"Create Lead": "Create Lead",
"Create Contact": "Create Contact",
"Create Task": "Create Task"
"Create Task": "Create Task",
"Create Case": "Create Case"
}
}

View File

@@ -11,6 +11,7 @@
"Task": "Task",
"Case": "Case",
"Document": "Document",
"DocumentFolder": "Document Folder",
"Campaign": "Campaign",
"TargetList": "Target List"
},
@@ -26,6 +27,7 @@
"Task": "Tasks",
"Case": "Cases",
"Document": "Documents",
"DocumentFolder": "Document Folders",
"Campaign": "Campaigns",
"TargetList": "Target Lists"
},
@@ -40,7 +42,7 @@
"OpportunitiesByStage": "Opportunities by Stage",
"OpportunitiesByLeadSource": "Opportunities by Lead Source",
"SalesByMonth": "Sales by Month",
"SalesPipeline": "Sales Pipeline"
"SalesPipeline": "Sales Pipeline"
},
"labels": {
"Create InboundEmail": "Create Inbound Email",

View File

@@ -27,7 +27,8 @@
},
"links": {
"targetLists": "Target Lists",
"campaignLogRecords": "Campaign Log"
"campaignLogRecords": "Campaign Log",
"campaign": "Campaign"
},
"options": {
"status": {
@@ -39,6 +40,7 @@
"Dead": "Dead"
},
"source": {
"": "None",
"Call": "Call",
"Email": "Email",
"Existing Customer": "Existing Customer",

View File

@@ -16,7 +16,8 @@
},
"links": {
"contacts": "Contacts",
"documents": "Documents"
"documents": "Documents",
"campaign": "Campaign"
},
"options": {
"stage": {

View File

@@ -23,6 +23,7 @@
}
},
"labels": {
"Create TargetList": "Create Target List"
"Create TargetList": "Create Target List",
"Opted Out": "Opted Out"
}
}

View File

@@ -16,7 +16,7 @@
"links": {
"contacts": "Контакти",
"opportunities": "Угоди",
"cases": "Кейси",
"cases": "Звернення",
"documents": "Документи",
"meetingsPrimary": "Meetings (expanded)",
"callsPrimary": "Calls (expanded)",
@@ -37,7 +37,7 @@
"Advertising": "Реклама",
"Apparel & Accessories": "Одяг та аксесуари",
"Automotive": "Автомобільні",
"Banking": "Банкінґ",
"Banking": "Банкінг",
"Biotechnology": "Біотехнології",
"Chemical": "Хемія",
"Computer": "Комп'ютери",
@@ -53,8 +53,8 @@
"Real Estate": "Нерухомість",
"Service": "Сервіс",
"Sports": "Спорт",
"Software": "Проґрамне забезпечення",
"Technology": "Технолоґія",
"Software": "Програмне забезпечення",
"Technology": "Технологія",
"Telecommunications": "Телекомунікації",
"Television": "Телебачення",
"Transportation": "Транспорт",
@@ -62,7 +62,7 @@
}
},
"labels": {
"Create Account": "Створити контраґента",
"Create Account": "Створити контрагента",
"Copy Billing": "Копія рахунку"
},
"presetFilters": {

View File

@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"parent": "Джерело",
"status": "статус",
"status": "Статус",
"dateStart": "Дата початку",
"dateEnd": "Дата закінчення",
"direction": "Напрямок",
@@ -12,7 +12,7 @@
"contacts": "Контакти",
"leads": "Ліди",
"reminders": "Нагадування",
"account": "Контраґент"
"account": "Контрагент"
},
"links": {
},

View File

@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"description": "Опис",
"status": "статус",
"status": "Статус",
"type": "Тип",
"startDate": "Дата Початку",
"endDate": "Дата Закінчення",
@@ -19,7 +19,7 @@
},
"links": {
"targetLists": "Цільові списки",
"accounts": "Контраґенти",
"accounts": "Контрагенти",
"contacts": "Контакти",
"leads": "Ліди",
"opportunities": "Угоди",

View File

@@ -1,9 +1,9 @@
{
"fields": {
"name": "Ім'я",
"number": "Нумер",
"status": "статус",
"account": "Контраґент",
"number": "Номер",
"status": "Статус",
"account": "Контрагент",
"contact": "Контакт",
"priority": "Пріоритет",
"type": "Тип",

View File

@@ -3,10 +3,10 @@
"name": "Ім'я",
"emailAddress": "Емейл",
"title": "Посада",
"account": "Контраґент",
"accounts": "Контраґенти",
"account": "Контрагент",
"accounts": "Контрагенти",
"phoneNumber": "Телефон",
"accountType": "Тип контраґента",
"accountType": "Тип контрагента",
"doNotCall": "Не дзвонити",
"address": "Адреса",
"opportunityRole": "Роль угоди",
@@ -18,7 +18,7 @@
},
"links": {
"opportunities": "Угоди",
"cases": "Кейси",
"cases": "Звернення",
"targetLists": "Цільові списки",
"campaignLogRecords": "Журнал кампанія"
},

View File

@@ -5,7 +5,7 @@
},
"fields": {
"name": "Ім'я",
"status": "статус",
"status": "Статус",
"file": "Файл",
"type": "Тип",
"source": "Джерело",
@@ -14,7 +14,7 @@
"description": "Опис"
},
"links": {
"accounts": "Контраґенти",
"accounts": "Контрагенти",
"opportunities": "Угоди"
},
"options": {

View File

@@ -1,6 +1,6 @@
{
"scopeNames": {
"Account": "Контраґент",
"Account": "Контрагент",
"Contact": "Контакт",
"Lead": "Лід",
"Target": "Ціль",
@@ -15,7 +15,7 @@
"TargetList": "Цільовий список"
},
"scopeNamesPlural": {
"Account": "Контраґенти",
"Account": "Контрагенти",
"Contact": "Контакти",
"Lead": "Ліди",
"Target": "Цілі",
@@ -24,7 +24,7 @@
"Calendar": "Календар",
"Call": "Дзвінки",
"Task": "Завдання",
"Case": "Кейси",
"Case": "Звернення",
"Document": "Документи",
"Campaign": "Кампанії",
"TargetList": "Цільові списки"
@@ -33,40 +33,40 @@
"Leads": "Мої ліди",
"Opportunities": "Мої угоди",
"Tasks": "Мої завдання",
"Cases": "Мої кейси",
"Cases": "Мої звернення",
"Calendar": "Календар",
"Calls": "Мої дзвінки",
"Meetings": "Мої зустрічі",
"OpportunitiesByStage": "Угоди на стадії",
"OpportunitiesByLeadSource": "Угоди за джерелом лідів",
"SalesByMonth": "Продажі по місяцях",
"SalesPipeline": "Джерела продажу"
"SalesPipeline": "Джерела продажу"
},
"labels": {
"Create InboundEmail": "Створити вхідну пошту",
"Activities": "Заходи",
"History": "Історія",
"Attendees": "Учасники",
"Schedule Meeting": "Розклад зустрічей",
"Schedule Call": "Розклад дзвінків",
"Schedule Meeting": "Запланувати зустріч",
"Schedule Call": "Запланувати дзвінок",
"Compose Email": "Написати емейл",
"Log Meeting": "Протокол зустрічей",
"Log Call": "Протокол дзвінків",
"Archive Email": "Архівувати листа",
"Log Meeting": "Записати зустріч",
"Log Call": "Записати дзвінок",
"Archive Email": "Записати емейл",
"Create Task": "Створити завдання",
"Tasks": "Завдання"
"Tasks": "Завдання"
},
"fields": {
"billingAddressCity": "Місто",
"billingAddressCountry": "Країна",
"billingAddressPostalCode": "Поштовий індекс",
"billingAddressState": "Реґіон",
"billingAddressState": "Регіон",
"billingAddressStreet": "Вулиця",
"addressCity": "Місто",
"addressStreet": "Вулиця",
"addressCountry": "Країна",
"addressState": "Реґіон",
"addressPostalCode": "Поштовий індекс",
"addressState": "Регіон",
"addressPostalCode": "Поштовий індекс",
"shippingAddressCity": "Місто доставки",
"shippingAddressStreet": "Вулиця доставки",
"shippingAddressCountry": "Країна доставки",
@@ -76,16 +76,16 @@
"links": {
"contacts": "Контакти",
"opportunities": "Угоди",
"leads": "Ліди",
"leads": "Ліди",
"meetings": "Зустрічі",
"calls": "Дзвінки",
"tasks": "Завдання",
"emails": "Листи",
"accounts": "Контраґенти",
"cases": "Кейси",
"accounts": "Контрагенти",
"cases": "Звернення",
"documents": "Документи",
"account": "Контраґент",
"opportunity": "Угода",
"account": "Контрагент",
"opportunity": "Угода",
"contact": "Контакт",
"parent": "Джерело"
},

View File

@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"team": "Команда",
"status": "статус",
"status": "Статус",
"assignToUser": "Зв'язатися з користувачем",
"host": "Хост",
"username": "Ім'я користувача",

View File

@@ -10,15 +10,15 @@
"title": "Посада",
"website": "Вебсайт",
"phoneNumber": "Телефон",
"accountName": "Ім'я контраґента",
"accountName": "Ім'я контрагента",
"doNotCall": "Не дзвонити",
"address": "Адреса",
"status": "статус",
"status": "Статус",
"source": "Джерело",
"opportunityAmount": "Сума угоди",
"opportunityAmountConverted": "Сума угоди (конвертована)",
"description": "Опис",
"createdAccount": "Контраґент",
"createdAccount": "Контрагент",
"createdContact": "Контакт",
"createdOpportunity": "Угода",
"campaign": "Кампанія",

View File

@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"parent": "Джерело",
"status": "статус",
"status": "Статус",
"dateStart": "Дата початку",
"dateEnd": "Дата закінчення",
"duration": "Тривалість",

View File

@@ -1,7 +1,7 @@
{
"fields": {
"name": "Ім'я",
"account": "Контраґент",
"account": "Контрагент",
"stage": "Стадія",
"amount": "Сума",
"probability": "Імовірність, %",
@@ -20,7 +20,7 @@
},
"options": {
"stage": {
"Prospecting": "Проспектінґ",
"Prospecting": "Проспектінг",
"Qualification": "Кваліфікація",
"Needs Analysis": "Вимагає аналізу",
"Value Proposition": "Цінова пропозиція",

View File

@@ -4,7 +4,7 @@
"emailAddress": "Емейл",
"title": "Посада",
"website": "Вебсайт",
"accountName": "Ім'я контраґента",
"accountName": "Ім'я контрагента",
"phoneNumber": "Телефон",
"doNotCall": "Не дзвонити",
"address": "Адреса",

View File

@@ -8,7 +8,7 @@
"targetLists": "Цільові списки"
},
"links": {
"accounts": "Контраґенти",
"accounts": "Контрагенти",
"contacts": "Контакти",
"leads": "Ліди",
"campaigns": "Кампанії"

View File

@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"parent": "Джерело",
"status": "статус",
"status": "Статус",
"dateStart": "Дата початку",
"dateEnd": "Належна дата",
"dateStartDate": "Date Start (all day)",
@@ -10,7 +10,7 @@
"priority": "Пріоритет",
"description": "Опис",
"isOverdue": "Прострочено",
"account": "Контраґент",
"account": "Контрагент",
"dateCompleted": "Date Completed",
"attachments": "Вкладення"
},

View File

@@ -3,14 +3,14 @@
"label":false,
"rows":[
[{"name":"file"}, {"name":"source"}],
[{"name":"name"}, {"name":"status"}],
[{"name":"type"}, false]
[{"name":"name"}, {"name":"folder"}]
]
},
{
"label": "Details",
"rows": [
[{"name":"publishDate"}, {"name":"expirationDate"}],
[{"name":"type"}, {"name":"publishDate"}],
[{"name":"status"}, {"name":"expirationDate"}],
[{"name":"description", "fullWidth": true}]
]
}

View File

@@ -5,7 +5,8 @@
[{"name":"source"}],
[{"name":"file"}],
[{"name":"name"}],
[{"name":"type"}],
[{"name":"folder"}],
[{"name":"type"}],
[{"name":"status"}]
]
}

View File

@@ -1,4 +1,5 @@
[
"accounts",
"createdAt",
"expirationDate",
"type",

View File

@@ -0,0 +1,19 @@
[
{
"label": "",
"rows": [
[
{
"name": "name"
},
{
"name": "parent"
},
{
"name": "description",
"fullWidth": true
}
]
]
}
]

View File

@@ -0,0 +1,17 @@
[
{
"label": "",
"rows": [
[
{
"name": "name"
}
],
[
{
"name": "parent"
}
]
]
}
]

View File

@@ -0,0 +1,3 @@
[
"parent"
]

View File

@@ -0,0 +1,10 @@
[
{
"name": "name",
"width": 50,
"link": true
},
{
"name": "parent"
}
]

View File

@@ -0,0 +1,10 @@
[
{
"name": "name",
"width": 50,
"link": true
},
{
"name": "parent"
}
]

View File

@@ -0,0 +1,3 @@
[
"children"
]

View File

@@ -8,5 +8,6 @@
"source",
"opportunityAmountConverted",
"targetLists",
"teams"
"teams",
"createdAccount"
]

View File

@@ -2,7 +2,7 @@
{"name":"name","width":25,"link":true},
{"name":"account","width":18},
{"name":"stage"},
{"name":"amount"},
{"name":"assignedUser"},
{"name":"createdAt"}
{"name":"createdAt"},
{"name":"amount", "align": "right"}
]

View File

@@ -1,6 +1,6 @@
[
{"name":"name", "width": 32, "link": true},
{"name":"stage"},
{"name":"amount"},
{"name":"createdAt"}
{"name":"name", "width": 35, "link": true},
{"name":"stage", "width": 25},
{"name":"createdAt"},
{"name":"amount", "align": "right"}
]

View File

@@ -4,18 +4,8 @@
"detail":"Crm:Call.Detail"
},
"recordViews":{
"list":"Crm:Call.Record.List"
},
"menu": {
"detail": {
"actions": [
{
"label": "Duplicate",
"action": "duplicate",
"acl": "edit"
}
]
}
"list":"Crm:Call.Record.List",
"detail":"Crm:Meeting.Record.Detail"
},
"sidePanels":{
"detail":[

View File

@@ -7,7 +7,7 @@
"label": "Target Lists",
"link": "#TargetList",
"acl": "read",
"style": "link",
"style": "default",
"aclScope": "TargetList"
}
]
@@ -35,15 +35,7 @@
}
},
"filterList": [
{
"name":"active",
"data": {
"status": {
"type": "in",
"value": ["Active"]
}
}
}
"active"
],
"boolFilterList": ["onlyMy"]
}

View File

@@ -1,24 +1,14 @@
{
"controller": "Controllers.Record",
"filterList": [
{
"name":"active",
"data": {
"status": {
"type": "in",
"value": ["Active"]
}
}
},
{
"name":"draft",
"data": {
"status": {
"type": "in",
"value": ["Draft"]
}
}
}
],
"boolFilterList": ["onlyMy"]
"controller": "Controllers.Record",
"views": {
"list": "Crm:Document.List"
},
"modalViews": {
"select": "Crm:Document.Modals.SelectRecords"
},
"filterList": [
"active",
"draft"
],
"boolFilterList": ["onlyMy"]
}

View File

@@ -0,0 +1,26 @@
{
"controller": "Controllers.RecordTree",
"collection": "Collections.Tree",
"menu": {
"listTree": {
"buttons": [
{
"label": "List View",
"link": "#DocumentFolder\/list",
"acl": "read",
"style": "default"
}
]
},
"list": {
"buttons": [
{
"label": "Tree View",
"link": "#DocumentFolder",
"acl": "read",
"style": "default"
}
]
}
}
}

View File

@@ -4,18 +4,8 @@
"detail":"Crm:Meeting.Detail"
},
"recordViews":{
"list":"Crm:Meeting.Record.List"
},
"menu": {
"detail": {
"actions": [
{
"label": "Duplicate",
"action": "duplicate",
"acl": "edit"
}
]
}
"list":"Crm:Meeting.Record.List",
"detail":"Crm:Meeting.Record.Detail"
},
"sidePanels":{
"detail":[

View File

@@ -1,4 +1,64 @@
{
"controller": "Controllers.Record",
"boolFilterList": ["onlyMy"]
"boolFilterList": ["onlyMy"],
"sidePanels":{
"detail":[
{
"name":"optedOut",
"label":"Opted Out",
"view":"Crm:TargetList.Record.Panels.OptedOut"
}
]
},
"relationshipPanels": {
"contacts": {
"actionList": [
{
"label": "Unlink All",
"action": "unlinkAllRelated",
"acl": "edit",
"data": {
"link": "contacts"
}
}
]
},
"leads": {
"actionList": [
{
"label": "Unlink All",
"action": "unlinkAllRelated",
"acl": "edit",
"data": {
"link": "leads"
}
}
]
},
"accounts": {
"actionList": [
{
"label": "Unlink All",
"action": "unlinkAllRelated",
"acl": "edit",
"data": {
"link": "accounts"
}
}
]
},
"users": {
"create": false,
"actionList": [
{
"label": "Unlink All",
"action": "unlinkAllRelated",
"acl": "edit",
"data": {
"link": "users"
}
}
]
}
}
}

View File

@@ -1,7 +1,8 @@
{
"controller": "Controllers.Record",
"recordViews":{
"list":"Crm:Task.Record.List"
"list": "Crm:Task.Record.List",
"detail": "Crm:Task.Record.Detail"
},
"views": {
"list": "Crm:Task.List",

View File

@@ -52,73 +52,55 @@
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"openedCount": {
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"clickedCount": {
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"optedOutCount": {
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"bouncedCount": {
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"hardBouncedCount": {
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"softBouncedCount": {
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"leadCreatedCount": {
"type": "int",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
},
"revenue": {
"type": "currency",
"notStorable": true,
"readOnly": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutFiltersDisabled": true
"disabled": true
}
},
"links": {

View File

@@ -131,8 +131,7 @@
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"noLoad": true,
"noSave": true
"noLoad": true
},
"targetList": {
"type": "link",

View File

@@ -62,6 +62,18 @@
},
"teams": {
"type": "linkMultiple"
},
"accounts": {
"type": "linkMultiple",
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"importDisabled": true,
"noLoad": true
},
"folder": {
"type": "link",
"view": "Fields.LinkCategoryTree"
}
},
"links": {
@@ -92,6 +104,11 @@
"entity": "Team",
"relationName": "entityTeam",
"layoutRelationshipsDisabled": true
},
"folder": {
"type": "belongsTo",
"foreign": "documents",
"entity": "DocumentFolder"
}
},
"collection": {

View File

@@ -0,0 +1,95 @@
{
"fields": {
"name": {
"type": "varchar",
"required": true
},
"description": {
"type": "text"
},
"createdAt": {
"type": "datetime",
"readOnly": true
},
"modifiedAt": {
"type": "datetime",
"readOnly": true
},
"createdBy": {
"type": "link",
"readOnly": true
},
"modifiedBy": {
"type": "link",
"readOnly": true
},
"teams": {
"type": "linkMultiple"
},
"parent": {
"type": "link"
},
"childList": {
"type": "jsonArray",
"notStorable": true
}
},
"links": {
"createdBy": {
"type": "belongsTo",
"entity": "User"
},
"modifiedBy": {
"type": "belongsTo",
"entity": "User"
},
"teams": {
"type": "hasMany",
"entity": "Team",
"relationName": "entityTeam",
"layoutRelationshipsDisabled": true
},
"parent": {
"type": "belongsTo",
"foreign": "children",
"entity": "DocumentFolder"
},
"children": {
"type": "hasMany",
"foreign": "parent",
"entity": "DocumentFolder"
},
"documents": {
"type": "hasMany",
"foreign": "folders",
"entity": "Document"
}
},
"collection": {
"sortBy": "parent",
"asc": true
},
"additionalTables": {
"DocumentFolderPath": {
"fields": {
"id": {
"type": "id",
"dbType": "int",
"len": "11",
"autoincrement": true,
"unique" : true
},
"ascendorId": {
"type": "varchar",
"len": "100",
"index": true
},
"descendorId" : {
"type": "varchar",
"len": "24",
"index": true
}
}
}
}
}

View File

@@ -138,8 +138,7 @@
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"importDisabled": true,
"noLoad": true,
"noSave": true
"noLoad": true
},
"targetList": {
"type": "link",

View File

@@ -54,7 +54,8 @@
},
"leadSource": {
"type": "varchar",
"view": "Crm:Opportunity.Fields.LeadSource"
"view": "Crm:Opportunity.Fields.LeadSource",
"customizationDisabled": true
},
"closeDate": {
"type": "date",

View File

@@ -66,22 +66,42 @@
"accounts": {
"type": "hasMany",
"entity": "Account",
"foreign": "targetLists"
"foreign": "targetLists",
"additionalColumns": {
"optedOut": {
"type": "bool"
}
}
},
"contacts": {
"type": "hasMany",
"entity": "Contact",
"foreign": "targetLists"
"foreign": "targetLists",
"additionalColumns": {
"optedOut": {
"type": "bool"
}
}
},
"leads": {
"type": "hasMany",
"entity": "Lead",
"foreign": "targetLists"
"foreign": "targetLists",
"additionalColumns": {
"optedOut": {
"type": "bool"
}
}
},
"users": {
"type": "hasMany",
"entity": "User",
"foreign": "targetLists"
"foreign": "targetLists",
"additionalColumns": {
"optedOut": {
"type": "bool"
}
}
}
},
"collection": {

View File

@@ -47,7 +47,7 @@
"readOnly": true,
"notStorable": true,
"view": "Crm:Task.Fields.IsOverdue",
"layoutFiltersDisabled": true
"disabled": true
},
"description": {
"type": "text"

View File

@@ -0,0 +1,12 @@
{
"entity": true,
"layouts": false,
"tab": false,
"acl": "recordAllTeamNo",
"module": "Crm",
"customizable": false,
"importable": false,
"type": "CategoryTree",
"stream": false,
"notifications": false
}

View File

@@ -0,0 +1,35 @@
<?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/.
************************************************************************/
namespace Espo\Modules\Crm\SelectManagers;
class Campaign extends \Espo\Core\SelectManagers\Base
{
protected function filterActive(&$result)
{
$result['whereClause'][] = array(
'status' => 'Active'
);
}
}

View File

@@ -0,0 +1,42 @@
<?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/.
************************************************************************/
namespace Espo\Modules\Crm\SelectManagers;
class Document extends \Espo\Core\SelectManagers\Base
{
protected function filterActive(&$result)
{
$result['whereClause'][] = array(
'status' => 'Active'
);
}
protected function filterDraft(&$result)
{
$result['whereClause'][] = array(
'status' => 'Draft'
);
}
}

View File

@@ -23,6 +23,9 @@
namespace Espo\Modules\Crm\Services;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\NotFound;
use \Espo\Core\Exceptions\Forbidden;
use \PDO;
class Activities extends \Espo\Core\Services\Base
@@ -61,11 +64,72 @@ class Activities extends \Espo\Core\Services\Base
protected function isPerson($scope)
{
return in_array($scope, array('Contact', 'Lead', 'User'));
return in_array($scope, ['Contact', 'Lead', 'User']);
}
protected function getMeetingQuery($scope, $id, $op = 'IN', $notIn = array())
protected function getUserMeetingQuery($id, $op, $notIn)
{
$sql = "
SELECT meeting.id AS 'id', meeting.name AS 'name', meeting.date_start AS 'dateStart', meeting.date_end AS 'dateEnd', 'Meeting' AS '_scope',
meeting.assigned_user_id AS assignedUserId, TRIM(CONCAT(assignedUser.first_name, ' ', assignedUser.last_name)) AS assignedUserName,
meeting.parent_type AS 'parentType', meeting.parent_id AS 'parentId', meeting.status AS status, meeting.created_at AS createdAt
FROM `meeting`
LEFT JOIN `user` AS `assignedUser` ON assignedUser.id = meeting.assigned_user_id
JOIN `meeting_user` AS `usersMiddle` ON usersMiddle.meeting_id = meeting.id AND usersMiddle.deleted = 0
WHERE meeting.deleted = 0 AND usersMiddle.user_id = '".$this->getUser()->id."'
";
if (!empty($notIn)) {
$sql .= "
AND meeting.status {$op} ('". implode("', '", $notIn) . "')
";
}
return $sql;
}
protected function getUserCallQuery($id, $op, $notIn)
{
$sql = "
SELECT call.id AS 'id', call.name AS 'name', call.date_start AS 'dateStart', call.date_end AS 'dateEnd', 'Call' AS '_scope',
call.assigned_user_id AS assignedUserId, TRIM(CONCAT(assignedUser.first_name, ' ', assignedUser.last_name)) AS assignedUserName,
call.parent_type AS 'parentType', call.parent_id AS 'parentId', call.status AS status, call.created_at AS createdAt
FROM `call`
LEFT JOIN `user` AS `assignedUser` ON assignedUser.id = call.assigned_user_id
JOIN `call_user` AS `usersMiddle` ON usersMiddle.call_id = call.id AND usersMiddle.deleted = 0
WHERE call.deleted = 0 AND usersMiddle.user_id = '".$this->getUser()->id."'
";
if (!empty($notIn)) {
$sql .= "
AND call.status {$op} ('". implode("', '", $notIn) . "')
";
}
return $sql;
}
protected function getUserEmailQuery($id, $op, $notIn)
{
$sql = "
SELECT email.id AS 'id', email.name AS 'name', email.date_sent AS 'dateStart', '' AS 'dateEnd', 'Email' AS '_scope',
email.assigned_user_id AS assignedUserId, TRIM(CONCAT(assignedUser.first_name, ' ', assignedUser.last_name)) AS assignedUserName,
email.parent_type AS 'parentType', email.parent_id AS 'parentId', email.status AS status, email.created_at AS createdAt
FROM `email`
LEFT JOIN `user` AS `assignedUser` ON assignedUser.id = email.assigned_user_id
JOIN `email_user` AS `usersMiddle` ON usersMiddle.email_id = email.id AND usersMiddle.deleted = 0
WHERE email.deleted = 0 AND usersMiddle.user_id = '".$this->getUser()->id."'
";
if (!empty($notIn)) {
$sql .= "
AND email.status {$op} ('". implode("', '", $notIn) . "')
";
}
return $sql;
}
protected function getMeetingQuery($scope, $id, $op = 'IN', $notIn = [])
{
if ($scope == 'User') {
return $this->getUserMeetingQuery($id, $op, $notIn);
}
$baseSql = "
SELECT meeting.id AS 'id', meeting.name AS 'name', meeting.date_start AS 'dateStart', meeting.date_end AS 'dateEnd', 'Meeting' AS '_scope',
meeting.assigned_user_id AS assignedUserId, TRIM(CONCAT(user.first_name, ' ', user.last_name)) AS assignedUserName,
@@ -143,8 +207,11 @@ class Activities extends \Espo\Core\Services\Base
return $sql;
}
protected function getCallQuery($scope, $id, $op = 'IN', $notIn = array())
protected function getCallQuery($scope, $id, $op = 'IN', $notIn = [])
{
if ($scope == 'User') {
return $this->getUserCallQuery($id, $op, $notIn);
}
$baseSql = "
SELECT call.id AS 'id', call.name AS 'name', call.date_start AS 'dateStart', call.date_end AS 'dateEnd', 'Call' AS '_scope',
call.assigned_user_id AS assignedUserId, TRIM(CONCAT(user.first_name, ' ', user.last_name)) AS assignedUserName,
@@ -222,8 +289,11 @@ class Activities extends \Espo\Core\Services\Base
return $sql;
}
protected function getEmailQuery($scope, $id, $op = 'IN', $notIn = array())
protected function getEmailQuery($scope, $id, $op = 'IN', $notIn = [])
{
if ($scope == 'User') {
return $this->getUserEmailQuery($id, $op, $notIn);
}
$baseSql = "
SELECT DISTINCT
email.id AS 'id', email.name AS 'name', email.date_sent AS 'dateStart', '' AS 'dateEnd', 'Email' AS '_scope',
@@ -379,26 +449,76 @@ class Activities extends \Espo\Core\Services\Base
);
}
public function getActivities($scope, $id, $params = array())
protected function accessCheck($entity)
{
if ($entity->getEntityType() == 'User') {
if ($this->getUser()->isAdmin()) {
return;
}
$e = $this->getAcl()->get('userPermission');
if ($this->getAcl()->get('userPermission') === 'no') {
if ($entity->id != $this->getUser()->id) {
throw new Forbidden();
}
} else if ($this->getAcl()->get('userPermission') === 'team') {
if ($entity->id != $this->getUser()->id) {
if (!$this->getUser()->has('teamsIds')) {
$this->getUser()->loadLinkMultipleField('teams');
}
$entity->loadLinkMultipleField('teams');
$teamIdList1 = $this->getUser()->get('teamsIds');
$teamIdList2 = $entity->get('teamsIds');
$inTeam = false;
foreach ($teamIdList1 as $id) {
if (in_array($id, $teamIdList2)) {
$inTeam = true;
break;
}
}
if (!$inTeam) {
throw new Forbidden();
}
}
}
} else {
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
}
}
public function getActivities($scope, $id, $params = [])
{
$entity = $this->getEntityManager()->getEntity($scope, $id);
if (!$entity) {
throw new NotFound();
}
$this->accessCheck($entity);
$fetchAll = empty($params['scope']);
$parts = array(
'Meeting' => ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'NOT IN', array('Held', 'Not Held')) : array(),
'Call' => ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'NOT IN', array('Held', 'Not Held')) : array(),
'Meeting' => ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'NOT IN', ['Held', 'Not Held']) : [],
'Call' => ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'NOT IN', ['Held', 'Not Held']) : [],
);
return $this->getResult($parts, $scope, $id, $params);
}
public function getHistory($scope, $id, $params)
{
$entity = $this->getEntityManager()->getEntity($scope, $id);
$this->accessCheck($entity);
$fetchAll = empty($params['scope']);
$parts = array(
'Meeting' => ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'IN', array('Held', 'Not Held')) : array(),
'Call' => ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'IN', array('Held', 'Not Held')) : array(),
'Email' => ($fetchAll || $params['scope'] == 'Email') ? $this->getEmailQuery($scope, $id, 'IN', array('Archived', 'Sent')) : array(),
'Meeting' => ($fetchAll || $params['scope'] == 'Meeting') ? $this->getMeetingQuery($scope, $id, 'IN', ['Held', 'Not Held']) : [],
'Call' => ($fetchAll || $params['scope'] == 'Call') ? $this->getCallQuery($scope, $id, 'IN', ['Held', 'Not Held']) : [],
'Email' => ($fetchAll || $params['scope'] == 'Email') ? $this->getEmailQuery($scope, $id, 'IN', ['Archived', 'Sent']) : [],
);
$result = $this->getResult($parts, $scope, $id, $params);

View File

@@ -33,5 +33,20 @@ class CaseObj extends \Espo\Services\Record
'emails'
);
public function afterCreate($entity, array $data)
{
parent::afterCreate($entity, $data);
if (!empty($data['emailId'])) {
$email = $this->getEntityManager()->getEntity('Email', $data['emailId']);
if ($email && !$email->get('parentId')) {
$email->set(array(
'parentType' => 'Case',
'parentId' => $entity->id
));
$this->getEntityManager()->saveEntity($email);
}
}
}
}

View File

@@ -47,5 +47,116 @@ class TargetList extends \Espo\Services\Record
$count += $this->getEntityManager()->getRepository('TargetList')->countRelated($entity, 'accounts');
$entity->set('entryCount', $count);
}
public function unlinkAll($id, $link)
{
$entity = $this->getRepository()->get($id);
$foreignEntityType = $entity->relations[$link]['entity'];
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
if (empty($foreignEntityType)) {
throw new Error();
}
$pdo = $this->getEntityManager()->getPDO();
$query = $this->getEntityManager()->getQuery();
$sql = null;
switch ($link) {
case 'contacts':
$sql = "UPDATE contact_target_list SET deleted = 1 WHERE target_list_id = " . $query->quote($entity->id);
break;
case 'leads':
$sql = "UPDATE lead_target_list SET deleted = 1 WHERE target_list_id = " . $query->quote($entity->id);
break;
case 'accounts':
$sql = "UPDATE account_target_list SET deleted = 1 WHERE target_list_id = " . $query->quote($entity->id);
break;
case 'users':
$sql = "UPDATE target_list_user SET deleted = 1 WHERE target_list_id = " . $query->quote($entity->id);
break;
}
if ($sql) {
if ($pdo->query($sql)) {
return true;
}
}
}
protected function findLinkedEntitiesOptedOut($id, $link, $params)
{
$collection = new \Espo\ORM\EntityCollection;
$pdo = $this->getEntityManager()->getPDO();
$query = $this->getEntityManager()->getQuery();
$sqlContact = $query->createSelectQuery('Contact', array(
'select' => ['id', 'name', 'createdAt', ['VALUE:Contact', '_scope']],
'customJoin' => 'JOIN contact_target_list AS j ON j.contact_id = contact.id AND j.deleted = 0 AND j.opted_out = 1',
'whereClause' => array(
'j.targetListId' => $id
)
));
$sqlLead = $query->createSelectQuery('Lead', array(
'select' => ['id', 'name', 'createdAt', ['VALUE:Lead', '_scope']],
'customJoin' => 'JOIN lead_target_list AS j ON j.lead_id = lead.id AND j.deleted = 0 AND j.opted_out = 1',
'whereClause' => array(
'j.targetListId' => $id
)
));
$sqlUser = $query->createSelectQuery('User', array(
'select' => ['id', 'name', 'createdAt', ['VALUE:User', '_scope']],
'customJoin' => 'JOIN target_list_user AS j ON j.user_id = user.id AND j.deleted = 0 AND j.opted_out = 1',
'whereClause' => array(
'j.targetListId' => $id
)
));
$sqlAccount = $query->createSelectQuery('Account', array(
'select' => ['id', 'name', 'createdAt', ['VALUE:Account', '_scope']],
'customJoin' => 'JOIN account_target_list AS j ON j.account_id = account.id AND j.deleted = 0 AND j.opted_out = 1',
'whereClause' => array(
'j.targetListId' => $id
)
));
$sql = "
{$sqlContact}
UNION
{$sqlLead}
UNION
{$sqlUser}
UNION
{$sqlAccount}
ORDER BY createdAt DESC
";
$sql = $query->limit($sql, $params['offset'], $params['maxSize']);
$sth = $pdo->prepare($sql);
$sth->execute();
$arr = [];
while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
$arr[] = $row;
}
$sqlCount = "SELECT COUNT(*) AS 'count' FROM ({$sql}) AS c";
$sth = $pdo->prepare($sqlCount);
$sth->execute();
$row = $sth->fetch(\PDO::FETCH_ASSOC);
$count = $row['count'];
return array(
'total' => $count,
'list' => $arr
);
}
}

View File

@@ -60,7 +60,7 @@ class Email extends \Espo\Core\Notificators\Base
$userIdList = [];
foreach ($emailUserIdList as $userId) {
if (!in_array($userId, $userIdList) && $userId != $this->getUser()->id) {
if (!in_array($userId, $userIdList) && !in_array($userId, $previousUserIdList) && $userId != $this->getUser()->id) {
$userIdList[] = $userId;
}
}

View File

@@ -251,9 +251,24 @@ abstract class Base
$fieldList = array_keys($entity->fields);
} else {
$fieldList = $fields;
foreach ($fieldList as $i => $field) {
if (!is_array($field)) {
$fieldList[$i] = $this->sanitizeAlias($field);
}
}
}
foreach ($fieldList as $field) {
if (is_array($field) && count($field) == 2) {
if (stripos($field[0], 'VALUE:') === 0) {
$part = substr($field[0], 6);
$part = $this->quote($part);
} else {
$part = $this->convertComplexExpression($entity, $field[0], $distinct);
}
$arr[] = $part . ' AS `' . $this->sanitizeAlias($field[1]) . '`';
continue;
}
if (array_key_exists($field, $entity->fields)) {
$fieldDefs = $entity->fields[$field];
} else {
@@ -359,12 +374,20 @@ abstract class Base
if (strpos($orderBy, 'LIST:') === 0) {
list($l, $field, $list) = explode(':', $orderBy);
$fieldPath = $this->getFieldPathForOrderBy($entity, $field);
return "FIELD(" . $fieldPath . ", '" . implode("', '", explode(",", $list)) . "')";
$part = "FIELD(" . $fieldPath . ", '" . implode("', '", explode(",", $list)) . "')";
if (!is_null($order)) {
$order = strtoupper($order);
if (!in_array($order, ['ASC', 'DESC'])) {
$order = 'ASC';
}
$part .= " " . $order;
}
return $part;
}
if (!is_null($order)) {
$order = strtoupper($order);
if (!in_array($order, array('ASC', 'DESC'))) {
if (!in_array($order, ['ASC', 'DESC'])) {
$order = 'ASC';
}
} else {
@@ -643,6 +666,11 @@ abstract class Base
return preg_replace('/[^A-Za-z0-9_]+/', '', $string);
}
public function sanitizeAlias($string)
{
return preg_replace('/[^A-Za-z0-9_:.]+/', '', $string);
}
protected function getJoins(IEntity $entity, array $joins, $left = false, $joinConditions = array())
{
$joinsArr = array();
@@ -679,6 +707,8 @@ abstract class Base
$alias = $this->sanitize($relationName);
$midAlias = $alias . 'Middle';
$join =
"{$pre}JOIN `{$relTable}` AS `{$midAlias}` ON {$this->toDb($entity->getEntityType())}." . $this->toDb($key) . " = {$midAlias}." . $this->toDb($nearKey)
. " AND "

View File

@@ -329,14 +329,12 @@ class RDB extends \Espo\ORM\Repository
return $this->getMapper()->sum($this->seed, $params, $field);
}
// @TODO use abstract class for list params
// @TODO join conditions
public function join()
{
$args = func_get_args();
if (empty($this->listParams['joins'])) {
$this->listParams['joins'] = array();
$this->listParams['joins'] = [];
}
foreach ($args as &$param) {
@@ -352,6 +350,27 @@ class RDB extends \Espo\ORM\Repository
return $this;
}
public function leftJoin()
{
$args = func_get_args();
if (empty($this->listParams['leftJoins'])) {
$this->listParams['leftJoins'] = [];
}
foreach ($args as &$param) {
if (is_array($param)) {
foreach ($param as $k => $v) {
$this->listParams['leftJoins'][] = $v;
}
} else {
$this->listParams['leftJoins'][] = $param;
}
}
return $this;
}
public function distinct()
{
$this->listParams['distinct'] = true;

View File

@@ -26,12 +26,10 @@ use Espo\ORM\Entity;
class Import extends \Espo\Core\ORM\Repositories\RDB
{
public function findRelated(Entity $entity, $link, $selectParams)
public function findRelated(Entity $entity, $link, $selectParams = array())
{
$entityType = $entity->get('entityType');
$selectParams['customJoin'] .= $this->getRelatedJoin($entity, $link);
return $this->getEntityManager()->getRepository($entityType)->find($selectParams);
@@ -68,17 +66,16 @@ class Import extends \Espo\Core\ORM\Repositories\RDB
return $sql;
}
public function countRelated(Entity $entity, $link, $selectParams)
public function countRelated(Entity $entity, $link, $selectParams = array())
{
$entityType = $entity->get('entityType');
$selectParams['customJoin'] .= $this->getRelatedJoin($entity, $link);
return $this->getEntityManager()->getRepository($entityType)->count($selectParams);
}
protected function afterRemove(Entity $entity, array $options)
protected function afterRemove(Entity $entity, array $options = array())
{
if ($entity->get('fileId')) {
$attachment = $this->getEntityManager()->getEntity('Attachment', $entity->get('fileId'));

View File

@@ -151,10 +151,5 @@
"medium": "Medium",
"large": "Large"
}
},
"layoutManagerDataAttributes": {
"width": "Width (%)",
"link": "Link",
"notSortable": "Not Sortable"
}
}

View File

@@ -53,6 +53,7 @@
"Posted": "Posted",
"Linked": "Linked",
"Unlinked": "Unlinked",
"Done": "Done",
"Access denied": "Access denied",
"Access": "Access",
"Are you sure?": "Are you sure?",
@@ -101,6 +102,7 @@
"PostalCode": "Postal Code",
"Followed": "Followed",
"Follow": "Follow",
"Followers": "Followers",
"Clear Local Cache": "Clear Local Cache",
"Actions": "Actions",
"Delete": "Delete",
@@ -109,6 +111,7 @@
"Edit": "Edit",
"View": "View",
"Cancel": "Cancel",
"Apply": "Apply",
"Unlink": "Unlink",
"Mass Update": "Mass Update",
"Export": "Export",
@@ -152,7 +155,9 @@
"Value": "Value",
"Current version": "Current version",
"List View": "List View",
"Tree View": "Tree View"
"Tree View": "Tree View",
"Unlink All": "Unlink All",
"Total": "Total"
},
"messages": {
"pleaseWait": "Please wait...",
@@ -175,9 +180,10 @@
"assignmentEmailNotificationSubject": "EspoCRM {entityType}: {Entity.name}",
"assignmentEmailNotificationBody": "{assignerUserName} has assigned {entityType} '{Entity.name}' to you.\n\n{recordUrl}",
"confirmation": "Are you sure?",
"unlinkAllConfirmation": "Are you sure you want to unlink all related records?",
"resetPreferencesConfirmation": "Are you sure you want to reset preferences to defaults?",
"removeRecordConfirmation": "Are you sure you want to remove the record?",
"unlinkRecordConfirmation": "Are you sure you want to unlink relationship?",
"unlinkRecordConfirmation": "Are you sure you want to unlink the related record?",
"removeSelectedRecordsConfirmation": "Are you sure you want to remove selected records?",
"massUpdateResult": "{count} records have been updated",
"massUpdateResultSingle": "{count} record has been updated",

View File

@@ -21,7 +21,7 @@
"targetUserPosition": "Target User Position"
},
"tooltips": {
"reply": "Notify email senders that their emails has been received.",
"reply": "Notify email senders that their emails has been received.\n\n Only one email will be sent to a particular recipient during some period of time to prevent looping.",
"createCase": "Automatically create case from incoming emails.",
"replyToAddress": "Specify email address of this mailbox to make responses come here.",
"caseDistribution": "How cases will be assigned to. Assigned directly to the user or among the team.",

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