Compare commits

...

132 Commits

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
+6 -1
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')
+13 -5
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);
}
}
}
+1 -1
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)
+11 -10
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()
+1
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')
);
}
+47 -14
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();
}
+15 -16
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);
}
}
}
+4
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'])) {
@@ -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);
@@ -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'])) {
@@ -0,0 +1,9 @@
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"customizable": true,
"importable": true,
"notifications": true
}
@@ -0,0 +1,9 @@
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"customizable": true,
"importable": false,
"notifications": false
}
@@ -0,0 +1,9 @@
{
"entity": true,
"layouts": true,
"tab": true,
"acl": true,
"customizable": true,
"importable": true,
"notifications": true
}
@@ -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');
@@ -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 */
@@ -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;
}
}
?>
}
@@ -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();
@@ -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) {
@@ -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]);
@@ -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) {
+11 -14
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);
@@ -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']);
+2 -2
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) {
+13 -5
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));
}
}
+9 -2
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;
+1 -1
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);
}
+14 -3
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;
}
}
+9 -3
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;
}
@@ -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,
@@ -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
{
}
@@ -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']);
}
}
@@ -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
{
}
@@ -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
{
}
@@ -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;
}
}
@@ -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
{
}
@@ -23,7 +23,8 @@
"tasksPrimary": "Tasks (expanded)",
"emailsPrimary": "Emails (expanded)",
"targetLists": "Target Lists",
"campaignLogRecords": "Campaign Log"
"campaignLogRecords": "Campaign Log",
"campaign": "Campaign"
},
"options": {
"type": {
@@ -15,7 +15,8 @@
"hardBouncedCount": "Hard Bounced",
"softBouncedCount": "Soft Bounced",
"leadCreatedCount": "Leads Created",
"revenue": "Revenue"
"revenue": "Revenue",
"revenueConverted": "revenue (converted)"
},
"links": {
"targetLists": "Target Lists",
@@ -20,7 +20,8 @@
"opportunities": "Opportunities",
"cases": "Cases",
"targetLists": "Target Lists",
"campaignLogRecords": "Campaign Log"
"campaignLogRecords": "Campaign Log",
"campaign": "Campaign"
},
"labels": {
"Create Contact": "Create Contact"
@@ -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": {
@@ -0,0 +1,9 @@
{
"labels": {
"Create DocumentFolder": "Create Document Folder",
"Manage Categories": "Manage Folders"
},
"links": {
"documents": "Documents"
}
}
@@ -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"
}
}
@@ -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",
@@ -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",
@@ -16,7 +16,8 @@
},
"links": {
"contacts": "Contacts",
"documents": "Documents"
"documents": "Documents",
"campaign": "Campaign"
},
"options": {
"stage": {
@@ -23,6 +23,7 @@
}
},
"labels": {
"Create TargetList": "Create Target List"
"Create TargetList": "Create Target List",
"Opted Out": "Opted Out"
}
}
@@ -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": {
@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"parent": "Джерело",
"status": "статус",
"status": "Статус",
"dateStart": "Дата початку",
"dateEnd": "Дата закінчення",
"direction": "Напрямок",
@@ -12,7 +12,7 @@
"contacts": "Контакти",
"leads": "Ліди",
"reminders": "Нагадування",
"account": "Контраґент"
"account": "Контрагент"
},
"links": {
},
@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"description": "Опис",
"status": "статус",
"status": "Статус",
"type": "Тип",
"startDate": "Дата Початку",
"endDate": "Дата Закінчення",
@@ -19,7 +19,7 @@
},
"links": {
"targetLists": "Цільові списки",
"accounts": "Контраґенти",
"accounts": "Контрагенти",
"contacts": "Контакти",
"leads": "Ліди",
"opportunities": "Угоди",
@@ -1,9 +1,9 @@
{
"fields": {
"name": "Ім'я",
"number": "Нумер",
"status": "статус",
"account": "Контраґент",
"number": "Номер",
"status": "Статус",
"account": "Контрагент",
"contact": "Контакт",
"priority": "Пріоритет",
"type": "Тип",
@@ -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": "Журнал кампанія"
},
@@ -5,7 +5,7 @@
},
"fields": {
"name": "Ім'я",
"status": "статус",
"status": "Статус",
"file": "Файл",
"type": "Тип",
"source": "Джерело",
@@ -14,7 +14,7 @@
"description": "Опис"
},
"links": {
"accounts": "Контраґенти",
"accounts": "Контрагенти",
"opportunities": "Угоди"
},
"options": {
@@ -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": "Джерело"
},
@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"team": "Команда",
"status": "статус",
"status": "Статус",
"assignToUser": "Зв'язатися з користувачем",
"host": "Хост",
"username": "Ім'я користувача",
@@ -10,15 +10,15 @@
"title": "Посада",
"website": "Вебсайт",
"phoneNumber": "Телефон",
"accountName": "Ім'я контраґента",
"accountName": "Ім'я контрагента",
"doNotCall": "Не дзвонити",
"address": "Адреса",
"status": "статус",
"status": "Статус",
"source": "Джерело",
"opportunityAmount": "Сума угоди",
"opportunityAmountConverted": "Сума угоди (конвертована)",
"description": "Опис",
"createdAccount": "Контраґент",
"createdAccount": "Контрагент",
"createdContact": "Контакт",
"createdOpportunity": "Угода",
"campaign": "Кампанія",
@@ -2,7 +2,7 @@
"fields": {
"name": "Ім'я",
"parent": "Джерело",
"status": "статус",
"status": "Статус",
"dateStart": "Дата початку",
"dateEnd": "Дата закінчення",
"duration": "Тривалість",
@@ -1,7 +1,7 @@
{
"fields": {
"name": "Ім'я",
"account": "Контраґент",
"account": "Контрагент",
"stage": "Стадія",
"amount": "Сума",
"probability": "Імовірність, %",
@@ -20,7 +20,7 @@
},
"options": {
"stage": {
"Prospecting": "Проспектінґ",
"Prospecting": "Проспектінг",
"Qualification": "Кваліфікація",
"Needs Analysis": "Вимагає аналізу",
"Value Proposition": "Цінова пропозиція",
@@ -4,7 +4,7 @@
"emailAddress": "Емейл",
"title": "Посада",
"website": "Вебсайт",
"accountName": "Ім'я контраґента",
"accountName": "Ім'я контрагента",
"phoneNumber": "Телефон",
"doNotCall": "Не дзвонити",
"address": "Адреса",
@@ -8,7 +8,7 @@
"targetLists": "Цільові списки"
},
"links": {
"accounts": "Контраґенти",
"accounts": "Контрагенти",
"contacts": "Контакти",
"leads": "Ліди",
"campaigns": "Кампанії"
@@ -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": "Вкладення"
},
@@ -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}]
]
}
@@ -5,7 +5,8 @@
[{"name":"source"}],
[{"name":"file"}],
[{"name":"name"}],
[{"name":"type"}],
[{"name":"folder"}],
[{"name":"type"}],
[{"name":"status"}]
]
}
@@ -1,4 +1,5 @@
[
"accounts",
"createdAt",
"expirationDate",
"type",
@@ -0,0 +1,19 @@
[
{
"label": "",
"rows": [
[
{
"name": "name"
},
{
"name": "parent"
},
{
"name": "description",
"fullWidth": true
}
]
]
}
]
@@ -0,0 +1,17 @@
[
{
"label": "",
"rows": [
[
{
"name": "name"
}
],
[
{
"name": "parent"
}
]
]
}
]
@@ -0,0 +1,3 @@
[
"parent"
]
@@ -0,0 +1,10 @@
[
{
"name": "name",
"width": 50,
"link": true
},
{
"name": "parent"
}
]
@@ -0,0 +1,10 @@
[
{
"name": "name",
"width": 50,
"link": true
},
{
"name": "parent"
}
]
@@ -0,0 +1,3 @@
[
"children"
]
@@ -8,5 +8,6 @@
"source",
"opportunityAmountConverted",
"targetLists",
"teams"
"teams",
"createdAccount"
]
@@ -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"}
]
@@ -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"}
]
@@ -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":[
@@ -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"]
}
@@ -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"]
}
@@ -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"
}
]
}
}
}
@@ -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":[
@@ -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"
}
}
]
}
}
}
@@ -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",
@@ -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": {
@@ -131,8 +131,7 @@
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"noLoad": true,
"noSave": true
"noLoad": true
},
"targetList": {
"type": "link",
@@ -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": {
@@ -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
}
}
}
}
}
@@ -138,8 +138,7 @@
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"importDisabled": true,
"noLoad": true,
"noSave": true
"noLoad": true
},
"targetList": {
"type": "link",
@@ -54,7 +54,8 @@
},
"leadSource": {
"type": "varchar",
"view": "Crm:Opportunity.Fields.LeadSource"
"view": "Crm:Opportunity.Fields.LeadSource",
"customizationDisabled": true
},
"closeDate": {
"type": "date",
@@ -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": {
@@ -47,7 +47,7 @@
"readOnly": true,
"notStorable": true,
"view": "Crm:Task.Fields.IsOverdue",
"layoutFiltersDisabled": true
"disabled": true
},
"description": {
"type": "text"
@@ -0,0 +1,12 @@
{
"entity": true,
"layouts": false,
"tab": false,
"acl": "recordAllTeamNo",
"module": "Crm",
"customizable": false,
"importable": false,
"type": "CategoryTree",
"stream": false,
"notifications": false
}
@@ -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'
);
}
}
@@ -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'
);
}
}
@@ -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);
@@ -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);
}
}
}
}
@@ -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
);
}
}
+1 -1
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;
}
}
+32 -2
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 "
+22 -3
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;
+3 -6
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'));
@@ -151,10 +151,5 @@
"medium": "Medium",
"large": "Large"
}
},
"layoutManagerDataAttributes": {
"width": "Width (%)",
"link": "Link",
"notSortable": "Not Sortable"
}
}
@@ -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",
@@ -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