Compare commits

...

149 Commits
3.5.2 ... 3.6.0

Author SHA1 Message Date
yuri
15497b96d1 change colors 2015-08-20 12:11:14 +03:00
yuri
9c3cf0b8dd rename theme 2015-08-20 12:00:30 +03:00
yuri
eeaff7ac9d css fix 2015-08-20 11:54:35 +03:00
yuri
17e48c4e9f fix create 2015-08-20 11:02:23 +03:00
yuri
4a878c73be lang 2015-08-20 10:49:59 +03:00
yuri
f05e41afb6 global search margin 2015-08-18 15:27:24 +03:00
yuri
ba8e63c40b fix user 2015-08-18 12:47:42 +03:00
yuri
5d6b9b4785 fix email import 2015-08-18 12:32:45 +03:00
yuri
06c0a59632 fix hookManager 2 2015-08-18 11:19:03 +03:00
yuri
f6df48278c fix hookManager 2015-08-18 11:14:51 +03:00
yuri
8207684193 fix grantfile 2015-08-18 10:45:07 +03:00
yuri
49ac186f80 disable isReturn 2015-08-17 17:48:25 +03:00
yuri
4325e1591c return to list and edit attributes fix 2015-08-17 17:43:05 +03:00
yuri
16eb1e0b6d back button 2015-08-17 17:25:32 +03:00
yuri
68668acf57 fix calendar dashlet 2015-08-17 15:42:48 +03:00
yuri
5e5e82d6b6 lang 2015-08-17 15:35:19 +03:00
yuri
8e23ef59a3 lang 2015-08-17 15:34:44 +03:00
yuri
0213064217 fix preset filters dropdown 2015-08-17 12:55:06 +03:00
yuri
d360cbcb4b fix restrictedMode check 2015-08-17 11:22:06 +03:00
yuri
d1dea478f7 fix email notifications 2015-08-17 10:54:03 +03:00
yuri
778e7b335e request check fixes 2015-08-14 16:54:50 +03:00
yuri
5fc502c6a2 fix install css 2015-08-14 16:40:27 +03:00
yuri
ba5bd60931 fix xss in link multiple with role 2015-08-14 16:28:50 +03:00
yuri
dfb72bb3e1 lang change 2015-08-14 15:04:20 +03:00
yuri
c4880b77e6 layout reset to default 2015-08-14 15:02:32 +03:00
yuri
8aab9d17b1 controller action changes 2015-08-14 13:33:17 +03:00
yuri
205beb068c fix layouts 2015-08-14 12:26:55 +03:00
yuri
80cf2f2e10 fix mavbar css 2015-08-14 12:25:13 +03:00
yuri
5982b27b90 last 7 days filter 2015-08-14 12:12:38 +03:00
yuri
d696ca3013 returnUrl for list-edit 2015-08-14 11:46:16 +03:00
yuri
9d10218487 cleanup 2015-08-14 11:42:14 +03:00
yuri
b41ef6094a returnUrl for quick edit 2015-08-14 11:40:21 +03:00
yuri
9a9a69ace4 improve navbar for mobile view 2015-08-14 11:28:48 +03:00
yuri
1000139d01 dont fit modal height if window height is small 2015-08-14 09:47:10 +03:00
yuri
129984f4c6 unique joins 2015-08-13 17:42:03 +03:00
yuri
d665bc54e0 fix acl 2015-08-13 17:28:21 +03:00
yuri
69b027698d acl global search 2015-08-13 17:24:49 +03:00
yuri
6ffff16f06 global search pagination 2015-08-13 17:22:29 +03:00
yuri
79393f16d3 importove global search 2015-08-13 17:15:07 +03:00
yuri
5821a06f9e fix case 2015-08-13 15:02:28 +03:00
yuri
3eb4cf27f2 case - contact many-to-many 2015-08-13 14:54:31 +03:00
yuri
94c345c386 -sticked 2015-08-13 12:56:33 +03:00
yuri
6fe7853eb7 added new noification type message 2015-08-13 09:44:55 +03:00
yuri
c45ea08ab2 cleanup 2015-08-12 17:56:06 +03:00
yuri
04d41e9c86 fix iinsert image 2015-08-12 17:55:19 +03:00
yuri
2850cffc6a audited param 2015-08-12 16:28:12 +03:00
yuri
0cdc15ecaf rel panels filx 2015-08-12 15:13:22 +03:00
yuri
ee85ee369b select document filter 2015-08-12 15:08:57 +03:00
yuri
738e56cc63 field manager change 2015-08-12 14:43:11 +03:00
yuri
6b38e1561b fix pre 2015-08-11 16:55:22 +03:00
yuri
a56d8b7d1c default layoutSmall change 2015-08-11 16:53:37 +03:00
yuri
db6e5ee778 remove sticked from history panel 2015-08-11 15:46:03 +03:00
yuri
a0bf1af3d3 mass update for emails 2015-08-11 12:23:44 +03:00
yuri
5b47178162 createDisabled 2 2015-08-11 12:13:57 +03:00
yuri
3f0684c1ea createDisabled 2015-08-11 12:07:19 +03:00
yuri
d2bc49755d email address autocomplete 2 2015-08-11 11:52:07 +03:00
yuri
0502c1768b auto complete search by email address 2015-08-11 11:49:14 +03:00
yuri
cf7d21db32 keep emails unread 2015-08-11 11:15:16 +03:00
yuri
977fb47efb version 2015-08-10 17:03:31 +03:00
yuri
6c7a3adcdd email fix 2015-08-10 16:45:47 +03:00
yuri
3b22e08840 fix users activities 2015-08-10 16:34:18 +03:00
yuri
8d79524c62 extension.php 2015-08-10 15:37:32 +03:00
yuri
3c70f28dc0 ability to use standartizized view name 2015-08-10 15:00:50 +03:00
yuri
79b37e7047 dashlet row actions 2015-08-10 14:56:06 +03:00
yuri
3050d8b5b5 account history fix 2015-08-10 13:57:44 +03:00
yuri
231a40e4a8 notification entityRemoved 2015-08-10 13:45:01 +03:00
yuri
1f1c87513b set defaults 2015-08-10 12:26:51 +03:00
yuri
910822334e group email accounts 2015-08-10 10:56:51 +03:00
yuri
af02d721c3 try catch getMessage 2015-08-10 10:45:08 +03:00
yuri
faf7cb9acd lang 2015-08-10 10:33:56 +03:00
yuri
73869d5674 fix link multiple 2015-08-07 17:05:13 +03:00
yuri
f60732170e ability to disable field param in entity manager 2015-08-07 16:00:54 +03:00
yuri
aecf04a4f8 fix theme and user change 2015-08-07 15:41:06 +03:00
yuri
7a383912c8 system notification 2015-08-07 15:30:50 +03:00
yuri
21c288badf fix theme 2015-08-07 13:26:29 +03:00
yuri
65492ea1dc lead convertion follow 2015-08-07 12:08:58 +03:00
yuri
7fd10104cc task dashlet dont show deferred 2015-08-07 11:39:59 +03:00
yuri
ad85c6fa3f lang 2015-08-07 11:18:14 +03:00
yuri
e32bf06e4d lang typo 2015-08-07 10:56:26 +03:00
yuri
f363d77cb7 max size error message 2015-08-06 17:21:40 +03:00
yuri
8986b6f426 campaign budget 2015-08-06 16:24:48 +03:00
yuri
8b35c064c8 sacura vertical theme 2015-08-06 15:05:49 +03:00
yuri
da5ea180ab fix login 2015-08-06 14:55:33 +03:00
yuri
3aca557420 fix enum 2015-08-06 13:10:42 +03:00
yuri
ea723742c7 theme change 2015-08-06 12:55:32 +03:00
yuri
d85675f540 vertical theme 2015-08-06 12:21:00 +03:00
yuri
9b4aeb8e4c vertival theme dev 2015-08-05 18:23:20 +03:00
yuri
2c539b1fe6 jobs link 2015-08-05 16:05:33 +03:00
yuri
5f31e7e148 modal change var names 2015-08-05 15:53:25 +03:00
yuri
cc9227910f jobs 2015-08-05 15:49:23 +03:00
yuri
85d8dfe885 change diff 2015-08-05 14:06:33 +03:00
yuri
563179bec7 vertical theme mobile 2015-08-05 13:18:33 +03:00
yuri
386ada05d6 cleanup 2015-08-05 13:02:57 +03:00
yuri
70c2992282 theme in preferences 2015-08-05 12:35:57 +03:00
yuri
36d6483a51 ability to create custom note types 2015-08-05 10:36:27 +03:00
yuri
01039b9881 attachment multiple field 2015-08-05 10:25:45 +03:00
yuri
bbd1297302 attachment block 2015-08-05 09:38:41 +03:00
yuri
88c0d8810c css changes 2015-08-05 09:35:19 +03:00
yuri
b365bc17db fix record.base 2015-08-05 09:35:12 +03:00
yuri
3f24e64988 fix enum translation 2015-08-04 17:25:23 +03:00
yuri
7a5ce2f4ee fix theme 2015-08-04 17:11:37 +03:00
yuri
a410fb903a theme fixes 2015-08-04 16:21:27 +03:00
yuri
ba4004d720 theme fix 2015-08-04 16:07:51 +03:00
yuri
cdc980c2b6 theme fixes 2015-08-04 15:53:38 +03:00
yuri
3578cc9523 vertical theme 2015-08-04 15:49:35 +03:00
yuri
09ff1d1e5a mobile view collapse fix 2015-08-04 11:29:34 +03:00
yuri
8e4a6ef183 stick panels 2015-08-04 11:13:02 +03:00
yuri
0d111a6bd8 Opportunity listForAccount 2015-08-04 11:08:41 +03:00
yuri
3e503852db translate links 2015-08-04 10:55:23 +03:00
yuri
d3fe435115 theme changes 2015-08-04 09:51:07 +03:00
yuri
e6a0080c0e vertical theme 2015-08-03 17:06:12 +03:00
yuri
b8ddb72d3b color change 2015-08-03 14:45:49 +03:00
yuri
58953ca009 show total count 2015-08-03 14:43:21 +03:00
yuri
d4e98155d0 gitignore 2015-08-03 12:28:21 +03:00
yuri
86fabe3352 email: get rid of assigned user 2015-08-03 12:00:06 +03:00
yuri
c5ccf8e6da Enum is Sorted 2015-08-03 11:43:49 +03:00
yuri
b7e0a0de23 Merge branch 'hotfix/3.5.3' 2015-08-03 11:12:34 +03:00
yuri
9c2a18f0aa indestry options added 2015-08-03 11:11:50 +03:00
yuri
aa95f2cf88 change currency field 2015-08-03 11:00:27 +03:00
yuri
8dd7f262a2 color change 2015-07-31 17:22:46 +03:00
yuri
b76feba080 remove css 2015-07-31 17:18:57 +03:00
yuri
1b5aea85ba themes 2015-07-31 17:18:24 +03:00
yuri
c89c7f5e0e stylesheet 2015-07-31 11:25:58 +03:00
yuri
bcaaf539be remove fields from settings 2015-07-31 11:23:06 +03:00
yuri
48f0988373 stylesheet 2015-07-31 11:17:22 +03:00
yuri
a0c0d37bfd navbar fix 2015-07-31 10:44:03 +03:00
yuri
9c696fe8d6 navbar fix 2015-07-31 10:43:38 +03:00
yuri
224e3475d8 Merge branch 'hotfix/3.5.3' 2015-07-30 17:05:29 +03:00
yuri
841c5f85bd fix email import 2015-07-30 14:38:23 +03:00
yuri
6745419a4f install lang fix 2015-07-30 11:40:21 +03:00
yuri
6bf9bb875a email address search improvement 2015-07-30 11:12:49 +03:00
yuri
d6e1322775 Merge branch 'hotfix/3.5.3' 2015-07-30 10:07:41 +03:00
yuri
bb0e97160c fix task layout 2015-07-30 10:00:48 +03:00
yuri
8124ceea6b email import fix 2015-07-29 16:48:53 +03:00
yuri
b5cb0c0881 email import fix 2015-07-29 16:43:46 +03:00
yuri
8747ccc105 show error message reason 2015-07-29 15:06:03 +03:00
yuri
8b0147484c userLimit 2015-07-29 13:56:18 +03:00
yuri
233037a1eb adminPanelIframeUrl 2015-07-29 11:16:19 +03:00
yuri
11446ef857 dashlet actions 2015-07-29 10:21:04 +03:00
Taras Machyshyn
4403131235 Merge branch 'hotfix/3.5.3' 2015-07-29 09:47:43 +03:00
Taras Machyshyn
9dde3d0645 Fixed a bug for module orders 2015-07-29 09:46:49 +03:00
yuri
2fcf00ef4f Merge branch 'stable' 2015-07-28 17:52:43 +03:00
yuri
e94564b5e7 Merge branch 'hotfix/3.5.2' 2015-07-28 14:38:49 +03:00
yuri
c6542c0698 restrictedMode 2015-07-27 11:02:42 +03:00
yuri
f74959ab00 fix user layout 2015-07-27 10:48:42 +03:00
yuri
2bb28ac270 Merge branch 'stable' 2015-07-24 17:04:59 +03:00
yuri
ca64d7c8ec Merge branch 'hotfix/3.5.1' 2015-07-23 16:10:25 +03:00
yuri
ce2a2a03a3 list changes 2015-07-23 14:50:15 +03:00
yuri
d947223b9b fix wysiwyg 2015-07-23 14:41:51 +03:00
318 changed files with 4153 additions and 986 deletions

5
.gitignore vendored
View File

@@ -9,7 +9,10 @@
/client
/test.php
/main.html
/frontend/client/css/bootstrap.css
/frontend/client/css/espo.css
/frontend/client/css/espo-vertical.css
/frontend/client/css/sakura.css
/frontend/client/css/sakura-vertical.css
/tests/testData/cache/*
composer.phar
vendor/

View File

@@ -59,22 +59,44 @@ module.exports = function (grunt) {
final: ['build/tmp'],
},
less: {
bootstrap: {
espo: {
options: {
yuicompress: true,
},
files: {
'frontend/client/css/bootstrap.css': 'frontend/less/espo/main.less',
'frontend/client/css/espo.css': 'frontend/less/espo/main.less',
},
},
sakura: {
options: {
yuicompress: true,
},
files: {
'frontend/client/css/sakura.css': 'frontend/less/sakura/main.less',
},
},
espoVertical: {
options: {
yuicompress: true,
},
files: {
'frontend/client/css/espo-vertical.css': 'frontend/less/espo-vertical/main.less',
},
},
sakuraVertical: {
options: {
yuicompress: true,
},
files: {
'frontend/client/css/sakura-vertical.css': 'frontend/less/sakura-vertical/main.less',
},
}
},
cssmin: {
minify: {
files: {
'build/tmp/client/css/espo.min.css': [
'frontend/client/css/bootstrap.css',
'frontend/client/css/datepicker.css',
'frontend/client/css/jquery.timepicker.css',
'build/tmp/client/css/espo.css': [
'frontend/client/css/espo.css',
]
}
},
@@ -131,6 +153,7 @@ module.exports = function (grunt) {
'rebuild.php',
'clear_cache.php',
'upgrade.php',
'extension.php',
'index.php',
'LICENSE.txt',
'.htaccess',

View File

@@ -22,8 +22,10 @@
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Error,
\Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\NotFound;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class Admin extends \Espo\Core\Controllers\Base
{
@@ -34,17 +36,19 @@ class Admin extends \Espo\Core\Controllers\Base
}
}
public function actionRebuild($params, $data)
public function postActionRebuild($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
$result = $this->getContainer()->get('dataManager')->rebuild();
return $result;
}
public function actionClearCache($params, $data)
public function postActionClearCache($params, $data)
{
$result = $this->getContainer()->get('dataManager')->clearCache();
return $result;
}
@@ -55,8 +59,13 @@ class Admin extends \Espo\Core\Controllers\Base
return $scheduledJob->getAllNamesOnly();
}
public function actionUploadUpgradePackage($params, $data)
public function postActionUploadUpgradePackage($params, $data)
{
if ($this->getConfig()->get('restrictedMode')) {
if (!$this->getUser()->get('isSuperAdmin')) {
throw new Forbidden();
}
}
$upgradeManager = new \Espo\Core\UpgradeManager($this->getContainer());
$upgradeId = $upgradeManager->upload($data);
@@ -68,10 +77,15 @@ class Admin extends \Espo\Core\Controllers\Base
);
}
public function actionRunUpgrade($params, $data)
public function postActionRunUpgrade($params, $data)
{
$upgradeManager = new \Espo\Core\UpgradeManager($this->getContainer());
if ($this->getConfig()->get('restrictedMode')) {
if (!$this->getUser()->get('isSuperAdmin')) {
throw new Forbidden();
}
}
$upgradeManager = new \Espo\Core\UpgradeManager($this->getContainer());
$upgradeManager->install($data);
return true;

View File

@@ -44,7 +44,7 @@ class App extends \Espo\Core\Controllers\Base
);
}
public function actionDestroyAuthToken($params, $data)
public function postActionDestroyAuthToken($params, $data)
{
$token = $data['token'];
if (empty($token)) {

View File

@@ -22,11 +22,18 @@
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class Attachment extends \Espo\Core\Controllers\Record
{
public function actionUpload($params, $data)
public function actionUpload($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
list($prefix, $contents) = explode(',', $data);
$contents = base64_decode($contents);

View File

@@ -18,49 +18,49 @@
*
* 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\Controllers;
use \Espo\Core\Exceptions\Forbidden;
class AuthToken extends \Espo\Core\Controllers\Record
{
{
protected function checkControllerAccess()
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
}
public function actionUpdate($params, $data)
{
throw new Forbidden();
}
}
public function actionCreate($params, $data)
{
throw new Forbidden();
}
public function actionListLinked($params, $data)
{
throw new Forbidden();
}
public function actionMassUpdate($params, $data)
{
throw new Forbidden();
}
public function actionCreateLink($params, $data)
{
throw new Forbidden();
}
public function actionRemoveLink($params, $data)
{
throw new Forbidden();
}
}
}

View File

@@ -23,6 +23,7 @@
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class EmailAccount extends \Espo\Core\Controllers\Record
{

View File

@@ -58,6 +58,11 @@ class Extension extends \Espo\Core\Controllers\Record
if (!$request->isPost()) {
throw new Forbidden();
}
if ($this->getConfig()->get('restrictedMode')) {
if (!$this->getUser()->get('isSuperAdmin')) {
throw new Forbidden();
}
}
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
@@ -71,11 +76,14 @@ class Extension extends \Espo\Core\Controllers\Record
if (!$request->isPost()) {
throw new Forbidden();
}
if ($this->getConfig()->get('restrictedMode')) {
if (!$this->getUser()->get('isSuperAdmin')) {
throw new Forbidden();
}
}
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
$manager->uninstall($data);
return true;
}
@@ -99,12 +107,18 @@ class Extension extends \Espo\Core\Controllers\Record
throw new Forbidden();
}
public function actionDelete($params)
public function actionDelete($params, $data, $request)
{
if (!$request->isDelete()) {
throw BadRequest();
}
if ($this->getConfig()->get('restrictedMode')) {
if (!$this->getUser()->get('isSuperAdmin')) {
throw new Forbidden();
}
}
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
$manager->delete($params);
return true;
}

View File

@@ -18,12 +18,13 @@
*
* 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\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class ExternalAccount extends \Espo\Core\Controllers\Record
{
@@ -34,7 +35,7 @@ class ExternalAccount extends \Espo\Core\Controllers\Record
$integrations = $this->getEntityManager()->getRepository('Integration')->find();
$arr = array();
foreach ($integrations as $entity) {
if ($entity->get('enabled') && $this->getMetadata()->get('integrations.' . $entity->id .'.allowUserAccounts')) {
if ($entity->get('enabled') && $this->getMetadata()->get('integrations.' . $entity->id .'.allowUserAccounts')) {
$arr[] = array(
'id' => $entity->id
);
@@ -77,15 +78,18 @@ class ExternalAccount extends \Espo\Core\Controllers\Record
return $entity->toArray();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
return $this->actionPatch($params, $data);
return $this->actionPatch($params, $data, $request);
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
list($integration, $userId) = explode('__', $params['id']);
if (!$request->isPost() && !$request->isPatch()) {
throw new BadRequest();
}
list($integration, $userId) = explode('__', $params['id']);
if ($this->getUser()->id != $userId) {
throw new Forbidden();

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\Controllers;
@@ -30,10 +30,10 @@ class GlobalSearch extends \Espo\Core\Controllers\Base
public function actionSearch($params, $data, $request)
{
$query = $params['query'];
$offset = $request->get('offset');
$maxSize = $request->get('maxSize');
$offset = intval($request->get('offset'));
$maxSize = intval($request->get('maxSize'));
return $this->getService('GlobalSearch')->find($query, $offset, $maxSize);
}
}

View File

@@ -71,10 +71,14 @@ class Import extends \Espo\Core\Controllers\Record
return $this->getContainer()->get('entityManager');
}
public function actionUploadFile($params, $data)
public function actionUploadFile($params, $data, $request)
{
$contents = $data;
if (!$request->isPost()) {
throw new BadRequest();
}
$attachment = $this->getEntityManager()->getEntity('Attachment');
$attachment->set('type', 'text/csv');
$attachment->set('role', 'Import File');
@@ -88,24 +92,34 @@ class Import extends \Espo\Core\Controllers\Record
);
}
public function actionRevert($params, $data)
public function actionRevert($params, $data, $request)
{
if (empty($data['id'])) {
throw new BadRequest();
}
if (!$request->isPost()) {
throw new BadRequest();
}
return $this->getService('Import')->revert($data['id']);
}
public function actionRemoveDuplicates($params, $data)
public function actionRemoveDuplicates($params, $data, $request)
{
if (empty($data['id'])) {
throw new BadRequest();
}
if (!$request->isPost()) {
throw new BadRequest();
}
return $this->getService('Import')->removeDuplicates($data['id']);
}
public function actionCreate($params, $data)
public function actionCreate($params, $data, $request)
{
if (!$request->isPost() && !$request->isPut()) {
throw new BadRequest();
}
$importParams = array(
'headerRow' => $data['headerRow'],
'fieldDelimiter' => $data['fieldDelimiter'],

View File

@@ -22,6 +22,9 @@
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class InboundEmail extends \Espo\Core\Controllers\Record
{
protected function checkControllerAccess()

View File

@@ -18,21 +18,23 @@
*
* 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\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class Integration extends \Espo\Core\Controllers\Record
{
{
protected function checkControllerAccess()
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
}
public function actionIndex($params, $data, $request)
{
return false;
@@ -40,22 +42,28 @@ class Integration extends \Espo\Core\Controllers\Record
public function actionRead($params, $data, $request)
{
$entity = $this->getEntityManager()->getEntity('Integration', $params['id']);
if (!$request->isPut() && !$request->isPatch()) {
throw new BadRequest();
}
$entity = $this->getEntityManager()->getEntity('Integration', $params['id']);
return $entity->toArray();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
return $this->actionPatch($params, $data);
return $this->actionPatch($params, $data, $request);
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
if (!$request->isPut() && !$request->isPatch()) {
throw new BadRequest();
}
$entity = $this->getEntityManager()->getEntity('Integration', $params['id']);
$entity->set($data);
$this->getEntityManager()->saveEntity($entity);
return $entity->toArray();
return $entity->toArray();
}
}

View File

@@ -0,0 +1,72 @@
<?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\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
class Job extends \Espo\Core\Controllers\Record
{
protected function checkControllerAccess()
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
}
public function actionCreate($params, $data)
{
throw new Forbidden();
}
public function actionUpdate($params, $data)
{
throw new Forbidden();
}
public function actionPatch($params, $data)
{
throw new Forbidden();
}
public function actionListLinked($params, $data, $request)
{
throw new Forbidden();
}
public function actionMassUpdate($params, $data, $request)
{
throw new Forbidden();
}
public function actionCreateLink($params, $data)
{
throw new Forbidden();
}
public function actionRemoveLink($params, $data)
{
throw new Forbidden();
}
}

View File

@@ -26,6 +26,7 @@ use Espo\Core\Utils as Utils;
use \Espo\Core\Exceptions\NotFound;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class Layout extends \Espo\Core\Controllers\Base
{
@@ -38,12 +39,16 @@ class Layout extends \Espo\Core\Controllers\Base
return $data;
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
if (!$request->isPut() && !$request->isPatch()) {
throw new BadRequest();
}
$layoutManager = $this->getContainer()->get('layout');
$layoutManager->set($data, $params['scope'], $params['name']);
$result = $layoutManager->save();
@@ -57,8 +62,20 @@ class Layout extends \Espo\Core\Controllers\Base
return $layoutManager->get($params['scope'], $params['name']);
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
return $this->actionUpdate($params, $data);
return $this->actionUpdate($params, $data, $request);
}
public function actionResetToDefault($params, $data, $request)
{
if (!$request->isPost()) {
throw new BadRequest();
}
if (empty($data['scope']) || empty($data['name'])) {
throw new BadRequest();
}
return $this->getContainer()->get('layout')->resetToDefault($data['scope'], $data['name']);
}
}

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\Controllers;
@@ -32,32 +32,32 @@ class Notification extends \Espo\Core\Controllers\Base
{
$scope = $params['scope'];
$id = $params['id'];
$userId = $this->getUser()->id;
$offset = intval($request->get('offset'));
$maxSize = intval($request->get('maxSize'));
$maxSize = intval($request->get('maxSize'));
$params = array(
'offset' => $offset,
'maxSize' => $maxSize,
);
$result = $this->getService('Notification')->getList($userId, $params);
$result = $this->getService('Notification')->getList($userId, $params);
return array(
'total' => $result['total'],
'list' => $result['collection']->toArray()
);
}
public function actionNotReadCount()
{
$userId = $this->getUser()->id;
return $this->getService('Notification')->getNotReadCount($userId);
}
public function actionMarkAllRead($params, $data, $request)
public function postActionMarkAllRead($params, $data, $request)
{
$userId = $this->getUser()->id;
return $this->getService('Notification')->markAllRead($userId);

View File

@@ -53,27 +53,34 @@ class Preferences extends \Espo\Core\Controllers\Base
}
}
public function actionDelete($params, $data)
public function actionDelete($params, $data, $request)
{
$userId = $params['id'];
if (empty($userId)) {
throw new BadRequest();
}
if (!$request->isDelete()) {
throw new BadRequest();
}
$this->handleUserAccess($userId);
return $this->getEntityManager()->getRepository('Preferences')->resetToDefaults($userId);
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
return $this->actionUpdate($params, $data);
return $this->actionUpdate($params, $data, $request);
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
$userId = $params['id'];
$this->handleUserAccess($userId);
if (!$request->isPost() && !$request->isPatch()) {
throw new BadRequest();
}
if (array_key_exists('smtpPassword', $data)) {
$data['smtpPassword'] = $this->getCrypt()->encrypt($data['smtpPassword']);
}

View File

@@ -24,6 +24,7 @@ namespace Espo\Controllers;
use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
use \Espo\Core\Exceptions\BadRequest;
class Settings extends \Espo\Core\Controllers\Base
{
@@ -46,17 +47,21 @@ class Settings extends \Espo\Core\Controllers\Base
return $this->getConfigData();
}
public function actionUpdate($params, $data)
public function actionUpdate($params, $data, $request)
{
return $this->actionPatch($params, $data);
return $this->actionPatch($params, $data, $request);
}
public function actionPatch($params, $data)
public function actionPatch($params, $data, $request)
{
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
if (!$request->isPut() && !$request->isPatch()) {
throw new BadRequest();
}
if (isset($data['useCache']) && $data['useCache'] != $this->getConfig()->get('useCache')) {
$this->getContainer()->get('dataManager')->clearCache();
}

View File

@@ -18,12 +18,12 @@
*
* 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\Controllers;
class Team extends \Espo\Core\Controllers\Record
{
}

View File

@@ -76,7 +76,7 @@ class Acl
public function checkScope($scope, $action = null, $isOwner = null, $inTeam = null, $entity = null)
{
return $this->getAclManager()->checkScope($this->getUser(), $subject, $action, $isOwner, $inTeam, $entity) ;
return $this->getAclManager()->checkScope($this->getUser(), $scope, $action, $isOwner, $inTeam, $entity) ;
}
}

View File

@@ -86,10 +86,12 @@ class Application
public function runClient()
{
$config = $this->getContainer()->get('config');
$themeManager = $this->getContainer()->get('themeManager');
$html = file_get_contents('main.html');
$html = str_replace('{{cacheTimestamp}}', $config->get('cacheTimestamp', 0), $html);
$html = str_replace('{{useCache}}', $config->get('useCache') ? 'true' : 'false' , $html);
$html = str_replace('{{stylesheet}}', $themeManager->getStylesheet(), $html);
$html = str_replace('{{runScript}}', 'app.start();' , $html);
echo $html;
exit;

View File

@@ -278,6 +278,14 @@ class Container
);
}
private function loadThemeManager()
{
return new \Espo\Core\Utils\ThemeManager(
$this->get('config'),
$this->get('metadata')
);
}
public function setUser($user)
{
$this->data['user'] = $user;

View File

@@ -87,18 +87,27 @@ class ControllerManager
$actionNameUcfirst = ucfirst($actionName);
$beforeMethodName = 'before' . $actionNameUcfirst;
$actionMethodName = 'action' . $actionNameUcfirst;
$afterMethodName = 'after' . $actionNameUcfirst;
$fullActionMethodName = strtolower($request->getMethod()) . ucfirst($actionMethodName);
if (method_exists($controller, $fullActionMethodName)) {
$primaryActionMethodName = $fullActionMethodName;
} else {
$primaryActionMethodName = $actionMethodName;
}
if (!method_exists($controller, $primaryActionMethodName)) {
throw new NotFound("Action '$actionName' (".$request->getMethod().") does not exist in controller '$controller'");
}
if (method_exists($controller, $beforeMethodName)) {
$controller->$beforeMethodName($params, $data, $request);
}
$actionMethodName = 'action' . $actionNameUcfirst;
if (!method_exists($controller, $actionMethodName)) {
throw new NotFound("Action '$actionMethodName' does not exist in controller '$controller'");
}
$result = $controller->$primaryActionMethodName($params, $data, $request);
$result = $controller->$actionMethodName($params, $data, $request);
$afterMethodName = 'after' . $actionNameUcfirst;
if (method_exists($controller, $afterMethodName)) {
$controller->$afterMethodName($params, $data, $request);
}

View File

@@ -129,7 +129,7 @@ class Record extends Base
$maxSize = self::MAX_SIZE_LIMIT;
}
if (!empty($maxSize) && $maxSize > self::MAX_SIZE_LIMIT) {
throw new Forbidden();
throw new Forbidden("Max should should not exceed " . self::MAX_SIZE_LIMIT . ". Use pagination (offset, limit).");
}
$result = $this->getRecordService()->findEntities(array(
@@ -198,7 +198,7 @@ class Record extends Base
public function actionExport($params, $data, $request)
{
if ($this->getConfig()->get('disableExport') && !$this->getUser()->isAdmin()) {
if ($this->getConfig()->get('exportDisabled') && !$this->getUser()->isAdmin()) {
throw new Forbidden();
}

View File

@@ -104,6 +104,9 @@ class HookManager
foreach ($this->data[$scope][$hookName] as $className) {
if (empty($this->hooks[$className])) {
$this->hooks[$className] = $this->createHookByClassName($className);
if (empty($this->hooks[$className])) {
continue;
}
}
$hook = $this->hooks[$className];
$hook->$hookName($injection, $options);
@@ -122,7 +125,7 @@ class HookManager
}
return $hook;
}
throw new Error("Class '$className' does not exist");
$GLOBALS['log']->error("Hook class '{$name}' does not exist.");
}
/**

View File

@@ -99,6 +99,7 @@ class Importer
$usersIds = $duplicate->get('usersIds');
$usersIds[] = $userId;
$duplicate->set('usersIds', $usersIds);
$this->getEntityManager()->saveEntity($duplicate);
if (!empty($teamsIds)) {
@@ -136,6 +137,10 @@ class Importer
$this->importPartDataToEmail($email, $message, $inlineIds, 'text/plain');
}
if (!$email->get('body') && $email->get('bodyPlain')) {
$email->set('body', $email->get('bodyPlain'));
}
$body = $email->get('body');
if (!empty($body)) {
foreach ($inlineIds as $cid => $attachmentId) {
@@ -251,6 +256,15 @@ class Importer
$type = strtok($part->contentType, ';');
}
$contentDisposition = false;
if (isset($part->ContentDisposition)) {
if (strpos(strtolower($part->ContentDisposition), 'attachment') === 0) {
$contentDisposition = 'attachment';
} else if (strpos(strtolower($part->ContentDisposition), 'inline') === 0) {
$contentDisposition = 'inline';
}
}
if (empty($type)) {
if (!empty($defaultContentType)) {
$type = $defaultContentType;
@@ -260,28 +274,26 @@ class Importer
}
$encoding = null;
$isAttachment = true;
if ($type == 'text/plain' || $type == 'text/html') {
$isAttachment = false;
$content = $this->getContentFromPart($part);
if ($type == 'text/plain') {
if ($email->get('bodyPlain')) {
$isAttachment = true;
} else {
$email->set('bodyPlain', $content);
if (!$email->get('body')) {
$email->set('body', $content);
if ($contentDisposition !== 'inline' && $contentDisposition !== 'attachment') {
$isAttachment = false;
$content = $this->getContentFromPart($part);
if ($type == 'text/plain') {
$bodyPlain = '';
if ($email->get('bodyPlain')) {
$bodyPlain .= $email->get('bodyPlain') . "\n";
}
}
} else if ($type == 'text/html') {
if ($email->get('isHtml')) {
$isAttachment = true;
} else {
$email->set('body', $content);
$bodyPlain .= $content;
$email->set('bodyPlain', $bodyPlain);
} else if ($type == 'text/html') {
$body = '';
if ($email->get('body')) {
$body .= $email->get('body') . "<br>";
}
$body .= $content;
$email->set('isHtml', true);
$email->set('body', $body);
}
}
}
@@ -293,13 +305,13 @@ class Importer
$fileName = null;
$contentId = null;
if (isset($part->ContentDisposition)) {
if (strpos($part->ContentDisposition, 'attachment') === 0) {
if ($contentDisposition) {
if ($contentDisposition === 'attachment') {
if (preg_match('/filename="?([^"]+)"?/i', $part->ContentDisposition, $m)) {
$fileName = $m[1];
$disposition = 'attachment';
}
} else if (strpos($part->ContentDisposition, 'inline') === 0) {
} else if ($contentDisposition === 'inline') {
if (isset($part->contentID)) {
$contentId = trim($part->contentID, '<>');
$fileName = $contentId;

View File

@@ -37,6 +37,7 @@ class Entity extends \Espo\ORM\Entity
$collection = $this->get($field, $defs);
$ids = array();
$names = new \stdClass();
$types = new \stdClass();
if (!empty($columns)) {
$columnsData = new \stdClass();
}
@@ -46,6 +47,7 @@ class Entity extends \Espo\ORM\Entity
$id = $e->id;
$ids[] = $id;
$names->$id = $e->get('name');
$types->$id = $e->get('type');
if (!empty($columns)) {
$columnsData->$id = new \stdClass();
foreach ($columns as $column => $f) {
@@ -57,6 +59,7 @@ class Entity extends \Espo\ORM\Entity
$this->set($field . 'Ids', $ids);
$this->set($field . 'Names', $names);
$this->set($field . 'Types', $types);
if (!empty($columns)) {
$this->set($field . 'Columns', $columnsData);
}

View File

@@ -226,6 +226,7 @@ class Base
$result['whereClause'][] = $part;
}
$result['joins'] = array_merge($result['joins'], $joins);
$result['joins'] = array_unique($result['joins']);
$result['distinct'] = true;
}
@@ -313,6 +314,46 @@ class Base
}
}
public function manageAccess(&$result)
{
if (empty($result)) {
$result = array();
}
if (empty($result['joins'])) {
$result['joins'] = [];
}
if (empty($result['leftJoins'])) {
$result['leftJoins'] = [];
}
if (empty($result['whereClause'])) {
$result['whereClause'] = array();
}
if (empty($result['customJoin'])) {
$result['customJoin'] = [];
}
$this->access($result);
}
public function manageTextFilter($textFilter, &$result)
{
if (empty($result)) {
$result = array();
}
if (empty($result['joins'])) {
$result['joins'] = [];
}
if (empty($result['leftJoins'])) {
$result['leftJoins'] = [];
}
if (empty($result['whereClause'])) {
$result['whereClause'] = array();
}
if (empty($result['customJoin'])) {
$result['customJoin'] = [];
}
$this->q(array('q' => $textFilter), $result);
}
protected function access(&$result)
{
if ($this->acl->checkReadOnlyOwn($this->entityType)) {
@@ -439,6 +480,24 @@ class Base
$where['type'] = 'after';
$dt->setTimezone(new \DateTimeZone('UTC'));
$where['value'] = $dt->format($format);
break;
case 'lastSevenDays':
$where['type'] = 'between';
$dtFrom = clone $dt;
$dt->setTimezone(new \DateTimeZone('UTC'));
$to = $dt->format($format);
$dtFrom->modify('-7 day');
$dtFrom->setTime(0, 0, 0);
$dtFrom->setTimezone(new \DateTimeZone('UTC'));
$from = $dtFrom->format($format);
$where['value'] = [$from, $to];
break;
case 'on':
$where['type'] = 'between';
@@ -568,6 +627,15 @@ class Base
case 'future':
$part[$item['field'] . '>='] = date('Y-m-d');
break;
case 'lastSevenDays':
$dt1 = new \DateTime();
$dt2 = clone $dt1;
$dt2->modify('-7 days');
$part['AND'] = array(
$item['field'] . '>=' => $dt2->format('Y-m-d'),
$item['field'] . '<=' => $dt1->format('Y-m-d'),
);
break;
case 'currentMonth':
$dt = new \DateTime();
$part['AND'] = array(

View File

@@ -25,19 +25,19 @@ namespace Espo\Core\Utils;
class Crypt
{
private $config;
private $key = null;
private $cryptKey = null;
private $iv = null;
public function __construct($config)
{
$this->config = $config;
$this->cryptKey = $config->get('cryptKey', '');
}
protected function getKey()
{
if (empty($this->key)) {
@@ -53,13 +53,13 @@ class Crypt
}
return $this->iv;
}
public function encrypt($string)
{
$iv = $this->getIv();
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->getKey(), $string, MCRYPT_MODE_CBC, $iv) . $iv);
}
public function decrypt($encryptedString)
{
$encryptedString = base64_decode($encryptedString);
@@ -68,7 +68,7 @@ class Crypt
$iv = substr($encryptedString, -16);
return trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->getKey(), $string, MCRYPT_MODE_CBC, $iv));
}
public function generateKey()
{
return md5(uniqid());

View File

@@ -0,0 +1,51 @@
<?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\Core\Utils\Database\Orm\Fields;
class AttachmentMultiple extends Base
{
protected function load($fieldName, $entityType)
{
$data = array(
$entityType => array (
'fields' => array(
$fieldName.'Ids' => array(
'type' => 'varchar',
'notStorable' => true
),
$fieldName.'Names' => array(
'type' => 'varchar',
'notStorable' => true
),
)
),
'unset' => array(
$entityType => array(
'fields.'.$fieldName,
),
),
);
return $data;
}
}

View File

@@ -32,7 +32,7 @@ class Attachments extends HasChildren
$entityName => array (
'fields' => array(
$linkName.'Types' => array(
'type' => 'varchar',
'type' => 'jsonObject',
'notStorable' => true,
),
),

View File

@@ -66,23 +66,31 @@ class Layout
return $this->metadata;
}
protected function sanitizeInput($name)
{
return preg_replace("([\.]{2,})", '', $name);
}
/**
* Get Layout context
*
* @param $controller
* @param $scope
* @param $name
*
* @return json
*/
public function get($controller, $name)
public function get($scope, $name)
{
if (isset($this->changedData[$controller][$name])) {
return Json::encode($this->changedData[$controller][$name]);
$scope = $this->sanitizeInput($scope);
$name = $this->sanitizeInput($name);
if (isset($this->changedData[$scope][$name])) {
return Json::encode($this->changedData[$scope][$name]);
}
$fileFullPath = Util::concatPath($this->getLayoutPath($controller, true), $name.'.json');
$fileFullPath = Util::concatPath($this->getLayoutPath($scope, true), $name.'.json');
if (!file_exists($fileFullPath)) {
$fileFullPath = Util::concatPath($this->getLayoutPath($controller), $name.'.json');
$fileFullPath = Util::concatPath($this->getLayoutPath($scope), $name.'.json');
}
if (!file_exists($fileFullPath)) {
@@ -101,21 +109,39 @@ class Layout
/**
* Set Layout data
* Ex. $controller = Account, $name = detail then will be created a file layoutFolder/Account/detail.json
* Ex. $scope = Account, $name = detail then will be created a file layoutFolder/Account/detail.json
*
* @param array $data
* @param string $controller - ex. Account
* @param string $scope - ex. Account
* @param string $name - detail
*
* @return void
*/
public function set($data, $controller, $name)
public function set($data, $scope, $name)
{
if (empty($controller) || empty($name)) {
$scope = $this->sanitizeInput($scope);
$name = $this->sanitizeInput($name);
if (empty($scope) || empty($name)) {
return false;
}
$this->changedData[$controller][$name] = $data;
$this->changedData[$scope][$name] = $data;
}
public function resetToDefault($scope, $name)
{
$scope = $this->sanitizeInput($scope);
$name = $this->sanitizeInput($name);
$filePath = 'custom/Espo/Custom/Resources/layouts/' . $scope . '/' . $name . '.json';
if ($this->getFileManager()->isFile($filePath)) {
$this->getFileManager()->removeFile($filePath);
}
if (!empty($this->changedData[$scope]) && !empty($this->changedData[$scope][$name])) {
unset($this->changedData[$scope][$name]);
}
return $this->get($scope, $name);
}
/**
@@ -128,14 +154,14 @@ class Layout
$result = true;
if (!empty($this->changedData)) {
foreach ($this->changedData as $controllerName => $rowData) {
foreach ($this->changedData as $scope => $rowData) {
foreach ($rowData as $layoutName => $layoutData) {
if (empty($controllerName) || empty($layoutName)) {
if (empty($scope) || empty($layoutName)) {
continue;
}
$layoutPath = $this->getLayoutPath($controllerName, true);
$layoutPath = $this->getLayoutPath($scope, true);
$data = Json::encode($layoutData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$result &= $this->getFileManager()->putContents(array($layoutPath, $layoutName.'.json'), $data);
@@ -162,17 +188,20 @@ class Layout
/**
* Merge layout data
* Ex. $controller= Account, $name= detail then will be created a file layoutFolder/Account/detail.json
* Ex. $scope= Account, $name= detail then will be created a file layoutFolder/Account/detail.json
*
* @param JSON string $data
* @param string $controller - ex. Account
* @param string $scope - ex. Account
* @param string $name - detail
*
* @return bool
*/
public function merge($data, $controller, $name)
public function merge($data, $scope, $name)
{
$prevData = $this->get($controller, $name);
$scope = $this->sanitizeInput($scope);
$name = $this->sanitizeInput($name);
$prevData = $this->get($scope, $name);
$prevDataArray = Json::getArrayData($prevData);
$dataArray = Json::getArrayData($data);
@@ -180,7 +209,7 @@ class Layout
$data = Util::merge($prevDataArray, $dataArray);
$data = Json::encode($data);
return $this->set($data, $controller, $name);
return $this->set($data, $scope, $name);
}
/**
@@ -208,9 +237,4 @@ class Layout
return $path;
}
}
?>

View File

@@ -478,8 +478,7 @@ class Metadata
}
}
krsort($modulesToSort);
asort($modulesToSort);
array_multisort(array_values($modulesToSort), SORT_ASC, array_keys($modulesToSort), SORT_ASC, $modulesToSort);
$this->moduleList = array_keys($modulesToSort);
}

View File

@@ -0,0 +1,52 @@
<?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\Core\Utils;
class ThemeManager
{
protected $config;
protected $metadata;
private $defaultName = 'Espo';
private $defaultStylesheet = 'Espo';
public function __construct(Config $config, Metadata $metadata)
{
$this->config = $config;
$this->metadata = $metadata;
}
public function getName()
{
return $this->config->get('theme', $this->defaultName);
}
public function getStylesheet()
{
return $this->metadata->get('themes.' . $this->getName() . '.stylesheet', 'client/css/espo.css');
}
}

View File

@@ -91,7 +91,7 @@ return array (
"tabList" => array("Account", "Contact", "Lead", "Opportunity", "Calendar", "Meeting", "Call", "Task", "Case", "Email", "Document", "Campaign"),
"quickCreateList" => array("Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case"),
'calendarDefaultEntity' => 'Meeting',
'disableExport' => false,
'exportDisabled' => false,
'assignmentEmailNotifications' => false,
'assignmentEmailNotificationsEntityList' => array('Lead', 'Opportunity', 'Task', 'Case'),
'assignmentNotificationsEntityList' => array('Meeting', 'Call', 'Task', 'Email'),
@@ -101,6 +101,8 @@ return array (
'maxEmailAccountCount' => 2,
'followCreatedEntities' => false,
'b2cMode' => false,
'restrictedMode' => false,
'theme' => 'Espo',
'isInstalled' => false,
);

View File

@@ -1,6 +1,6 @@
[{
"label":"",
"rows": [
[{"name":"name"}]
[{"name":"name", "fullWidth": true}, false]
]
}]

View File

@@ -84,7 +84,10 @@ return array (
'permissionMap',
'permissionRules',
'passwordSalt',
'cryptKey'
'cryptKey',
'restrictedMode',
'userLimit',
'stylesheet'
),
'adminItems' =>
array (

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\EntryPoints;
@@ -29,7 +29,7 @@ use \Espo\Core\Exceptions\BadRequest;
class ChangePassword extends \Espo\Core\EntryPoints\Base
{
public static $authRequired = false;
public function run()
{
$requestId = $_GET['id'];
@@ -38,6 +38,7 @@ class ChangePassword extends \Espo\Core\EntryPoints\Base
}
$config = $this->getConfig();
$themeManager = $this->getThemeManager();
$p = $this->getEntityManager()->getRepository('PasswordChangeRequest')->where(array(
'requestId' => $requestId
@@ -56,9 +57,15 @@ class ChangePassword extends \Espo\Core\EntryPoints\Base
$html = file_get_contents('main.html');
$html = str_replace('{{cacheTimestamp}}', $config->get('cacheTimestamp', 0), $html);
$html = str_replace('{{useCache}}', $config->get('useCache') ? 'true' : 'false' , $html);
$html = str_replace('{{stylesheet}}', $themeManager->getStylesheet(), $html);
$html = str_replace('{{runScript}}', $runScript , $html);
echo $html;
exit;
}
protected function getThemeManager()
{
return $this->getContainer()->get('themeManager');
}
}

View File

@@ -29,7 +29,9 @@ class Notifications extends \Espo\Core\Hooks\Base
{
public static $order = 10;
protected $noticatorsHash = array();
protected $notifatorsHash = array();
private $streamService;
protected function init()
{
@@ -44,6 +46,11 @@ class Notifications extends \Espo\Core\Hooks\Base
return $this->getInjection('container');
}
protected function getServiceFactory()
{
return $this->getContainer()->get('serviceFactory');
}
protected function getMetadata()
{
return $this->getInjection('metadata');
@@ -59,7 +66,7 @@ class Notifications extends \Espo\Core\Hooks\Base
protected function getNotificator($entityType)
{
if (empty($this->noticatorsHash[$entityType])) {
if (empty($this->notifatorsHash[$entityType])) {
$normalizedName = Util::normilizeClassName($entityType);
$className = '\\Espo\\Custom\\Notificators\\' . $normalizedName;
@@ -81,19 +88,19 @@ class Notifications extends \Espo\Core\Hooks\Base
$notificator->inject($name, $this->getContainer()->get($name));
}
$this->noticatorsHash[$entityType] = $notificator;
$this->notifatorsHash[$entityType] = $notificator;
}
return $this->noticatorsHash[$entityType];
return $this->notifatorsHash[$entityType];
}
public function afterSave(Entity $entity, array $options = array())
{
$entityType = $entity->getEntityType();
if (!empty($options['silent']) && !empty($options['noNotifications'])) {
return;
}
$entityType = $entity->getEntityType();
if (!$this->checkHasStream($entityType)) {
if (in_array($entityType, $this->getConfig()->get('assignmentNotificationsEntityList', []))) {
$notificator = $this->getNotificator($entityType);
@@ -102,5 +109,43 @@ class Notifications extends \Espo\Core\Hooks\Base
}
}
public function beforeRemove(Entity $entity, array $options = array())
{
if (!empty($options['silent']) && !empty($options['noNotifications'])) {
return;
}
$entityType = $entity->getEntityType();
if ($this->checkHasStream($entityType)) {
$followersData = $this->getStreamService()->getEntityFollowers($entity);
foreach ($followersData['idList'] as $userId) {
if ($userId === $this->getUser()->id) {
continue;
}
$notification = $this->getEntityManager()->getEntity('Notification');
$notification->set(array(
'userId' => $userId,
'type' => 'EntityRemoved',
'data' => array(
'entityType' => $entity->getEntityType(),
'entityId' => $entity->id,
'entityName' => $entity->get('name'),
'userId' => $this->getUser()->id,
'userName' => $this->getUser()->get('name')
)
));
$this->getEntityManager()->saveEntity($notification);
}
}
}
protected function getStreamService()
{
if (empty($this->streamService)) {
$this->streamService = $this->getServiceFactory()->create('Stream');
}
return $this->streamService;
}
}

View File

@@ -137,7 +137,7 @@ class Stream extends \Espo\Core\Hooks\Base
protected function getAutofollowUserIdList(Entity $entity, array $ignoreList = array())
{
$entityType = $entity->getEntityName();
$entityType = $entity->getEntityType();
$pdo = $this->getEntityManager()->getPDO();
$userIdList = [];
@@ -160,7 +160,7 @@ class Stream extends \Espo\Core\Hooks\Base
public function afterSave(Entity $entity, array $options = array())
{
$entityName = $entity->getEntityName();
$entityName = $entity->getEntityType();
if ($this->checkHasStream($entity)) {
if ($entity->isNew()) {

View File

@@ -40,7 +40,7 @@ class Cleanup extends \Espo\Core\Jobs\Base
protected function cleanupJobs()
{
$query = "DELETE FROM `job` WHERE DATE(modified_at) < '".$this->getDate()."' ";
$query = "DELETE FROM `job` WHERE DATE(modified_at) < '".$this->getCleanupFromDate()."' AND status <> 'Pending'";
$pdo = $this->getEntityManager()->getPDO();
$sth = $pdo->prepare($query);
@@ -64,14 +64,14 @@ class Cleanup extends \Espo\Core\Jobs\Base
$delSql = "DELETE FROM `scheduled_job_log_record`
WHERE scheduled_job_id = '".$id."'
AND DATE(created_at) < '".$this->getDate()."'
AND DATE(created_at) < '".$this->getCleanupFromDate()."'
AND id NOT IN ('".implode("', '", $lastRowIds)."')
";
$pdo->query($delSql);
}
}
protected function getDate($format = 'Y-m-d')
protected function getCleanupFromDate($format = 'Y-m-d')
{
$datetime = new \DateTime();
$datetime->modify($this->period);

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;
@@ -27,16 +27,19 @@ use \Espo\Core\Exceptions\BadRequest;
class Lead extends \Espo\Core\Controllers\Record
{
public function actionConvert($params, $data)
{
public function actionConvert($params, $data, $request)
{
if (empty($data['id'])) {
throw new BadRequest();
}
if (!$request->isPost()) {
throw new BadRequest();
}
$entity = $this->getRecordService()->convert($data['id'], $data['records']);
if (!empty($entity)) {
return $entity->toArray();
}
throw new Error();
throw new Error();
}
}

View File

@@ -34,7 +34,7 @@ class CheckEmailAccounts extends \Espo\Core\Jobs\Base
try {
$service->fetchFromMailServer($entity);
} catch (\Exception $e) {
$GLOBALS['log']->error('Job CheckEmailAccounts: [' . $e->getCode() . '] ' .$e->getMessage());
$GLOBALS['log']->error('Job CheckEmailAccounts '.$entity->id.': [' . $e->getCode() . '] ' .$e->getMessage());
}
}

View File

@@ -34,7 +34,7 @@ class CheckInboundEmails extends \Espo\Core\Jobs\Base
try {
$service->fetchFromMailServer($entity);
} catch (\Exception $e) {
$GLOBALS['log']->error('Job CheckInboundEmails: [' . $e->getCode() . '] ' .$e->getMessage());
$GLOBALS['log']->error('Job CheckInboundEmails '.$entity->id.': [' . $e->getCode() . '] ' .$e->getMessage());
}
}

View File

@@ -0,0 +1,67 @@
<?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;
use Espo\ORM\Entity;
class CaseObj extends \Espo\Core\ORM\Repositories\RDB
{
public function afterSave(Entity $entity, array $options)
{
$result = parent::afterSave($entity, $options);
$this->handleAfterSaveContacts($entity, $options);
return $result;
}
protected function handleAfterSaveContacts(Entity $entity, array $options)
{
$contactIdChanged = $entity->has('contactId') && $entity->get('contactId') != $entity->getFetched('contactId');
if ($contactIdChanged) {
$contactId = $entity->get('contactId');
if (empty($contactId)) {
$this->unrelate($entity, 'contacts', $entity->getFetched('contactId'));
return;
}
}
if ($contactIdChanged) {
$pdo = $this->getEntityManager()->getPDO();
$sql = "
SELECT id FROM case_contact
WHERE
contact_id = ".$pdo->quote($contactId)." AND
case_id = ".$pdo->quote($entity->id)." AND
deleted = 0
";
$sth = $pdo->prepare($sql);
$sth->execute();
if (!$sth->fetch()) {
$this->relate($entity, 'contacts', $contactId);
}
}
}
}

View File

@@ -43,7 +43,17 @@ class Contact extends \Espo\Core\ORM\Repositories\RDB
public function afterSave(Entity $entity, array $options)
{
$result = parent::afterSave($entity, $options);
$this->handleAfterSaveAccounts($entity, $options);
if ($entity->has('targetListId') && $entity->isNew()) {
$this->relate($entity, 'targetLists', $entity->get('targetListId'));
}
return $result;
}
protected function handleAfterSaveAccounts(Entity $entity, array $options)
{
$accountIdChanged = $entity->has('accountId') && $entity->get('accountId') != $entity->getFetched('accountId');
$titleChanged = $entity->has('title') && $entity->get('title') != $entity->getFetched('title');
@@ -51,7 +61,7 @@ class Contact extends \Espo\Core\ORM\Repositories\RDB
$accountId = $entity->get('accountId');
if (empty($accountId)) {
$this->unrelate($entity, 'accounts', $entity->getFetched('accountId'));
return $result;
return;
}
}
@@ -59,7 +69,7 @@ class Contact extends \Espo\Core\ORM\Repositories\RDB
if (empty($accountId)) {
$accountId = $entity->getFetched('accountId');
if (empty($accountId)) {
return $result;
return;
}
}
}
@@ -91,12 +101,6 @@ 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

@@ -44,12 +44,15 @@
"Computer": "Computer",
"Education": "Education",
"Electronics": "Electronics",
"Energy": "Energy",
"Entertainment & Leisure": "Entertainment & Leisure",
"Finance": "Finance",
"Food & Beverage": "Food & Beverage",
"Grocery": "Grocery",
"Healthcare": "Healthcare",
"Insurance": "Insurance",
"Legal": "Legal",
"Manufacturing": "Manufacturing",
"Publishing": "Publishing",
"Real Estate": "Real Estate",
"Service": "Service",

View File

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

View File

@@ -5,11 +5,21 @@
"status": "Status",
"account": "Account",
"contact": "Contact",
"contacts": "Contacts",
"priority": "Priority",
"type": "Type",
"description": "Description"
"description": "Description",
"inboundEmail": "Inbound Email"
},
"links": {
"inboundEmail": "Inbound Email",
"account": "Account",
"contact": "Contact (Primary)",
"Contacts": "Contacts",
"meetings": "Meetings",
"calls": "Calls",
"tasks": "Tasks",
"emails": "Emails"
},
"options": {
"status": {

View File

@@ -21,7 +21,10 @@
"cases": "Cases",
"targetLists": "Target Lists",
"campaignLogRecords": "Campaign Log",
"campaign": "Campaign"
"campaign": "Campaign",
"account": "Account (Primary)",
"accounts": "Accounts",
"casesPrimary": "Cases (Primary)"
},
"labels": {
"Create Contact": "Create Contact"

View File

@@ -56,7 +56,7 @@
"Log Call": "Log Call",
"Archive Email": "Archive Email",
"Create Task": "Create Task",
"Tasks": "Tasks"
"Tasks": "Tasks"
},
"fields": {
"billingAddressCity": "City",
@@ -68,7 +68,7 @@
"addressStreet": "Street",
"addressCountry": "Country",
"addressState": "State",
"addressPostalCode": "Postal Code",
"addressPostalCode": "Postal Code",
"shippingAddressCity": "City (Shipping)",
"shippingAddressStreet": "Street (Shipping)",
"shippingAddressCountry": "Country (Shipping)",
@@ -78,7 +78,7 @@
"links": {
"contacts": "Contacts",
"opportunities": "Opportunities",
"leads": "Leads",
"leads": "Leads",
"meetings": "Meetings",
"calls": "Calls",
"tasks": "Tasks",
@@ -87,7 +87,7 @@
"cases": "Cases",
"documents": "Documents",
"account": "Account",
"opportunity": "Opportunity",
"opportunity": "Opportunity",
"contact": "Contact",
"parent": "Parent"
},

View File

@@ -28,7 +28,10 @@
"links": {
"targetLists": "Target Lists",
"campaignLogRecords": "Campaign Log",
"campaign": "Campaign"
"campaign": "Campaign",
"createdAccount": "Account",
"createdContact": "Contact",
"createdOpportunity": "Opportunity"
},
"options": {
"status": {

View File

@@ -38,8 +38,8 @@
"Send Invitations": "Отправить приглашения"
},
"presetFilters": {
"planned": "Запланированный",
"held": "Выполнен",
"planned": "Запланированные",
"held": "Завершенные",
"todays": "На сегодня"
}
}

View File

@@ -36,7 +36,7 @@
"Create Case": "Создать обращение"
},
"presetFilters": {
"open": "Открыть",
"closed": "Закрыто"
"open": "Открытые",
"closed": "Закрытые"
}
}

View File

@@ -33,8 +33,8 @@
"Send Invitations": "Отправить приглашения"
},
"presetFilters": {
"planned": "Запланированный",
"held": "Выполнен",
"planned": "Запланированные",
"held": "Завершенные",
"todays": "На сегодня"
}
}

View File

@@ -5,6 +5,7 @@
[{"name":"name"}, {"name":"status"}],
[{"name":"type"}, {"name":"startDate"}],
[{"name":"targetLists"}, {"name":"endDate"}],
[false, {"name":"budget"}],
[{"name":"description", "fullWidth": true}]
]
}

View File

@@ -4,7 +4,7 @@
"rows":[
[{"name":"name"},{"name":"number"}],
[{"name":"status"},{"name":"account"}],
[{"name":"priority"},{"name":"contact"}],
[{"name":"priority"},{"name":"contacts"}],
[{"name":"type"},false],
[{"name":"description", "fullWidth": true}]
]

View File

@@ -1,6 +1,6 @@
[
{
"label":false,
"label": "",
"rows":[
[{"name":"file"}, {"name":"source"}],
[{"name":"name"}, {"name":"folder"}]

View File

@@ -1,6 +1,6 @@
[
{
"label": false,
"label": "",
"rows": [
[{"name":"file"}, {"name":"source"}],
[{"name":"name"}],

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
[{"name":"dateStart"},{"name":"dateCompleted"}],
[{"name":"dateEnd"},false],
[{"name":"description", "fullWidth": true}],
[{"name":"attachments"},false]
[{"name":"attachments", "fullWidth": true}]
]
}
]

View File

@@ -24,9 +24,9 @@
},
"relationshipPanels":{
"contacts":{
"actions":[
],
"layout":"listForAccount"
},
"opportunities":{
"layout":"listForAccount"
},
"campaignLogRecords": {

View File

@@ -19,7 +19,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
],
"detailSmall":[
@@ -33,7 +34,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
],
"edit":[
@@ -47,7 +49,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
],
"editSmall":[
@@ -61,7 +64,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
]
},

View File

@@ -37,5 +37,35 @@
"filterList": [
"active"
],
"formDependency": {
"type": {
"map": {
"Email": [
{
"action": "show",
"fields": ["targetLists"]
}
],
"Newsletter": [
{
"action": "show",
"fields": ["targetLists"]
}
],
"Mail": [
{
"action": "show",
"fields": ["targetLists"]
}
]
},
"default": [
{
"action": "hide",
"fields": ["targetLists"]
}
]
}
},
"boolFilterList": ["onlyMy"]
}

View File

@@ -27,6 +27,9 @@
"rowActionsView": "Record.RowActions.Empty",
"select": false,
"create": false
},
"opportunities":{
"layout":"listForAccount"
}
},
"boolFilterList": ["onlyMy"],

View File

@@ -19,7 +19,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
],
"detailSmall":[
@@ -33,7 +34,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
],
"edit":[
@@ -47,7 +49,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
],
"editSmall":[
@@ -61,7 +64,8 @@
"contacts",
"leads"
]
}
},
"sticked": true
}
]
},

View File

@@ -13,7 +13,8 @@
{
"name":"history",
"label":"History",
"view":"Crm:Record.Panels.History"
"view":"Crm:Record.Panels.History",
"sticked": true
},
{
"name":"tasks",
@@ -35,6 +36,9 @@
"additionalLayouts": {
"detailConvert": {
"type": "detail"
},
"listForAccount": {
"type": "listSmall"
}
}
}

View File

@@ -17,14 +17,15 @@
},
"type": {
"type": "enum",
"options": ["", "Customer", "Investor", "Partner", "Reseller"]
"options": ["", "Customer", "Investor", "Partner", "Reseller"],
"default": ""
},
"industry": {
"type": "enum",
"options": [
"",
"Agriculture",
"Advertising",
"Agriculture",
"Apparel & Accessories",
"Automotive",
"Banking",
@@ -33,12 +34,15 @@
"Computer",
"Education",
"Electronics",
"Energy",
"Entertainment & Leisure",
"Finance",
"Food & Beverage",
"Grocery",
"Healthcare",
"Insurance",
"Legal",
"Manufacturing",
"Publishing",
"Real Estate",
"Service",
@@ -49,7 +53,9 @@
"Television",
"Transportation",
"Venture Capital"
]
],
"default": "",
"isSorted": true
},
"sicCode": {
"type": "varchar",

View File

@@ -101,6 +101,9 @@
"notStorable": true,
"readOnly": true,
"disabled": true
},
"budget": {
"type": "currency"
}
},
"links": {

View File

@@ -41,6 +41,10 @@
"type": "link",
"view": "Crm:Case.Fields.Contact"
},
"contacts": {
"type": "linkMultiple",
"view": "crm:views/case/fields/contacts"
},
"inboundEmail": {
"type": "link",
"readOnly": true
@@ -100,7 +104,13 @@
"contact": {
"type": "belongsTo",
"entity": "Contact",
"foreign": "cases"
"foreign": "casesPrimary"
},
"contacts": {
"type": "hasMany",
"entity": "Contact",
"foreign": "cases",
"layoutRelationshipsDisabled": true
},
"meetings": {
"type": "hasChildren",

View File

@@ -183,10 +183,16 @@
"entity": "Opportunity",
"foreign": "contacts"
},
"casesPrimary": {
"type": "hasMany",
"entity": "Case",
"foreign": "contact",
"layoutRelationshipsDisabled": true
},
"cases": {
"type": "hasMany",
"entity": "Case",
"foreign": "contact"
"foreign": "contacts"
},
"meetings": {
"type": "hasMany",

View File

@@ -36,7 +36,8 @@
},
"source": {
"type": "enum",
"options": ["", "Call", "Email", "Existing Customer", "Partner", "Public Relations", "Web Site", "Campaign", "Other"]
"options": ["", "Call", "Email", "Existing Customer", "Partner", "Public Relations", "Web Site", "Campaign", "Other"],
"default": ""
},
"opportunityAmount": {
"type": "currency",

View File

@@ -53,9 +53,10 @@
"max": 100
},
"leadSource": {
"type": "varchar",
"type": "enum",
"view": "Crm:Opportunity.Fields.LeadSource",
"customizationDisabled": true
"customizationOptionsDisabled": true,
"default": ""
},
"closeDate": {
"type": "date",

View File

@@ -84,8 +84,7 @@
"type": "linkMultiple"
},
"attachments": {
"type": "linkMultiple",
"view": "Fields.AttachmentMultiple"
"type": "attachmentMultiple"
}
},
"links": {
@@ -114,13 +113,6 @@
"account": {
"type": "belongsTo",
"entity": "Account"
},
"attachments": {
"type": "hasChildren",
"entity": "Attachment",
"foreign": "parent",
"relationName": "attachments",
"layoutRelationshipsDisabled": true
}
},
"collection": {

View File

@@ -37,14 +37,21 @@ class Task extends \Espo\Core\SelectManagers\Base
protected function filterActual(&$result)
{
$result['whereClause'][] = array(
'status!=' => array('Completed', 'Canceled')
'status!=' => ['Completed', 'Canceled']
);
}
protected function filterActualNotDeferred(&$result)
{
$result['whereClause'][] = array(
'status!=' => ['Completed', 'Canceled', 'Deferred']
);
}
protected function filterCompleted(&$result)
{
$result['whereClause'][] = array(
'status' => array('Completed')
'status' => ['Completed']
);
}

View File

@@ -69,6 +69,7 @@ class Activities extends \Espo\Core\Services\Base
protected function getUserMeetingQuery($id, $op, $notIn)
{
$pdo = $this->getEntityManager()->getPDO();
$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,
@@ -76,7 +77,7 @@ class Activities extends \Espo\Core\Services\Base
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."'
WHERE meeting.deleted = 0 AND usersMiddle.user_id = ".$pdo->quote($id)."
";
if (!empty($notIn)) {
$sql .= "
@@ -88,6 +89,7 @@ class Activities extends \Espo\Core\Services\Base
protected function getUserCallQuery($id, $op, $notIn)
{
$pdo = $this->getEntityManager()->getPDO();
$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,
@@ -95,7 +97,7 @@ class Activities extends \Espo\Core\Services\Base
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."'
WHERE call.deleted = 0 AND usersMiddle.user_id = ".$pdo->quote($id)."
";
if (!empty($notIn)) {
$sql .= "
@@ -107,6 +109,7 @@ class Activities extends \Espo\Core\Services\Base
protected function getUserEmailQuery($id, $op, $notIn)
{
$pdo = $this->getEntityManager()->getPDO();
$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,
@@ -114,7 +117,7 @@ class Activities extends \Espo\Core\Services\Base
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."'
WHERE email.deleted = 0 AND usersMiddle.user_id = ".$pdo->quote($id)."
";
if (!empty($notIn)) {
$sql .= "
@@ -326,7 +329,7 @@ class Activities extends \Espo\Core\Services\Base
";
}
if ($this->isPerson($scope)) {
if ($this->isPerson($scope) || $scope == 'Account') {
$sql = $sql . "
UNION
" . $baseSql;

View File

@@ -94,7 +94,6 @@ class Lead extends \Espo\Services\Record
$entityManager = $this->getEntityManager();
if (!empty($recordsData->Account)) {
$account = $entityManager->getEntity('Account');
$account->set(get_object_vars($recordsData->Account));
@@ -173,6 +172,19 @@ class Lead extends \Espo\Services\Record
}
}
$streamService = $this->getStreamService();
if ($streamService->checkIsFollowed($lead, $this->getUser()->id)) {
if (!empty($opportunity)) {
$streamService->followEntity($opportunity, $this->getUser()->id);
}
if (!empty($account)) {
$streamService->followEntity($account, $this->getUser()->id);
}
if (!empty($contact)) {
$streamService->followEntity($contact, $this->getUser()->id);
}
}
return $lead;
}
}

View File

@@ -29,30 +29,6 @@ use \Espo\ORM\Entity;
class Task extends \Espo\Services\Record
{
public function loadAdditionalFields(Entity $entity)
{
parent::loadAdditionalFields($entity);
if ($entity->id) {
$this->loadAttachmentsTypes($entity);
}
}
protected function loadAttachmentsTypes(Entity $entity)
{
$types = new \stdClass();
$attachmentsIds = $entity->get('attachmentsIds');
if (!empty($attachmentsIds)) {
foreach ($attachmentsIds as $id) {
$attachment = $this->getEntityManager()->getEntity('Attachment', $id);
if ($attachment) {
$types->$id = $attachment->get('type');
}
}
}
$entity->set('attachmentsTypes', $types);
}
}

View File

@@ -43,13 +43,17 @@ class Email extends \Espo\Core\Notificators\Base
public function process(Entity $entity)
{
if ($entity->get('status') != 'Archived' && $entity->get('status') != 'Sent') {
if ($entity->get('status') !== 'Archived' && $entity->get('status') !== 'Sent') {
return;
}
$previousUserIdList = $entity->getFetched('usersIds');
if (!is_array($previousUserIdList)) {
if ($entity->get('status') === 'Sent') {
$previousUserIdList = [];
} else {
$previousUserIdList = $entity->getFetched('usersIds');
if (!is_array($previousUserIdList)) {
$previousUserIdList = [];
}
}
$emailUserIdList = $entity->get('usersIds');
@@ -70,6 +74,10 @@ class Email extends \Espo\Core\Notificators\Base
'emailName' => $entity->get('name'),
);
if (!$entity->has('from')) {
$this->getEntityManager()->getRepository('Email')->loadFromField($entity);
}
$from = $entity->get('from');
if ($from) {
$person = $this->getEntityManager()->getRepository('EmailAddress')->getEntityByAddress($from, null, ['User', 'Contact', 'Lead']);

View File

@@ -137,6 +137,7 @@ abstract class Base
}
if (!empty($params['joins']) && is_array($params['joins'])) {
$params['joins'] = array_unique($params['joins']);
$joinsRelated = $this->getJoins($entity, $params['joins'], false, $params['joinConditions']);
if (!empty($joinsRelated)) {
if (!empty($joinsPart)) {
@@ -147,6 +148,7 @@ abstract class Base
}
if (!empty($params['leftJoins']) && is_array($params['leftJoins'])) {
$params['leftJoins'] = array_unique($params['leftJoins']);
$joinsRelated = $this->getJoins($entity, $params['leftJoins'], true, $params['joinConditions']);
if (!empty($joinsRelated)) {
if (!empty($joinsPart)) {

View File

@@ -45,12 +45,12 @@
"ldapUserLoginFilter": "Login Filter benutzen",
"ldapAccountDomainNameShort": "Domain Name Konto kurz",
"ldapOptReferrals": "Opt Referrals",
"disableExport": "Export deaktivieren (nur Admin ist berechtigt)",
"exportDisabled": "Export deaktivieren (nur Admin ist berechtigt)",
"assignmentNotificationsEntityList": "Entitäten über die bei Zuweisung benachrichtigt werden soll",
"assignmentEmailNotifications": "E-Mail Nachrichten bei Zuweisungen senden",
"assignmentEmailNotificationsEntityList": "Entitäten über die mit E-Mail bei Zuweisung benachrichtigt werden soll",
"b2cMode": "B2C Modus",
"disableAvatars": "Avatare deaktivieren",
"avatarsDisabled": "Avatare deaktivieren",
"followCreatedEntities": "Erstellten Einträgen beobachten"
},
"options": {

View File

@@ -20,7 +20,7 @@
"Teams": "Teams",
"Roles": "Roles",
"Outbound Emails": "Outbound Emails",
"Inbound Emails": "Inbound Emails",
"Inbound Emails": "Group Email Accounts",
"Email Templates": "Email Templates",
"Import": "Import",
"Layout Manager": "Layout Manager",
@@ -45,7 +45,9 @@
"Edit Entity": "Edit Entity",
"Create Link": "Create Link",
"Edit Link": "Edit Link",
"Notifications": "Notifications"
"Notifications": "Notifications",
"Jobs": "Jobs",
"Reset to Default": "Reset to Default"
},
"layouts": {
"list": "List",
@@ -85,7 +87,8 @@
"varchar": "Varchar",
"file": "File",
"image": "Image",
"multiEnum": "Multi-Enum"
"multiEnum": "Multi-Enum",
"attachmentMultiple": "Attachment Multiple"
},
"fields": {
"type": "Type",
@@ -106,12 +109,14 @@
"noEmptyString": "No Empty String",
"defaultType": "Default Type",
"seeMoreDisabled": "Disable Text Cut",
"entityList": "Entity List"
"entityList": "Entity List",
"isSorted": "Is Sorted (alphabetically)",
"audited": "Audited"
},
"messages": {
"upgradeVersion": "Your EspoCRM will be upgraded to version <strong>{version}</strong>. This can take some time.",
"upgradeDone": "Your EspoCRM has been upgraded to version <strong>{version}</strong>.",
"upgradeBackup": "We recommend you to make backup of your EspoCRM files and data before upgrade.",
"upgradeBackup": "We recommend to make a backup of your EspoCRM files and data before upgrade.",
"thousandSeparatorEqualsDecimalMark": "Thousand separator can not be same as decimal mark",
"userHasNoEmailAddress": "User has not email address.",
"selectEntityType": "Select entity type in the left menu.",

View File

@@ -38,7 +38,8 @@
"Forward": "Forward",
"Original message": "Original message",
"Forwarded message": "Forwarded message",
"Email Accounts": "Email Accounts",
"Email Accounts": "Personal Email Accounts",
"Inbound Emails": "Group Email Accounts",
"Email Templates": "Email Templates",
"Send Test Email": "Send Test Email",
"Send": "Send",

View File

@@ -11,7 +11,8 @@
"fetchSince": "Fetch Since",
"emailAddress": "Email Address",
"sentFolder": "Sent Folder",
"storeSentEmails": "Store Sent Emails"
"storeSentEmails": "Store Sent Emails",
"keepFetchedEmailsUnread": "Keep Fetched Emails Unread"
},
"links": {
},

View File

@@ -5,17 +5,18 @@
"Team": "Team",
"Role": "Role",
"EmailTemplate": "Email Template",
"EmailAccount": "Email Account",
"EmailAccountScope": "Email Account",
"EmailAccount": "Personal Email Account",
"EmailAccountScope": "Personal Email Account",
"OutboundEmail": "Outbound Email",
"ScheduledJob": "Scheduled Job",
"ExternalAccount": "External Account",
"Extension": "Extension",
"Dashboard": "Dashboard",
"InboundEmail": "Inbound Email Account",
"InboundEmail": "Group Email Account",
"Stream": "Stream",
"Import": "Import",
"Template": "Template"
"Template": "Template",
"Job": "Job"
},
"scopeNamesPlural": {
"Email": "Emails",
@@ -23,17 +24,18 @@
"Team": "Teams",
"Role": "Roles",
"EmailTemplate": "Email Templates",
"EmailAccount": "Email Accounts",
"EmailAccountScope": "Email Accounts",
"EmailAccount": "Personal Email Accounts",
"EmailAccountScope": "Personal Email Accounts",
"OutboundEmail": "Outbound Emails",
"ScheduledJob": "Scheduled Jobs",
"ExternalAccount": "External Accounts",
"Extension": "Extensions",
"Dashboard": "Dashboard",
"InboundEmail": "Inbound Email Accounts",
"InboundEmail": "Group Email Accounts",
"Stream": "Stream",
"Import": "Import Results",
"Template": "Templates"
"Template": "Templates",
"Job": "Jobs"
},
"labels": {
"Misc": "Misc",
@@ -160,7 +162,8 @@
"Tree View": "Tree View",
"Unlink All": "Unlink All",
"Total": "Total",
"Print to PDF": "Print to PDF"
"Print to PDF": "Print to PDF",
"Default": "Default"
},
"messages": {
"pleaseWait": "Please wait...",
@@ -251,7 +254,8 @@
},
"notificationMessages": {
"assign": "{entityType} {entity} has been assigned to you",
"emailReceived": "Email received from {from}"
"emailReceived": "Email received from {from}",
"entityRemoved": "{user} removed {entityType} {entity}"
},
"streamMessages": {
"create": "{user} created {entityType} {entity}",
@@ -391,7 +395,8 @@
"currentQuarter": "Current Quarter",
"lastQuarter": "Last Quarter",
"currentYear": "Current Year",
"lastYear": "Last Year"
"lastYear": "Last Year",
"lastSevenDays": "Last 7 Days"
},
"searchRanges": {
"is": "Is",
@@ -530,5 +535,11 @@
"redo":"Redo"
}
}
},
"themes": {
"Espo": "Espo",
"Sakura": "Sakura",
"EspoVertical": "Espo (Vertical)",
"SakuraVertical": "Sakura (Vertical)"
}
}

View File

@@ -1,6 +1,7 @@
{
"fields": {
"name": "Name",
"emailAddress": "Email Address",
"team": "Team",
"status": "Status",
"assignToUser": "Assign to User",

View File

@@ -0,0 +1,20 @@
{
"fields": {
"status": "Status",
"executeTime": "Execute At",
"attempts": "Attempts Left",
"failedAttempts": "Failed Attempts",
"serviceName": "Service",
"method": "Method",
"scheduledJob": "Scheduled Job",
"data": "Data"
},
"options": {
"status": {
"Pending": "Pending",
"Success": "Success",
"Running": "Running",
"Failed": "Failed"
}
}
}

View File

@@ -22,7 +22,8 @@
"autoFollowEntityTypeList": "Auto-Follow",
"signature": "Email Signature",
"dashboardTabList": "Tab List",
"defaultReminders": "Default Reminders"
"defaultReminders": "Default Reminders",
"theme": "Theme"
},
"links": {
},

View File

@@ -45,13 +45,16 @@
"ldapUserLoginFilter": "User Login Filter",
"ldapAccountDomainNameShort": "Account Domain Name Short",
"ldapOptReferrals": "Opt Referrals",
"disableExport": "Disable Export (only admin is allowed)",
"exportDisabled": "Disable Export (only admin is allowed)",
"assignmentNotificationsEntityList": "Entities to Notify about upon Assignment",
"assignmentEmailNotifications": "Send Email Notifications upon Assignment",
"assignmentEmailNotificationsEntityList": "Entities to Notify about with Email upon Assignment",
"b2cMode": "B2C Mode",
"disableAvatars": "Disable Avatars",
"followCreatedEntities": "Follow Created Entities"
"avatarsDisabled": "Disable Avatars",
"followCreatedEntities": "Follow Created Entities",
"displayListViewRecordCount": "Display Total Count (on List View)",
"theme": "Theme",
"userThemesDisabled": "Disable User Themes"
},
"options": {
"weekStart": {
@@ -62,7 +65,7 @@
"tooltips": {
"recordsPerPageSmall": "Count of records in relatinship panels.",
"outboundEmailIsShared": "Allow users to sent emails via this SMTP.",
"followCreatedEntities": "If user created a record he/she will follow it automatically."
"followCreatedEntities": "Users will automatically follow records they created."
},
"labels": {
"System": "System",

View File

@@ -10,12 +10,12 @@
"defaultCurrency": "Moneda por Defecto",
"baseCurrency": "Moneda Base",
"currencyRates": "Valores Tarifa",
"currencyList": "Lista de Moneda",
"language": "Idioma",
"companyLogo": "Logo Compañia",
"smtpServer": "Servidor",
"smtpPort": "Puerto",
"smtpAuth": "Autenticación",
@@ -26,14 +26,14 @@
"outboundEmailFromName": "De Nombre",
"outboundEmailFromAddress": "De la dirección",
"outboundEmailIsShared": "Es Compartido",
"recordsPerPage": "Registros por Página",
"recordsPerPageSmall": "Registros Por Página (Pequeño)",
"recordsPerPageSmall": "Registros Por Página (Pequeño)",
"tabList": "Lista Pestaña",
"quickCreateList": "Crear Lista Rápida",
"exportDelimiter": "Exportar Delimitador",
"authenticationMethod": "Método de Autentificación",
"ldapHost": "Host",
"ldapPort": "Puerto",
@@ -50,7 +50,7 @@
"ldapUserLoginFilter": "Usar Filtro en el Login",
"ldapAccountDomainNameShort": "Nombre Dominio Corto para la Cuenta",
"ldapOptReferrals": "Referencias Opt",
"disableExport": "Desactivar Exportar (Solo admin está permitido)",
"exportDisabled": "Desactivar Exportar (Solo admin está permitido)",
"assignmentEmailNotifications": "Enviar Correos Electrónicos de notificación sobre Asignación",
"assignmentEmailNotificationsEntityList": "Entidades a Notificar"
},

View File

@@ -52,12 +52,12 @@
"ldapUserLoginFilter": "Filtre des Connexions utilisateur",
"ldapAccountDomainNameShort": "Compte du nom de domaine réduit",
"ldapOptReferrals": "Abonnés parrainés",
"disableExport": "Désactiver l'Export (seul l'administrateur pourra le faire)",
"exportDisabled": "Désactiver l'Export (seul l'administrateur pourra le faire)",
"assignmentNotificationsEntityList": "Entités à prévenir lors d'affectations",
"assignmentEmailNotifications": "Envoyer des notifications emal lors d'affectations",
"assignmentEmailNotificationsEntityList": "Entités à notifier par email lors d'affectations",
"b2cMode": "Mode B2C",
"disableAvatars": "Désactiver les Avatars"
"avatarsDisabled": "Désactiver les Avatars"
},
"options": {
"weekStart": {

View File

@@ -50,11 +50,11 @@
"ldapUserLoginFilter": "Gebruikers Login Filter",
"ldapAccountDomainNameShort": "Korte Gebruikers Domein Naam",
"ldapOptReferrals": "Opt Referentie",
"disableExport": "Exporteren Uitschakelen (Uitsluitend toegestaan voor Admin)",
"exportDisabled": "Exporteren Uitschakelen (Uitsluitend toegestaan voor Admin)",
"assignmentEmailNotifications": "Verzend Email na Toewijzing",
"assignmentEmailNotificationsEntityList": "Entities om over te informeren",
"b2cMode": "B2C Mode",
"disableAvatars": "Avatar uitschakelen"
"avatarsDisabled": "Avatar uitschakelen"
},
"options": {
"weekStart": {

View File

@@ -50,7 +50,7 @@
"ldapUserLoginFilter": "Filtr użytkoników ",
"ldapAccountDomainNameShort": "Krótka nazwa konta domeny",
"ldapOptReferrals": "Opt Referrals",
"disableExport": "Disable Export (only admin is allowed)",
"exportDisabled": "Disable Export (only admin is allowed)",
"assignmentEmailNotifications": "Wyślij powiadominie do użytkonika o przypisaniu",
"assignmentEmailNotificationsEntityList": "Entities to Notify About"
},

View File

@@ -52,11 +52,11 @@
"ldapUserLoginFilter": "Filtro para Login de Usuário",
"ldapAccountDomainNameShort": "Nome curto do Domínio da Conta",
"ldapOptReferrals": "Referências Opt",
"disableExport": "Desabilitar exportação (permitido apenas para administradores)",
"exportDisabled": "Desabilitar exportação (permitido apenas para administradores)",
"assignmentEmailNotifications": "Enviar notificações sobre as designações por e-mail",
"assignmentEmailNotificationsEntityList": "Entidades para notificar",
"b2cMode": "Modo B2C",
"disableAvatars": "Desabilitar Avatares"
"avatarsDisabled": "Desabilitar Avatares"
},
"options": {
"weekStart": {

View File

@@ -47,7 +47,7 @@
"ldapUserLoginFilter": "User Login Filter",
"ldapAccountDomainNameShort": "Account Domain Name Short",
"ldapOptReferrals": "Opt Referrals",
"disableExport": "Disable Export (only admin is allowed)"
"exportDisabled": "Disable Export (only admin is allowed)"
},
"options": {
"weekStart": {

View File

@@ -50,7 +50,7 @@
"ldapUserLoginFilter": "Фильтер пользовательской авторизации",
"ldapAccountDomainNameShort": "Краткая учетная запись домена",
"ldapOptReferrals": "Opt Referrals",
"disableExport": "Отмена экпортирования (доступно только администратору)",
"exportDisabled": "Отмена экпортирования (доступно только администратору)",
"assignmentEmailNotifications": "Оповещать по email при назначении",
"assignmentEmailNotificationsEntityList": "Список вещей для оповещения",

View File

@@ -45,12 +45,12 @@
"ldapUserLoginFilter": "Фільтр логіну користувача",
"ldapAccountDomainNameShort": "Account Domain Name Short",
"ldapOptReferrals": "Оптові реферали",
"disableExport": "Вимкнути експортування (доступно лише адміністру)",
"exportDisabled": "Вимкнути експортування (доступно лише адміністру)",
"assignmentNotificationsEntityList": "Entities to Notify about upon Assignment",
"assignmentEmailNotifications": "Посилати сповіщення на емейл при назначенні",
"assignmentEmailNotificationsEntityList": "Entities to Notify about with Email upon Assignment",
"b2cMode": "Режим В2С",
"disableAvatars": "Вимкнути аватари",
"avatarsDisabled": "Вимкнути аватари",
"followCreatedEntities": "Follow Created Entities"
},
"options": {

View File

@@ -47,7 +47,7 @@
"ldapUserLoginFilter": "Bộ lọc đăng nhập",
"ldapAccountDomainNameShort": "Tên miền tài khoản",
"ldapOptReferrals": "Được giới thiệu",
"disableExport": "Disable Export (only admin is allowed)"
"exportDisabled": "Disable Export (only admin is allowed)"
},
"options": {
"weekStart": {

View File

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

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