Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a89872b785 | |||
| b6bc0471b8 | |||
| afd0f5a5ef | |||
| 533fff4d30 | |||
| 7b0b3d05ad | |||
| e09c101f82 | |||
| 4e6b300477 | |||
| 7453eb583a | |||
| b5d2eb93aa | |||
| b5b8ab4a8e | |||
| b6010a7ad7 | |||
| ebdb649bd7 | |||
| 220d4d1b77 | |||
| 2f8e1aeaf2 | |||
| 20db3b0bd2 | |||
| 6d796c0b20 | |||
| 100fec9409 | |||
| 0b5beeba85 | |||
| 909d31b9fe | |||
| d9522cf555 | |||
| e42a877bf6 | |||
| 8beeb64cbf | |||
| da3a18e766 | |||
| ffb94b960d | |||
| f73ac0779b | |||
| 8003af682e | |||
| cbf7bf18f2 | |||
| ceaa0a8322 | |||
| 8ca410d994 | |||
| 5fc7f2509d | |||
| f2e1ba3780 | |||
| 93ac871640 | |||
| 187a8a02a1 | |||
| bcf5686eec | |||
| 027507b61e | |||
| 49b3d17952 | |||
| e0958cfeec | |||
| 00e12b50b7 | |||
| 44b8b00106 | |||
| e0855e3092 | |||
| cb0d70430a | |||
| 042575ce6b | |||
| 5cd18b57d2 | |||
| d1e46e3d9a | |||
| 1a3348f2c2 | |||
| 75edc5c165 | |||
| 60184ebbbe | |||
| ddb6cb7483 | |||
| 2ddd44a2fe | |||
| ce55866445 | |||
| 2bc7f85a58 | |||
| a27df2c41f | |||
| 170581dd28 | |||
| 00b0c904ae | |||
| fdab17265f | |||
| 96d0a7db00 | |||
| 2835928ac0 | |||
| 4c22b42b99 | |||
| 64ddaa40a8 | |||
| bc7006e193 | |||
| 1ae0a9df28 | |||
| a4f9280eba | |||
| 2528fc34c8 | |||
| 979f07bf9b | |||
| 52bff4de1c | |||
| 4c017361f8 | |||
| 84aa1339d9 | |||
| 987cd4a121 | |||
| e50ad5106f | |||
| 071cbcb0fb | |||
| 43f1cb9af9 | |||
| 220c55e9b4 | |||
| 927610efb0 | |||
| 78fcaf1fa3 | |||
| 3bd9af031d | |||
| 16ee3d68e8 | |||
| d49ee3c187 | |||
| 2e544f1ccf | |||
| 1c51125e66 | |||
| 1ed47f5d0d | |||
| 133fa0cb36 | |||
| 295904bf4c | |||
| 951230f7e1 | |||
| ee58886206 | |||
| 41575f6f13 | |||
| 18ae33d417 | |||
| 36daf5a762 | |||
| 8a41a2cd05 | |||
| 61497d2a01 | |||
| a3045a88f3 | |||
| fd072520a9 | |||
| 6ef9d8428f | |||
| 33a7176165 | |||
| 044de7d744 | |||
| 801ba05cd8 | |||
| 0dc5a4e4f3 | |||
| 230ed63e67 | |||
| 7316866a1a | |||
| c7be54d9c9 | |||
| dcc118ec5d | |||
| 84e98054b7 | |||
| f37308f5a5 | |||
| 9688b3be3c | |||
| 6490a1ca97 | |||
| c15fc89f52 | |||
| 9207030ccb | |||
| 018eb44de6 | |||
| bc92b96a6b | |||
| ee57c9223c | |||
| f799ffc61f | |||
| bdb1f2f3b6 | |||
| fd755df67b | |||
| e9edd01d08 | |||
| dea7e0b33e | |||
| 25b50baa1a | |||
| 8a53657b9c | |||
| c6ecbf4942 | |||
| 56d11807a1 | |||
| cf3b4b284a | |||
| 953a59cb57 | |||
| f60061a387 | |||
| c627d84273 | |||
| 5a16ee0493 | |||
| 14252d6e9e | |||
| c0de3e1c4e | |||
| e0c3530ae5 | |||
| 8b4070f9ae | |||
| 0b6c5c2862 | |||
| ce9ff10cb0 | |||
| de688bfa12 | |||
| f4ef5fc36f | |||
| b1e184c6d1 | |||
| fbf665fd76 | |||
| 95fd66a7a6 | |||
| 4b98ea79e0 | |||
| 2e916a2ba8 | |||
| 26a4d1c6ff | |||
| 33fef4c90e | |||
| 2dc15294fc | |||
| 171df33736 | |||
| 9b846b45bc | |||
| bc65975f74 | |||
| a5308831f2 | |||
| ef05e4e9f4 | |||
| 83f0a81eb7 | |||
| 9e28d4a261 | |||
| a9b3320302 | |||
| b67580ee1d | |||
| 884ccb5265 | |||
| ac56c3f79e | |||
| e218056683 | |||
| 838288a463 | |||
| e74b90d048 | |||
| b2a653a3dc | |||
| 48ee7d4cc7 | |||
| 1d38f80748 | |||
| c712366737 | |||
| 71f40fd440 | |||
| 9049ebfa8c | |||
| f4b9356173 | |||
| beaa4a29ba | |||
| 632ee66155 | |||
| 4446bc3167 | |||
| 785934c7cc | |||
| ac36096d55 | |||
| 8ec8cb3c56 | |||
| 1f7cb2402d | |||
| 987ba2faa5 | |||
| a24f697dca | |||
| 3825893ec1 | |||
| a4ed78b953 | |||
| c7947d0c46 | |||
| eed9059913 | |||
| 20a1053413 | |||
| 16aca57f17 | |||
| a225748840 | |||
| fff1be9d07 | |||
| b73ca3f1bc | |||
| 93a4f999b6 | |||
| 3030643ce4 | |||
| e814ea9ec6 | |||
| de16e9a9ce | |||
| 62fba991e2 | |||
| 832c63e014 | |||
| 46913c3574 | |||
| 0194d364b8 | |||
| 1c7785f6ba |
@@ -18,13 +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\BadRequest;
|
||||
|
||||
class App extends \Espo\Core\Controllers\Record
|
||||
class App extends \Espo\Core\Controllers\Base
|
||||
{
|
||||
public function actionUser()
|
||||
{
|
||||
|
||||
@@ -18,16 +18,16 @@
|
||||
*
|
||||
* 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 EmailAccount extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
{
|
||||
public function actionGetFolders($params, $data, $request)
|
||||
{
|
||||
{
|
||||
return $this->getRecordService()->getFolders(array(
|
||||
'host' => $request->get('host'),
|
||||
'port' => $request->get('port'),
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
<?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\BadRequest;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class EntityManager extends \Espo\Core\Controllers\Base
|
||||
{
|
||||
protected function checkControllerAccess()
|
||||
{
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
public function actionCreateEntity($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (empty($data['name']) || empty($data['type'])) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$name = $data['name'];
|
||||
$type = $data['type'];
|
||||
|
||||
$name = filter_var($name, \FILTER_SANITIZE_STRING);
|
||||
$type = filter_var($type, \FILTER_SANITIZE_STRING);
|
||||
|
||||
$params = array();
|
||||
|
||||
if (!empty($data['labelSingular'])) {
|
||||
$params['labelSingular'] = $data['labelSingular'];
|
||||
}
|
||||
if (!empty($data['labelPlural'])) {
|
||||
$params['labelPlural'] = $data['labelPlural'];
|
||||
}
|
||||
if (!empty($data['stream'])) {
|
||||
$params['stream'] = $data['stream'];
|
||||
}
|
||||
|
||||
$result = $this->getContainer()->get('entityManagerUtil')->create($name, $type, $params);
|
||||
|
||||
if ($result) {
|
||||
$tabList = $this->getConfig()->get('tabList', []);
|
||||
$tabList[] = $name;
|
||||
$this->getConfig()->set('tabList', $tabList);
|
||||
$this->getConfig()->save();
|
||||
|
||||
$this->getContainer()->get('dataManager')->rebuild();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function actionUpdateEntity($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (empty($data['name'])) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
$name = $data['name'];
|
||||
$name = filter_var($name, \FILTER_SANITIZE_STRING);
|
||||
|
||||
$result = $this->getContainer()->get('entityManagerUtil')->update($name, $data);
|
||||
|
||||
if ($result) {
|
||||
$this->getContainer()->get('dataManager')->clearCache();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function actionRemoveEntity($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (empty($data['name'])) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
$name = $data['name'];
|
||||
$name = filter_var($name, \FILTER_SANITIZE_STRING);
|
||||
|
||||
$result = $this->getContainer()->get('entityManagerUtil')->delete($name);
|
||||
|
||||
if ($result) {
|
||||
$tabList = $this->getConfig()->get('tabList', []);
|
||||
if (($key = array_search($name, $tabList)) !== false) {
|
||||
unset($tabList[$key]);
|
||||
$tabList = array_values($tabList);
|
||||
}
|
||||
$this->getConfig()->set('tabList', $tabList);
|
||||
$this->getConfig()->save();
|
||||
|
||||
$this->getContainer()->get('dataManager')->clearCache();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function actionCreateLink($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$paramList = [
|
||||
'entity',
|
||||
'entityForeign',
|
||||
'link',
|
||||
'linkForeign',
|
||||
'label',
|
||||
'labelForeign',
|
||||
'linkType'
|
||||
];
|
||||
|
||||
$d = array();
|
||||
foreach ($paramList as $item) {
|
||||
if (empty($data[$item])) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
$d[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
|
||||
}
|
||||
|
||||
$result = $this->getContainer()->get('entityManagerUtil')->createLink($d);
|
||||
|
||||
if ($result) {
|
||||
$this->getContainer()->get('dataManager')->rebuild();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function actionUpdateLink($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$paramList = [
|
||||
'entity',
|
||||
'entityForeign',
|
||||
'link',
|
||||
'linkForeign',
|
||||
'label',
|
||||
'labelForeign'
|
||||
];
|
||||
|
||||
$d = array();
|
||||
foreach ($paramList as $item) {
|
||||
$d[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
|
||||
}
|
||||
|
||||
$result = $this->getContainer()->get('entityManagerUtil')->updateLink($d);
|
||||
|
||||
if ($result) {
|
||||
$this->getContainer()->get('dataManager')->clearCache();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function actionRemoveLink($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$paramList = [
|
||||
'entity',
|
||||
'link',
|
||||
];
|
||||
$d = array();
|
||||
foreach ($paramList as $item) {
|
||||
$d[$item] = filter_var($data[$item], \FILTER_SANITIZE_STRING);
|
||||
}
|
||||
|
||||
$result = $this->getContainer()->get('entityManagerUtil')->deleteLink($d);
|
||||
|
||||
if ($result) {
|
||||
$this->getContainer()->get('dataManager')->clearCache();
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class Extension extends \Espo\Core\Controllers\Record
|
||||
|
||||
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
|
||||
|
||||
$manager->install($data['id']);
|
||||
$manager->install($data);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class Extension extends \Espo\Core\Controllers\Record
|
||||
|
||||
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
|
||||
|
||||
$manager->uninstall($data['id']);
|
||||
$manager->uninstall($data);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class Extension extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
|
||||
|
||||
$manager->delete($params['id']);
|
||||
$manager->delete($params);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ use \Espo\Core\Exceptions\Forbidden;
|
||||
class ExternalAccount extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
public static $defaultAction = 'list';
|
||||
|
||||
|
||||
public function actionList($params, $data, $request)
|
||||
{
|
||||
$integrations = $this->getEntityManager()->getRepository('Integration')->find();
|
||||
$integrations = $this->getEntityManager()->getRepository('Integration')->find();
|
||||
$arr = array();
|
||||
foreach ($integrations as $entity) {
|
||||
if ($entity->get('enabled') && $this->getMetadata()->get('integrations.' . $entity->id .'.allowUserAccounts')) {
|
||||
@@ -44,22 +44,22 @@ class ExternalAccount extends \Espo\Core\Controllers\Record
|
||||
'list' => $arr
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function actionGetOAuth2Info($params, $data, $request)
|
||||
{
|
||||
$id = $request->get('id');
|
||||
list($integration, $userId) = explode('__', $id);
|
||||
|
||||
|
||||
|
||||
if ($this->getUser()->id != $userId) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$entity = $this->getEntityManager()->getEntity('Integration', $integration);
|
||||
if ($entity) {
|
||||
return array(
|
||||
'clientId' => $entity->get('clientId'),
|
||||
'redirectUri' => $this->getConfig()->get('siteUrl') . '/oauthcallback',
|
||||
'redirectUri' => $this->getConfig()->get('siteUrl') . '?entryPoint=oauthCallback',
|
||||
'isConnected' => $this->getRecordService()->ping($integration, $userId)
|
||||
);
|
||||
}
|
||||
@@ -67,57 +67,57 @@ class ExternalAccount extends \Espo\Core\Controllers\Record
|
||||
|
||||
public function actionRead($params, $data, $request)
|
||||
{
|
||||
list($integration, $userId) = explode('__', $params['id']);
|
||||
list($integration, $userId) = explode('__', $params['id']);
|
||||
|
||||
if ($this->getUser()->id != $userId) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']);
|
||||
}
|
||||
|
||||
$entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']);
|
||||
return $entity->toArray();
|
||||
}
|
||||
|
||||
|
||||
public function actionUpdate($params, $data)
|
||||
{
|
||||
return $this->actionPatch($params, $data);
|
||||
}
|
||||
|
||||
|
||||
public function actionPatch($params, $data)
|
||||
{
|
||||
list($integration, $userId) = explode('__', $params['id']);
|
||||
|
||||
|
||||
|
||||
if ($this->getUser()->id != $userId) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
|
||||
if (isset($data['enabled']) && !$data['enabled']) {
|
||||
$data['data'] = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']);
|
||||
$entity->set($data);
|
||||
$this->getEntityManager()->saveEntity($entity);
|
||||
|
||||
return $entity->toArray();
|
||||
|
||||
return $entity->toArray();
|
||||
}
|
||||
|
||||
|
||||
public function actionAuthorizationCode($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new Error('Bad HTTP method type.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$id = $data['id'];
|
||||
$code = $data['code'];
|
||||
|
||||
list($integration, $userId) = explode('__', $id);
|
||||
|
||||
list($integration, $userId) = explode('__', $id);
|
||||
|
||||
if ($this->getUser()->id != $userId) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$service = $this->getRecordService();
|
||||
|
||||
$service = $this->getRecordService();
|
||||
return $service->authorizationCode($integration, $userId, $code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class Layout extends \Espo\Core\Controllers\Base
|
||||
{
|
||||
$data = $this->getContainer()->get('layout')->get($params['scope'], $params['name']);
|
||||
if (empty($data)) {
|
||||
throw new NotFound("Layout " . $params['scope'] . ":" . $params['name'] . ' is not found');
|
||||
throw new NotFound("Layout " . $params['scope'] . ":" . $params['name'] . ' is not found.');
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
@@ -43,16 +43,18 @@ class Layout extends \Espo\Core\Controllers\Base
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$result = $this->getContainer()->get('layout')->set($data, $params['scope'], $params['name']);
|
||||
|
||||
$layoutManager = $this->getContainer()->get('layout');
|
||||
$layoutManager->set($data, $params['scope'], $params['name']);
|
||||
$result = $layoutManager->save();
|
||||
|
||||
if ($result === false) {
|
||||
throw new Error("Error while saving layout");
|
||||
throw new Error("Error while saving layout.");
|
||||
}
|
||||
|
||||
$this->getContainer()->get('dataManager')->updateCacheTimestamp();
|
||||
|
||||
return $this->getContainer()->get('layout')->get($params['scope'], $params['name']);
|
||||
return $layoutManager->get($params['scope'], $params['name']);
|
||||
}
|
||||
|
||||
public function actionPatch($params, $data)
|
||||
|
||||
@@ -79,6 +79,28 @@ class Container
|
||||
return $className;
|
||||
}
|
||||
|
||||
protected function loadLog()
|
||||
{
|
||||
$logConfig = $this->get('config')->get('logger');
|
||||
|
||||
$log = new \Espo\Core\Utils\Log('Espo');
|
||||
|
||||
$levelCode = $log->getLevelCode($logConfig['level']);
|
||||
|
||||
if ($logConfig['isRotate']) {
|
||||
$handler = new \Espo\Core\Utils\Log\Monolog\Handler\RotatingFileHandler($logConfig['path'], $logConfig['maxRotateFiles'], $levelCode);
|
||||
} else {
|
||||
$handler = new \Espo\Core\Utils\Log\Monolog\Handler\StreamHandler($logConfig['path'], $levelCode);
|
||||
}
|
||||
$log->pushHandler($handler);
|
||||
|
||||
$errorHandler = new \Monolog\ErrorHandler($log);
|
||||
$errorHandler->registerExceptionHandler(null, false);
|
||||
$errorHandler->registerErrorHandler(array(), false);
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
protected function loadContainer()
|
||||
{
|
||||
return $this;
|
||||
@@ -221,7 +243,7 @@ class Container
|
||||
|
||||
private function loadScheduledJob()
|
||||
{
|
||||
return new \Espo\Core\Cron\ScheduledJob(
|
||||
return new \Espo\Core\Utils\ScheduledJob(
|
||||
$this
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Espo\Core\Controllers;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\NotFound;
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
use \Espo\Core\Utils\Util;
|
||||
|
||||
class Record extends Base
|
||||
@@ -301,5 +302,24 @@ class Record extends Base
|
||||
$id = $params['id'];
|
||||
return $this->getRecordService()->unfollow($id);
|
||||
}
|
||||
|
||||
public function actionMerge($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (empty($data['targetId']) || empty($data['sourceIds']) || !is_array($data['sourceIds'])) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
$targetId = $data['targetId'];
|
||||
$sourceIds = $data['sourceIds'];
|
||||
|
||||
if (!$this->getAcl()->check($this->name, 'edit')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
return $this->getRecordService()->merge($targetId, $sourceIds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,17 +22,18 @@
|
||||
|
||||
namespace Espo\Core;
|
||||
|
||||
use \PDO;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
|
||||
class CronManager
|
||||
{
|
||||
private $container;
|
||||
private $config;
|
||||
private $fileManager;
|
||||
private $entityManager;
|
||||
|
||||
private $scheduledJobCron;
|
||||
private $serviceCron;
|
||||
|
||||
private $jobService;
|
||||
private $scheduledJobService;
|
||||
private $scheduledJobUtil;
|
||||
|
||||
const PENDING = 'Pending';
|
||||
const RUNNING = 'Running';
|
||||
@@ -41,19 +42,18 @@ class CronManager
|
||||
|
||||
protected $lastRunTime = 'data/cache/application/cronLastRunTime.php';
|
||||
|
||||
|
||||
public function __construct(\Espo\Core\Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
$this->config = $this->container->get('config');
|
||||
$this->fileManager = $this->container->get('fileManager');
|
||||
$this->entityManager = $this->container->get('entityManager');
|
||||
$this->serviceFactory = $this->container->get('serviceFactory');
|
||||
|
||||
$this->scheduledJobCron = $this->container->get('scheduledJob');
|
||||
$this->serviceCron = new \Espo\Core\Cron\Service( $this->container->get('serviceFactory'));
|
||||
|
||||
$this->jobService = $this->container->get('serviceFactory')->create('job');
|
||||
$this->scheduledJobService = $this->container->get('serviceFactory')->create('scheduledJob');
|
||||
$this->scheduledJobUtil = $this->container->get('scheduledJob');
|
||||
$this->cronJob = new \Espo\Core\Utils\Cron\Job($this->config, $this->entityManager);
|
||||
$this->cronScheduledJob = new \Espo\Core\Utils\Cron\ScheduledJob($this->config, $this->entityManager);
|
||||
}
|
||||
|
||||
protected function getContainer()
|
||||
@@ -71,30 +71,34 @@ class CronManager
|
||||
return $this->fileManager;
|
||||
}
|
||||
|
||||
protected function getJobService()
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->jobService;
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
protected function getScheduledJobService()
|
||||
protected function getServiceFactory()
|
||||
{
|
||||
return $this->scheduledJobService;
|
||||
return $this->serviceFactory;
|
||||
}
|
||||
|
||||
protected function getScheduledJobCron()
|
||||
protected function getScheduledJobUtil()
|
||||
{
|
||||
return $this->scheduledJobCron;
|
||||
return $this->scheduledJobUtil;
|
||||
}
|
||||
|
||||
protected function getServiceCron()
|
||||
protected function getCronJob()
|
||||
{
|
||||
return $this->serviceCron;
|
||||
return $this->cronJob;
|
||||
}
|
||||
|
||||
protected function getCronScheduledJob()
|
||||
{
|
||||
return $this->cronScheduledJob;
|
||||
}
|
||||
|
||||
protected function getLastRunTime()
|
||||
{
|
||||
$lastRunTime = $this->getFileManager()->getContents($this->lastRunTime);
|
||||
$lastRunTime = $this->getFileManager()->getPhpContents($this->lastRunTime);
|
||||
if (!is_int($lastRunTime)) {
|
||||
$lastRunTime = time() - (intval($this->getConfig()->get('cron.minExecutionTime')) + 60);
|
||||
}
|
||||
@@ -104,7 +108,7 @@ class CronManager
|
||||
|
||||
protected function setLastRunTime($time)
|
||||
{
|
||||
return $this->getFileManager()->putContentsPHP($this->lastRunTime, $time);
|
||||
return $this->getFileManager()->putPhpContents($this->lastRunTime, $time);
|
||||
}
|
||||
|
||||
protected function checkLastRunTime()
|
||||
@@ -120,65 +124,144 @@ class CronManager
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run Cron
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
if (!$this->checkLastRunTime()) {
|
||||
$GLOBALS['log']->info('Cron Manager: Stop cron running, too frequency execution');
|
||||
return; //stop cron running, too frequency execution
|
||||
$GLOBALS['log']->info('CronManager: Stop cron running, too frequent execution.');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setLastRunTime(time());
|
||||
|
||||
$entityManager = $this->getEntityManager();
|
||||
|
||||
$cronJob = $this->getCronJob();
|
||||
$cronScheduledJob = $this->getCronScheduledJob();
|
||||
|
||||
//Check scheduled jobs and create related jobs
|
||||
$this->createJobsFromScheduledJobs();
|
||||
|
||||
$pendingJobs = $this->getJobService()->getPendingJobs();
|
||||
$pendingJobs = $cronJob->getPendingJobs();
|
||||
|
||||
foreach ($pendingJobs as $job) {
|
||||
|
||||
$this->getJobService()->updateEntity($job['id'], array(
|
||||
'status' => self::RUNNING,
|
||||
));
|
||||
$jobEntity = $entityManager->getEntity('Job', $job['id']);
|
||||
|
||||
if (!isset($jobEntity)) {
|
||||
$GLOBALS['log']->error('CronManager: empty Job entity ['.$job['id'].'].');
|
||||
continue;
|
||||
}
|
||||
|
||||
$jobEntity->set('status', self::RUNNING);
|
||||
$entityManager->saveEntity($jobEntity);
|
||||
|
||||
$isSuccess = true;
|
||||
|
||||
try {
|
||||
if (!empty($job['scheduled_job_id'])) {
|
||||
$this->getScheduledJobCron()->run($job);
|
||||
$this->runScheduledJob($job);
|
||||
} else {
|
||||
$this->getServiceCron()->run($job);
|
||||
$this->runService($job);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$isSuccess = false;
|
||||
$GLOBALS['log']->error('Failed job running, job ['.$job['id'].']. Error Details: '.$e->getMessage());
|
||||
$GLOBALS['log']->error('CronManager: Failed job running, job ['.$job['id'].']. Error Details: '.$e->getMessage());
|
||||
}
|
||||
|
||||
$status = $isSuccess ? self::SUCCESS : self::FAILED;
|
||||
|
||||
$this->getJobService()->updateEntity($job['id'], array(
|
||||
'status' => $status,
|
||||
));
|
||||
$jobEntity->set('status', $status);
|
||||
$entityManager->saveEntity($jobEntity);
|
||||
|
||||
//set status in the schedulerJobLog
|
||||
if (!empty($job['scheduled_job_id'])) {
|
||||
$this->getScheduledJobService()->addLogRecord($job['scheduled_job_id'], $status);
|
||||
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], $status);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Scheduled Job
|
||||
*
|
||||
* @param array $job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function runScheduledJob(array $job)
|
||||
{
|
||||
$jobName = $job['method'];
|
||||
|
||||
$className = $this->getScheduledJobUtil()->get($jobName);
|
||||
if ($className === false) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$jobClass = new $className($this->container);
|
||||
$method = $this->getScheduledJobUtil()->getMethodName();
|
||||
if (!method_exists($jobClass, $method)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$jobClass->$method();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Service
|
||||
*
|
||||
* @param array $job
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function runService(array $job)
|
||||
{
|
||||
$serviceName = $job['service_name'];
|
||||
|
||||
if (!$this->getServiceFactory()->checkExists($serviceName)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$service = $this->getServiceFactory()->create($serviceName);
|
||||
$serviceMethod = $job['method'];
|
||||
|
||||
if (!method_exists($service, $serviceMethod)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$data = $job['data'];
|
||||
if (Json::isJSON($data)) {
|
||||
$data = Json::decode($data, true);
|
||||
}
|
||||
|
||||
$service->$serviceMethod($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check scheduled jobs and create related jobs
|
||||
* @return array List of created Jobs
|
||||
*
|
||||
* @return array List of created Jobs
|
||||
*/
|
||||
protected function createJobsFromScheduledJobs()
|
||||
{
|
||||
$activeScheduledJobs = $this->getScheduledJobService()->getActiveJobs();
|
||||
$entityManager = $this->getEntityManager();
|
||||
|
||||
$activeScheduledJobs = $this->getCronScheduledJob()->getActiveJobs();
|
||||
|
||||
$cronJob = $this->getCronJob();
|
||||
$runningScheduledJobs = $cronJob->getActiveJobs('scheduled_job_id', self::RUNNING, PDO::FETCH_COLUMN);
|
||||
|
||||
$createdJobs = array();
|
||||
foreach ($activeScheduledJobs as $scheduledJob) {
|
||||
|
||||
if (in_array($scheduledJob['id'], $runningScheduledJobs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scheduling = $scheduledJob['scheduling'];
|
||||
|
||||
$cronExpression = \Cron\CronExpression::factory($scheduling);
|
||||
@@ -186,26 +269,30 @@ class CronManager
|
||||
try {
|
||||
$prevDate = $cronExpression->getPreviousRunDate()->format('Y-m-d H:i:s');
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('ScheduledJob ['.$scheduledJob['id'].']: CronExpression - Impossible CRON expression ['.$scheduling.']');
|
||||
$GLOBALS['log']->error('CronManager: ScheduledJob ['.$scheduledJob['id'].']: CronExpression - Impossible CRON expression ['.$scheduling.']');
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($cronExpression->isDue()) {
|
||||
$prevDate = date('Y-m-d H:i:00');
|
||||
$prevDate = date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$existsJob = $this->getJobService()->getJobByScheduledJob($scheduledJob['id'], $prevDate);
|
||||
$existsJob = $cronJob->getJobByScheduledJob($scheduledJob['id'], $prevDate);
|
||||
|
||||
if (!isset($existsJob) || empty($existsJob)) {
|
||||
//create a job
|
||||
$data = array(
|
||||
//create a new job
|
||||
$jobEntity = $entityManager->getEntity('Job');
|
||||
$jobEntity->set(array(
|
||||
'name' => $scheduledJob['name'],
|
||||
'status' => self::PENDING,
|
||||
'scheduledJobId' => $scheduledJob['id'],
|
||||
'executeTime' => $prevDate,
|
||||
'method' => $scheduledJob['job'],
|
||||
);
|
||||
$createdJobs[] = $this->getJobService()->createEntity($data);
|
||||
));
|
||||
$jobEntityId = $entityManager->saveEntity($jobEntity);
|
||||
if (!empty($jobEntityId)) {
|
||||
$createdJobs[] = $jobEntityId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ class ExtensionManager extends Upgrades\Base
|
||||
|
||||
protected $params = array(
|
||||
'packagePath' => 'data/upload/extensions',
|
||||
|
||||
'backupPath' => 'data/.backup/extensions',
|
||||
|
||||
'scriptNames' => array(
|
||||
|
||||
@@ -29,33 +29,33 @@ use \Espo\Core\Exceptions\NotFound;
|
||||
class ClientManager
|
||||
{
|
||||
protected $entityManager;
|
||||
|
||||
|
||||
protected $metadata;
|
||||
|
||||
|
||||
protected $clientMap = array();
|
||||
|
||||
|
||||
public function __construct($entityManager, $metadata, $config)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->metadata = $metadata;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
|
||||
protected function getMetadata()
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
|
||||
public function storeAccessToken($hash, $data)
|
||||
{
|
||||
if (!empty($this->clientMap[$hash]) && !empty($this->clientMap[$hash]['externalAccountEntity'])) {
|
||||
@@ -64,37 +64,37 @@ class ClientManager
|
||||
$externalAccountEntity->set('tokenType', $data['tokenType']);
|
||||
$this->getEntityManager()->saveEntity($externalAccountEntity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function create($integration, $userId)
|
||||
{
|
||||
$authMethod = $this->getMetadata()->get("integrations.{$integration}.authMethod");
|
||||
$methodName = 'create' . ucfirst($authMethod);
|
||||
return $this->$methodName($integration, $userId);
|
||||
$authMethod = $this->getMetadata()->get("integrations.{$integration}.authMethod");
|
||||
$methodName = 'create' . ucfirst($authMethod);
|
||||
return $this->$methodName($integration, $userId);
|
||||
}
|
||||
|
||||
|
||||
protected function createOAuth2($integration, $userId)
|
||||
{
|
||||
{
|
||||
$integrationEntity = $this->getEntityManager()->getEntity('Integration', $integration);
|
||||
$externalAccountEntity = $this->getEntityManager()->getEntity('ExternalAccount', $integration . '__' . $userId);
|
||||
|
||||
|
||||
$className = $this->getMetadata()->get("integrations.{$integration}.clientClassName");
|
||||
|
||||
$redirectUri = $this->getConfig()->get('siteUrl') . '/oauthcallback'; // TODO move to client class
|
||||
|
||||
|
||||
$redirectUri = $this->getConfig()->get('siteUrl') . '?entryPoint=oauthCallback'; // TODO move to client class
|
||||
|
||||
if (!$externalAccountEntity) {
|
||||
throw new Error("External Account {$integration} not found for {$userId}");
|
||||
}
|
||||
|
||||
|
||||
if (!$integrationEntity->get('enabled')) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!$externalAccountEntity->get('enabled')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$oauth2Client = new \Espo\Core\ExternalAccount\OAuth2\Client();
|
||||
|
||||
}
|
||||
|
||||
$oauth2Client = new \Espo\Core\ExternalAccount\OAuth2\Client();
|
||||
|
||||
$client = new $className($oauth2Client, array(
|
||||
'endpoint' => $this->getMetadata()->get("integrations.{$integration}.params.endpoint"),
|
||||
'tokenEndpoint' => $this->getMetadata()->get("integrations.{$integration}.params.tokenEndpoint"),
|
||||
@@ -105,12 +105,12 @@ class ClientManager
|
||||
'refreshToken' => $externalAccountEntity->get('refreshToken'),
|
||||
'tokenType' => $externalAccountEntity->get('tokenType'),
|
||||
), $this);
|
||||
|
||||
|
||||
$this->addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId);
|
||||
|
||||
return $client;
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
|
||||
protected function addToClientMap($client, $integrationEntity, $externalAccountEntity, $userId)
|
||||
{
|
||||
$this->clientMap[spl_object_hash($client)] = array(
|
||||
|
||||
@@ -29,9 +29,9 @@ use \Espo\Core\ExternalAccount\OAuth2\Client;
|
||||
abstract class OAuth2Abstract implements IClient
|
||||
{
|
||||
protected $client = null;
|
||||
|
||||
|
||||
protected $manager = null;
|
||||
|
||||
|
||||
protected $paramList = array(
|
||||
'endpoint',
|
||||
'tokenEndpoint',
|
||||
@@ -42,33 +42,33 @@ abstract class OAuth2Abstract implements IClient
|
||||
'refreshToken',
|
||||
'redirectUri',
|
||||
);
|
||||
|
||||
|
||||
protected $clientId = null;
|
||||
|
||||
|
||||
protected $clientSecret = null;
|
||||
|
||||
|
||||
protected $accessToken = null;
|
||||
|
||||
|
||||
protected $refreshToken = null;
|
||||
|
||||
|
||||
protected $redirectUri = null;
|
||||
|
||||
|
||||
public function __construct($client, array $params = array(), $manager = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
|
||||
$this->setParams($params);
|
||||
|
||||
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
|
||||
public function getParam($name)
|
||||
{
|
||||
if (in_array($name, $this->paramList)) {
|
||||
return $this->$name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function setParam($name, $value)
|
||||
{
|
||||
if (in_array($name, $this->paramList)) {
|
||||
@@ -79,7 +79,7 @@ abstract class OAuth2Abstract implements IClient
|
||||
$this->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function setParams(array $params)
|
||||
{
|
||||
foreach ($this->paramList as $name) {
|
||||
@@ -88,42 +88,41 @@ abstract class OAuth2Abstract implements IClient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function afterTokenRefreshed($data)
|
||||
{
|
||||
if ($this->manager) {
|
||||
$this->manager->storeAccessToken(spl_object_hash($this), $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getAccessTokenFromAuthorizationCode($code)
|
||||
{
|
||||
$r = $this->client->getAccessToken($this->getParam('tokenEndpoint'), Client::GRANT_TYPE_AUTHORIZATION_CODE, array(
|
||||
'code' => $code,
|
||||
'redirect_uri' => $this->getParam('redirectUri')
|
||||
));
|
||||
|
||||
|
||||
|
||||
if ($r['code'] == 200) {
|
||||
$data = array();
|
||||
if (!empty($r['result'])) {
|
||||
$data['accessToken'] = $r['result']['access_token'];
|
||||
$data['tokenType'] = $r['result']['token_type'];
|
||||
$data['refreshToken'] = $r['result']['refresh_token'];
|
||||
$data['refreshToken'] = $r['result']['refresh_token'];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
abstract protected function getPingUrl();
|
||||
|
||||
|
||||
public function ping()
|
||||
{
|
||||
if (empty($this->accessToken) || empty($this->clientId) || empty($this->clientSecret)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$url = $this->getPingUrl();
|
||||
|
||||
try {
|
||||
@@ -133,35 +132,48 @@ abstract class OAuth2Abstract implements IClient
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function request($url, $params = array(), $httpMethod = Client::HTTP_METHOD_GET, $allowRenew = true)
|
||||
|
||||
public function request($url, $params = null, $httpMethod = Client::HTTP_METHOD_GET, $contentType = null, $allowRenew = true)
|
||||
{
|
||||
$r = $this->client->request($url, $params, $httpMethod);
|
||||
|
||||
$httpHeaders = array();
|
||||
if (!empty($contentType)) {
|
||||
$httpHeaders['Content-Type'] = $contentType;
|
||||
switch ($contentType) {
|
||||
case Client::CONTENT_TYPE_MULTIPART_FORM_DATA:
|
||||
$httpHeaders['Content-Length'] = strlen($params);
|
||||
break;
|
||||
case Client::CONTENT_TYPE_APPLICATION_JSON:
|
||||
$httpHeaders['Content-Length'] = strlen($params);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$r = $this->client->request($url, $params, $httpMethod, $httpHeaders);
|
||||
|
||||
$code = null;
|
||||
if (!empty($r['code'])) {
|
||||
$code = $r['code'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($code == 200) {
|
||||
return $r['result'];
|
||||
} else {
|
||||
$handledData = $this->handleErrorResponse($r);
|
||||
|
||||
|
||||
if ($allowRenew && is_array($handledData)) {
|
||||
if ($handledData['action'] == 'refreshToken') {
|
||||
if ($this->refreshToken()) {
|
||||
return $this->request($url, $params, $httpMethod, false);
|
||||
if ($this->refreshToken()) {
|
||||
return $this->request($url, $params, $httpMethod, $contentType, false);
|
||||
}
|
||||
} else if ($handledData['action'] == 'renew') {
|
||||
return $this->request($url, $params, $httpMethod, false);
|
||||
return $this->request($url, $params, $httpMethod, $contentType, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw new Error("Error after requesting {$httpMethod} {$url}.", $code);
|
||||
}
|
||||
|
||||
|
||||
protected function refreshToken()
|
||||
{
|
||||
if (!empty($this->refreshToken)) {
|
||||
@@ -183,11 +195,11 @@ abstract class OAuth2Abstract implements IClient
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function handleErrorResponse($r)
|
||||
{
|
||||
if ($r['code'] == 401 && !empty($r['result'])) {
|
||||
$result = $r['result'];
|
||||
$result = $r['result'];
|
||||
if (strpos($r['header'], 'error=invalid_token') !== false) {
|
||||
return array(
|
||||
'action' => 'refreshToken'
|
||||
@@ -198,7 +210,7 @@ abstract class OAuth2Abstract implements IClient
|
||||
);
|
||||
}
|
||||
} else if ($r['code'] == 400 && !empty($r['result'])) {
|
||||
if ($r['result']['error'] == 'invalid_token') {
|
||||
if ($r['result']['error'] == 'invalid_token') {
|
||||
return array(
|
||||
'action' => 'refreshToken'
|
||||
);
|
||||
|
||||
@@ -32,8 +32,9 @@ class Client
|
||||
const TOKEN_TYPE_BEARER = 'Bearer';
|
||||
const TOKEN_TYPE_OAUTH = 'OAuth';
|
||||
|
||||
const CONTENT_TYPE_APPLICATION = 0;
|
||||
const CONTENT_TYPE_MULTIPART = 1;
|
||||
const CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENENCODED = 'application/x-www-form-urlencoded';
|
||||
const CONTENT_TYPE_MULTIPART_FORM_DATA = 'multipart/form-data';
|
||||
const CONTENT_TYPE_APPLICATION_JSON = 'application/json';
|
||||
|
||||
const HTTP_METHOD_GET = 'GET';
|
||||
const HTTP_METHOD_POST = 'POST';
|
||||
@@ -118,7 +119,7 @@ class Client
|
||||
$this->accessTokenSecret = $accessTokenSecret;
|
||||
}
|
||||
|
||||
public function request($url, $params = array(), $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = array(), $contentType = self::CONTENT_TYPE_MULTIPART)
|
||||
public function request($url, $params = null, $httpMethod = self::HTTP_METHOD_GET, array $httpHeaders = array())
|
||||
{
|
||||
if ($this->accessToken) {
|
||||
switch ($this->tokenType) {
|
||||
@@ -137,10 +138,10 @@ class Client
|
||||
}
|
||||
}
|
||||
|
||||
return $this->execute($url, $params, $httpMethod, $httpHeaders, $contentType);
|
||||
return $this->execute($url, $params, $httpMethod, $httpHeaders);
|
||||
}
|
||||
|
||||
private function execute($url, $params = array(), $httpMethod, array $httpHeaders = array(), $contentType = self::CONTENT_TYPE_MULTIPART)
|
||||
private function execute($url, $params = null, $httpMethod, array $httpHeaders = array())
|
||||
{
|
||||
$curlOptions = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
@@ -153,8 +154,10 @@ class Client
|
||||
$curlOptions[CURLOPT_POST] = true;
|
||||
case self::HTTP_METHOD_PUT:
|
||||
case self::HTTP_METHOD_PATCH:
|
||||
if (self::CONTENT_TYPE_APPLICATION === $contentType) {
|
||||
if (is_array($params)) {
|
||||
$postFields = http_build_query($params, null, '&');
|
||||
} else {
|
||||
$postFields = $params;
|
||||
}
|
||||
$curlOptions[CURLOPT_POSTFIELDS] = $postFields;
|
||||
break;
|
||||
@@ -165,7 +168,9 @@ class Client
|
||||
if (strpos($url, '?') === false) {
|
||||
$url .= '?';
|
||||
}
|
||||
$url .= http_build_query($params, null, '&');
|
||||
if (is_array($params)) {
|
||||
$url .= http_build_query($params, null, '&');
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -242,7 +247,7 @@ class Client
|
||||
throw new \Exception();
|
||||
}
|
||||
|
||||
return $this->execute($url, $params, self::HTTP_METHOD_POST, $httpHeaders, self::CONTENT_TYPE_APPLICATION);
|
||||
return $this->execute($url, $params, self::HTTP_METHOD_POST, $httpHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class HookManager
|
||||
protected function loadHooks()
|
||||
{
|
||||
if ($this->getConfig()->get('useCache') && file_exists($this->cacheFile)) {
|
||||
$this->data = $this->getFileManager()->getContents($this->cacheFile);
|
||||
$this->data = $this->getFileManager()->getPhpContents($this->cacheFile);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class HookManager
|
||||
$this->data = array_merge_recursive($this->data, $this->getHookData($this->paths['customPath']));
|
||||
|
||||
if ($this->getConfig()->get('useCache')) {
|
||||
$this->getFileManager()->putContentsPHP($this->cacheFile, $this->data);
|
||||
$this->getFileManager()->putPhpContents($this->cacheFile, $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,9 @@ class HookManager
|
||||
$sortedHookList = array_merge($sortedHookList, $hookDetails);
|
||||
}
|
||||
|
||||
$hooks[$scopeName][$hookName] = isset($hooks[$scopeName][$hookName]) ? array_merge($hooks[$scopeName][$hookName], $sortedHookList) : $sortedHookList;
|
||||
$normalizedScopeName = Util::normilizeScopeName($scopeName);
|
||||
|
||||
$hooks[$normalizedScopeName][$hookName] = isset($hooks[$normalizedScopeName][$hookName]) ? array_merge($hooks[$normalizedScopeName][$hookName], $sortedHookList) : $sortedHookList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ namespace Espo\Core\Jobs;
|
||||
|
||||
use \Espo\Core\Container;
|
||||
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
private $container;
|
||||
|
||||
+7
-21
@@ -22,31 +22,17 @@
|
||||
|
||||
namespace Espo\Core\Loaders;
|
||||
|
||||
use Espo\Core\Utils,
|
||||
Espo\Core\Utils\Log\Monolog\Handler;
|
||||
|
||||
class Log extends Base
|
||||
class EntityManagerUtil extends Base
|
||||
{
|
||||
public function load()
|
||||
{
|
||||
$logConfig = $this->getContainer()->get('config')->get('logger');
|
||||
$entityManager = new \Espo\Core\Utils\EntityManager(
|
||||
$this->getContainer()->get('metadata'),
|
||||
$this->getContainer()->get('language'),
|
||||
$this->getContainer()->get('fileManager')
|
||||
);
|
||||
|
||||
$log = new Utils\Log('Espo');
|
||||
|
||||
$levelCode = $log->getLevelCode($logConfig['level']);
|
||||
|
||||
if ($logConfig['isRotate']) {
|
||||
$handler = new Handler\RotatingFileHandler($logConfig['path'], $logConfig['maxRotateFiles'], $levelCode);
|
||||
} else {
|
||||
$handler = new Handler\StreamHandler($logConfig['path'], $levelCode);
|
||||
}
|
||||
$log->pushHandler($handler);
|
||||
|
||||
$errorHandler = new \Monolog\ErrorHandler($log);
|
||||
$errorHandler->registerExceptionHandler(null, false);
|
||||
$errorHandler->registerErrorHandler(array(), false);
|
||||
|
||||
return $log;
|
||||
return $entityManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
@@ -24,21 +24,23 @@ namespace Espo\Core\Mail;
|
||||
|
||||
use \Zend\Mime\Mime as Mime;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
|
||||
class Importer
|
||||
{
|
||||
private $entityManager;
|
||||
|
||||
|
||||
private $fileManager;
|
||||
|
||||
private $config;
|
||||
|
||||
|
||||
public function __construct($entityManager, $fileManager, $config)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
$this->fileManager = $fileManager;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->entityManager;
|
||||
@@ -47,44 +49,48 @@ class Importer
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
|
||||
protected function getFileManager()
|
||||
{
|
||||
return $this->fileManager;
|
||||
}
|
||||
|
||||
|
||||
public function importMessage($message, $userId, $teamsIds = array())
|
||||
{
|
||||
try {
|
||||
$email = $this->getEntityManager()->getEntity('Email');
|
||||
|
||||
|
||||
$subject = $message->subject;
|
||||
if ($subject !== '0' && empty($subject)) {
|
||||
$subject = '--empty--';
|
||||
}
|
||||
|
||||
|
||||
$email->set('isHtml', false);
|
||||
$email->set('name', $subject);
|
||||
$email->set('status', 'Archived');
|
||||
$email->set('attachmentsIds', array());
|
||||
$email->set('assignedUserId', $userId);
|
||||
$email->set('teamsIds', $teamsIds);
|
||||
|
||||
|
||||
$fromArr = $this->getAddressListFromMessage($message, 'from');
|
||||
if (isset($message->from)) {
|
||||
$email->set('fromName', $message->from);
|
||||
}
|
||||
|
||||
$toArr = $this->getAddressListFromMessage($message, 'to');
|
||||
$ccArr = $this->getAddressListFromMessage($message, 'cc');
|
||||
|
||||
$email->set('from', $fromArr[0]);
|
||||
$email->set('to', implode(';', $this->getAddressListFromMessage($message, 'to')));
|
||||
$email->set('cc', implode(';', $this->getAddressListFromMessage($message, 'cc')));
|
||||
|
||||
$email->set('to', implode(';', $toArr));
|
||||
$email->set('cc', implode(';', $ccArr));
|
||||
|
||||
if (isset($message->messageId) && !empty($message->messageId)) {
|
||||
$email->set('messageId', $message->messageId);
|
||||
if (isset($message->deliveredTo)) {
|
||||
$email->set('messageIdInternal', $message->messageId . '-' . $message->deliveredTo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->checkIsDuplicate($email)) {
|
||||
return false;
|
||||
}
|
||||
@@ -103,9 +109,9 @@ class Importer
|
||||
$email->set('deliveryDate', $deliveryDate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$inlineIds = array();
|
||||
|
||||
|
||||
if ($message->isMultipart()) {
|
||||
foreach (new \RecursiveIteratorIterator($message) as $part) {
|
||||
$this->importPartDataToEmail($email, $part, $inlineIds);
|
||||
@@ -113,7 +119,7 @@ class Importer
|
||||
} else {
|
||||
$this->importPartDataToEmail($email, $message, $inlineIds);
|
||||
}
|
||||
|
||||
|
||||
$body = $email->get('body');
|
||||
if (!empty($body)) {
|
||||
foreach ($inlineIds as $cid => $attachmentId) {
|
||||
@@ -122,6 +128,8 @@ class Importer
|
||||
$email->set('body', $body);
|
||||
}
|
||||
|
||||
$parentFound = false;
|
||||
|
||||
if (isset($message->references) && !empty($message->references)) {
|
||||
$reference = str_replace(array('/', '@'), " ", trim($message->references, '<>'));
|
||||
$parentType = $parentId = null;
|
||||
@@ -131,54 +139,76 @@ class Importer
|
||||
if (!empty($parentType) && !empty($parentId)) {
|
||||
$email->set('parentType', $parentType);
|
||||
$email->set('parentId', $parentId);
|
||||
$parentFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$email->has('parentId')) {
|
||||
if (!$parentFound) {
|
||||
$from = $email->get('from');
|
||||
if ($from) {
|
||||
$contact = $this->getEntityManager()->getRepository('Contact')->where(array(
|
||||
'emailAddress' => $from
|
||||
))->findOne();
|
||||
if ($contact) {
|
||||
if (!$this->getConfig()->get('b2cMode')) {
|
||||
if ($contact->get('accountId')) {
|
||||
$email->set('parentType', 'Account');
|
||||
$email->set('parentId', $contact->get('accountId'));
|
||||
}
|
||||
} else {
|
||||
$email->set('parentType', 'Contact');
|
||||
$email->set('parentId', $contact->id);
|
||||
}
|
||||
$parentFound = $this->findParent($email, $from);
|
||||
}
|
||||
if (!$parentFound) {
|
||||
if (!empty($toArr)) {
|
||||
$parentFound = $this->findParent($email, $toArr[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getEntityManager()->saveEntity($email);
|
||||
|
||||
|
||||
return $email;
|
||||
|
||||
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
protected function checkIsDuplicate($email)
|
||||
|
||||
protected function findParent(Entity $email, $emailAddress)
|
||||
{
|
||||
if ($email->get('messageIdInternal')) {
|
||||
$contact = $this->getEntityManager()->getRepository('Contact')->where(array(
|
||||
'emailAddress' => $emailAddress
|
||||
))->findOne();
|
||||
if ($contact) {
|
||||
if (!$this->getConfig()->get('b2cMode')) {
|
||||
if ($contact->get('accountId')) {
|
||||
$email->set('parentType', 'Account');
|
||||
$email->set('parentId', $contact->get('accountId'));
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$email->set('parentType', 'Contact');
|
||||
$email->set('parentId', $contact->id);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$account = $this->getEntityManager()->getRepository('Account')->where(array(
|
||||
'emailAddress' => $emailAddress
|
||||
))->findOne();
|
||||
if ($account) {
|
||||
$email->set('parentType', 'Account');
|
||||
$email->set('parentId', $account->id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkIsDuplicate(Entity $email)
|
||||
{
|
||||
if ($email->get('messageId')) {
|
||||
$duplicate = $this->getEntityManager()->getRepository('Email')->where(array(
|
||||
'messageIdInternal' => $email->get('messageIdInternal')
|
||||
'messageId' => $email->get('messageId')
|
||||
))->findOne();
|
||||
if ($duplicate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function getAddressListFromMessage($message, $type)
|
||||
{
|
||||
$addressList = array();
|
||||
if (isset($message->$type)) {
|
||||
|
||||
|
||||
$list = $message->getHeader($type)->getAddressList();
|
||||
foreach ($list as $address) {
|
||||
$addressList[] = $address->getEmail();
|
||||
@@ -186,82 +216,97 @@ class Importer
|
||||
}
|
||||
return $addressList;
|
||||
}
|
||||
|
||||
|
||||
protected function importPartDataToEmail(\Espo\Entities\Email $email, $part, &$inlineIds = array())
|
||||
{
|
||||
try {
|
||||
if (!$part->getHeaders() || !isset($part->contentType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = strtok($part->contentType, ';');
|
||||
$encoding = null;
|
||||
|
||||
switch ($type) {
|
||||
case 'text/plain':
|
||||
$content = $this->getContentFromPart($part);
|
||||
if (!$email->get('body')) {
|
||||
$email->set('body', $content);
|
||||
}
|
||||
$email->set('bodyPlain', $content);
|
||||
break;
|
||||
case 'text/html':
|
||||
$content = $this->getContentFromPart($part);
|
||||
$email->set('body', $content);
|
||||
$email->set('isHtml', true);
|
||||
break;
|
||||
default:
|
||||
$content = $part->getContent();
|
||||
$disposition = null;
|
||||
|
||||
$fileName = null;
|
||||
$contentId = null;
|
||||
|
||||
if (isset($part->ContentDisposition)) {
|
||||
if (strpos($part->ContentDisposition, 'attachment') === 0) {
|
||||
if (preg_match('/filename="?([^"]+)"?/i', $part->ContentDisposition, $m)) {
|
||||
$fileName = $m[1];
|
||||
$disposition = 'attachment';
|
||||
}
|
||||
} else if (strpos($part->ContentDisposition, 'inline') === 0) {
|
||||
$contentId = trim($part->contentID, '<>');
|
||||
$fileName = $contentId;
|
||||
$disposition = 'inline';
|
||||
|
||||
$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 (isset($part->contentTransferEncoding)) {
|
||||
$encoding = strtolower($part->getHeader('Content-Transfer-Encoding')->getTransferEncoding());
|
||||
}
|
||||
|
||||
$attachment = $this->getEntityManager()->getEntity('Attachment');
|
||||
$attachment->set('name', $fileName);
|
||||
$attachment->set('type', $type);
|
||||
|
||||
if ($disposition == 'inline') {
|
||||
$attachment->set('role', 'Inline Attachment');
|
||||
} else if ($type == 'text/html') {
|
||||
if ($email->get('isHtml')) {
|
||||
$isAttachment = true;
|
||||
} else {
|
||||
$attachment->set('role', 'Attachment');
|
||||
$email->set('body', $content);
|
||||
$email->set('isHtml', true);
|
||||
}
|
||||
|
||||
if ($encoding == 'base64') {
|
||||
$content = base64_decode($content);
|
||||
}
|
||||
|
||||
$attachment->set('size', strlen($content));
|
||||
|
||||
$this->getEntityManager()->saveEntity($attachment);
|
||||
|
||||
$path = 'data/upload/' . $attachment->id;
|
||||
$this->getFileManager()->putContents($path, $content);
|
||||
|
||||
if ($disposition == 'attachment') {
|
||||
$attachmentsIds = $email->get('attachmentsIds');
|
||||
$attachmentsIds[] = $attachment->id;
|
||||
$email->set('attachmentsIds', $attachmentsIds);
|
||||
} else if ($disposition == 'inline') {
|
||||
$inlineIds[$contentId] = $attachment->id;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isAttachment) {
|
||||
$content = $part->getContent();
|
||||
$disposition = null;
|
||||
|
||||
$fileName = null;
|
||||
$contentId = null;
|
||||
|
||||
if (isset($part->ContentDisposition)) {
|
||||
if (strpos($part->ContentDisposition, 'attachment') === 0) {
|
||||
if (preg_match('/filename="?([^"]+)"?/i', $part->ContentDisposition, $m)) {
|
||||
$fileName = $m[1];
|
||||
$disposition = 'attachment';
|
||||
}
|
||||
} else if (strpos($part->ContentDisposition, 'inline') === 0) {
|
||||
$contentId = trim($part->contentID, '<>');
|
||||
$fileName = $contentId;
|
||||
$disposition = 'inline';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($part->contentTransferEncoding)) {
|
||||
$encoding = strtolower($part->getHeader('Content-Transfer-Encoding')->getTransferEncoding());
|
||||
}
|
||||
|
||||
$attachment = $this->getEntityManager()->getEntity('Attachment');
|
||||
$attachment->set('name', $fileName);
|
||||
$attachment->set('type', $type);
|
||||
|
||||
if ($disposition == 'inline') {
|
||||
$attachment->set('role', 'Inline Attachment');
|
||||
} else {
|
||||
$attachment->set('role', 'Attachment');
|
||||
}
|
||||
|
||||
if ($encoding == 'base64') {
|
||||
$content = base64_decode($content);
|
||||
}
|
||||
|
||||
$attachment->set('size', strlen($content));
|
||||
|
||||
$this->getEntityManager()->saveEntity($attachment);
|
||||
|
||||
$path = 'data/upload/' . $attachment->id;
|
||||
$this->getFileManager()->putContents($path, $content);
|
||||
|
||||
if ($disposition == 'attachment') {
|
||||
$attachmentsIds = $email->get('attachmentsIds');
|
||||
$attachmentsIds[] = $attachment->id;
|
||||
$email->set('attachmentsIds', $attachmentsIds);
|
||||
} else if ($disposition == 'inline') {
|
||||
$inlineIds[$contentId] = $attachment->id;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
|
||||
protected function getContentFromPart($part)
|
||||
{
|
||||
if ($part instanceof \Zend\Mime\Part) {
|
||||
@@ -271,20 +316,20 @@ class Importer
|
||||
}
|
||||
} else {
|
||||
$content = $part->getContent();
|
||||
|
||||
|
||||
$encoding = null;
|
||||
|
||||
|
||||
if (isset($part->contentTransferEncoding)) {
|
||||
$cteHeader = $part->getHeader('Content-Transfer-Encoding');
|
||||
$encoding = strtolower($cteHeader->getTransferEncoding());
|
||||
}
|
||||
|
||||
|
||||
if ($encoding == 'base64') {
|
||||
$content = base64_decode($content);
|
||||
}
|
||||
|
||||
|
||||
$charset = 'UTF-8';
|
||||
|
||||
|
||||
if (isset($part->contentType)) {
|
||||
$ctHeader = $part->getHeader('Content-Type');
|
||||
$charsetParamValue = $ctHeader->getParameter('charset');
|
||||
@@ -292,17 +337,17 @@ class Importer
|
||||
$charset = strtoupper($charsetParamValue);
|
||||
}
|
||||
}
|
||||
|
||||
if ($charset !== 'UTF-8') {
|
||||
$content = mb_convert_encoding($content, 'UTF-8', $charset);
|
||||
}
|
||||
|
||||
|
||||
if (isset($part->contentTransferEncoding)) {
|
||||
$cteHeader = $part->getHeader('Content-Transfer-Encoding');
|
||||
if ($cteHeader->getTransferEncoding() == 'quoted-printable') {
|
||||
$content = quoted_printable_decode($content);
|
||||
}
|
||||
}
|
||||
|
||||
if ($charset !== 'UTF-8') {
|
||||
$content = mb_convert_encoding($content, 'UTF-8', $charset);
|
||||
}
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
@@ -23,17 +23,19 @@
|
||||
namespace Espo\Core\Mail\Storage;
|
||||
|
||||
class Imap extends \Zend\Mail\Storage\Imap
|
||||
{
|
||||
{
|
||||
protected $messageClass = '\\Espo\\Core\\Mail\\Storage\\Message';
|
||||
|
||||
public function getIdsFromUID($uid)
|
||||
{
|
||||
$uid = intval($uid) + 1;
|
||||
return $this->protocol->search(array('UID ' . $uid . ':*'));
|
||||
}
|
||||
|
||||
|
||||
public function getIdsFromDate($date)
|
||||
{
|
||||
{
|
||||
return $this->protocol->search(array('SINCE "' . $date . '"'));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
+19
-37
@@ -20,48 +20,30 @@
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Cron;
|
||||
namespace Espo\Core\Mail\Storage;
|
||||
|
||||
use Espo\Core\Utils\Json,
|
||||
Espo\Core\Exceptions\NotFound;
|
||||
|
||||
class Service
|
||||
class Message extends \Zend\Mail\Storage\Message
|
||||
{
|
||||
private $serviceFactory;
|
||||
|
||||
public function __construct(\Espo\Core\ServiceFactory $serviceFactory)
|
||||
public function __isset($name)
|
||||
{
|
||||
$this->serviceFactory = $serviceFactory;
|
||||
$headers = $this->getHeaders();
|
||||
if (empty($headers) || !is_object($headers)) {
|
||||
return false;
|
||||
}
|
||||
return $this->getHeaders()->has($name);
|
||||
}
|
||||
|
||||
protected function getServiceFactory()
|
||||
public function isMultipart()
|
||||
{
|
||||
return $this->serviceFactory;
|
||||
if (!isset($this->contentType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return stripos($this->contentType, 'multipart/') === 0;
|
||||
} catch (Exception\ExceptionInterface $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function run($job)
|
||||
{
|
||||
$serviceName = $job['service_name'];
|
||||
|
||||
if (!$this->getServiceFactory()->checkExists($serviceName)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$service = $this->getServiceFactory()->create($serviceName);
|
||||
$serviceMethod = $job['method'];
|
||||
|
||||
if (!method_exists($service, $serviceMethod)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$data = $job['data'];
|
||||
if (Json::isJSON($data)) {
|
||||
$data = Json::decode($data, true);
|
||||
}
|
||||
|
||||
$service->$serviceMethod($data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,8 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
|
||||
protected $injections = array();
|
||||
|
||||
private $restoreData = null;
|
||||
|
||||
public function inject($name, $object)
|
||||
{
|
||||
$this->injections[$name] = $object;
|
||||
@@ -139,6 +141,14 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
{
|
||||
parent::beforeRemove($entity);
|
||||
$this->getEntityManager()->getHookManager()->process($this->entityName, 'beforeRemove', $entity);
|
||||
|
||||
$nowString = date('Y-m-d H:i:s', time());
|
||||
if ($entity->hasField('modifiedAt')) {
|
||||
$entity->set('modifiedAt', $nowString);
|
||||
}
|
||||
if ($entity->hasField('modifiedById')) {
|
||||
$entity->set('modifiedById', $this->getEntityManager()->getUser()->id);
|
||||
}
|
||||
}
|
||||
|
||||
protected function afterRemove(Entity $entity)
|
||||
@@ -161,12 +171,22 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
protected function beforeSave(Entity $entity)
|
||||
{
|
||||
parent::beforeSave($entity);
|
||||
|
||||
$this->getEntityManager()->getHookManager()->process($this->entityName, 'beforeSave', $entity);
|
||||
}
|
||||
|
||||
protected function afterSave(Entity $entity)
|
||||
{
|
||||
if (!empty($this->restoreData)) {
|
||||
$entity->set($this->restoreData);
|
||||
$this->restoreData = null;
|
||||
}
|
||||
parent::afterSave($entity);
|
||||
|
||||
$this->handleEmailAddressSave($entity);
|
||||
$this->handlePhoneNumberSave($entity);
|
||||
$this->handleSpecifiedRelations($entity);
|
||||
|
||||
$this->getEntityManager()->getHookManager()->process($this->entityName, 'afterSave', $entity);
|
||||
}
|
||||
|
||||
@@ -214,14 +234,10 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$entity->clear('createdById');
|
||||
$entity->clear('createdAt');
|
||||
}
|
||||
$this->restoreData = $restoreData;
|
||||
|
||||
$result = parent::save($entity);
|
||||
|
||||
$entity->set($restoreData);
|
||||
|
||||
$this->handleEmailAddressSave($entity);
|
||||
$this->handlePhoneNumberSave($entity);
|
||||
$this->handleSpecifiedRelations($entity);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -279,9 +295,18 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$data->$columnName = $foreignEntity->get($columnField);
|
||||
}
|
||||
$existingColumnsData->$foreignId = $data;
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($entity->has($fieldName)) {
|
||||
$entity->setFetched($fieldName, $existingIds);
|
||||
}
|
||||
if ($entity->has($columnsFieldsName) && !empty($columns)) {
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
}
|
||||
|
||||
foreach ($existingIds as $id) {
|
||||
if (!in_array($id, $specifiedIds)) {
|
||||
$toRemoveIds[] = $id;
|
||||
|
||||
@@ -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\Core;
|
||||
|
||||
@@ -29,11 +29,11 @@ use \Espo\Core\Utils\Util;
|
||||
class SelectManagerFactory
|
||||
{
|
||||
private $entityManager;
|
||||
|
||||
|
||||
private $user;
|
||||
|
||||
|
||||
private $acl;
|
||||
|
||||
|
||||
private $metadata;
|
||||
|
||||
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, $metadata)
|
||||
@@ -43,26 +43,28 @@ class SelectManagerFactory
|
||||
$this->acl = $acl;
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
|
||||
public function create($entityName)
|
||||
{
|
||||
$className = '\\Espo\\Custom\\SelectManagers\\' . Util::normilizeClassName($entityName);
|
||||
$normalizedName = Util::normilizeClassName($entityName);
|
||||
|
||||
$className = '\\Espo\\Custom\\SelectManagers\\' . $normalizedName;
|
||||
if (!class_exists($className)) {
|
||||
$moduleName = $this->metadata->getScopeModuleName($entityName);
|
||||
if ($moduleName) {
|
||||
$className = '\\Espo\\Modules\\' . $moduleName . '\\SelectManagers\\' . Util::normilizeClassName($entityName);
|
||||
$className = '\\Espo\\Modules\\' . $moduleName . '\\SelectManagers\\' . $normalizedName;
|
||||
} else {
|
||||
$className = '\\Espo\\SelectManagers\\' . Util::normilizeClassName($entityName);
|
||||
}
|
||||
$className = '\\Espo\\SelectManagers\\' . $normalizedName;
|
||||
}
|
||||
if (!class_exists($className)) {
|
||||
$className = '\\Espo\\Core\\SelectManagers\\Base';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$selectManager = new $className($this->entityManager, $this->user, $this->acl, $this->metadata);
|
||||
$selectManager->setEntityName($entityName);
|
||||
|
||||
|
||||
return $selectManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,20 @@ class Base
|
||||
$this->entityManager = $entityManager;
|
||||
$this->user = $user;
|
||||
$this->acl = $acl;
|
||||
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
protected function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setEntityName($entityName)
|
||||
{
|
||||
$this->entityName = $entityName;
|
||||
@@ -100,6 +111,28 @@ class Base
|
||||
return $this->seed;
|
||||
}
|
||||
|
||||
protected function textFilter($value, &$result)
|
||||
{
|
||||
$fieldDefs = $this->getSeed()->getFields();
|
||||
$fieldList = $this->getTextFilterFields();
|
||||
$d = array();
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if (
|
||||
strlen($item['value']) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH
|
||||
&&
|
||||
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
|
||||
) {
|
||||
$d[$field . '*'] = '%' . $value . '%';
|
||||
} else {
|
||||
$d[$field . '*'] = $value . '%';
|
||||
}
|
||||
}
|
||||
$result['whereClause'][] = array(
|
||||
'OR' => $d
|
||||
);
|
||||
}
|
||||
|
||||
protected function where($params, &$result)
|
||||
{
|
||||
if (!empty($params['where']) && is_array($params['where'])) {
|
||||
@@ -112,34 +145,18 @@ class Base
|
||||
if (!empty($p)) {
|
||||
$params['where'][] = $p;
|
||||
}
|
||||
$this->boolFilter($filter, $result);
|
||||
}
|
||||
} else if ($item['type'] == 'textFilter' && !empty($item['value'])) {
|
||||
if (!empty($item['value'])) {
|
||||
if (empty($result['whereClause'])) {
|
||||
$result['whereClause'] = array();
|
||||
}
|
||||
$fieldDefs = $this->getSeed()->getFields();
|
||||
$fieldList = $this->getTextFilterFields();
|
||||
$d = array();
|
||||
foreach ($fieldList as $field) {
|
||||
if (
|
||||
strlen($item['value']) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH
|
||||
&&
|
||||
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
|
||||
) {
|
||||
$d[$field . '*'] = '%' . $item['value'] . '%';
|
||||
} else {
|
||||
$d[$field . '*'] = $item['value'] . '%';
|
||||
}
|
||||
}
|
||||
$where['OR'] = $d;
|
||||
$this->textFilter($item['value'], $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$linkedWith = array();
|
||||
$ignoreList = array('linkedWith', 'boolFilters');
|
||||
foreach ($params['where'] as $item) {
|
||||
foreach ($params['where'] as $item) {
|
||||
if (!in_array($item['type'], $ignoreList)) {
|
||||
$part = $this->getWherePart($item);
|
||||
if (!empty($part)) {
|
||||
@@ -160,7 +177,6 @@ class Base
|
||||
if (is_array($idsValue) && count($idsValue) == 1) {
|
||||
$idsValue = $idsValue[0];
|
||||
}
|
||||
|
||||
|
||||
$relDefs = $this->getSeed()->getRelations();
|
||||
|
||||
@@ -180,8 +196,6 @@ class Base
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (!empty($part)) {
|
||||
@@ -192,17 +206,13 @@ class Base
|
||||
|
||||
}
|
||||
|
||||
$result['whereClause'] = $where;
|
||||
$result['whereClause'] = array_merge($result['whereClause'], $where);
|
||||
}
|
||||
}
|
||||
|
||||
protected function q($params, &$result)
|
||||
{
|
||||
if (!empty($params['q'])) {
|
||||
if (empty($result['whereClause'])) {
|
||||
$result['whereClause'] = array();
|
||||
}
|
||||
|
||||
$fieldDefs = $this->getSeed()->getFields();
|
||||
|
||||
$value = $params['q'];
|
||||
@@ -221,38 +231,49 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
$result['whereClause']['OR'] = $d;
|
||||
$result['whereClause'][] = array(
|
||||
'OR' => $d
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function access(&$result)
|
||||
{
|
||||
if ($this->acl->checkReadOnlyOwn($this->entityName)) {
|
||||
|
||||
if (!array_key_exists('whereClause', $result)) {
|
||||
$result['whereClause'] = array();
|
||||
}
|
||||
$result['whereClause']['assignedUserId'] = $this->user->id;
|
||||
$this->accessOnlyOwn($result);
|
||||
}
|
||||
if (!$this->user->isAdmin() && $this->acl->checkReadOnlyTeam($this->entityName)) {
|
||||
if (!array_key_exists('whereClause', $result)) {
|
||||
$result['whereClause'] = array();
|
||||
}
|
||||
$result['distinct'] = true;
|
||||
if (!array_key_exists('joins', $result)) {
|
||||
$result['joins'] = array();
|
||||
}
|
||||
if (!in_array('teams', $result['joins'])) {
|
||||
$result['leftJoins'][] = 'teams';
|
||||
}
|
||||
|
||||
$result['whereClause']['OR'] = array(
|
||||
'Team.id' => $this->user->get('teamsIds'),
|
||||
'assignedUserId' => $this->user->id
|
||||
);
|
||||
$this->accessOnlyTeam($result);
|
||||
}
|
||||
}
|
||||
|
||||
protected function accessOnlyOwn(&$result)
|
||||
{
|
||||
if (!$this->getSeed()->hasField('assignedUserId')) {
|
||||
return;
|
||||
}
|
||||
$result['whereClause'][] = array(
|
||||
'assignedUserId' => $this->getUser()->id
|
||||
);
|
||||
}
|
||||
|
||||
protected function accessOnlyTeam(&$result)
|
||||
{
|
||||
if (!$this->getSeed()->hasField('teamsIds')) {
|
||||
return;
|
||||
}
|
||||
$result['distinct'] = true;
|
||||
if (!in_array('teams', $result['joins'])) {
|
||||
$result['leftJoins'][] = 'teams';
|
||||
}
|
||||
$result['whereClause'][] = array(
|
||||
'OR' => array(
|
||||
'Team.id' => $this->user->get('teamsIds'),
|
||||
'assignedUserId' => $this->getUser()->id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function getAclParams()
|
||||
{
|
||||
$result = array();
|
||||
@@ -262,7 +283,11 @@ class Base
|
||||
|
||||
public function getSelectParams(array $params, $withAcl = false)
|
||||
{
|
||||
$result = array();
|
||||
$result = array(
|
||||
'joins' => array(),
|
||||
'leftJoins' => array(),
|
||||
'whereClause' => array()
|
||||
);
|
||||
|
||||
$this->order($params, $result);
|
||||
$this->limit($params, $result);
|
||||
@@ -408,6 +433,14 @@ class Base
|
||||
return $part;
|
||||
}
|
||||
|
||||
protected function boolFilter($filterName, &$result)
|
||||
{
|
||||
$method = 'boolFilter' . ucfirst($filterName);
|
||||
if (method_exists($this, $method)) {
|
||||
$this->$method($result);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getBoolFilterWhere($filterName)
|
||||
{
|
||||
$method = 'getBoolFilterWhere' . ucfirst($filterName);
|
||||
@@ -416,12 +449,10 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
protected function getBoolFilterWhereOnlyMy()
|
||||
protected function boolFilterOnlyMy(&$result)
|
||||
{
|
||||
return array(
|
||||
'type' => 'equals',
|
||||
'field' => 'assignedUserId',
|
||||
'value' => $this->user->id,
|
||||
$result['whereClause'][] = array(
|
||||
'assignedUserId' => $this->getUser()->id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,31 +48,6 @@ class ServiceFactory
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
$config = $this->getContainer()->get('config');
|
||||
|
||||
if (file_exists($this->cacheFile) && $config->get('useCache')) {
|
||||
$this->data = $this->getFileManager()->getContents($this->cacheFile);
|
||||
} else {
|
||||
$this->data = $this->getClassNameHash($this->paths['corePath']);
|
||||
|
||||
foreach ($this->getContainer()->get('metadata')->getModuleList() as $moduleName) {
|
||||
$path = str_replace('{*}', $moduleName, $this->paths['modulePath']);
|
||||
$this->data = array_merge($this->data, $this->getClassNameHash($path));
|
||||
}
|
||||
|
||||
$this->data = array_merge($this->data, $this->getClassNameHash($this->paths['customPath']));
|
||||
|
||||
if ($config->get('useCache')) {
|
||||
$result = $this->getFileManager()->putContentsPHP($this->cacheFile, $this->data);
|
||||
if ($result == false) {
|
||||
throw new \Espo\Core\Exceptions\Error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getFileManager()
|
||||
{
|
||||
return $this->container->get('fileManager');
|
||||
@@ -83,15 +58,19 @@ class ServiceFactory
|
||||
return $this->container;
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
$classParser = $this->getContainer()->get('classParser');
|
||||
$classParser->setAllowedMethods(null);
|
||||
$this->data = $classParser->getData($this->paths, $this->cacheFile);
|
||||
}
|
||||
|
||||
protected function getClassName($name)
|
||||
{
|
||||
$name = Util::normilizeClassName($name);
|
||||
|
||||
if (!isset($this->data)) {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
$name = ucfirst($name);
|
||||
if (isset($this->data[$name])) {
|
||||
return $this->data[$name];
|
||||
}
|
||||
@@ -127,28 +106,5 @@ class ServiceFactory
|
||||
}
|
||||
throw new Error("Class '$className' does not exist");
|
||||
}
|
||||
|
||||
// TODO delegate to another class
|
||||
protected function getClassNameHash($dirs)
|
||||
{
|
||||
if (is_string($dirs)) {
|
||||
$dirs = (array) $dirs;
|
||||
}
|
||||
|
||||
$data = array();
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($dir)) {
|
||||
$fileList = $this->getFileManager()->getFileList($dir, false, '\.php$', true);
|
||||
foreach ($fileList as $file) {
|
||||
$filePath = Util::concatPath($dir, $file);
|
||||
$className = Util::getClassName($filePath);
|
||||
$fileName = $this->getFileManager()->getFileName($filePath);
|
||||
$data[$fileName] = $className;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Templates\Controllers;
|
||||
|
||||
|
||||
class Base extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Templates\Controllers;
|
||||
|
||||
|
||||
class Person extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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\Core\Templates\Entities;
|
||||
|
||||
class Base 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\Core\Templates\Entities;
|
||||
|
||||
class Person extends \Espo\Core\Entities\Person
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"controller": "Controllers.Record",
|
||||
"boolFilters": ["onlyMy"]
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"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
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "link",
|
||||
"required": true
|
||||
},
|
||||
"teams": {
|
||||
"type": "linkMultiple"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"createdBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "EntityTeam"
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"sortBy": "createdAt",
|
||||
"asc": false
|
||||
},
|
||||
"indexes": {
|
||||
"name": {
|
||||
"columns": ["name", "deleted"]
|
||||
},
|
||||
"assignedUser": {
|
||||
"columns": ["assignedUserId", "deleted"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"controller": "Controllers.Record",
|
||||
"boolFilters": ["onlyMy"]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": {
|
||||
"type": "personName"
|
||||
},
|
||||
"salutationName": {
|
||||
"type": "enum",
|
||||
"options": ["", "Mr.", "Mrs.", "Dr."]
|
||||
},
|
||||
"firstName": {
|
||||
"type": "varchar",
|
||||
"maxLength": 100,
|
||||
"default": ""
|
||||
},
|
||||
"lastName": {
|
||||
"type": "varchar",
|
||||
"maxLength": 100,
|
||||
"required": true,
|
||||
"default": ""
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"emailAddress": {
|
||||
"type": "email"
|
||||
},
|
||||
"phoneNumber": {
|
||||
"type": "phone",
|
||||
"typeList": ["Mobile", "Office", "Home", "Fax", "Other"],
|
||||
"defaultType": "Mobile"
|
||||
},
|
||||
"address": {
|
||||
"type": "address"
|
||||
},
|
||||
"addressStreet": {
|
||||
"type": "text",
|
||||
"maxLength": 255,
|
||||
"dbType": "varchar"
|
||||
},
|
||||
"addressCity": {
|
||||
"type": "varchar"
|
||||
},
|
||||
"addressState": {
|
||||
"type": "varchar"
|
||||
},
|
||||
"addressCountry": {
|
||||
"type": "varchar"
|
||||
},
|
||||
"addressPostalCode": {
|
||||
"type": "varchar"
|
||||
},
|
||||
"modifiedAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "link",
|
||||
"readOnly": true
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "link",
|
||||
"readOnly": true
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "link",
|
||||
"required": true
|
||||
},
|
||||
"teams": {
|
||||
"type": "linkMultiple"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"createdBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"assignedUser": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "EntityTeam"
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
"sortBy": "createdAt",
|
||||
"asc": false
|
||||
},
|
||||
"indexes": {
|
||||
"firstName": {
|
||||
"columns": ["firstName", "deleted"]
|
||||
},
|
||||
"name": {
|
||||
"columns": ["firstName", "lastName"]
|
||||
},
|
||||
"assignedUser": {
|
||||
"columns": ["assignedUserId", "deleted"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Templates\Repositories;
|
||||
|
||||
|
||||
class Base extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Templates\Repositories;
|
||||
|
||||
|
||||
class Person extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Templates\Services;
|
||||
|
||||
|
||||
class Base extends \Espo\Services\Record
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?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\Templates\Services;
|
||||
|
||||
|
||||
class Person extends \Espo\Services\Record
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class UpgradeManager extends Upgrades\Base
|
||||
|
||||
protected $params = array(
|
||||
'packagePath' => 'data/upload/upgrades',
|
||||
'backupPath' => 'data/.backup/upgrades',
|
||||
|
||||
'scriptNames' => array(
|
||||
'before' => 'BeforeUpgrade',
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
|
||||
namespace Espo\Core\Upgrades\Actions;
|
||||
|
||||
use Espo\Core\Utils\Util,
|
||||
Espo\Core\Utils\Json,
|
||||
Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Core\Utils\System;
|
||||
use Espo\Core\Utils\Json;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use vierbergenlars\SemVer;
|
||||
|
||||
abstract class Base
|
||||
{
|
||||
@@ -179,46 +181,55 @@ abstract class Base
|
||||
*/
|
||||
protected function isAcceptable()
|
||||
{
|
||||
$manifest = $this->getManifest();
|
||||
|
||||
$res = $this->checkPackageType();
|
||||
$res &= $this->checkVersions();
|
||||
|
||||
//check php version
|
||||
if (isset($manifest['php'])) {
|
||||
$res &= $this->checkVersions($manifest['php'], System::getPhpVersion(), 'Your PHP version does not support this installation package.');
|
||||
}
|
||||
|
||||
//check acceptableVersions
|
||||
if (isset($manifest['acceptableVersions'])) {
|
||||
$res &= $this->checkVersions($manifest['acceptableVersions'], $this->getConfig()->get('version'), 'Your EspoCRM version doesn\'t match for this installation package.');
|
||||
}
|
||||
|
||||
return (bool) $res;
|
||||
}
|
||||
|
||||
protected function checkVersions()
|
||||
protected function checkVersions($versionList, $currentVersion, $errorMessage = '')
|
||||
{
|
||||
$manifest = $this->getManifest();
|
||||
|
||||
/** check acceptable versions */
|
||||
$version = $manifest['acceptableVersions'];
|
||||
if (empty($version)) {
|
||||
if (empty($versionList)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$currentVersion = $this->getConfig()->get('version');
|
||||
|
||||
if (is_string($version)) {
|
||||
$version = (array) $version;
|
||||
if (is_string($versionList)) {
|
||||
$versionList = (array) $versionList;
|
||||
}
|
||||
|
||||
foreach ($version as $strVersion) {
|
||||
try {
|
||||
$semver = new SemVer\version($currentVersion);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('Cannot recognize currentVersion ['.$currentVersion.'], error: '.$e->getMessage().'.');
|
||||
return;
|
||||
}
|
||||
|
||||
$strVersion = trim($strVersion);
|
||||
foreach ($versionList as $version) {
|
||||
|
||||
if ($strVersion == $currentVersion) {
|
||||
return true;
|
||||
$isInRange = false;
|
||||
try {
|
||||
$isInRange = $semver->satisfies(new SemVer\expression($version));
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('Version identification error: '.$e->getMessage().'.');
|
||||
}
|
||||
|
||||
$strVersion = str_replace('\\', '', $strVersion);
|
||||
$strVersion = preg_quote($strVersion);
|
||||
$strVersion = str_replace('\\*', '+', $strVersion);
|
||||
|
||||
if (preg_match('/^'.$strVersion.'/', $currentVersion)) {
|
||||
if ($isInRange) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->throwErrorAndRemovePackage('Your EspoCRM version doesn\'t match for this installation package.');
|
||||
$this->throwErrorAndRemovePackage($errorMessage);
|
||||
}
|
||||
|
||||
protected function checkPackageType()
|
||||
@@ -293,7 +304,7 @@ abstract class Base
|
||||
/**
|
||||
* Get a list of files defined in manifest.json
|
||||
*
|
||||
* @return [type] [description]
|
||||
* @return array
|
||||
*/
|
||||
protected function getDeleteFileList()
|
||||
{
|
||||
@@ -341,6 +352,18 @@ abstract class Base
|
||||
return $this->data['fileList'];
|
||||
}
|
||||
|
||||
protected function getRestoreFileList()
|
||||
{
|
||||
if (!isset($this->data['restoreFileList'])) {
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
$backupFilePath = Util::concatPath($backupPath, self::FILES);
|
||||
|
||||
$this->data['restoreFileList'] = $this->getFileManager()->getFileList($backupFilePath, true, '', true, true);
|
||||
}
|
||||
|
||||
return $this->data['restoreFileList'];
|
||||
}
|
||||
|
||||
protected function copy($sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false)
|
||||
{
|
||||
try {
|
||||
@@ -467,18 +490,31 @@ abstract class Base
|
||||
/**
|
||||
* Execute an action. For ex., execute uninstall action in install
|
||||
*
|
||||
* @param [type] $actionName [description]
|
||||
* @param [type] $data [description]
|
||||
* @return [type] [description]
|
||||
* @param string $actionName
|
||||
* @param string $data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function executeAction($actionName, $data)
|
||||
{
|
||||
$currentAction = $this->getActionManager()->getAction();
|
||||
$actionManager = $this->getActionManager();
|
||||
|
||||
$this->getActionManager()->setAction($actionName);
|
||||
$this->getActionManager()->run($data);
|
||||
$currentAction = $actionManager->getAction();
|
||||
|
||||
$actionManager->setAction($actionName);
|
||||
$actionManager->run($data);
|
||||
|
||||
$actionManager->setAction($currentAction);
|
||||
}
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected function finalize()
|
||||
{
|
||||
|
||||
$this->getActionManager()->setAction($currentAction);
|
||||
}
|
||||
|
||||
protected function beforeRunAction()
|
||||
@@ -491,5 +527,27 @@ abstract class Base
|
||||
|
||||
}
|
||||
|
||||
protected function clearCache()
|
||||
{
|
||||
return $this->getContainer()->get('dataManager')->clearCache();
|
||||
}
|
||||
|
||||
}
|
||||
protected function checkIsWritable()
|
||||
{
|
||||
$fullFileList = array_merge($this->getDeleteFileList(), $this->getCopyFileList());
|
||||
|
||||
$result = $this->getFileManager()->isWritableList($fullFileList);
|
||||
if (!$result) {
|
||||
$permissionDeniedList = $this->getFileManager()->getLastPermissionDeniedList();
|
||||
throw new Error("Permission denied in <br>". implode(", <br>", $permissionDeniedList));
|
||||
}
|
||||
}
|
||||
|
||||
protected function backupExistingFiles()
|
||||
{
|
||||
$fullFileList = array_merge($this->getDeleteFileList(), $this->getCopyFileList());
|
||||
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
return $this->copy('', array($backupPath, self::FILES), false, $fullFileList);
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,18 @@ namespace Espo\Core\Upgrades\Actions\Base;
|
||||
|
||||
class Delete extends \Espo\Core\Upgrades\Actions\Base
|
||||
{
|
||||
public function run($processId)
|
||||
public function run($data)
|
||||
{
|
||||
$processId = $data['id'];
|
||||
|
||||
$GLOBALS['log']->debug('Delete package process ['.$processId.']: start run.');
|
||||
|
||||
if (empty($processId)) {
|
||||
throw new Error('Delete package package ID was not specified.');
|
||||
}
|
||||
|
||||
$this->initialize();
|
||||
|
||||
$this->setProcessId($processId);
|
||||
|
||||
$this->beforeRunAction();
|
||||
@@ -41,6 +45,8 @@ class Delete extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$this->afterRunAction();
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$GLOBALS['log']->debug('Delete package process ['.$processId.']: end run.');
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
namespace Espo\Core\Upgrades\Actions\Base;
|
||||
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Utils\Util;
|
||||
|
||||
class Install extends \Espo\Core\Upgrades\Actions\Base
|
||||
{
|
||||
@@ -39,8 +40,10 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
|
||||
* @param string $processId Upgrade/Extension ID, gotten in upload stage
|
||||
* @return bool
|
||||
*/
|
||||
public function run($processId)
|
||||
public function run($data)
|
||||
{
|
||||
$processId = $data['id'];
|
||||
|
||||
$GLOBALS['log']->debug('Installation process ['.$processId.']: start run.');
|
||||
|
||||
if (empty($processId)) {
|
||||
@@ -49,6 +52,8 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$this->setProcessId($processId);
|
||||
|
||||
$this->initialize();
|
||||
|
||||
$this->isCopied = false;
|
||||
|
||||
/** check if an archive is unzipped, if no then unzip */
|
||||
@@ -58,22 +63,25 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
|
||||
$this->isAcceptable();
|
||||
}
|
||||
|
||||
//check permissions copied and deleted files
|
||||
$this->checkIsWritable();
|
||||
|
||||
$this->backupExistingFiles();
|
||||
|
||||
$this->beforeRunAction();
|
||||
|
||||
/* run before install script */
|
||||
$this->runScript('before');
|
||||
|
||||
/* remove files defined in a manifest */
|
||||
if (!$this->deleteFiles()) {
|
||||
$this->throwErrorAndRemovePackage('Permission denied to delete files.');
|
||||
}
|
||||
|
||||
/* copy files from directory "Files" to EspoCRM files */
|
||||
if (!$this->copyFiles()) {
|
||||
$this->throwErrorAndRemovePackage('Cannot copy files.');
|
||||
}
|
||||
$this->isCopied = true;
|
||||
|
||||
/* remove files defined in a manifest */
|
||||
$this->deleteFiles(true);
|
||||
|
||||
if (!$this->systemRebuild()) {
|
||||
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
|
||||
}
|
||||
@@ -83,23 +91,37 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$this->afterRunAction();
|
||||
|
||||
$this->clearCache();
|
||||
|
||||
/* delete unziped files */
|
||||
$this->deletePackageFiles();
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$GLOBALS['log']->debug('Installation process ['.$processId.']: end run.');
|
||||
}
|
||||
|
||||
protected function restoreFiles()
|
||||
{
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
|
||||
$res = true;
|
||||
if ($this->isCopied) {
|
||||
$res &= $this->copy(array($backupPath, self::FILES), '', true);
|
||||
$GLOBALS['log']->info('Restore: copy back');
|
||||
if (!$this->isCopied) {
|
||||
return;
|
||||
}
|
||||
|
||||
$res &= $this->getFileManager()->removeInDir($backupPath, true);
|
||||
$GLOBALS['log']->info('Installer: Restore previous files.');
|
||||
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
$backupFilePath = Util::concatPath($backupPath, self::FILES);
|
||||
|
||||
$backupFileList = $this->getRestoreFileList();
|
||||
$copyFileList = $this->getCopyFileList();
|
||||
$deleteFileList = array_diff($copyFileList, $backupFileList);
|
||||
|
||||
$res = $this->copy($backupFilePath, '', true);
|
||||
$res &= $this->getFileManager()->remove($deleteFileList, null, true);
|
||||
|
||||
if ($res) {
|
||||
$this->getFileManager()->removeInDir($backupPath, true);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
@@ -22,13 +22,16 @@
|
||||
|
||||
namespace Espo\Core\Upgrades\Actions\Base;
|
||||
|
||||
use Espo\Core\Exceptions\Error,
|
||||
Espo\Core\Utils\Util;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\Core\Utils\Json;
|
||||
|
||||
class Uninstall extends \Espo\Core\Upgrades\Actions\Base
|
||||
{
|
||||
public function run($processId)
|
||||
public function run($data)
|
||||
{
|
||||
$processId = $data['id'];
|
||||
|
||||
$GLOBALS['log']->debug('Uninstallation process ['.$processId.']: start run.');
|
||||
|
||||
if (empty($processId)) {
|
||||
@@ -37,44 +40,50 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$this->setProcessId($processId);
|
||||
|
||||
$this->initialize();
|
||||
|
||||
$this->checkIsWritable();
|
||||
|
||||
$this->beforeRunAction();
|
||||
|
||||
/* run before install script */
|
||||
$this->runScript('beforeUninstall');
|
||||
if (!isset($data['isNotRunScriptBefore']) || !$data['isNotRunScriptBefore']) {
|
||||
$this->runScript('beforeUninstall');
|
||||
}
|
||||
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
if (file_exists($backupPath)) {
|
||||
|
||||
/* remove extension files, saved in fileList */
|
||||
if (!$this->deleteFiles(true)) {
|
||||
throw new Error('Permission denied to delete files.');
|
||||
}
|
||||
|
||||
/* copy core files */
|
||||
if (!$this->copyFiles()) {
|
||||
throw new Error('Cannot copy files.');
|
||||
throw new $this->throwErrorAndRemovePackage('Cannot copy files.');
|
||||
}
|
||||
|
||||
/* remove extension files, saved in fileList */
|
||||
if (!$this->deleteFiles(true)) {
|
||||
throw new $this->throwErrorAndRemovePackage('Permission denied to delete files.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->systemRebuild()) {
|
||||
throw new Error('Error occurred while EspoCRM rebuild.');
|
||||
throw new $this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
|
||||
}
|
||||
|
||||
/* run before install script */
|
||||
$this->runScript('afterUninstall');
|
||||
/* run after uninstall script */
|
||||
if (!isset($data['isNotRunScriptAfter']) || !$data['isNotRunScriptAfter']) {
|
||||
$this->runScript('afterUninstall');
|
||||
}
|
||||
|
||||
$this->afterRunAction();
|
||||
|
||||
$this->clearCache();
|
||||
|
||||
/* delete backup files */
|
||||
$this->deletePackageFiles();
|
||||
|
||||
$GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.');
|
||||
}
|
||||
$this->finalize();
|
||||
|
||||
protected function getDeleteFileList()
|
||||
{
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
return $extensionEntity->get('fileList');
|
||||
$GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.');
|
||||
}
|
||||
|
||||
protected function restoreFiles()
|
||||
@@ -87,6 +96,13 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
|
||||
}
|
||||
|
||||
$res = $this->copy($filesPath, '', true);
|
||||
|
||||
$manifestJson = $this->getFileManager()->getContents(array($packagePath, $this->manifestName));
|
||||
$manifest = Json::decode($manifestJson, true);
|
||||
if (!empty($manifest['delete'])) {
|
||||
$res &= $this->getFileManager()->remove($manifest['delete'], null, true);
|
||||
}
|
||||
|
||||
$res &= $this->getFileManager()->removeInDir($packagePath, true);
|
||||
|
||||
return $res;
|
||||
@@ -129,4 +145,41 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
|
||||
throw new Error($errorMessage);
|
||||
}
|
||||
|
||||
protected function getCopyFileList()
|
||||
{
|
||||
if (!isset($this->data['fileList'])) {
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
$filesPath = Util::concatPath($backupPath, self::FILES);
|
||||
|
||||
$this->data['fileList'] = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
|
||||
}
|
||||
|
||||
return $this->data['fileList'];
|
||||
}
|
||||
|
||||
protected function getRestoreFileList()
|
||||
{
|
||||
if (!isset($this->data['restoreFileList'])) {
|
||||
$packagePath = $this->getPackagePath();
|
||||
$filesPath = Util::concatPath($packagePath, self::FILES);
|
||||
|
||||
if (!file_exists($filesPath)) {
|
||||
$this->unzipArchive($packagePath);
|
||||
}
|
||||
|
||||
$this->data['restoreFileList'] = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
|
||||
}
|
||||
|
||||
return $this->data['restoreFileList'];
|
||||
}
|
||||
|
||||
protected function getDeleteFileList()
|
||||
{
|
||||
$packageFileList = $this->getRestoreFileList();
|
||||
$backupFileList = $this->getCopyFileList();
|
||||
|
||||
$deleteFileList = array_diff($packageFileList, $backupFileList);
|
||||
|
||||
return $deleteFileList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,10 @@ class Upload extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$GLOBALS['log']->debug('Installation process ['.$processId.']: start upload the package.');
|
||||
|
||||
$this->initialize();
|
||||
|
||||
$this->beforeRunAction();
|
||||
|
||||
$packagePath = $this->getPackagePath();
|
||||
$packageArchivePath = $this->getPackagePath(true);
|
||||
|
||||
@@ -55,6 +59,10 @@ class Upload extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$this->isAcceptable();
|
||||
|
||||
$this->afterRunAction();
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$GLOBALS['log']->debug('Installation process ['.$processId.']: end upload the package.');
|
||||
|
||||
return $processId;
|
||||
|
||||
@@ -35,29 +35,15 @@ class Delete extends \Espo\Core\Upgrades\Actions\Base\Delete
|
||||
*/
|
||||
protected function getExtensionEntity()
|
||||
{
|
||||
return $this->extensionEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Extension Entity
|
||||
*
|
||||
* @param \Espo\Entities\Extension $extensionEntity
|
||||
*/
|
||||
protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity)
|
||||
{
|
||||
$this->extensionEntity = $extensionEntity;
|
||||
}
|
||||
|
||||
protected function beforeRunAction()
|
||||
{
|
||||
$processId = $this->getProcessId();
|
||||
|
||||
/** get extension entity */
|
||||
$extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
|
||||
if (!isset($extensionEntity)) {
|
||||
throw new Error('Extension Entity not found.');
|
||||
if (!isset($this->extensionEntity)) {
|
||||
$processId = $this->getProcessId();
|
||||
$this->extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
|
||||
if (!isset($this->extensionEntity)) {
|
||||
throw new Error('Extension Entity not found.');
|
||||
}
|
||||
}
|
||||
$this->setExtensionEntity($extensionEntity);
|
||||
|
||||
return $this->extensionEntity;
|
||||
}
|
||||
|
||||
protected function afterRunAction()
|
||||
|
||||
@@ -37,8 +37,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
$this->uninstallExtension();
|
||||
$this->deleteExtension();
|
||||
}
|
||||
|
||||
$this->copyExistingFiles();
|
||||
}
|
||||
|
||||
protected function afterRunAction()
|
||||
@@ -51,12 +49,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function copyExistingFiles()
|
||||
protected function backupExistingFiles()
|
||||
{
|
||||
$fileList = $this->getCopyFileList();
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
parent::backupExistingFiles();
|
||||
|
||||
$res = $this->copy('', array($backupPath, self::FILES), false, $fileList);
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
|
||||
/** copy scripts files */
|
||||
$packagePath = $this->getPackagePath();
|
||||
@@ -65,19 +62,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function restoreFiles()
|
||||
{
|
||||
$res = true;
|
||||
if ($this->isCopied) {
|
||||
$extensionFileList = $this->getCopyFileList();
|
||||
$res &= $this->getFileManager()->remove($extensionFileList);
|
||||
}
|
||||
|
||||
$res &= parent::restoreFiles();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function isNew()
|
||||
{
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
@@ -208,7 +192,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
{
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
|
||||
$this->executeAction(ExtensionManager::UNINSTALL, $extensionEntity->get('id'));
|
||||
$this->executeAction(ExtensionManager::UNINSTALL, array(
|
||||
'id' => $extensionEntity->get('id'),
|
||||
'isNotRunScriptAfter' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +208,7 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
{
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
|
||||
$this->executeAction(ExtensionManager::DELETE, $extensionEntity->get('id'));
|
||||
$this->executeAction(ExtensionManager::DELETE, array('id' => $extensionEntity->get('id')));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -35,29 +35,15 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall
|
||||
*/
|
||||
protected function getExtensionEntity()
|
||||
{
|
||||
return $this->extensionEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Extension Entity
|
||||
*
|
||||
* @param \Espo\Entities\Extension $extensionEntity [description]
|
||||
*/
|
||||
protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity)
|
||||
{
|
||||
$this->extensionEntity = $extensionEntity;
|
||||
}
|
||||
|
||||
protected function beforeRunAction()
|
||||
{
|
||||
$processId = $this->getProcessId();
|
||||
|
||||
/** get extension entity */
|
||||
$extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
|
||||
if (!isset($extensionEntity)) {
|
||||
throw new Error('Extension Entity not found.');
|
||||
if (!isset($this->extensionEntity)) {
|
||||
$processId = $this->getProcessId();
|
||||
$this->extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
|
||||
if (!isset($this->extensionEntity)) {
|
||||
throw new Error('Extension Entity not found.');
|
||||
}
|
||||
}
|
||||
$this->setExtensionEntity($extensionEntity);
|
||||
|
||||
return $this->extensionEntity;
|
||||
}
|
||||
|
||||
protected function afterRunAction()
|
||||
@@ -68,4 +54,14 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall
|
||||
$extensionEntity->set('isInstalled', false);
|
||||
$this->getEntityManager()->saveEntity($extensionEntity);
|
||||
}
|
||||
|
||||
protected function getRestoreFileList()
|
||||
{
|
||||
if (!isset($this->data['restoreFileList'])) {
|
||||
$extensionEntity = $this->getExtensionEntity();
|
||||
$this->data['restoreFileList'] = $extensionEntity->get('fileList');
|
||||
}
|
||||
|
||||
return $this->data['restoreFileList'];
|
||||
}
|
||||
}
|
||||
@@ -24,17 +24,12 @@ namespace Espo\Core\Upgrades\Actions\Upgrade;
|
||||
|
||||
class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
{
|
||||
protected function systemRebuild()
|
||||
protected function finalize()
|
||||
{
|
||||
$manifest = $this->getManifest();
|
||||
|
||||
$res = $this->getConfig()->set('version', $manifest['version']);
|
||||
if (method_exists($this->getConfig(), 'save')) {
|
||||
$res = $this->getConfig()->save();
|
||||
}
|
||||
$res &= parent::systemRebuild();
|
||||
|
||||
return $res;
|
||||
$this->getConfig()->set('version', $manifest['version']);
|
||||
$this->getConfig()->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
namespace Espo\Core\Utils;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
class Auth
|
||||
{
|
||||
@@ -72,6 +73,10 @@ class Auth
|
||||
$user = $this->authentication->login($username, $password, $authToken);
|
||||
|
||||
if ($user) {
|
||||
if (!$user->isActive()) {
|
||||
$GLOBALS['log']->debug("AUTH: Trying to login as user '".$user->get('userName')."' which is not active.");
|
||||
return false;
|
||||
}
|
||||
$entityManager->setUser($user);
|
||||
$this->container->setUser($user);
|
||||
$GLOBALS['log']->debug('AUTH: Result of authenticate is [true]');
|
||||
|
||||
@@ -82,14 +82,14 @@ class Autoload
|
||||
protected function init()
|
||||
{
|
||||
if (file_exists($this->cacheFile) && $this->getConfig()->get('useCache')) {
|
||||
$this->data = $this->getFileManager()->getContents($this->cacheFile);
|
||||
$this->data = $this->getFileManager()->getPhpContents($this->cacheFile);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data = $this->unify();
|
||||
|
||||
if ($this->getConfig()->get('useCache')) {
|
||||
$result = $this->getFileManager()->putContentsPHP($this->cacheFile, $this->data);
|
||||
$result = $this->getFileManager()->putPhpContents($this->cacheFile, $this->data);
|
||||
if ($result == false) {
|
||||
throw new \Espo\Core\Exceptions\Error('Autoload: Cannot save unified autoload.');
|
||||
}
|
||||
|
||||
@@ -150,7 +150,8 @@ class Config
|
||||
|
||||
$removeData = empty($this->removeData) ? null : $this->removeData;
|
||||
|
||||
$result = $this->getFileManager()->mergeContentsPHP($this->configPath, $values, $removeData);
|
||||
$result = $this->getFileManager()->mergePhpContents($this->configPath, $values, $removeData);
|
||||
|
||||
if ($result) {
|
||||
$this->changedData = array();
|
||||
$this->removeData = array();
|
||||
@@ -162,7 +163,7 @@ class Config
|
||||
|
||||
public function getDefaults()
|
||||
{
|
||||
return $this->getFileManager()->getContents($this->defaultConfigPath);
|
||||
return $this->getFileManager()->getPhpContents($this->defaultConfigPath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,9 +179,9 @@ class Config
|
||||
|
||||
$configPath = file_exists($this->configPath) ? $this->configPath : $this->defaultConfigPath;
|
||||
|
||||
$this->data = $this->getFileManager()->getContents($configPath);
|
||||
$this->data = $this->getFileManager()->getPhpContents($configPath);
|
||||
|
||||
$systemConfig = $this->getFileManager()->getContents($this->systemConfigPath);
|
||||
$systemConfig = $this->getFileManager()->getPhpContents($this->systemConfigPath);
|
||||
$this->data = Util::merge($systemConfig, $this->data);
|
||||
|
||||
return $this->data;
|
||||
@@ -261,7 +262,7 @@ class Config
|
||||
}
|
||||
|
||||
if (empty($this->adminItems)) {
|
||||
$this->adminItems= Util::merge($data['systemItems'], $data['adminItems']);
|
||||
$this->adminItems = array_merge($data['systemItems'], $data['adminItems']);
|
||||
}
|
||||
|
||||
return $this->adminItems;
|
||||
|
||||
@@ -20,13 +20,40 @@
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Services;
|
||||
namespace Espo\Core\Utils\Cron;
|
||||
|
||||
use \PDO;
|
||||
use \Espo\Core\CronManager;
|
||||
use \Espo\Core\Utils\Config;
|
||||
use \Espo\Core\ORM\EntityManager;
|
||||
|
||||
class Job extends Record
|
||||
class Job
|
||||
{
|
||||
private $config;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Pending Jobs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPendingJobs()
|
||||
{
|
||||
/** Mark Failed old jobs and remove pending duplicates */
|
||||
@@ -52,9 +79,10 @@ class Job extends Record
|
||||
*
|
||||
* @param string $displayColumns
|
||||
* @param string $status
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getActiveJobs($displayColumns = '*', $status = CronManager::PENDING, $fetchMode = PDO::FETCH_ASSOC)
|
||||
public function getActiveJobs($displayColumns = '*', $status = CronManager::PENDING, $fetchMode = PDO::FETCH_ASSOC)
|
||||
{
|
||||
$jobConfigs = $this->getConfig()->get('cron');
|
||||
|
||||
@@ -66,7 +94,7 @@ class Job extends Record
|
||||
`status` = '" . $status . "'
|
||||
AND execute_time BETWEEN '".date('Y-m-d H:i:s', $periodTime)."' AND '".date('Y-m-d H:i:s', $currentTime)."'
|
||||
AND deleted = 0
|
||||
ORDER BY execute_time DESC ".$limit;
|
||||
ORDER BY execute_time ASC ".$limit;
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sth = $pdo->prepare($query);
|
||||
@@ -77,6 +105,14 @@ class Job extends Record
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Jobs by ScheduledJobId and date
|
||||
*
|
||||
* @param string $scheduledJobId
|
||||
* @param string $date
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJobByScheduledJob($scheduledJobId, $date)
|
||||
{
|
||||
$query = "SELECT * FROM job WHERE
|
||||
@@ -146,7 +182,4 @@ class Job extends Record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+42
-20
@@ -18,41 +18,68 @@
|
||||
*
|
||||
* 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\Services;
|
||||
namespace Espo\Core\Utils\Cron;
|
||||
|
||||
use \PDO;
|
||||
use \Espo\Core\Utils\Config;
|
||||
use \Espo\Core\ORM\EntityManager;
|
||||
|
||||
class ScheduledJob extends \Espo\Services\Record
|
||||
{
|
||||
class ScheduledJob
|
||||
{
|
||||
private $config;
|
||||
|
||||
private $entityManager;
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active Scheduler Jobs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getActiveJobs()
|
||||
{
|
||||
$query = "SELECT * FROM scheduled_job WHERE
|
||||
`status` = 'Active'
|
||||
AND deleted = 0";
|
||||
AND deleted = 0";
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
|
||||
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
|
||||
$list = array();
|
||||
foreach ($rows as $row) {
|
||||
$list[] = $row;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add record to ScheduledJobLogRecord about executed job
|
||||
* @param string $scheduledJobId
|
||||
* @param string $status
|
||||
*
|
||||
* @return string Id of created ScheduledJobLogRecord
|
||||
* @param string $scheduledJobId
|
||||
* @param string $status
|
||||
*
|
||||
* @return string ID of created ScheduledJobLogRecord
|
||||
*/
|
||||
public function addLogRecord($scheduledJobId, $status)
|
||||
{
|
||||
@@ -72,12 +99,7 @@ class ScheduledJob extends \Espo\Services\Record
|
||||
'executionTime' => $lastRun,
|
||||
));
|
||||
$scheduledJobLogId = $entityManager->saveEntity($scheduledJobLog);
|
||||
//$entityManager->getRepository('ScheduledJobLogRecord')->relate($scheduledJobLog, 'scheduledJob', $scheduledJob);
|
||||
|
||||
return $scheduledJobLogId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $scheduledJobLogId;
|
||||
}
|
||||
}
|
||||
@@ -333,7 +333,7 @@ class Converter
|
||||
$fileList = $this->getFileManager()->getFileList($this->customTablePath, false, '\.php$', true);
|
||||
|
||||
foreach($fileList as $fileName) {
|
||||
$fileData = $this->getFileManager()->getContents( array($this->customTablePath, $fileName) );
|
||||
$fileData = $this->getFileManager()->getPhpContents( array($this->customTablePath, $fileName) );
|
||||
if (is_array($fileData)) {
|
||||
$customTables = Util::merge($customTables, $fileData);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
<?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;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\Conflict;
|
||||
use \Espo\Core\Utils\Json;
|
||||
|
||||
class EntityManager
|
||||
{
|
||||
private $metadata;
|
||||
|
||||
private $language;
|
||||
|
||||
private $fileManager;
|
||||
|
||||
private $metadataUtils;
|
||||
|
||||
public function __construct(Metadata $metadata, Language $language, File\Manager $fileManager)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->language = $language;
|
||||
$this->fileManager = $fileManager;
|
||||
|
||||
$this->metadataUtils = new \Espo\Core\Utils\Metadata\Utils($this->metadata);
|
||||
}
|
||||
|
||||
protected function getMetadata()
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
protected function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
protected function getFileManager()
|
||||
{
|
||||
return $this->fileManager;
|
||||
}
|
||||
|
||||
protected function getMetadataUtils()
|
||||
{
|
||||
return $this->metadataUtils;
|
||||
}
|
||||
|
||||
public function create($name, $type, $params = array())
|
||||
{
|
||||
if ($this->getMetadata()->get('scopes.' . $name)) {
|
||||
throw new Conflict('Entity ['.$name.'] already exists.');
|
||||
}
|
||||
if (empty($name) || empty($type)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$normalizedName = Util::normilizeClassName($name);
|
||||
|
||||
$contents = "<" . "?" . "php\n".
|
||||
"namespace Espo\Custom\Entities;\n".
|
||||
"class {$normalizedName} extends \Espo\Core\Templates\Entities\\{$type}\n".
|
||||
"{\n".
|
||||
"}\n";
|
||||
|
||||
$filePath = "custom/Espo/Custom/Entities/{$normalizedName}.php";
|
||||
$this->getFileManager()->putContents($filePath, $contents);
|
||||
|
||||
$contents = "<" . "?" . "php\n".
|
||||
"namespace Espo\Custom\Controllers;\n".
|
||||
"class {$normalizedName} extends \Espo\Core\Templates\Controllers\\{$type}\n".
|
||||
"{\n".
|
||||
"}\n";
|
||||
$filePath = "custom/Espo/Custom/Controllers/{$normalizedName}.php";
|
||||
$this->getFileManager()->putContents($filePath, $contents);
|
||||
|
||||
$contents = "<" . "?" . "php\n".
|
||||
"namespace Espo\Custom\Services;\n".
|
||||
"class {$normalizedName} extends \Espo\Core\Templates\Services\\{$type}\n".
|
||||
"{\n".
|
||||
"}\n";
|
||||
$filePath = "custom/Espo/Custom/Services/{$normalizedName}.php";
|
||||
$this->getFileManager()->putContents($filePath, $contents);
|
||||
|
||||
$contents = "<" . "?" . "php\n".
|
||||
"namespace Espo\Custom\Repositories;\n".
|
||||
"class {$normalizedName} extends \Espo\Core\Templates\Repositories\\{$type}\n".
|
||||
"{\n".
|
||||
"}\n";
|
||||
|
||||
$filePath = "custom/Espo/Custom/Repositories/{$normalizedName}.php";
|
||||
$this->getFileManager()->putContents($filePath, $contents);
|
||||
|
||||
$stream = false;
|
||||
if (!empty($params['stream'])) {
|
||||
$stream = $params['stream'];
|
||||
}
|
||||
$labelSingular = $name;
|
||||
if (!empty($params['labelSingular'])) {
|
||||
$labelSingular = $params['labelSingular'];
|
||||
}
|
||||
$labelPlural = $name;
|
||||
if (!empty($params['labelPlural'])) {
|
||||
$labelPlural = $params['labelPlural'];
|
||||
}
|
||||
$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
|
||||
);
|
||||
$this->getMetadata()->set('scopes', $name, $scopeData);
|
||||
|
||||
$filePath = "application/Espo/Core/Templates/Metadata/{$type}/entityDefs.json";
|
||||
$entityDefsData = Json::decode($this->getFileManager()->getContents($filePath), true);
|
||||
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
|
||||
|
||||
$filePath = "application/Espo/Core/Templates/Metadata/{$type}/clientDefs.json";
|
||||
$clientDefsData = Json::decode($this->getFileManager()->getContents($filePath), true);
|
||||
$this->getMetadata()->set('clientDefs', $name, $clientDefsData);
|
||||
|
||||
$this->getLanguage()->set('Global', 'scopeNames', $name, $labelSingular);
|
||||
$this->getLanguage()->set('Global', 'scopeNamesPlural', $name, $labelPlural);
|
||||
$this->getLanguage()->set($name, 'labels', 'Create ' . $name, $labelCreate);
|
||||
|
||||
$this->getMetadata()->save();
|
||||
$this->getLanguage()->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function update($name, $data)
|
||||
{
|
||||
if (!$this->getMetadata()->get('scopes.' . $name)) {
|
||||
throw new Error('Entity ['.$name.'] does not exist.');
|
||||
}
|
||||
|
||||
if (isset($data['stream'])) {
|
||||
$scopeData = array(
|
||||
'stream' => (true == $data['stream'])
|
||||
);
|
||||
$this->getMetadata()->set('scopes', $name, $scopeData);
|
||||
}
|
||||
|
||||
if (!empty($data['labelSingular'])) {
|
||||
$labelSingular = $data['labelSingular'];
|
||||
$this->getLanguage()->set('Global', 'scopeNames', $name, $labelSingular);
|
||||
$labelCreate = $this->getLanguage()->translate('Create') . ' ' . $labelSingular;
|
||||
$this->getLanguage()->set($name, 'labels', 'Create ' . $name, $labelCreate);
|
||||
}
|
||||
|
||||
if (!empty($data['labelPlural'])) {
|
||||
$labelPlural = $data['labelPlural'];
|
||||
$this->getLanguage()->set('Global', 'scopeNamesPlural', $name, $labelPlural);
|
||||
}
|
||||
|
||||
$this->getMetadata()->save();
|
||||
$this->getLanguage()->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function delete($name)
|
||||
{
|
||||
if (!$this->isCustom($name)) {
|
||||
throw new Forbidden;
|
||||
}
|
||||
|
||||
$normalizedName = Util::normilizeClassName($name);
|
||||
|
||||
$unsets = array(
|
||||
'entityDefs',
|
||||
'clientDefs',
|
||||
'scopes'
|
||||
);
|
||||
$res = $this->getMetadata()->delete('entityDefs', $name);
|
||||
$res = $this->getMetadata()->delete('clientDefs', $name);
|
||||
$res = $this->getMetadata()->delete('scopes', $name);
|
||||
|
||||
$this->getFileManager()->removeFile("custom/Espo/Custom/Resources/metadata/entityDefs/{$name}.json");
|
||||
$this->getFileManager()->removeFile("custom/Espo/Custom/Resources/metadata/clientDefs/{$name}.json");
|
||||
$this->getFileManager()->removeFile("custom/Espo/Custom/Resources/metadata/scopes/{$name}.json");
|
||||
|
||||
$this->getFileManager()->removeFile("custom/Espo/Custom/Entities/{$normalizedName}.php");
|
||||
$this->getFileManager()->removeFile("custom/Espo/Custom/Services/{$normalizedName}.php");
|
||||
$this->getFileManager()->removeFile("custom/Espo/Custom/Controllers/{$normalizedName}.php");
|
||||
$this->getFileManager()->removeFile("custom/Espo/Custom/Repositories/{$normalizedName}.php");
|
||||
|
||||
try {
|
||||
$this->getLanguage()->delete('Global', 'scopeNames', $name);
|
||||
$this->getLanguage()->delete('Global', 'scopeNamesPlural', $name);
|
||||
} catch (\Exception $e) {}
|
||||
|
||||
$this->getMetadata()->save();
|
||||
$this->getLanguage()->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isCustom($name)
|
||||
{
|
||||
return $this->getMetadata()->get('scopes.' . $name . '.isCustom');
|
||||
}
|
||||
|
||||
public function createLink(array $params)
|
||||
{
|
||||
$linkType = $params['linkType'];
|
||||
|
||||
$entity = $params['entity'];
|
||||
$link = $params['link'];
|
||||
$entityForeign = $params['entityForeign'];
|
||||
$linkForeign = $params['linkForeign'];
|
||||
|
||||
$label = $params['label'];
|
||||
$labelForeign = $params['labelForeign'];
|
||||
|
||||
if (empty($linkType)) {
|
||||
throw new Error();
|
||||
}
|
||||
if (empty($entity) || empty($entityForeign)) {
|
||||
throw new Error();
|
||||
}
|
||||
if (empty($entityForeign) || empty($linkForeign)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if ($this->getMetadata()->get('entityDefs.' . $entity . '.links.' . $link)) {
|
||||
throw new Conflict('Link ['.$entity.'::'.$link.'] already exists.');
|
||||
}
|
||||
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.links.' . $linkForeign)) {
|
||||
throw new Conflict('Link ['.$entityForeign.'::'.$linkForeign.'] already exists.');
|
||||
}
|
||||
|
||||
switch ($linkType) {
|
||||
case 'oneToMany':
|
||||
if ($this->getMetadata()->get('entityDefs.' . $entityForeign . '.field.' . $linkForeign)) {
|
||||
throw new Conflict('Field ['.$entityForeign.'::'.$linkForeign.'] already exists.');
|
||||
}
|
||||
$dataLeft = array(
|
||||
'links' => array(
|
||||
$link => array(
|
||||
'type' => 'hasMany',
|
||||
'foreign' => $linkForeign,
|
||||
'entity' => $entityForeign,
|
||||
'isCustom' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
$dataRight = array(
|
||||
'fields' => array(
|
||||
$linkForeign => array(
|
||||
'type' => 'link'
|
||||
)
|
||||
),
|
||||
'links' => array(
|
||||
$linkForeign => array(
|
||||
'type' => 'belongsTo',
|
||||
'foreign' => $link,
|
||||
'entity' => $entity,
|
||||
'isCustom' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
case 'manyToOne':
|
||||
if ($this->getMetadata()->get('entityDefs.' . $entity . '.field.' . $link)) {
|
||||
throw new Conflict('Field ['.$entity.'::'.$link.'] already exists.');
|
||||
}
|
||||
$dataLeft = array(
|
||||
'fields' => array(
|
||||
$link => array(
|
||||
'type' => 'link'
|
||||
)
|
||||
),
|
||||
'links' => array(
|
||||
$link => array(
|
||||
'type' => 'belongsTo',
|
||||
'foreign' => $linkForeign,
|
||||
'entity' => $entityForeign,
|
||||
'isCustom' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
$dataRight = array(
|
||||
'links' => array(
|
||||
$linkForeign => array(
|
||||
'type' => 'hasMany',
|
||||
'foreign' => $link,
|
||||
'entity' => $entity,
|
||||
'isCustom' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
case 'manyToMany':
|
||||
$dataLeft = array(
|
||||
'links' => array(
|
||||
$link => array(
|
||||
'type' => 'hasMany',
|
||||
'foreign' => $linkForeign,
|
||||
'entity' => $entityForeign,
|
||||
'isCustom' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
$dataRight = array(
|
||||
'links' => array(
|
||||
$linkForeign => array(
|
||||
'type' => 'hasMany',
|
||||
'foreign' => $link,
|
||||
'entity' => $entity,
|
||||
'isCustom' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->getMetadata()->set('entityDefs', $entity, $dataLeft);
|
||||
$this->getMetadata()->set('entityDefs', $entityForeign, $dataRight);
|
||||
$this->getMetadata()->save();
|
||||
|
||||
$this->getLanguage()->set($entity, 'fields', $link, $label);
|
||||
$this->getLanguage()->set($entity, 'links', $link, $label);
|
||||
$this->getLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
|
||||
$this->getLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
|
||||
|
||||
$this->getLanguage()->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updateLink(array $params)
|
||||
{
|
||||
$entity = $params['entity'];
|
||||
$link = $params['link'];
|
||||
$entityForeign = $params['entityForeign'];
|
||||
$linkForeign = $params['linkForeign'];
|
||||
|
||||
$label = $params['label'];
|
||||
$labelForeign = $params['labelForeign'];
|
||||
|
||||
if (empty($entity) || empty($entityForeign)) {
|
||||
throw new Error();
|
||||
}
|
||||
if (empty($entityForeign) || empty($linkForeign)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$this->getLanguage()->set($entity, 'fields', $link, $label);
|
||||
$this->getLanguage()->set($entity, 'links', $link, $label);
|
||||
$this->getLanguage()->set($entityForeign, 'fields', $linkForeign, $labelForeign);
|
||||
$this->getLanguage()->set($entityForeign, 'links', $linkForeign, $labelForeign);
|
||||
|
||||
$this->getLanguage()->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteLink(array $params)
|
||||
{
|
||||
$entity = $params['entity'];
|
||||
$link = $params['link'];
|
||||
|
||||
if (!$this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.isCustom")) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$entityForeign = $this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.entity");
|
||||
$linkForeign = $this->getMetadata()->get("entityDefs.{$entity}.links.{$link}.foreign");
|
||||
|
||||
if (empty($entity) || empty($entityForeign)) {
|
||||
throw new Error();
|
||||
}
|
||||
if (empty($entityForeign) || empty($linkForeign)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$this->getMetadata()->delete('entityDefs', $entity, array(
|
||||
'fields.' . $link,
|
||||
'links.' . $link
|
||||
));
|
||||
$this->getMetadata()->delete('entityDefs', $entityForeign, array(
|
||||
'fields.' . $linkForeign,
|
||||
'links.' . $linkForeign
|
||||
));
|
||||
$this->getMetadata()->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ class FieldManager
|
||||
|
||||
protected $customOptionName = 'isCustom';
|
||||
|
||||
|
||||
public function __construct(Metadata $metadata, Language $language)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
@@ -122,7 +121,8 @@ class FieldManager
|
||||
'links.'.$name,
|
||||
);
|
||||
|
||||
$res = $this->getMetadata()->delete($unsets, $this->metadataType, $scope);
|
||||
$this->getMetadata()->delete($this->metadataType, $scope, $unsets);
|
||||
$res = $this->getMetadata()->save();
|
||||
$res &= $this->deleteLabel($name, $scope);
|
||||
|
||||
return (bool) $res;
|
||||
@@ -132,25 +132,26 @@ class FieldManager
|
||||
{
|
||||
$fieldDef = $this->normalizeDefs($name, $fieldDef, $scope);
|
||||
|
||||
$data = Json::encode($fieldDef);
|
||||
$res = $this->getMetadata()->set($data, $this->metadataType, $scope);
|
||||
$this->getMetadata()->set($this->metadataType, $scope, $fieldDef);
|
||||
$res = $this->getMetadata()->save();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function setTranslatedOptions($name, $value, $scope)
|
||||
{
|
||||
return $this->getLanguage()->set($name, $value, 'options', $scope);
|
||||
return $this->getLanguage()->set($scope, 'options', $name, $value);
|
||||
}
|
||||
|
||||
protected function setLabel($name, $value, $scope)
|
||||
{
|
||||
return $this->getLanguage()->set($name, $value, 'fields', $scope);
|
||||
return $this->getLanguage()->set($scope, 'fields', $name, $value);
|
||||
}
|
||||
|
||||
protected function deleteLabel($name, $scope)
|
||||
{
|
||||
$this->getLanguage()->delete($name, 'fields', $scope);
|
||||
$this->getLanguage()->delete($scope, 'fields', $name);
|
||||
$this->getLanguage()->delete($scope, 'options', $name);
|
||||
return $this->getLanguage()->save();
|
||||
}
|
||||
|
||||
@@ -177,6 +178,7 @@ class FieldManager
|
||||
$unnecessaryFields = array(
|
||||
'name',
|
||||
'label',
|
||||
'translatedOptions',
|
||||
);
|
||||
|
||||
foreach ($unnecessaryFields as $fieldName) {
|
||||
|
||||
@@ -60,7 +60,7 @@ class ClassParser
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
public function setAllowedMethods(array $methods)
|
||||
public function setAllowedMethods($methods)
|
||||
{
|
||||
$this->allowedMethods = $methods;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class ClassParser
|
||||
}
|
||||
|
||||
if ($cacheFile && file_exists($cacheFile) && $this->getConfig()->get('useCache')) {
|
||||
$data = $this->getFileManager()->getContents($cacheFile);
|
||||
$data = $this->getFileManager()->getPhpContents($cacheFile);
|
||||
} else {
|
||||
$data = $this->getClassNameHash($paths['corePath']);
|
||||
|
||||
@@ -104,7 +104,7 @@ class ClassParser
|
||||
}
|
||||
|
||||
if ($cacheFile && $this->getConfig()->get('useCache')) {
|
||||
$result = $this->getFileManager()->putContentsPHP($cacheFile, $data);
|
||||
$result = $this->getFileManager()->putPhpContents($cacheFile, $data);
|
||||
if ($result == false) {
|
||||
throw new \Espo\Core\Exceptions\Error();
|
||||
}
|
||||
@@ -129,11 +129,18 @@ class ClassParser
|
||||
$filePath = Util::concatPath($dir, $file);
|
||||
$className = Util::getClassName($filePath);
|
||||
$fileName = $this->getFileManager()->getFileName($filePath);
|
||||
$fileName = ucfirst($fileName);
|
||||
|
||||
$scopeName = ucfirst($fileName);
|
||||
$normalizedScopeName = Util::normilizeScopeName($scopeName);
|
||||
|
||||
if (empty($this->allowedMethods)) {
|
||||
$data[$normalizedScopeName] = $className;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->allowedMethods as $methodName) {
|
||||
if (method_exists($className, $methodName)) {
|
||||
$data[$fileName] = $className;
|
||||
$data[$normalizedScopeName] = $className;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ class Manager
|
||||
{
|
||||
private $permission;
|
||||
|
||||
private $permissionDeniedList = array();
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Config $config = null)
|
||||
{
|
||||
$params = null;
|
||||
@@ -156,17 +158,31 @@ class Manager
|
||||
$fullPath = $this->concatPaths($path);
|
||||
|
||||
if (file_exists($fullPath)) {
|
||||
|
||||
if (strtolower(substr($fullPath, -4))=='.php') {
|
||||
return include($fullPath);
|
||||
if (isset($maxlen)) {
|
||||
return file_get_contents($fullPath, $useIncludePath, $context, $offset, $maxlen);
|
||||
} else {
|
||||
if (isset($maxlen)) {
|
||||
return file_get_contents($fullPath, $useIncludePath, $context, $offset, $maxlen);
|
||||
} else {
|
||||
return file_get_contents($fullPath, $useIncludePath, $context, $offset);
|
||||
}
|
||||
return file_get_contents($fullPath, $useIncludePath, $context, $offset);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PHP array from PHP file
|
||||
*
|
||||
* @param string | array $path
|
||||
* @return array | bool
|
||||
*/
|
||||
public function getPhpContents($path)
|
||||
{
|
||||
$fullPath = $this->concatPaths($path);
|
||||
|
||||
if (file_exists($fullPath) && strtolower(substr($fullPath, -4)) == '.php') {
|
||||
$phpContents = include($fullPath);
|
||||
if (is_array($phpContents)) {
|
||||
return $phpContents;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -206,7 +222,7 @@ class Manager
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function putContentsPHP($path, $data)
|
||||
public function putPhpContents($path, $data)
|
||||
{
|
||||
return $this->putContents($path, $this->getPHPFormat($data), LOCK_EX);
|
||||
}
|
||||
@@ -235,19 +251,23 @@ class Manager
|
||||
*
|
||||
* @param string | array $path
|
||||
* @param string $content JSON string
|
||||
* @param bool $isJSON
|
||||
* @param bool $isReturnJson
|
||||
* @param string | array $removeOptions - List of unset keys from content
|
||||
* @param bool $isReturn - Is result to be returned or stored
|
||||
* @param bool $isPhp - Is merge php files
|
||||
*
|
||||
* @return bool | array
|
||||
*/
|
||||
public function mergeContents($path, $content, $isJSON = false, $removeOptions = null, $isReturn = false)
|
||||
public function mergeContents($path, $content, $isReturnJson = false, $removeOptions = null, $isPhp = false)
|
||||
{
|
||||
$fileContent = $this->getContents($path);
|
||||
if ($isPhp) {
|
||||
$fileContent = $this->getPhpContents($path);
|
||||
} else {
|
||||
$fileContent = $this->getContents($path);
|
||||
}
|
||||
|
||||
$fullPath = $this->concatPaths($path);
|
||||
if (file_exists($fullPath) && ($fileContent === false || empty($fileContent))) {
|
||||
throw new Error('Failed to read file [' . $fullPath .'].');
|
||||
throw new Error('FileManager: Failed to read file [' . $fullPath .'].');
|
||||
}
|
||||
|
||||
$savedDataArray = Utils\Json::getArrayData($fileContent);
|
||||
@@ -259,12 +279,13 @@ class Manager
|
||||
}
|
||||
|
||||
$data = Utils\Util::merge($savedDataArray, $newDataArray);
|
||||
if ($isJSON) {
|
||||
|
||||
if ($isReturnJson) {
|
||||
$data = Utils\Json::encode($data, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
if ($isReturn) {
|
||||
return $data;
|
||||
if ($isPhp) {
|
||||
return $this->putPhpContents($path, $data);
|
||||
}
|
||||
|
||||
return $this->putContents($path, $data);
|
||||
@@ -278,11 +299,9 @@ class Manager
|
||||
* @param string | array $removeOptions - List of unset keys from content
|
||||
* @return bool
|
||||
*/
|
||||
public function mergeContentsPHP($path, $content, $removeOptions = null)
|
||||
public function mergePhpContents($path, $content, $removeOptions = null)
|
||||
{
|
||||
$data = $this->mergeContents($path, $content, false, $removeOptions, true);
|
||||
|
||||
return $this->putContentsPHP($path, $data);
|
||||
return $this->mergeContents($path, $content, false, $removeOptions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,7 +321,7 @@ class Manager
|
||||
* Unset some element of content data
|
||||
*
|
||||
* @param string | array $path
|
||||
* @param array | string $unsets [description]
|
||||
* @param array | string $unsets
|
||||
* @return bool
|
||||
*/
|
||||
public function unsetContents($path, $unsets, $isJSON = true)
|
||||
@@ -315,7 +334,12 @@ class Manager
|
||||
|
||||
$currentDataArray = Utils\Json::getArrayData($currentData);
|
||||
|
||||
$unsettedData = Utils\Util::unsetInArray($currentDataArray, $unsets);
|
||||
$unsettedData = Utils\Util::unsetInArray($currentDataArray, $unsets, true);
|
||||
|
||||
if (is_null($unsettedData) || (is_array($unsettedData) && empty($unsettedData))) {
|
||||
$fullPath = $this->concatPaths($path);
|
||||
return $this->unlink($fullPath);
|
||||
}
|
||||
|
||||
if ($isJSON) {
|
||||
return $this->putContentsJson($path, $unsettedData);
|
||||
@@ -576,6 +600,24 @@ class Manager
|
||||
$items = (array) $items;
|
||||
}
|
||||
|
||||
$permissionDeniedList = array();
|
||||
foreach ($items as $item) {
|
||||
if (isset($dirPath)) {
|
||||
$item = Utils\Util::concatPath($dirPath, $item);
|
||||
}
|
||||
|
||||
if (!is_writable($item)) {
|
||||
$permissionDeniedList[] = $item;
|
||||
} else if (!is_writable(dirname($item))) {
|
||||
$permissionDeniedList[] = dirname($item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($permissionDeniedList)) {
|
||||
$betterPermissionList = $this->getPermissionUtils()->arrangePermissionList($permissionDeniedList);
|
||||
throw new Error("Permission denied in <br>". implode(", <br>", $betterPermissionList));
|
||||
}
|
||||
|
||||
$result = true;
|
||||
foreach ($items as $item) {
|
||||
if (isset($dirPath)) {
|
||||
@@ -734,5 +776,73 @@ return '.var_export($content, true).';
|
||||
?>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if $paths are writable. Permission denied list are defined in getLastPermissionDeniedList()
|
||||
*
|
||||
* @param array $paths
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWritableList(array $paths)
|
||||
{
|
||||
$permissionDeniedList = array();
|
||||
|
||||
$result = true;
|
||||
foreach ($paths as $path) {
|
||||
$rowResult = $this->isWritable($path);
|
||||
if (!$rowResult) {
|
||||
$permissionDeniedList[] = $path;
|
||||
}
|
||||
$result &= $rowResult;
|
||||
}
|
||||
|
||||
if (!empty($permissionDeniedList)) {
|
||||
$this->permissionDeniedList = $this->getPermissionUtils()->arrangePermissionList($permissionDeniedList);
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last permission denied list
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getLastPermissionDeniedList()
|
||||
{
|
||||
return $this->permissionDeniedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if $path is writable
|
||||
*
|
||||
* @param string | array $path
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isWritable($path)
|
||||
{
|
||||
$existFile = $this->getExistsPath($path);
|
||||
|
||||
return is_writable($existFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exists path. Ex. if check /var/www/espocrm/custom/someFile.php and this file doesn't extist, result will be /var/www/espocrm/custom
|
||||
*
|
||||
* @param string | array $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getExistsPath($path)
|
||||
{
|
||||
$fullPath = $this->concatPaths($path);
|
||||
|
||||
if (!file_exists($fullPath)) {
|
||||
$fullPath = $this->getExistsPath(pathinfo($fullPath, PATHINFO_DIRNAME));
|
||||
}
|
||||
|
||||
return $fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,6 @@ class Language
|
||||
}
|
||||
|
||||
$this->clearChanges();
|
||||
$this->init(true);
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
@@ -234,6 +233,7 @@ class Language
|
||||
{
|
||||
$this->changedData = array();
|
||||
$this->deletedData = array();
|
||||
$this->init(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,53 +253,76 @@ class Language
|
||||
|
||||
/**
|
||||
* Set/change a label
|
||||
* @param string | array $label
|
||||
* @param mixed $value
|
||||
* @param string $category
|
||||
*
|
||||
* @param string $scope
|
||||
* @param string $category
|
||||
* @param string | array $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($label, $value, $category = 'labels', $scope = 'Global')
|
||||
public function set($scope, $category, $name, $value)
|
||||
{
|
||||
if (is_array($label)) {
|
||||
foreach ($label as $rowLabel => $rowValue) {
|
||||
$this->set($rowLabel, $rowValue, $category, $scope);
|
||||
if (is_array($name)) {
|
||||
foreach ($name as $rowLabel => $rowValue) {
|
||||
$this->set($scope, $category, $rowLabel, $rowValue);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$this->changedData[$scope][$category][$label] = $value;
|
||||
$this->changedData[$scope][$category][$name] = $value;
|
||||
|
||||
$currentLanguage = $this->getLanguage();
|
||||
$this->data[$currentLanguage][$scope][$category][$label] = $value;
|
||||
if (!isset($this->data[$currentLanguage])) {
|
||||
$this->init();
|
||||
}
|
||||
$this->data[$currentLanguage][$scope][$category][$name] = $value;
|
||||
|
||||
$this->undelete($scope, $category, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a label
|
||||
*
|
||||
* @param string $label
|
||||
* @param string $name
|
||||
* @param string $category
|
||||
* @param string $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete($label, $category = 'labels', $scope = 'Global')
|
||||
public function delete($scope, $category, $name)
|
||||
{
|
||||
if (is_array($label)) {
|
||||
foreach ($label as $rowLabel) {
|
||||
$this->delete($rowLabel, $category, $scope);
|
||||
if (is_array($name)) {
|
||||
foreach ($name as $rowLabel) {
|
||||
$this->delete($scope, $category, $rowLabel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deletedData[$scope][$category][] = $label;
|
||||
$this->deletedData[$scope][$category][] = $name;
|
||||
|
||||
$currentLanguage = $this->getLanguage();
|
||||
if (isset($this->data[$currentLanguage][$scope][$category][$label])) {
|
||||
unset($this->data[$currentLanguage][$scope][$category][$label]);
|
||||
if (!isset($this->data[$currentLanguage])) {
|
||||
$this->init();
|
||||
}
|
||||
|
||||
if (isset($this->changedData[$scope][$category][$label])) {
|
||||
unset($this->changedData[$scope][$category][$label]);
|
||||
if (isset($this->data[$currentLanguage][$scope][$category][$name])) {
|
||||
unset($this->data[$currentLanguage][$scope][$category][$name]);
|
||||
}
|
||||
|
||||
if (isset($this->changedData[$scope][$category][$name])) {
|
||||
unset($this->changedData[$scope][$category][$name]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function undelete($scope, $category, $name)
|
||||
{
|
||||
if (isset($this->deletedData[$scope][$category])) {
|
||||
foreach ($this->deletedData[$scope][$category] as $key => $labelName) {
|
||||
if ($name === $labelName) {
|
||||
unset($this->deletedData[$scope][$category][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +342,7 @@ class Language
|
||||
|
||||
if ($this->getConfig()->get('useCache')) {
|
||||
$i18nCacheFile = str_replace('{*}', $i18nName, $this->cacheFile);
|
||||
$result &= $this->getFileManager()->putContentsPHP($i18nCacheFile, $i18nData);
|
||||
$result &= $this->getFileManager()->putPhpContents($i18nCacheFile, $i18nData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +353,7 @@ class Language
|
||||
|
||||
$currentLanguage = $this->getLanguage();
|
||||
if (empty($this->data[$currentLanguage])) {
|
||||
$this->data[$currentLanguage] = $this->getFileManager()->getContents($this->getLangCacheFile());
|
||||
$this->data[$currentLanguage] = $this->getFileManager()->getPhpContents($this->getLangCacheFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,23 +18,26 @@
|
||||
*
|
||||
* 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 Layout
|
||||
{
|
||||
private $fileManager;
|
||||
private $metadata;
|
||||
|
||||
|
||||
private $metadata;
|
||||
|
||||
private $changedData = array();
|
||||
|
||||
/**
|
||||
* @var string - uses for loading default values
|
||||
*/
|
||||
private $name = 'layout';
|
||||
private $name = 'layout';
|
||||
|
||||
protected $params = array(
|
||||
protected $params = array(
|
||||
'defaultsPath' => 'application/Espo/Core/defaults',
|
||||
);
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
@@ -42,10 +45,10 @@ class Layout
|
||||
*/
|
||||
private $paths = array(
|
||||
'corePath' => 'application/Espo/Resources/layouts',
|
||||
'modulePath' => 'application/Espo/Modules/{*}/Resources/layouts',
|
||||
'customPath' => 'custom/Espo/Custom/Resources/layouts',
|
||||
);
|
||||
|
||||
'modulePath' => 'application/Espo/Modules/{*}/Resources/layouts',
|
||||
'customPath' => 'custom/Espo/Custom/Resources/layouts',
|
||||
);
|
||||
|
||||
|
||||
public function __construct(\Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Metadata $metadata)
|
||||
{
|
||||
@@ -63,7 +66,6 @@ class Layout
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Layout context
|
||||
*
|
||||
@@ -73,13 +75,17 @@ class Layout
|
||||
* @return json
|
||||
*/
|
||||
public function get($controller, $name)
|
||||
{
|
||||
$fileFullPath = Util::concatPath($this->getLayoutPath($controller, true), $name.'.json');
|
||||
if (!file_exists($fileFullPath)) {
|
||||
$fileFullPath = Util::concatPath($this->getLayoutPath($controller), $name.'.json');
|
||||
}
|
||||
{
|
||||
if (isset($this->changedData[$controller][$name])) {
|
||||
return Json::encode($this->changedData[$controller][$name]);
|
||||
}
|
||||
|
||||
if (!file_exists($fileFullPath)) {
|
||||
$fileFullPath = Util::concatPath($this->getLayoutPath($controller, true), $name.'.json');
|
||||
if (!file_exists($fileFullPath)) {
|
||||
$fileFullPath = Util::concatPath($this->getLayoutPath($controller), $name.'.json');
|
||||
}
|
||||
|
||||
if (!file_exists($fileFullPath)) {
|
||||
//load defaults
|
||||
$defaultPath = $this->params['defaultsPath'];
|
||||
$fileFullPath = Util::concatPath( Util::concatPath($defaultPath, $this->name), $name.'.json' );
|
||||
@@ -91,34 +97,68 @@ class Layout
|
||||
}
|
||||
|
||||
return $this->getFileManager()->getContents($fileFullPath);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Layout data
|
||||
* Ex. $controller= Account, $name= detail then will be created a file layoutFolder/Account/detail.json
|
||||
* Ex. $controller = Account, $name = detail then will be created a file layoutFolder/Account/detail.json
|
||||
*
|
||||
* @param JSON string $data
|
||||
* @param array $data
|
||||
* @param string $controller - ex. Account
|
||||
* @param string $name - detail
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($data, $controller, $name)
|
||||
{
|
||||
if (empty($controller) || empty($name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$layoutPath = $this->getLayoutPath($controller, true);
|
||||
|
||||
if (!Json::isJSON($data)) {
|
||||
$data = Json::encode($data);
|
||||
}
|
||||
|
||||
return $this->getFileManager()->putContents(array($layoutPath, $name.'.json'), $data);
|
||||
$this->changedData[$controller][$name] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$result = true;
|
||||
|
||||
if (!empty($this->changedData)) {
|
||||
foreach ($this->changedData as $controllerName => $rowData) {
|
||||
foreach ($rowData as $layoutName => $layoutData) {
|
||||
|
||||
if (empty($controllerName) || empty($layoutName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$layoutPath = $this->getLayoutPath($controllerName, true);
|
||||
$data = Json::encode($layoutData);
|
||||
|
||||
$result &= $this->getFileManager()->putContents(array($layoutPath, $layoutName.'.json'), $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($result == true) {
|
||||
$this->clearChanges();
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear unsaved changes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearChanges()
|
||||
{
|
||||
$this->changedData = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge layout data
|
||||
@@ -133,15 +173,15 @@ class Layout
|
||||
public function merge($data, $controller, $name)
|
||||
{
|
||||
$prevData = $this->get($controller, $name);
|
||||
|
||||
$prevDataArray= Json::getArrayData($prevData);
|
||||
$dataArray= Json::getArrayData($data);
|
||||
|
||||
$data= Util::merge($prevDataArray, $dataArray);
|
||||
$data= Json::encode($data);
|
||||
$prevDataArray = Json::getArrayData($prevData);
|
||||
$dataArray = Json::getArrayData($data);
|
||||
|
||||
$data = Util::merge($prevDataArray, $dataArray);
|
||||
$data = Json::encode($data);
|
||||
|
||||
return $this->set($data, $controller, $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Layout path, ex. application/Modules/Crm/Layouts/Account
|
||||
@@ -152,23 +192,23 @@ class Layout
|
||||
* @return string
|
||||
*/
|
||||
protected function getLayoutPath($entityName, $isCustom = false)
|
||||
{
|
||||
$path = $this->paths['customPath'];
|
||||
{
|
||||
$path = $this->paths['customPath'];
|
||||
|
||||
if (!$isCustom) {
|
||||
$moduleName = $this->getMetadata()->getScopeModuleName($entityName);
|
||||
|
||||
|
||||
$path = $this->paths['corePath'];
|
||||
if ($moduleName !== false) {
|
||||
$path = str_replace('{*}', $moduleName, $this->paths['modulePath']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
$path = Util::concatPath($path, $entityName);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,13 @@ class Metadata
|
||||
*/
|
||||
private $name = 'metadata';
|
||||
|
||||
/**
|
||||
* Path to modules
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $pathToModules = 'application/Espo/Modules';
|
||||
|
||||
private $cacheFile = 'data/cache/application/metadata.php';
|
||||
|
||||
private $paths = array(
|
||||
@@ -61,6 +68,10 @@ class Metadata
|
||||
*/
|
||||
protected $defaultModuleOrder = 10;
|
||||
|
||||
private $deletedData = array();
|
||||
|
||||
private $changedData = array();
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Config $config, \Espo\Core\Utils\File\Manager $fileManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
@@ -124,13 +135,13 @@ class Metadata
|
||||
}
|
||||
|
||||
if (file_exists($this->cacheFile) && !$reload) {
|
||||
$this->meta = $this->getFileManager()->getContents($this->cacheFile);
|
||||
$this->meta = $this->getFileManager()->getPhpContents($this->cacheFile);
|
||||
} else {
|
||||
$this->meta = $this->getUnifier()->unify($this->name, $this->paths, true);
|
||||
$this->meta = $this->setLanguageFromConfig($this->meta);
|
||||
|
||||
if ($this->getConfig()->get('useCache')) {
|
||||
$isSaved = $this->getFileManager()->putContentsPHP($this->cacheFile, $this->meta);
|
||||
$isSaved = $this->getFileManager()->putPhpContents($this->cacheFile, $this->meta);
|
||||
if ($isSaved === false) {
|
||||
$GLOBALS['log']->emergency('Metadata:init() - metadata has not been saved to a cache file');
|
||||
}
|
||||
@@ -213,52 +224,139 @@ class Metadata
|
||||
|
||||
/**
|
||||
* Set Metadata data
|
||||
* Ex. $type= menu, $scope= Account then will be created a file metadataFolder/menu/Account.json
|
||||
* Ex. $key1 = menu, $key2 = Account then will be created a file metadataFolder/menu/Account.json
|
||||
*
|
||||
* @param string $key1
|
||||
* @param string $key2
|
||||
* @param JSON string $data
|
||||
* @param string $type - ex. menu
|
||||
* @param string $scope - Account
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function set($data, $type, $scope)
|
||||
public function set($key1, $key2, $data)
|
||||
{
|
||||
$path = $this->paths['customPath'];
|
||||
$newData = array(
|
||||
$key1 => array(
|
||||
$key2 => $data,
|
||||
),
|
||||
);
|
||||
|
||||
$result = $this->getFileManager()->mergeContents(array($path, $type, $scope.'.json'), $data, true);
|
||||
if ($result === false) {
|
||||
throw new Error("Error saving metadata. See log file for details.");
|
||||
}
|
||||
$this->changedData = Util::merge($this->changedData, $newData);
|
||||
$this->meta = Util::merge($this->getData(), $newData);
|
||||
|
||||
$this->init(true);
|
||||
|
||||
return $result;
|
||||
$this->undelete($key1, $key2, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset some fields and other stuff in metadat
|
||||
*
|
||||
* @param string $key1
|
||||
* @param string $key2
|
||||
* @param array | string $unsets Ex. 'fields.name'
|
||||
* @param string $type Ex. 'entityDefs'
|
||||
* @param string $scope
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function delete($unsets, $type, $scope)
|
||||
public function delete($key1, $key2, $unsets)
|
||||
{
|
||||
if (!is_array($unsets)) {
|
||||
$unsets = (array) $unsets;
|
||||
}
|
||||
|
||||
$normalizedData = array(
|
||||
'__APPEND__',
|
||||
);
|
||||
$metaUnsetData = array();
|
||||
foreach ($unsets as $unsetItem) {
|
||||
$normalizedData[] = $unsetItem;
|
||||
$metaUnsetData[] = implode('.', array($key1, $key2, $unsetItem));
|
||||
}
|
||||
|
||||
$unsetData = array(
|
||||
$key1 => array(
|
||||
$key2 => $normalizedData,
|
||||
),
|
||||
);
|
||||
|
||||
$this->deletedData = Util::merge($this->deletedData, $unsetData);
|
||||
$this->deletedData = Util::unsetInArrayByValue('__APPEND__', $this->deletedData);
|
||||
|
||||
$this->meta = Util::unsetInArray($this->getData(), $metaUnsetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undelete the deleted items
|
||||
*
|
||||
* @param string $key1
|
||||
* @param string $key2
|
||||
* @param array $data
|
||||
* @return void
|
||||
*/
|
||||
protected function undelete($key1, $key2, $data)
|
||||
{
|
||||
if (isset($this->deletedData[$key1][$key2])) {
|
||||
foreach ($this->deletedData[$key1][$key2] as $unsetIndex => $unsetItem) {
|
||||
$value = Util::getValueByKey($data, $unsetItem);
|
||||
if (isset($value)) {
|
||||
unset($this->deletedData[$key1][$key2][$unsetIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear unsaved changes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearChanges()
|
||||
{
|
||||
$this->changedData = array();
|
||||
$this->deletedData = array();
|
||||
$this->init(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save changes
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$path = $this->paths['customPath'];
|
||||
|
||||
$result = $this->getFileManager()->unsetContents(array($path, $type, $scope.'.json'), $unsets, true);
|
||||
|
||||
if ($result == false) {
|
||||
$GLOBALS['log']->warning('Delete metadata items available only for custom code.');
|
||||
$result = true;
|
||||
if (!empty($this->changedData)) {
|
||||
foreach ($this->changedData as $key1 => $keyData) {
|
||||
foreach ($keyData as $key2 => $data) {
|
||||
if (!empty($data)) {
|
||||
$result &= $this->getFileManager()->mergeContents(array($path, $key1, $key2.'.json'), $data, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->init(true);
|
||||
if (!empty($this->deletedData)) {
|
||||
foreach ($this->deletedData as $key1 => $keyData) {
|
||||
foreach ($keyData as $key2 => $unsetData) {
|
||||
if (!empty($unsetData)) {
|
||||
$rowResult = $this->getFileManager()->unsetContents(array($path, $key1, $key2.'.json'), $unsetData, true);
|
||||
if ($rowResult == false) {
|
||||
$GLOBALS['log']->warning('Metadata items ['.$key1.'.'.$key2.'] can be deleted for custom code only.');
|
||||
}
|
||||
$result &= $rowResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
if ($result == false) {
|
||||
throw new Error("Error saving metadata. See log file for details.");
|
||||
}
|
||||
|
||||
$this->clearChanges();
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
|
||||
public function getOrmMetadata($reload = false)
|
||||
{
|
||||
if (!empty($this->ormMeta) && is_array($this->ormMeta) && !$reload) {
|
||||
@@ -270,7 +368,7 @@ class Metadata
|
||||
}
|
||||
|
||||
if (empty($this->ormMeta)) {
|
||||
$this->ormMeta = $this->getFileManager()->getContents($this->ormCacheFile);
|
||||
$this->ormMeta = $this->getFileManager()->getPhpContents($this->ormCacheFile);
|
||||
}
|
||||
|
||||
return $this->ormMeta;
|
||||
@@ -278,8 +376,10 @@ class Metadata
|
||||
|
||||
public function setOrmMetadata(array $ormMeta)
|
||||
{
|
||||
$result = true;
|
||||
|
||||
if ($this->getConfig()->get('useCache')) {
|
||||
$result = $this->getFileManager()->putContentsPHP($this->ormCacheFile, $ormMeta);
|
||||
$result = $this->getFileManager()->putPhpContents($this->ormCacheFile, $ormMeta);
|
||||
if ($result == false) {
|
||||
throw new \Espo\Core\Exceptions\Error('Metadata::setOrmMetadata() - Cannot save ormMetadata to a file');
|
||||
}
|
||||
@@ -313,30 +413,27 @@ class Metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Scopes
|
||||
* Load modules
|
||||
*
|
||||
* @return array
|
||||
* @return void
|
||||
*/
|
||||
public function getScopes()
|
||||
protected function loadModuleList()
|
||||
{
|
||||
if (!empty($this->scopes)) {
|
||||
return $this->scopes;
|
||||
}
|
||||
$modules = $this->getFileManager()->getFileList($this->pathToModules, false, '', false);
|
||||
|
||||
$scopeList = $this->get('scopes');
|
||||
if (!is_array($scopeList)) {
|
||||
$this->init(true);
|
||||
$scopeList = $this->get('scopes');
|
||||
}
|
||||
|
||||
$scopes = array();
|
||||
if (is_array($scopeList)) {
|
||||
foreach ($scopeList as $name => $details) {
|
||||
$scopes[$name] = isset($details['module']) ? $details['module'] : false;
|
||||
$modulesToSort = array();
|
||||
if (is_array($modules)) {
|
||||
foreach ($modules as $moduleName) {
|
||||
if (!empty($moduleName) && !isset($modulesToSort[$moduleName])) {
|
||||
$modulesToSort[$moduleName] = $this->getModuleConfig()->get($moduleName . '.order', $this->defaultModuleOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->scopes = $scopes;
|
||||
krsort($modulesToSort);
|
||||
asort($modulesToSort);
|
||||
|
||||
$this->moduleList = array_keys($modulesToSort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,24 +443,10 @@ class Metadata
|
||||
*/
|
||||
public function getModuleList()
|
||||
{
|
||||
if (!empty($this->moduleList)) {
|
||||
return $this->moduleList;
|
||||
if (!isset($this->moduleList)) {
|
||||
$this->loadModuleList();
|
||||
}
|
||||
|
||||
$scopes = $this->getScopes();
|
||||
|
||||
$modulesToSort = array();
|
||||
foreach ($scopes as $moduleName) {
|
||||
if (!empty($moduleName) && !isset($modulesToSort[$moduleName])) {
|
||||
$modulesToSort[$moduleName] = $this->getModuleConfig()->get($moduleName . '.order', $this->defaultModuleOrder);
|
||||
}
|
||||
}
|
||||
|
||||
krsort($modulesToSort);
|
||||
asort($modulesToSort);
|
||||
|
||||
$this->moduleList = array_keys($modulesToSort);
|
||||
|
||||
return $this->moduleList;
|
||||
}
|
||||
|
||||
@@ -399,26 +482,4 @@ class Metadata
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if scope exists
|
||||
*
|
||||
* @param string $scopeName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isScopeExists($scopeName)
|
||||
{
|
||||
$scopeModuleMap = $this->getScopes();
|
||||
|
||||
$lowerEntityName = strtolower($scopeName);
|
||||
foreach($scopeModuleMap as $rowEntityName => $rowModuleName) {
|
||||
if ($lowerEntityName == strtolower($rowEntityName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -84,12 +84,12 @@ class Module
|
||||
protected function init()
|
||||
{
|
||||
if (file_exists($this->cacheFile) && $this->getConfig()->get('useCache')) {
|
||||
$this->data = $this->getFileManager()->getContents($this->cacheFile);
|
||||
$this->data = $this->getFileManager()->getPhpContents($this->cacheFile);
|
||||
} else {
|
||||
$this->data = $this->getUnifier()->unify($this->paths, true);
|
||||
|
||||
if ($this->getConfig()->get('useCache')) {
|
||||
$result = $this->getFileManager()->putContentsPHP($this->cacheFile, $this->data);
|
||||
$result = $this->getFileManager()->putPhpContents($this->cacheFile, $this->data);
|
||||
if ($result == false) {
|
||||
throw new \Espo\Core\Exceptions\Error('Module - Cannot save unified modules.');
|
||||
}
|
||||
|
||||
@@ -95,12 +95,12 @@ class Route
|
||||
protected function init()
|
||||
{
|
||||
if (file_exists($this->cacheFile) && $this->getConfig()->get('useCache')) {
|
||||
$this->data = $this->getFileManager()->getContents($this->cacheFile);
|
||||
$this->data = $this->getFileManager()->getPhpContents($this->cacheFile);
|
||||
} else {
|
||||
$this->data = $this->unify();
|
||||
|
||||
if ($this->getConfig()->get('useCache')) {
|
||||
$result = $this->getFileManager()->putContentsPHP($this->cacheFile, $this->data);
|
||||
$result = $this->getFileManager()->putPhpContents($this->cacheFile, $this->data);
|
||||
if ($result == false) {
|
||||
throw new \Espo\Core\Exceptions\Error('Route - Cannot save unified routes');
|
||||
}
|
||||
@@ -127,7 +127,7 @@ class Route
|
||||
protected function getAddData($currData, $routeFile)
|
||||
{
|
||||
if (file_exists($routeFile)) {
|
||||
$content= $this->getFileManager()->getContents($routeFile);
|
||||
$content = $this->getFileManager()->getContents($routeFile);
|
||||
$arrayContent = Json::getArrayData($content);
|
||||
if (empty($arrayContent)) {
|
||||
$GLOBALS['log']->error('Route::unify() - Empty file or syntax error - ['.$routeFile.']');
|
||||
|
||||
+5
-17
@@ -20,14 +20,14 @@
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Cron;
|
||||
namespace Espo\Core\Utils;
|
||||
|
||||
use Espo\Core\Exceptions\NotFound,
|
||||
Espo\Core\Utils\Util;
|
||||
use Espo\Core\Exceptions\NotFound;
|
||||
|
||||
class ScheduledJob
|
||||
{
|
||||
private $container;
|
||||
|
||||
private $systemUtil;
|
||||
|
||||
protected $data = null;
|
||||
@@ -54,7 +54,6 @@ class ScheduledJob
|
||||
'default' => '* * * * * {PHP-BIN-DIR} -f {CRON-FILE}',
|
||||
);
|
||||
|
||||
|
||||
public function __construct(\Espo\Core\Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
@@ -76,19 +75,9 @@ class ScheduledJob
|
||||
return $this->systemUtil;
|
||||
}
|
||||
|
||||
public function run(array $job)
|
||||
public function getMethodName()
|
||||
{
|
||||
$jobName = $job['method'];
|
||||
|
||||
$className = $this->getClassName($jobName);
|
||||
if ($className === false) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
$jobClass = new $className($this->container);
|
||||
$method = $this->allowedMethod;
|
||||
|
||||
$jobClass->$method();
|
||||
return $this->allowedMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +150,6 @@ class ScheduledJob
|
||||
$this->data = $classParser->getData($this->paths, $this->cacheFile);
|
||||
}
|
||||
|
||||
|
||||
public function getSetupMessage()
|
||||
{
|
||||
$language = $this->getContainer()->get('language');
|
||||
@@ -101,4 +101,20 @@ class System
|
||||
{
|
||||
return (defined("PHP_BINDIR"))? PHP_BINDIR.DIRECTORY_SEPARATOR.'php' : 'php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get php version (only digits and dots)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getPhpVersion()
|
||||
{
|
||||
$version = phpversion();
|
||||
|
||||
if (preg_match('/^[0-9\.]+[0-9]/', $version, $matches)) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ class Util
|
||||
if ($appendKey !== false) {
|
||||
unset($newValue[$appendKey]);
|
||||
$newValue = array_merge($currentArray[$newName], $newValue);
|
||||
} else if (!static::isSingleArray($newValue)) {
|
||||
} else if (!static::isSingleArray($newValue) || !static::isSingleArray($currentArray[$newName])) {
|
||||
$newValue = static::merge($currentArray[$newName], $newValue);
|
||||
}
|
||||
|
||||
@@ -288,6 +288,23 @@ class Util
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove 'Obj' if name is reserved PHP word.
|
||||
*
|
||||
* @param string $name
|
||||
* @return string
|
||||
*/
|
||||
public static function normilizeScopeName($name)
|
||||
{
|
||||
foreach (self::$reservedWords as $reservedWord) {
|
||||
if ($reservedWord.'Obj' == $name) {
|
||||
return $reservedWord;
|
||||
}
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Naming according to prefix or postfix type
|
||||
*
|
||||
@@ -351,10 +368,11 @@ class Util
|
||||
* array('EntityName1.unset1', 'EntityName1.unset2', .....)
|
||||
* OR
|
||||
* 'EntityName1.unset1'
|
||||
* @param bool $unsetParentEmptyArray - If unset empty parent array after unsets
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function unsetInArray(array $content, $unsets)
|
||||
public static function unsetInArray(array $content, $unsets, $unsetParentEmptyArray = false)
|
||||
{
|
||||
if (empty($unsets)) {
|
||||
return $content;
|
||||
@@ -379,11 +397,19 @@ class Util
|
||||
|
||||
$unsetElem = $currVal . "['{$lastKey}']";
|
||||
|
||||
$currVal = "
|
||||
$evalString = "
|
||||
if (isset({$unsetElem}) || ( is_array({$currVal}) && array_key_exists('{$lastKey}', {$currVal}) )) {
|
||||
unset({$unsetElem});
|
||||
} ";
|
||||
eval($currVal);
|
||||
eval($evalString);
|
||||
|
||||
if ($unsetParentEmptyArray) {
|
||||
$evalString = "
|
||||
if (is_array({$currVal}) && empty({$currVal})) {
|
||||
unset({$currVal});
|
||||
} ";
|
||||
eval($evalString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +82,10 @@ return array (
|
||||
'authenticationMethod' => 'Espo',
|
||||
'globalSearchEntityList' =>
|
||||
array (
|
||||
0 => 'Account',
|
||||
1 => 'Contact',
|
||||
2 => 'Lead',
|
||||
3 => 'Opportunity',
|
||||
'Account',
|
||||
'Contact',
|
||||
'Lead',
|
||||
'Opportunity',
|
||||
),
|
||||
"tabList" => array("Account", "Contact", "Lead", "Opportunity", "Calendar", "Meeting", "Call", "Task", "Case", "Email"),
|
||||
"quickCreateList" => array("Account", "Contact", "Lead", "Opportunity", "Meeting", "Call", "Task", "Case"),
|
||||
@@ -94,6 +94,7 @@ return array (
|
||||
'assignmentEmailNotifications' => false,
|
||||
'assignmentEmailNotificationsEntityList' => array('Lead', 'Opportunity', 'Task', 'Case'),
|
||||
'emailMessageMaxSize' => 10,
|
||||
'notificationsCheckInterval' => 10,
|
||||
'isInstalled' => false,
|
||||
);
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ return array (
|
||||
'permissionMap',
|
||||
'permissionRules',
|
||||
'passwordSalt',
|
||||
'cryptKey'
|
||||
),
|
||||
'adminItems' =>
|
||||
array (
|
||||
|
||||
@@ -18,23 +18,22 @@
|
||||
*
|
||||
* 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\Entities;
|
||||
|
||||
class Email extends \Espo\Core\ORM\Entity
|
||||
{
|
||||
|
||||
protected function _getSubject()
|
||||
{
|
||||
return $this->get('name');
|
||||
}
|
||||
|
||||
|
||||
protected function _setSubject($value)
|
||||
{
|
||||
return $this->set('name', $value);
|
||||
}
|
||||
|
||||
|
||||
public function addAttachment(\Espo\Entities\Attachment $attachment)
|
||||
{
|
||||
if (!empty($this->id)) {
|
||||
@@ -45,16 +44,16 @@ class Email extends \Espo\Core\ORM\Entity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getBodyPlain()
|
||||
{
|
||||
$bodyPlain = $this->get('bodyPlain');
|
||||
$bodyPlain = $this->get('bodyPlain');
|
||||
if (!empty($bodyPlain)) {
|
||||
return $bodyPlain;
|
||||
}
|
||||
|
||||
$body = $this->get('body');
|
||||
|
||||
|
||||
$breaks = array("<br />","<br>","<br/>","<br />","<br />","<br/>","<br>");
|
||||
$body = str_ireplace($breaks, "\r\n", $body);
|
||||
$body = strip_tags($body);
|
||||
@@ -65,7 +64,7 @@ class Email extends \Espo\Core\ORM\Entity
|
||||
{
|
||||
return $this->getBodyPlain();
|
||||
}
|
||||
|
||||
|
||||
public function getBodyForSending()
|
||||
{
|
||||
$body = $this->get('body');
|
||||
@@ -75,19 +74,19 @@ class Email extends \Espo\Core\ORM\Entity
|
||||
$body = str_replace("?entryPoint=attachment&id={$attachment->id}", "cid:{$attachment->id}", $body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$body = str_replace("<table class=\"table table-bordered\">", "<table class=\"table table-bordered\" width=\"100%\">", $body);
|
||||
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
|
||||
public function getInlineAttachments()
|
||||
{
|
||||
$attachmentList = array();
|
||||
$body = $this->get('body');
|
||||
if (!empty($body)) {
|
||||
if (preg_match_all("/\?entryPoint=attachment&id=([^&=\"']+)/", $body, $matches)) {
|
||||
if (!empty($matches[1]) && is_array($matches[1])) {
|
||||
if (preg_match_all("/\?entryPoint=attachment&id=([^&=\"']+)/", $body, $matches)) {
|
||||
if (!empty($matches[1]) && is_array($matches[1])) {
|
||||
foreach($matches[1] as $id) {
|
||||
$attachment = $this->entityManager->getEntity('Attachment', $id);
|
||||
if ($attachment) {
|
||||
@@ -96,7 +95,7 @@ class Email extends \Espo\Core\ORM\Entity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return $attachmentList;
|
||||
}
|
||||
|
||||
@@ -18,14 +18,19 @@
|
||||
*
|
||||
* 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\Entities;
|
||||
|
||||
class User extends \Espo\Core\Entities\Person
|
||||
{
|
||||
{
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->get('isAdmin');
|
||||
}
|
||||
|
||||
public function isActive()
|
||||
{
|
||||
return $this->get('isActive');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,45 +29,49 @@ use \Espo\Core\Exceptions\BadRequest;
|
||||
class Download extends \Espo\Core\EntryPoints\Base
|
||||
{
|
||||
public static $authRequired = true;
|
||||
|
||||
|
||||
protected $fileTypesToShowInline = array(
|
||||
'application/pdf',
|
||||
'application/vnd.ms-word',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.oasis.opendocument.text',
|
||||
'application/vnd.oasis.opendocument.spreadsheet',
|
||||
'text/plain',
|
||||
);
|
||||
|
||||
|
||||
public function run()
|
||||
{
|
||||
{
|
||||
$id = $_GET['id'];
|
||||
if (empty($id)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
|
||||
$attachment = $this->getEntityManager()->getEntity('Attachment', $id);
|
||||
|
||||
|
||||
if (!$attachment) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($attachment->get('parentId') && $attachment->get('parentType')) {
|
||||
$parent = $this->getEntityManager()->getEntity($attachment->get('parentType'), $attachment->get('parentId'));
|
||||
$parent = $this->getEntityManager()->getEntity($attachment->get('parentType'), $attachment->get('parentId'));
|
||||
if (!$this->getAcl()->check($parent)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$fileName = "data/upload/{$attachment->id}";
|
||||
|
||||
|
||||
if (!file_exists($fileName)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
|
||||
|
||||
$type = $attachment->get('type');
|
||||
|
||||
|
||||
$disposition = 'attachment';
|
||||
if (in_array($type, $this->fileTypesToShowInline)) {
|
||||
$disposition = 'inline';
|
||||
}
|
||||
|
||||
|
||||
|
||||
header('Content-Description: File Transfer');
|
||||
if ($type) {
|
||||
header('Content-Type: ' . $type);
|
||||
@@ -80,7 +84,7 @@ class Download extends \Espo\Core\EntryPoints\Base
|
||||
ob_clean();
|
||||
flush();
|
||||
readfile($fileName);
|
||||
exit;
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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\EntryPoints;
|
||||
|
||||
use \Espo\Core\Exceptions\NotFound;
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
class OauthCallback extends \Espo\Core\EntryPoints\Base
|
||||
{
|
||||
public static $authRequired = false;
|
||||
|
||||
public function run()
|
||||
{
|
||||
echo "EspoCRM rocks !!!";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,17 +78,7 @@ class Notifications extends \Espo\Core\Hooks\Base
|
||||
}
|
||||
}
|
||||
if (!empty($userIdList)) {
|
||||
$job = $this->getEntityManager()->getEntity('Job');
|
||||
$job->set(array(
|
||||
'serviceName' => 'Notification',
|
||||
'method' => 'notifyAboutNoteFromJob',
|
||||
'data' => array(
|
||||
'userIdList' => $userIdList,
|
||||
'noteId' => $entity->id
|
||||
),
|
||||
'executeTime' => date('Y-m-d H:i:s'),
|
||||
));
|
||||
$this->getEntityManager()->saveEntity($job);
|
||||
$this->getNotificationService()->notifyAboutNote($userIdList, $entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?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\Jobs;
|
||||
|
||||
use \Espo\Core\Exceptions;
|
||||
|
||||
class Cleanup extends \Espo\Core\Jobs\Base
|
||||
{
|
||||
protected $period = '-1 month';
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->cleanupJobs();
|
||||
$this->cleanupScheduledJobLog();
|
||||
$this->cleanupAttachments();
|
||||
}
|
||||
|
||||
protected function cleanupJobs()
|
||||
{
|
||||
$query = "DELETE FROM `job` WHERE DATE(modified_at) < '".$this->getDate()."' ";
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
|
||||
protected function cleanupScheduledJobLog()
|
||||
{
|
||||
$lastTenRecords = "SELECT c.id FROM (
|
||||
SELECT i1.id
|
||||
FROM scheduled_job_log_record i1
|
||||
CROSS JOIN scheduled_job_log_record i2 ON ( i1.scheduled_job_id = i2.scheduled_job_id
|
||||
AND i1.id < i2.id )
|
||||
GROUP BY i1.id
|
||||
HAVING COUNT( * ) <10
|
||||
ORDER BY i1.created_at DESC
|
||||
) AS c";
|
||||
|
||||
$query = "DELETE FROM `scheduled_job_log_record` WHERE DATE(created_at) < '".$this->getDate()."' AND id NOT IN (".$lastTenRecords.") ";
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
|
||||
protected function getDate($format = 'Y-m-d')
|
||||
{
|
||||
$datetime = new \DateTime();
|
||||
$datetime->modify($this->period);
|
||||
return $datetime->format($format);
|
||||
}
|
||||
|
||||
protected function cleanupAttachments()
|
||||
{
|
||||
$dateBefore = date('Y-m-d H:i:s', time() - 3600 * 24);
|
||||
|
||||
$collection = $this->getEntityManager()->getRepository('Attachment')->where(array(
|
||||
'role' => array(
|
||||
'Import File',
|
||||
'Export File'
|
||||
),
|
||||
'createdAt<' => $dateBefore
|
||||
))->limit(0, 100)->find();
|
||||
|
||||
foreach ($collection as $e) {
|
||||
$this->getEntityManager()->removeEntity($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
@@ -27,21 +27,21 @@ use \Espo\ORM\Entity;
|
||||
class Invitations
|
||||
{
|
||||
protected $entityManager;
|
||||
|
||||
|
||||
protected $smtpParams;
|
||||
|
||||
protected $mailSender;
|
||||
|
||||
|
||||
protected $config;
|
||||
|
||||
|
||||
protected $dateTime;
|
||||
|
||||
|
||||
protected $language;
|
||||
|
||||
|
||||
protected $fileManager;
|
||||
|
||||
|
||||
protected $ics;
|
||||
|
||||
|
||||
public function __construct($entityManager, $smtpParams, $mailSender, $config, $dateTime, $language, $fileManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
@@ -52,17 +52,17 @@ class Invitations
|
||||
$this->language = $language;
|
||||
$this->fileManager = $fileManager;
|
||||
}
|
||||
|
||||
|
||||
protected function getEntityManager()
|
||||
{
|
||||
return $this->entityManager;
|
||||
}
|
||||
|
||||
|
||||
protected function parseInvitationTemplate($contents, $entity, $invitee = null, $uid = null)
|
||||
{
|
||||
|
||||
|
||||
$contents = str_replace('{eventType}', strtolower($this->language->translate($entity->getEntityName(), 'scopeNames')), $contents);
|
||||
|
||||
|
||||
foreach ($entity->getFields() as $field => $d) {
|
||||
if (empty($d['type'])) continue;
|
||||
$key = '{'.$field.'}';
|
||||
@@ -97,6 +97,7 @@ class Invitations
|
||||
if ($uid) {
|
||||
$contents = str_replace('{acceptLink}', $siteUrl . '?entryPoint=eventConfirmation&action=accept&uid=' . $uid->get('name'), $contents);
|
||||
$contents = str_replace('{declineLink}', $siteUrl . '?entryPoint=eventConfirmation&action=decline&uid=' . $uid->get('name'), $contents);
|
||||
$contents = str_replace('{tentativeLink}', $siteUrl . '?entryPoint=eventConfirmation&action=tentativeLink&uid=' . $uid->get('name'), $contents);
|
||||
}
|
||||
return $contents;
|
||||
}
|
||||
@@ -118,7 +119,7 @@ class Invitations
|
||||
|
||||
return file_get_contents($fileName);
|
||||
}
|
||||
|
||||
|
||||
public function sendInvitation(Entity $entity, Entity $invitee, $link)
|
||||
{
|
||||
$uid = $this->getEntityManager()->getEntity('UniqueId');
|
||||
@@ -144,14 +145,14 @@ class Invitations
|
||||
|
||||
$subject = $this->parseInvitationTemplate($subjectTpl, $entity, $invitee, $uid);
|
||||
$subject = str_replace(array("\n", "\r"), '', $subject);
|
||||
|
||||
|
||||
$body = $this->parseInvitationTemplate($bodyTpl, $entity, $invitee, $uid);
|
||||
|
||||
$email->set('subject', $subject);
|
||||
$email->set('body', $body);
|
||||
$email->set('isHtml', true);
|
||||
$this->getEntityManager()->saveEntity($email);
|
||||
|
||||
|
||||
$attachmentName = ucwords($this->language->translate($entity->getEntityName(), 'scopeNames')).'.ics';
|
||||
$attachment = $this->getEntityManager()->getEntity('Attachment');
|
||||
$attachment->set(array(
|
||||
@@ -159,7 +160,7 @@ class Invitations
|
||||
'type' => 'text/calendar',
|
||||
'contents' => $this->getIscContents($entity),
|
||||
));
|
||||
|
||||
|
||||
$email->addAttachment($attachment);
|
||||
|
||||
$emailSender = $this->mailSender;
|
||||
@@ -168,21 +169,21 @@ class Invitations
|
||||
$emailSender->useSmtp($this->smtpParams);
|
||||
}
|
||||
$emailSender->send($email);
|
||||
|
||||
|
||||
$this->getEntityManager()->removeEntity($email);
|
||||
}
|
||||
|
||||
|
||||
protected function getIscContents(Entity $entity)
|
||||
{
|
||||
$user = $entity->get('assignedUser');
|
||||
|
||||
|
||||
$who = '';
|
||||
$email = '';
|
||||
if ($user) {
|
||||
$who = $user->get('name');
|
||||
$email = $user->get('emailAddress');
|
||||
}
|
||||
|
||||
|
||||
$ics = new Ics('//EspoCRM//EspoCRM Calendar//EN', array(
|
||||
'startDate' => strtotime($entity->get('dateStart')),
|
||||
'endDate' => strtotime($entity->get('dateEnd')),
|
||||
@@ -192,9 +193,9 @@ class Invitations
|
||||
'email' => $email,
|
||||
'description' => $entity->get('description'),
|
||||
));
|
||||
|
||||
|
||||
return $ics->get();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ use \Espo\Core\Exceptions\Error;
|
||||
class EventConfirmation extends \Espo\Core\EntryPoints\Base
|
||||
{
|
||||
public static $authRequired = false;
|
||||
|
||||
|
||||
public function run()
|
||||
{
|
||||
$uid = $_GET['uid'];
|
||||
@@ -40,40 +40,42 @@ class EventConfirmation extends \Espo\Core\EntryPoints\Base
|
||||
if (empty($uid) || empty($action)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!in_array($action, array('accept', 'decline'))) {
|
||||
|
||||
if (!in_array($action, array('accept', 'decline', 'tentative'))) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
|
||||
$uniqueId = $this->getEntityManager()->getRepository('UniqueId')->where(array('name' => $uid))->findOne();
|
||||
|
||||
|
||||
if (!$uniqueId) {
|
||||
throw new NotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$data = $uniqueId->get('data');
|
||||
|
||||
|
||||
$eventType = $data->eventType;
|
||||
$eventId = $data->eventId;
|
||||
$inviteeType = $data->inviteeType;
|
||||
$inviteeId = $data->inviteeId;
|
||||
$link = $data->link;
|
||||
|
||||
|
||||
if (!empty($eventType) && !empty($eventId) && !empty($inviteeType) && !empty($inviteeId) && !empty($link)) {
|
||||
$event = $this->getEntityManager()->getEntity($eventType, $eventId);
|
||||
$invitee = $this->getEntityManager()->getEntity($inviteeType, $inviteeId);
|
||||
if ($event && $invitee) {
|
||||
$relDefs = $event->getRelations();
|
||||
$tableName = Util::toUnderscore($relDefs[$link]['relationName']);
|
||||
|
||||
|
||||
$status = 'None';
|
||||
if ($action == 'accept') {
|
||||
$status = 'Accepted';
|
||||
} else if ($action == 'decline') {
|
||||
$status = 'Declined';
|
||||
} else if ($action == 'tentative') {
|
||||
$status = 'Tentative';
|
||||
}
|
||||
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sql = "
|
||||
UPDATE `{$tableName}` SET status = '{$status}'
|
||||
@@ -82,14 +84,14 @@ class EventConfirmation extends \Espo\Core\EntryPoints\Base
|
||||
|
||||
$sth = $pdo->prepare($sql);
|
||||
$sth->execute();
|
||||
|
||||
|
||||
$this->getEntityManager()->getRepository('UniqueId')->remove($uniqueId);
|
||||
|
||||
|
||||
echo $status;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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\Jobs;
|
||||
|
||||
use \Espo\Core\Exceptions;
|
||||
|
||||
class CheckEmailAccounts extends \Espo\Core\Jobs\Base
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$service = $this->getServiceFactory()->create('EmailAccount');
|
||||
$collection = $this->getEntityManager()->getRepository('EmailAccount')->where(array('status' => 'Active'))->find();
|
||||
foreach ($collection as $entity) {
|
||||
try {
|
||||
$service->fetchFromMailServer($entity);
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('Job CheckEmailAccounts: [' . $e->getCode() . '] ' .$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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\Jobs;
|
||||
|
||||
@@ -27,24 +27,18 @@ use \Espo\Core\Exceptions;
|
||||
class CheckInboundEmails extends \Espo\Core\Jobs\Base
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$service = $this->getServiceFactory()->create('InboundEmail');
|
||||
{
|
||||
$service = $this->getServiceFactory()->create('InboundEmail');
|
||||
$collection = $this->getEntityManager()->getRepository('InboundEmail')->where(array('status' => 'Active'))->find();
|
||||
foreach ($collection as $entity) {
|
||||
try {
|
||||
$service->fetchFromMailServer($entity);
|
||||
} catch (\Exception $e) {}
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('Job CheckInboundEmails: [' . $e->getCode() . '] ' .$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$service = $this->getServiceFactory()->create('EmailAccount');
|
||||
$collection = $this->getEntityManager()->getRepository('EmailAccount')->where(array('status' => 'Active'))->find();
|
||||
foreach ($collection as $entity) {
|
||||
try {
|
||||
$service->fetchFromMailServer($entity);
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
"contacts": "Contacts",
|
||||
"opportunities": "Opportunities",
|
||||
"cases": "Cases",
|
||||
"documents": "Documents"
|
||||
"documents": "Documents",
|
||||
"meetingsPrimary": "Meetings (Internal)",
|
||||
"callsPrimary": "Calls (Internal)",
|
||||
"tasksPrimary": "Tasks (Internal)"
|
||||
},
|
||||
"options": {
|
||||
"type": {
|
||||
@@ -26,13 +29,32 @@
|
||||
"Reseller": "Reseller"
|
||||
},
|
||||
"industry": {
|
||||
"Apparel": "Apparel",
|
||||
"Agriculture": "Agriculture",
|
||||
"Advertising": "Advertising",
|
||||
"Apparel & Accessories": "Apparel & Accessories",
|
||||
"Automotive": "Automotive",
|
||||
"Banking": "Banking",
|
||||
"Computer Software": "Computer Software",
|
||||
"Biotechnology": "Biotechnology",
|
||||
"Chemical": "Chemical",
|
||||
"Computer": "Computer",
|
||||
"Education": "Education",
|
||||
"Electronics": "Electronics",
|
||||
"Entertainment & Leisure": "Entertainment & Leisure",
|
||||
"Finance": "Finance",
|
||||
"Insurance": "Insurance"
|
||||
"Food & Beverage": "Food & Beverage",
|
||||
"Grocery": "Grocery",
|
||||
"Insurance": "Insurance",
|
||||
"Legal": "Legal",
|
||||
"Publishing": "Publishing",
|
||||
"Real Estate": "Real Estate",
|
||||
"Service": "Service",
|
||||
"Sports": "Sports",
|
||||
"Sofware": "Sofware",
|
||||
"Technology": "Technology",
|
||||
"Telecommunications": "Telecommunications",
|
||||
"Television": "Television",
|
||||
"Transportation": "Transportation",
|
||||
"Venture Capital": "Venture Capital"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
"acceptanceStatus": {
|
||||
"None": "None",
|
||||
"Accepted": "Accepted",
|
||||
"Declined": "Declined"
|
||||
"Declined": "Declined",
|
||||
"Tentative": "Tentative"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"trashFolder": "Trash Folder",
|
||||
"ssl": "SSL",
|
||||
"createCase": "Create Case",
|
||||
"reply": "Reply",
|
||||
"reply": "Auto-Reply",
|
||||
"caseDistribution": "Case Distribution",
|
||||
"replyEmailTemplate": "Reply Email Template",
|
||||
"replyFromAddress": "Reply From Address",
|
||||
@@ -23,10 +23,11 @@
|
||||
"tooltips": {
|
||||
"reply": "Notify email senders that their emails has been received.",
|
||||
"createCase": "Automatically create case from incoming emails.",
|
||||
"replyToAddress": "Specify email address of this mailbox to make response come here.",
|
||||
"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.",
|
||||
"assignToUser": "User emails/cases will be assigned to.",
|
||||
"team": "Team emails/cases will be related to."
|
||||
"team": "Team emails/cases will be related to.",
|
||||
"targetUserPosition": "Define the position of users which will be destributed with cases."
|
||||
},
|
||||
"links": {
|
||||
},
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"acceptanceStatus": {
|
||||
"None": "None",
|
||||
"Accepted": "Accepted",
|
||||
"Declined": "Declined"
|
||||
"Declined": "Declined",
|
||||
"Tentative": "Tentative"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"options": {
|
||||
"job": {
|
||||
"CheckInboundEmails": "Check Inbound Emails",
|
||||
"CheckEmailAccounts": "Check Personal Email Accounts",
|
||||
"SendEmailReminders": "Send Email Reminders"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
["billingAddress","sicCode","industry","type"]
|
||||
[
|
||||
"assignedUser",
|
||||
"billingAddress",
|
||||
"emailAddress",
|
||||
"industry",
|
||||
"type"
|
||||
]
|
||||
@@ -1 +1,10 @@
|
||||
["status","dateStart","parent"]
|
||||
[
|
||||
"assignedUser",
|
||||
"dateStart",
|
||||
"direction",
|
||||
"status",
|
||||
"parent",
|
||||
"users",
|
||||
"contacts",
|
||||
"leads"
|
||||
]
|
||||
@@ -1 +1 @@
|
||||
["status"]
|
||||
["status", "assignedUser", "users"]
|
||||
@@ -1 +1,10 @@
|
||||
["number","status","priority","account","contact","description"]
|
||||
[
|
||||
"account",
|
||||
"assignedUser",
|
||||
"contact",
|
||||
"number",
|
||||
"status",
|
||||
"priority",
|
||||
"description",
|
||||
"teams"
|
||||
]
|
||||
@@ -1 +1,7 @@
|
||||
["account","emailAddress","address", "title"]
|
||||
[
|
||||
"account",
|
||||
"assignedUser",
|
||||
"emailAddress",
|
||||
"address",
|
||||
"title"
|
||||
]
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
{"name":"status"}
|
||||
],
|
||||
[
|
||||
{"name":"team"}
|
||||
|
||||
{"name":"team"},
|
||||
{"name":"replyToAddress"}
|
||||
],
|
||||
[
|
||||
{"name":"assignToUser"}
|
||||
@@ -41,10 +41,6 @@
|
||||
],
|
||||
[
|
||||
{"name":"targetUserPosition"},
|
||||
{"name":"replyToAddress"}
|
||||
],
|
||||
[
|
||||
false,
|
||||
{"name":"replyFromAddress"}
|
||||
],
|
||||
[
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
[
|
||||
"assignedUser",
|
||||
"address",
|
||||
"status",
|
||||
"source",
|
||||
"opportunityAmountConverted",
|
||||
"teams",
|
||||
"address"
|
||||
"teams"
|
||||
]
|
||||
|
||||
@@ -1 +1,9 @@
|
||||
["status","dateStart","parent"]
|
||||
[
|
||||
"assignedUser",
|
||||
"dateStart",
|
||||
"status",
|
||||
"parent",
|
||||
"users",
|
||||
"contacts",
|
||||
"leads"
|
||||
]
|
||||
@@ -1 +1 @@
|
||||
["status"]
|
||||
["status", "assignedUser", "users"]
|
||||
@@ -1,10 +1,11 @@
|
||||
[
|
||||
"teams",
|
||||
"assignedUser",
|
||||
"stage",
|
||||
"probability",
|
||||
"account",
|
||||
"amountConverted",
|
||||
"assignedUser",
|
||||
"closeDate",
|
||||
"leadSource"
|
||||
"contacts",
|
||||
"leadSource",
|
||||
"probability",
|
||||
"stage",
|
||||
"teams"
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{"name":"name", "width": 32, "link": true},
|
||||
{"name":"account"},
|
||||
{"name":"stage"},
|
||||
{"name":"amount"},
|
||||
{"name":"createdAt"}
|
||||
]
|
||||
@@ -1 +1,4 @@
|
||||
["address"]
|
||||
[
|
||||
"address",
|
||||
"emailAddress"
|
||||
]
|
||||
@@ -1 +1,7 @@
|
||||
["status","priority","dateStart","dateEnd"]
|
||||
[
|
||||
"assignedUser",
|
||||
"status",
|
||||
"priority",
|
||||
"dateStart",
|
||||
"dateEnd"
|
||||
]
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"true" : [
|
||||
{
|
||||
"action": "show",
|
||||
"fields": ["replyEmailTemplate", "replyFromAddress", "replyFromName", "replyToAddress"]
|
||||
"fields": ["replyEmailTemplate", "replyFromAddress", "replyFromName"]
|
||||
}, {
|
||||
"action": "setRequired",
|
||||
"fields": ["replyEmailTemplate"]
|
||||
@@ -58,7 +58,7 @@
|
||||
"default": [
|
||||
{
|
||||
"action": "hide",
|
||||
"fields": ["replyEmailTemplate", "replyFromAddress", "replyFromName", "replyToAddress"]
|
||||
"fields": ["replyEmailTemplate", "replyFromAddress", "replyFromName"]
|
||||
}, {
|
||||
"action": "setNotRequired",
|
||||
"fields": ["replyEmailTemplate"]
|
||||
|
||||
@@ -21,7 +21,35 @@
|
||||
},
|
||||
"industry": {
|
||||
"type": "enum",
|
||||
"options": ["", "Apparel", "Banking", "Computer Software", "Education", "Electronics", "Finance", "Insurance"]
|
||||
"options": [
|
||||
"",
|
||||
"Agriculture",
|
||||
"Advertising",
|
||||
"Apparel & Accessories",
|
||||
"Automotive",
|
||||
"Banking",
|
||||
"Biotechnology",
|
||||
"Chemical",
|
||||
"Computer",
|
||||
"Education",
|
||||
"Electronics",
|
||||
"Entertainment & Leisure",
|
||||
"Finance",
|
||||
"Food & Beverage",
|
||||
"Grocery",
|
||||
"Insurance",
|
||||
"Legal",
|
||||
"Publishing",
|
||||
"Real Estate",
|
||||
"Service",
|
||||
"Sports",
|
||||
"Sofware",
|
||||
"Technology",
|
||||
"Telecommunications",
|
||||
"Television",
|
||||
"Transportation",
|
||||
"Venture Capital"
|
||||
]
|
||||
},
|
||||
"sicCode": {
|
||||
"type": "varchar",
|
||||
@@ -116,7 +144,8 @@
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "EntityTeam"
|
||||
"relationName": "EntityTeam",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"contacts": {
|
||||
"type": "hasMany",
|
||||
@@ -138,25 +167,47 @@
|
||||
"entity": "Document",
|
||||
"foreign": "accounts"
|
||||
},
|
||||
"meetingsPrimary": {
|
||||
"type": "hasMany",
|
||||
"entity": "Meeting",
|
||||
"foreign": "account",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"emailsPrimary": {
|
||||
"type": "hasMany",
|
||||
"entity": "Email",
|
||||
"foreign": "account",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"callsPrimary": {
|
||||
"type": "hasMany",
|
||||
"entity": "Call",
|
||||
"foreign": "account",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"meetings": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Meeting",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"calls": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Call",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"tasks": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Task",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"emails": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Email",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
|
||||
@@ -54,11 +54,12 @@
|
||||
"type": "enum",
|
||||
"notStorable": true,
|
||||
"disabled": true,
|
||||
"options": ["None", "Accepted", "Declined"]
|
||||
"options": ["None", "Accepted", "Tentative", "Declined"]
|
||||
},
|
||||
"users": {
|
||||
"type": "linkMultiple",
|
||||
"disabled": true,
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutListDisabled": true,
|
||||
"view": "Crm:Meeting.Fields.Attendees",
|
||||
"columns": {
|
||||
"status": "acceptanceStatus"
|
||||
@@ -66,7 +67,8 @@
|
||||
},
|
||||
"contacts": {
|
||||
"type": "linkMultiple",
|
||||
"disabled": true,
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutListDisabled": true,
|
||||
"view": "Crm:Meeting.Fields.Contacts",
|
||||
"columns": {
|
||||
"status": "acceptanceStatus"
|
||||
@@ -74,7 +76,8 @@
|
||||
},
|
||||
"leads": {
|
||||
"type": "linkMultiple",
|
||||
"disabled": true,
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutListDisabled": true,
|
||||
"view": "Crm:Meeting.Fields.Attendees",
|
||||
"columns": {
|
||||
"status": "acceptanceStatus"
|
||||
@@ -124,7 +127,8 @@
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "EntityTeam"
|
||||
"relationName": "EntityTeam",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"users": {
|
||||
"type": "hasMany",
|
||||
@@ -136,7 +140,8 @@
|
||||
"len": "36",
|
||||
"default": "None"
|
||||
}
|
||||
}
|
||||
},
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"contacts": {
|
||||
"type": "hasMany",
|
||||
@@ -148,7 +153,8 @@
|
||||
"len": "36",
|
||||
"default": "None"
|
||||
}
|
||||
}
|
||||
},
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"leads": {
|
||||
"type": "hasMany",
|
||||
@@ -160,7 +166,8 @@
|
||||
"len": "36",
|
||||
"default": "None"
|
||||
}
|
||||
}
|
||||
},
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"parent": {
|
||||
"type": "belongsToParent",
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
"type": "link",
|
||||
"view": "Crm:Case.Fields.Contact"
|
||||
},
|
||||
"inboundEmail": {
|
||||
"type": "link",
|
||||
"readOnly": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
@@ -80,7 +84,12 @@
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "EntityTeam"
|
||||
"relationName": "EntityTeam",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"inboundEmail": {
|
||||
"type": "belongsTo",
|
||||
"entity": "InboundEmail"
|
||||
},
|
||||
"account": {
|
||||
"type": "belongsTo",
|
||||
@@ -95,22 +104,26 @@
|
||||
"meetings": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Meeting",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"calls": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Call",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"tasks": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Task",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"emails": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Email",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"salutationName": {
|
||||
"type": "enum",
|
||||
"options": ["", "Mr.", "Mrs.", "Dr.", "Drs."]
|
||||
"options": ["", "Mr.", "Mrs.", "Dr."]
|
||||
},
|
||||
"firstName": {
|
||||
"type": "varchar",
|
||||
@@ -140,7 +140,8 @@
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "EntityTeam"
|
||||
"relationName": "EntityTeam",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"account": {
|
||||
"type": "belongsTo",
|
||||
@@ -155,7 +156,8 @@
|
||||
"type": "varchar",
|
||||
"len": 50
|
||||
}
|
||||
}
|
||||
},
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"opportunities": {
|
||||
"type": "hasMany",
|
||||
@@ -170,21 +172,26 @@
|
||||
"meetings": {
|
||||
"type": "hasMany",
|
||||
"entity": "Meeting",
|
||||
"foreign": "contacts"
|
||||
"foreign": "contacts",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"calls": {
|
||||
"type": "hasMany",
|
||||
"entity": "Call",
|
||||
"foreign": "contacts"
|
||||
"foreign": "contacts",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"tasks": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Task",
|
||||
"foreign": "parent"
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"activities": {
|
||||
"type": "joint",
|
||||
"links": ["meetings", "calls", "tasks"]
|
||||
"emails": {
|
||||
"type": "hasChildren",
|
||||
"entity": "Task",
|
||||
"foreign": "parent",
|
||||
"layoutRelationshipsDisabled": true
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
|
||||
@@ -89,7 +89,8 @@
|
||||
"teams": {
|
||||
"type": "hasMany",
|
||||
"entity": "Team",
|
||||
"relationName": "EntityTeam"
|
||||
"relationName": "EntityTeam",
|
||||
"layoutRelationshipsDisabled": true
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
},
|
||||
"targetUserPosition": {
|
||||
"type": "enum",
|
||||
"view": "Crm:InboundEmail.Fields.TargetUserPosition"
|
||||
"view": "Crm:InboundEmail.Fields.TargetUserPosition",
|
||||
"tooltip": true
|
||||
},
|
||||
"reply": {
|
||||
"type": "bool",
|
||||
@@ -71,7 +72,8 @@
|
||||
},
|
||||
"replyToAddress": {
|
||||
"type": "varchar",
|
||||
"tooltip": true
|
||||
"tooltip": true,
|
||||
"required": true
|
||||
},
|
||||
"replyFromName": {
|
||||
"type": "varchar"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user