Compare commits

..

33 Commits

Author SHA1 Message Date
yuri e0c3530ae5 change version 2015-01-29 12:34:13 +02:00
yuri 0b6c5c2862 Merge branch 'hotfix/2.9.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/2.9.2 2015-01-29 12:13:54 +02:00
yuri ce9ff10cb0 fix imap 2015-01-29 12:03:43 +02:00
Taras Machyshyn de688bfa12 CronManager minor changes 2015-01-29 11:44:47 +02:00
yuri 95fd66a7a6 Entity::isSaved 2015-01-28 10:38:57 +02:00
yuri 171df33736 move logic to Repository::afterSave 2015-01-27 16:27:05 +02:00
yuri ef05e4e9f4 add cryptKey to systemItems 2015-01-27 11:40:51 +02:00
yuri b67580ee1d Merge branch 'hotfix/2.9.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/2.9.2 2015-01-26 17:36:06 +02:00
Taras Machyshyn ac56c3f79e added 'Cleanup' job 2015-01-26 15:24:10 +02:00
Taras Machyshyn e218056683 Cron job improvements 2015-01-26 12:28:00 +02:00
yuri e74b90d048 fix email notFound 2015-01-26 11:12:25 +02:00
yuri b2a653a3dc fix App controller 2015-01-26 11:09:54 +02:00
yuri 48ee7d4cc7 Merge branch 'hotfix/2.9.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/2.9.2 2015-01-23 18:19:25 +02:00
yuri 1d38f80748 improve email accounts 2015-01-23 18:19:02 +02:00
Taras Machyshyn c712366737 cron jobs improvements and fixes 2015-01-23 17:44:59 +02:00
yuri 71f40fd440 checlemailaccounts job 2015-01-23 17:12:12 +02:00
yuri 9049ebfa8c fix email importer 2015-01-23 17:05:33 +02:00
yuri 632ee66155 Merge branch 'hotfix/2.9.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/2.9.1 2015-01-22 15:14:25 +02:00
yuri 4446bc3167 fix oauth client 2015-01-22 15:14:16 +02:00
Taras Machyshyn 785934c7cc Language: chnaged set(), delete() methods 2015-01-22 14:49:40 +02:00
Taras Machyshyn ac36096d55 Merge branch 'hotfix/2.9.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/2.9.1 2015-01-22 13:15:15 +02:00
Taras Machyshyn 8ec8cb3c56 Metadata: fixed description 2015-01-22 13:14:55 +02:00
Taras Machyshyn 1f7cb2402d changed Metadata methods set(), get(), added save() 2015-01-22 13:14:06 +02:00
yuri 3825893ec1 integrations changes 2015-01-22 11:28:17 +02:00
yuri a4ed78b953 change version 2015-01-21 17:22:08 +02:00
yuri eed9059913 integration changes 2015-01-21 17:11:12 +02:00
yuri 20a1053413 fix select manager 2015-01-21 15:50:41 +02:00
Taras Machyshyn e814ea9ec6 added 'save()' method to Layout 2015-01-20 15:34:29 +02:00
Taras Machyshyn de16e9a9ce added '2015' to 'About' page 2015-01-20 12:36:52 +02:00
Taras Machyshyn 62fba991e2 fixed pt_BR translation syntax errors 2015-01-20 12:31:46 +02:00
Taras Machyshyn 832c63e014 added 'getPhpContents()' to FileManager 2015-01-20 12:22:04 +02:00
yuri 0194d364b8 change opportunity list small layout 2015-01-19 15:09:30 +02:00
yuri 1c7785f6ba fix salutation list 2015-01-19 10:41:45 +02:00
74 changed files with 1548 additions and 733 deletions
+2 -2
View File
@@ -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'),
@@ -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);
}
}
+7 -5
View File
@@ -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)
+1 -1
View File
@@ -221,7 +221,7 @@ class Container
private function loadScheduledJob()
{
return new \Espo\Core\Cron\ScheduledJob(
return new \Espo\Core\Utils\ScheduledJob(
$this
);
}
+131 -44
View File
@@ -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;
}
}
}
@@ -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);
}
}
+2 -2
View File
@@ -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);
}
}
-1
View File
@@ -24,7 +24,6 @@ namespace Espo\Core\Jobs;
use \Espo\Core\Container;
abstract class Base
{
private $container;
+44 -39
View File
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -27,18 +27,18 @@ use \Zend\Mime\Mime as Mime;
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,29 +47,29 @@ 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);
@@ -77,14 +77,14 @@ class Importer
$email->set('from', $fromArr[0]);
$email->set('to', implode(';', $this->getAddressListFromMessage($message, 'to')));
$email->set('cc', implode(';', $this->getAddressListFromMessage($message, 'cc')));
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,17 +103,18 @@ class Importer
$email->set('deliveryDate', $deliveryDate);
}
}
$inlineIds = array();
if ($message->isMultipart()) {
foreach (new \RecursiveIteratorIterator($message) as $part) {
echo "-";
$this->importPartDataToEmail($email, $part, $inlineIds);
}
} else {
$this->importPartDataToEmail($email, $message, $inlineIds);
}
$body = $email->get('body');
if (!empty($body)) {
foreach ($inlineIds as $cid => $attachmentId) {
@@ -156,12 +157,12 @@ class Importer
}
$this->getEntityManager()->saveEntity($email);
return $email;
} catch (\Exception $e) {}
}
protected function checkIsDuplicate($email)
{
if ($email->get('messageIdInternal')) {
@@ -173,12 +174,12 @@ class Importer
}
}
}
protected function getAddressListFromMessage($message, $type)
{
$addressList = array();
if (isset($message->$type)) {
$list = $message->getHeader($type)->getAddressList();
foreach ($list as $address) {
$addressList[] = $address->getEmail();
@@ -186,13 +187,17 @@ 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);
@@ -209,10 +214,10 @@ class Importer
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)) {
@@ -225,32 +230,32 @@ class Importer
$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;
@@ -261,7 +266,7 @@ class Importer
}
} catch (\Exception $e) {}
}
protected function getContentFromPart($part)
{
if ($part instanceof \Zend\Mime\Part) {
@@ -271,20 +276,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,11 +297,11 @@ 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') {
+7 -5
View File
@@ -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 . '"'));
}
}
@@ -20,48 +20,21 @@
* 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 isMultipart()
{
$this->serviceFactory = $serviceFactory;
}
protected function getServiceFactory()
{
return $this->serviceFactory;
}
public function run($job)
{
$serviceName = $job['service_name'];
if (!$this->getServiceFactory()->checkExists($serviceName)) {
throw new NotFound();
if (!isset($this->contentType)) {
return false;
}
$service = $this->getServiceFactory()->create($serviceName);
$serviceMethod = $job['method'];
if (!method_exists($service, $serviceMethod)) {
throw new NotFound();
try {
return stripos($this->contentType, 'multipart/') === 0;
} catch (Exception\ExceptionInterface $e) {
return false;
}
$data = $job['data'];
if (Json::isJSON($data)) {
$data = Json::decode($data, true);
}
$service->$serviceMethod($data);
}
}
}
@@ -161,12 +161,18 @@ 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)
{
parent::afterSave($entity);
$this->handleEmailAddressSave($entity);
$this->handlePhoneNumberSave($entity);
$this->handleSpecifiedRelations($entity);
$this->getEntityManager()->getHookManager()->process($this->entityName, 'afterSave', $entity);
}
@@ -218,9 +224,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$entity->set($restoreData);
$this->handleEmailAddressSave($entity);
$this->handlePhoneNumberSave($entity);
$this->handleSpecifiedRelations($entity);
return $result;
}
+12 -9
View File
@@ -132,14 +132,16 @@ class Base
$d[$field . '*'] = $item['value'] . '%';
}
}
$where['OR'] = $d;
$where[] = array(
'OR' => $d
);
}
}
}
$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 +162,6 @@ class Base
if (is_array($idsValue) && count($idsValue) == 1) {
$idsValue = $idsValue[0];
}
$relDefs = $this->getSeed()->getRelations();
@@ -180,8 +181,6 @@ class Base
}
}
}
}
if (!empty($part)) {
@@ -221,7 +220,9 @@ class Base
}
}
$result['whereClause']['OR'] = $d;
$result['whereClause'][] = array(
'OR' => $d
);
}
}
@@ -246,9 +247,11 @@ class Base
$result['leftJoins'][] = 'teams';
}
$result['whereClause']['OR'] = array(
'Team.id' => $this->user->get('teamsIds'),
'assignedUserId' => $this->user->id
$result['whereClause'][] = array(
'OR' => array(
'Team.id' => $this->user->get('teamsIds'),
'assignedUserId' => $this->user->id
)
);
}
}
+2 -2
View File
@@ -53,7 +53,7 @@ class ServiceFactory
$config = $this->getContainer()->get('config');
if (file_exists($this->cacheFile) && $config->get('useCache')) {
$this->data = $this->getFileManager()->getContents($this->cacheFile);
$this->data = $this->getFileManager()->getPhpContents($this->cacheFile);
} else {
$this->data = $this->getClassNameHash($this->paths['corePath']);
@@ -65,7 +65,7 @@ class ServiceFactory
$this->data = array_merge($this->data, $this->getClassNameHash($this->paths['customPath']));
if ($config->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();
}
+2 -2
View File
@@ -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.');
}
+5 -4
View File
@@ -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;
@@ -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
}
}
}
}
}
@@ -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);
}
+7 -7
View File
@@ -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,25 @@ 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);
return $this->getLanguage()->save();
}
@@ -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();
}
+38 -21
View File
@@ -156,17 +156,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 +220,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 +249,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 +277,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 +297,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);
}
/**
+45 -22
View File
@@ -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());
}
}
}
+85 -45
View File
@@ -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;
}
}
}
+117 -26
View File
@@ -61,6 +61,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 +128,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 +217,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 +361,7 @@ class Metadata
}
if (empty($this->ormMeta)) {
$this->ormMeta = $this->getFileManager()->getContents($this->ormCacheFile);
$this->ormMeta = $this->getFileManager()->getPhpContents($this->ormCacheFile);
}
return $this->ormMeta;
@@ -279,7 +370,7 @@ class Metadata
public function setOrmMetadata(array $ormMeta)
{
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');
}
+2 -2
View File
@@ -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.');
}
+3 -3
View File
@@ -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.']');
@@ -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');
@@ -83,6 +83,7 @@ return array (
'permissionMap',
'permissionRules',
'passwordSalt',
'cryptKey'
),
'adminItems' =>
array (
@@ -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 !!!";
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
namespace Espo\Jobs;
use \Espo\Core\Exceptions;
class Cleanup extends \Espo\Core\Jobs\Base
{
protected $period = '-1 month';
public function run()
{
$this->cleanupJobs();
$this->cleanupScheduledJobLog();
}
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);
}
}
@@ -0,0 +1,42 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
namespace Espo\Modules\Crm\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) {}
}
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,8 +27,8 @@ 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 {
@@ -36,15 +36,7 @@ class CheckInboundEmails extends \Espo\Core\Jobs\Base
} catch (\Exception $e) {}
}
$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;
}
}
}
@@ -3,6 +3,7 @@
"options": {
"job": {
"CheckInboundEmails": "Check Inbound Emails",
"CheckEmailAccounts": "Check Personal Email Accounts",
"SendEmailReminders": "Send Email Reminders"
}
}
@@ -1,6 +1,6 @@
[
{"name":"name", "width": 32, "link": true},
{"name":"account"},
{"name":"stage"},
{"name":"amount"},
{"name":"createdAt"}
]
@@ -5,7 +5,7 @@
},
"salutationName": {
"type": "enum",
"options": ["", "Mr.", "Mrs.", "Dr.", "Drs."]
"options": ["", "Mr.", "Mrs.", "Dr."]
},
"firstName": {
"type": "varchar",
@@ -5,7 +5,7 @@
},
"salutationName": {
"type": "enum",
"options": ["", "Mr.", "Mrs.", "Dr.", "Drs."]
"options": ["", "Mr.", "Mrs.", "Dr."]
},
"firstName": {
"type": "varchar",
+13 -1
View File
@@ -28,8 +28,10 @@ abstract class Entity implements IEntity
private $isNew = false;
private $isSaved = false;
/**
* Entity name.
* Entity name (entity type).
* @var string
*/
protected $entityName;
@@ -226,6 +228,16 @@ abstract class Entity implements IEntity
$this->isNew = $isNew;
}
public function isSaved()
{
return $this->isSaved;
}
public function setIsSaved($isSaved)
{
$this->isSaved = $isSaved;
}
public function getEntityName()
{
return $this->entityName;
+2 -1
View File
@@ -132,12 +132,13 @@ class RDB extends \Espo\ORM\Repository
public function save(Entity $entity)
{
$this->beforeSave($entity);
if ($entity->isNew()) {
if ($entity->isNew() && !$entity->isSaved()) {
$result = $this->getMapper()->insert($entity);
} else {
$result = $this->getMapper()->update($entity);
}
if ($result) {
$entity->setIsSaved(true);
$this->afterSave($entity);
if ($entity->isNew()) {
$entity->setIsNew(false);
@@ -132,7 +132,8 @@
"authTokens": "Active auth sessions. IP address and last access date.",
"authentication": "Authentication settings.",
"currency": "Currency settings and rates.",
"extensions": "Install or uninstall extensions."
"extensions": "Install or uninstall extensions.",
"integrations": "Integration with third-party services."
},
"options": {
"previewSize": {
@@ -235,8 +235,7 @@
"salutationName": {
"Mr.": "Mr.",
"Mrs.": "Mrs.",
"Dr.": "Dr.",
"Drs.": "Drs."
"Dr.": "Dr."
},
"language": {
"af_ZA":"Afrikaans",
@@ -6,7 +6,8 @@
"redirectUri": "Redirect URI"
},
"messages": {
"selectIntegration": "Select an integration in menu."
"selectIntegration": "Select an integration from menu.",
"noIntegrations": "No Integrations is available."
},
"help": {
"Google": "<p><b>Obtain OAuth 2.0 credentials from the Google Developers Console.</b></p><p>Visit <a href=\"https://console.developers.google.com/project\">Google Developers Console</a> to obtain OAuth 2.0 credentials such as a Client ID and Client Secret that are known to both Google and EspoCRM application.</p>"
@@ -28,7 +28,9 @@
"Teams and Access Control": "Teams and Access Control",
"Forgot Password?": "Forgot Password?",
"Password Change Request": "Password Change Request",
"Email Address": "Email Address"
"Email Address": "Email Address",
"External Accounts": "External Accounts",
"Email Accounts": "Email Accounts"
},
"tooltips": {
"defaultTeam": "All records created by this user will be related to this team by default.",
@@ -8,7 +8,7 @@
"users": "Usuários"
},
"tooltips": {
"roles": "Todos os usuários deste time terão acesso as configurações das regras selecionadas."
"roles": "Todos os usuários deste time terão acesso as configurações das regras selecionadas.",
"positionList": "Posições disponíveis neste time. E.g. Vendedor, Gerente."
},
"labels": {
@@ -40,7 +40,7 @@
"messages": {
"passwordWillBeSent": "A senha será enviada para o email do usuário.",
"accountInfoEmailSubject": "Informações da Conta",
"accountInfoEmailBody": "Informações da sua conta:\n\nUsuário: {userName}\nSenha: {password}\n\n{siteUrl}",,
"accountInfoEmailBody": "Informações da sua conta:\n\nUsuário: {userName}\nSenha: {password}\n\n{siteUrl}",
"passwordChangeLinkEmailSubject": "Solicitação para troca da senha",
"passwordChangeLinkEmailBody": "Você pode atualizar sua senha usando este link {link}. Esta URL é única e expirará em breve.",
"passwordChanged": "A senha foi atualizada",
@@ -1 +1,6 @@
[{"name":"name","width":30,"link":true},{"name":"host"},{"name":"status"}]
[
{"name":"name","width":30,"link":true},
{"name":"host"},
{"name":"status"},
{"name": "assignedUser"}
]
@@ -22,6 +22,11 @@
"label":"Currency",
"description":"currency"
},
{
"url":"#Admin/integrations",
"label":"Integrations",
"description":"integrations"
},
{
"url":"#Admin/upgrade",
"label":"Upgrade",
@@ -1,5 +1,5 @@
{
"controller": "Controllers.Record",
"controller": "Controllers.EmailAccount",
"recordViews":{
"list":"EmailAccount.Record.List",
"detail": "EmailAccount.Record.Detail",
@@ -22,7 +22,7 @@
},
"salutationName": {
"type": "enum",
"options": ["", "Mr.", "Mrs.", "Dr.", "Drs."]
"options": ["", "Mr.", "Mrs.", "Dr."]
},
"firstName": {
"type": "varchar",
@@ -1,22 +0,0 @@
{
"fields": {
"clientId": {
"type": "varchar",
"maxLength": 255,
"required": true
},
"clientSecret": {
"type": "varchar",
"maxLength": 255,
"required": true
}
},
"params": {
"endpoint": "https://accounts.google.com/o/oauth2/auth",
"tokenEndpoint": "https://accounts.google.com/o/oauth2/token",
"scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar"
},
"allowUserAccounts": true,
"authMethod": "OAuth2",
"clientClassName": "\\Espo\\Core\\ExternalAccount\\Clients\\Google"
}
@@ -18,19 +18,20 @@
*
* 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\SelectManagers;
class EmailAccount extends \Espo\Core\SelectManagers\Base
{
{
protected function access(&$result)
{
if (!array_key_exists('whereClause', $result)) {
$result['whereClause'] = array();
}
$result['whereClause']['assignedUserId'] = $this->user->id;
if (!$this->user->isAdmin()) {
$result['whereClause']['assignedUserId'] = $this->user->id;
}
}
}
+1 -1
View File
@@ -94,7 +94,7 @@ class Email extends Record
public function getEntity($id = null)
{
$entity = parent::getEntity($id);
if (!empty($id)) {
if (!empty($entity) && !empty($id)) {
if ($entity->get('fromEmailAddressName')) {
$entity->set('from', $entity->get('fromEmailAddressName'));
+62 -60
View File
@@ -28,102 +28,104 @@ use \Espo\Core\Exceptions\Error;
use \Espo\Core\Exceptions\Forbidden;
class EmailAccount extends Record
{
{
protected $internalFields = array('password');
protected $readOnlyFields = array('assignedUserId', 'fetchData');
protected $readOnlyFields = array('fetchData');
const PORTION_LIMIT = 10;
protected function init()
{
$this->dependencies[] = 'fileManager';
$this->dependencies[] = 'crypt';
}
protected function getFileManager()
{
return $this->injections['fileManager'];
}
protected function getCrypt()
{
return $this->injections['crypt'];
}
protected function handleInput(&$data)
{
parent::handleInput($data);
{
parent::handleInput($data);
if (array_key_exists('password', $data)) {
$data['password'] = $this->getCrypt()->encrypt($data['password']);
}
}
public function getFolders($params)
{
{
$password = $params['password'];
if (!empty($params['id'])) {
$entity = $this->getEntityManager()->getEntity('EmailAccount', $params['id']);
if ($entity) {
$password = $this->getCrypt()->decrypt($entity->get('password'));
}
}
}
$imapParams = array(
'host' => $params['host'],
'port' => $params['port'],
'user' => $params['username'],
'password' => $password,
);
if (!empty($params['ssl'])) {
$imapParams['ssl'] = 'SSL';
}
$foldersArr = array();
$storage = new \Zend\Mail\Storage\Imap($imapParams);
}
$foldersArr = array();
$storage = new \Zend\Mail\Storage\Imap($imapParams);
$folders = new \RecursiveIteratorIterator($storage->getFolders(), \RecursiveIteratorIterator::SELF_FIRST);
foreach ($folders as $name => $folder) {
foreach ($folders as $name => $folder) {
$foldersArr[] = mb_convert_encoding($folder->getGlobalName(), 'UTF-8', 'UTF7-IMAP');
}
return $foldersArr;
}
public function createEntity($data)
{
$entity = parent::createEntity($data);
if ($entity) {
$entity->set('assignedUserId', $this->getUser()->id);
if (!$this->getUser()->isAdmin()) {
$entity->set('assignedUserId', $this->getUser()->id);
}
$this->getEntityManager()->saveEntity($entity);
}
return $entity;
}
public function fetchFromMailServer(Entity $emailAccount)
{
if ($emailAccount->get('status') != 'Active') {
throw new Error();
}
$importer = new \Espo\Core\Mail\Importer($this->getEntityManager(), $this->getFileManager(), $this->getConfig());
$maxSize = $this->getConfig()->get('emailMessageMaxSize');
$user = $this->getEntityManager()->getEntity('User', $emailAccount->get('assignedUserId'));
if (!$user) {
throw new Error();
}
$userId = $user->id;
$teamId = $user->get('defaultTeam');
$teamId = $user->get('defaultTeam');
$fetchData = json_decode($emailAccount->get('fetchData'), true);
if (empty($fetchData)) {
$fetchData = array();
$fetchData = array();
}
if (!array_key_exists('lastUID', $fetchData)) {
$fetchData['lastUID'] = array();
@@ -131,31 +133,31 @@ class EmailAccount extends Record
if (!array_key_exists('lastUID', $fetchData)) {
$fetchData['lastDate'] = array();
}
$imapParams = array(
'host' => $emailAccount->get('host'),
'port' => $emailAccount->get('port'),
'user' => $emailAccount->get('username'),
'password' => $this->getCrypt()->decrypt($emailAccount->get('password')),
);
if ($emailAccount->get('ssl')) {
$imapParams['ssl'] = 'SSL';
}
$storage = new \Espo\Core\Mail\Storage\Imap($imapParams);
$monitoredFolders = $emailAccount->get('monitoredFolders');
$monitoredFolders = $emailAccount->get('monitoredFolders');
if (empty($monitoredFolders)) {
throw new Error();
throw new Error();
}
$monitoredFoldersArr = explode(',', $monitoredFolders);
$monitoredFoldersArr = explode(',', $monitoredFolders);
foreach ($monitoredFoldersArr as $folder) {
$folder = mb_convert_encoding(trim($folder), 'UTF7-IMAP', 'UTF-8');
$storage->selectFolder($folder);
$lastUID = 0;
$lastDate = 0;
if (!empty($fetchData['lastUID'][$folder])) {
@@ -175,32 +177,32 @@ class EmailAccount extends Record
return false;
}
}
if ((count($ids) == 1) && !empty($lastUID)) {
if ($storage->getUniqueId($ids[0]) == $lastUID) {
continue;
}
}
$k = 0;
foreach ($ids as $i => $id) {
$k = 0;
foreach ($ids as $i => $id) {
if ($k == count($ids) - 1) {
$lastUID = $storage->getUniqueId($id);
}
if ($maxSize) {
if ($storage->getSize($id) > $maxSize * 1024 * 1024) {
continue;
}
}
$message = $storage->getMessage($id);
$email = $importer->importMessage($message, $userId, array($teamId));
if ($k == count($ids) - 1) {
$lastUID = $storage->getUniqueId($id);
if ($message) {
$dt = new \DateTime($message->date);
if ($dt) {
@@ -209,22 +211,22 @@ class EmailAccount extends Record
}
}
}
if ($k == self::PORTION_LIMIT - 1) {
$lastUID = $storage->getUniqueId($id);
break;
}
$k++;
}
}
$fetchData['lastUID'][$folder] = $lastUID;
$fetchData['lastDate'][$folder] = $lastDate;
$emailAccount->set('fetchData', json_encode($fetchData));
$this->getEntityManager()->saveEntity($emailAccount);
$this->getEntityManager()->saveEntity($emailAccount);
}
return true;
return true;
}
}
+6 -6
View File
@@ -1,5 +1,5 @@
<div class="page-header">
<h3>EspoCRM - Open Source CRM application</h3>
<h3>EspoCRM - Open Source CRM application</h3>
</div>
<div class="row">
@@ -8,19 +8,19 @@
<strong>Version {{version}}</strong>
</p>
<p>
Copyright &copy; 2014 EspoCRM: Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko.
Copyright &copy; 2014-2015 EspoCRM: Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko.
<br>
Website: <a href="http://www.espocrm.com">http://www.espocrm.com</a>.
</p>
</p>
<p>
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.
</p>
</p>
<p>
EspoCRM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty
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.
</p>
</div>
@@ -0,0 +1,47 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
Espo.define('Controllers.EmailAccount', 'Controllers.Record', function (Dep) {
return Dep.extend({
list: function (options) {
var userId = (options || {}).userId;
if (!userId) {
Dep.prototype.list.call(this, options);
} else {
this.getCollection(function (collection) {
collection.where = [{
type: 'equals',
field: 'assignedUserId',
value: userId
}];
this.main(this.getViewName('list'), {
scope: this.name,
collection: collection,
});
});
}
},
});
});
@@ -78,7 +78,12 @@ Espo.define('Views.Admin.Integrations.Index', 'View', function (Dep) {
renderDefaultPage: function () {
$('#integration-header').html('').hide();
$('#integration-content').html(this.translate('selectIntegration', 'messages', 'Integration'));
if (this.integrationList.length) {
var msg = this.translate('selectIntegration', 'messages', 'Integration');
} else {
var msg = '<p class="lead">' + this.translate('noIntegrations', 'messages', 'Integration') + '</p>';
}
$('#integration-content').html(msg);
},
renderHeader: function () {
@@ -17,22 +17,22 @@
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
************************************************************************/
Espo.define('Views.Admin.Integrations.OAuth2', 'Views.Admin.Integrations.Edit', function (Dep) {
return Dep.extend({
template: 'admin.integrations.oauth2',
data: function () {
return _.extend({
// TODO fetch from server
redirectUri: this.getConfig().get('siteUrl') + '/oauthcallback'
redirectUri: this.getConfig().get('siteUrl') + '?entryPoint=oauthCallback'
}, Dep.prototype.data.call(this));
},
},
});
});
@@ -17,19 +17,29 @@
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
************************************************************************/
Espo.define('Views.EmailAccount.Record.Edit', ['Views.Record.Edit', 'Views.EmailAccount.Record.Detail'], function (Dep, Detail) {
return Dep.extend({
return Dep.extend({
afterRender: function () {
Dep.prototype.afterRender.call(this);
Detail.prototype.initSslFieldListening.call(this);
Dep.prototype.afterRender.call(this);
Detail.prototype.initSslFieldListening.call(this);
if (this.getUser().isAdmin()) {
var fieldView = this.getFieldView('assignedUser');
if (fieldView) {
fieldView.readOnly = false;
fieldView.setMode('edit');
fieldView.render();
}
}
},
});
});
+3 -4
View File
@@ -22,7 +22,7 @@
Espo.define('Views.Email.List', 'Views.List', function (Dep) {
return Dep.extend({
createButton: false,
setup: function () {
@@ -34,7 +34,6 @@ Espo.define('Views.Email.List', 'Views.List', function (Dep) {
"link": "#EmailAccount"
});
}
},
actionComposeEmail: function () {
@@ -48,7 +47,7 @@ Espo.define('Views.Email.List', 'Views.List', function (Dep) {
view.notify(false);
});
},
getSearchDefaultData: function () {
return {
bool: {
@@ -56,7 +55,7 @@ Espo.define('Views.Email.List', 'Views.List', function (Dep) {
}
};
},
});
});
@@ -40,38 +40,38 @@ Espo.define('Views.ExternalAccount.Index', 'View', function (Dep) {
},
},
setup: function () {
setup: function () {
this.externalAccountList = this.collection.toJSON();
this.userId = this.getUser().id;
this.id = this.options.id || null;
this.id = this.options.id || null;
if (this.id) {
this.userId = this.id.split('__')[1];
}
this.on('after:render', function () {
this.renderHeader();
this.on('after:render', function () {
this.renderHeader();
if (!this.id) {
this.renderDefaultPage();
} else {
this.openExternalAccount(this.id);
}
});
}
});
},
openExternalAccount: function (id) {
this.id = id;
var integration = this.integration = id.split('__')[0];
this.userId = id.split('__')[1];
this.getRouter().navigate('#ExternalAccount/edit/' + id, {trigger: false});
var viewName =
this.getMetadata().get('integrations.' + integration + '.userView') ||
'ExternalAccount.' + this.getMetadata().get('integrations.' + integration + '.authMethod');
this.notify('Loading...');
this.getRouter().navigate('#ExternalAccount/edit/' + id, {trigger: false});
var viewName =
this.getMetadata().get('integrations.' + integration + '.userView') ||
'ExternalAccount.' + this.getMetadata().get('integrations.' + integration + '.authMethod');
this.notify('Loading...');
this.createView('content', viewName, {
el: '#external-account-content',
id: id,
@@ -79,10 +79,10 @@ Espo.define('Views.ExternalAccount.Index', 'View', function (Dep) {
}, function (view) {
this.renderHeader();
view.render();
this.notify(false);
this.notify(false);
$(window).scrollTop(0);
}.bind(this));
},
},
renderDefaultPage: function () {
$('#external-account-header').html('').hide();
@@ -17,18 +17,18 @@
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
************************************************************************/
Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
return Dep.extend({
template: 'external-account.oauth2',
events: {
},
data: function () {
return {
integration: this.integration,
@@ -36,9 +36,9 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
isConnected: this.isConnected
};
},
isConnected: false,
events: {
'click button[data-action="cancel"]': function () {
this.getRouter().navigate('#ExternalAccount', {trigger: true});
@@ -50,26 +50,25 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
this.connect();
}
},
setup: function () {
this.integration = this.options.integration;
this.id = this.options.id;
this.helpText = false;
if (this.getLanguage().has(this.integration, 'help', 'ExternalAccount')) {
this.helpText = this.translate(this.integration, 'help', 'ExternalAccount');
}
this.fieldList = [];
this.dataFieldList = [];
this.dataFieldList = [];
this.model = new Espo.Model();
this.model.id = this.id;
this.model.name = 'ExternalAccount';
this.model.urlRoot = 'ExternalAccount';
this.model.defs = {
fields: {
enabled: {
@@ -77,32 +76,32 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
type: 'bool'
},
}
};
this.wait(true);
};
this.wait(true);
this.model.populateDefaults();
this.listenToOnce(this.model, 'sync', function () {
this.createFieldView('bool', 'enabled');
$.ajax({
url: 'ExternalAccount/action/getOAuth2Info?id=' + this.id,
dataType: 'json'
}).done(function (respose) {
this.clientId = respose.clientId;
this.redirectUri = respose.redirectUri;
if (respose.isConnected) {
this.redirectUri = respose.redirectUri;
if (respose.isConnected) {
this.isConnected = true;
}
this.wait(false);
}
this.wait(false);
}.bind(this));
}, this);
this.model.fetch();
this.model.fetch();
},
hideField: function (name) {
this.$el.find('label.field-label-' + name).addClass('hide');
this.$el.find('div.field-' + name).addClass('hide');
@@ -111,7 +110,7 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
view.enabled = false;
}
},
showField: function (name) {
this.$el.find('label.field-label-' + name).removeClass('hide');
this.$el.find('div.field-' + name).removeClass('hide');
@@ -120,12 +119,12 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
view.enabled = true;
}
},
afterRender: function () {
if (!this.model.get('enabled')) {
this.$el.find('.data-panel').addClass('hidden');
}
this.listenTo(this.model, 'change:enabled', function () {
if (this.model.get('enabled')) {
this.$el.find('.data-panel').removeClass('hidden');
@@ -134,7 +133,7 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
}
}, this);
},
createFieldView: function (type, name, readOnly, params) {
this.createView(name, this.getFieldManager().getViewName(type), {
model: this.model,
@@ -148,45 +147,45 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
});
this.fieldList.push(name);
},
save: function () {
this.fieldList.forEach(function (field) {
var view = this.getView(field);
if (!view.readOnly) {
view.fetchToModel();
}
}, this);
}, this);
var notValid = false;
this.fieldList.forEach(function (field) {
notValid = this.getView(field).validate() || notValid;
}, this);
if (notValid) {
this.notify('Not valid', 'error');
return;
}
this.listenToOnce(this.model, 'sync', function () {
}
this.listenToOnce(this.model, 'sync', function () {
this.notify('Saved', 'success');
if (!this.model.get('enabled')) {
if (!this.model.get('enabled')) {
this.setNotConnected();
}
}, this);
this.notify('Saving...');
this.model.save();
},
popup: function (options, callback) {
options.windowName = options.windowName || 'ConnectWithOAuth';
options.windowOptions = options.windowOptions || 'location=0,status=0,width=800,height=400';
options.callback = options.callback || function(){ window.location.reload(); };
var self = this;
var path = options.path;
var arr = [];
var params = (options.params || {});
for (var name in params) {
@@ -195,17 +194,17 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
}
}
path += '?' + arr.join('&');
var parseUrl = function (str) {
var code = null;
var error = null;
str = str.substr(str.indexOf('?') + 1, str.length);
str.split('&').forEach(function (part) {
var arr = part.split('=');
var name = decodeURI(arr[0]);
var value = decodeURI(arr[1] || '');
if (name == 'code') {
code = value;
}
@@ -223,14 +222,14 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
}
}
}
popup = window.open(path, options.windowName, options.windowOptions);
interval = window.setInterval(function () {
if (popup.closed) {
window.clearInterval(interval);
} else {
var res = parseUrl(popup.location.href.toString());
if (res) {
if (res) {
callback.call(self, res);
popup.close();
window.clearInterval(interval);
@@ -238,9 +237,8 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
}
}, 500);
},
connect: function () {
this.notify('Please wait...');
this.popup({
path: this.getMetadata().get('integrations.' + this.integration + '.params.endpoint'),
params: {
@@ -250,7 +248,7 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
response_type: 'code',
access_type: 'offline',
approval_prompt: 'force'
}
}
}, function (res) {
if (res.errror) {
this.notify(false);
@@ -268,35 +266,35 @@ Espo.define('Views.ExternalAccount.OAuth2', 'View', function (Dep) {
dataType: 'json',
error: function () {
this.$el.find('[data-action="connect"]').removeClass('disabled');
}.bind(this)
}.bind(this)
}).done(function (response) {
this.notify(false);
if (response === true) {
this.setConneted();
} else {
this.setNotConneted();
this.setNotConneted();
}
this.$el.find('[data-action="connect"]').removeClass('disabled');
this.$el.find('[data-action="connect"]').removeClass('disabled');
}.bind(this));
} else {
this.notify('Error occured', 'error');
}
});
},
setConneted: function () {
this.isConnected = true;
this.$el.find('[data-action="connect"]').addClass('hidden');;
this.$el.find('.connected-label').removeClass('hidden');
},
setNotConnected: function () {
this.isConnected = false;
this.$el.find('[data-action="connect"]').removeClass('hidden');;
this.$el.find('.connected-label').addClass('hidden');
},
});
});
+8 -5
View File
@@ -40,18 +40,23 @@ Espo.define('Views.List', ['Views.Main', 'SearchManager'], function (Dep, Search
searchPanel: true,
searchManager: true,
searchManager: null,
createButton: true,
setup: function () {
this.setupSearchManager();
this.setupSorting();
this.collection.maxSize = this.getConfig().get('recordsPerPage') || this.collection.maxSize;
if (this.getMetadata().get('clientDefs.' + this.scope + '.disableSearchPanel')) {
this.searchPanel = false;
}
if (this.searchPanel) {
this.setupSearchManager();
}
this.setupSorting();
if (this.searchPanel) {
this.createView('search', 'Record.Search', {
collection: this.collection,
@@ -86,8 +91,6 @@ Espo.define('Views.List', ['Views.Main', 'SearchManager'], function (Dep, Search
var searchManager = new SearchManager(collection, 'list', this.getStorage(), this.getDateTime(), this.getSearchDefaultData());
searchManager.loadStored();
collection.where = searchManager.getWhere();
collection.maxSize = this.getConfig().get('recordsPerPage') || collection.maxSize;
this.searchManager = searchManager;
},
+1 -1
View File
@@ -279,7 +279,7 @@ Espo.define('Views.Record.Detail', 'View', function (Dep) {
getFieldView: function (name) {
var view = this.getView('record').getView(name) || null;
if (!view && this.getView('side')) {
view = this.getView('side').getView(name);
view = (this.getView('side').getFields() || {})[name];
}
return view || null;
},
+42 -18
View File
@@ -18,26 +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/.
************************************************************************/
Espo.define('Views.User.Record.Detail', 'Views.Record.Detail', function (Dep) {
return Dep.extend({
sideView: 'User.Record.DetailSide',
editModeEnabled: false,
setup: function () {
Dep.prototype.setup.call(this);
this.buttons = _.clone(this.buttons);
if (this.model.id == this.getUser().id || this.getUser().isAdmin()) {
this.buttons.push({
name: 'preferences',
label: 'Preferences',
style: 'default'
});
if (!this.model.get('isAdmin')) {
this.buttons.push({
name: 'access',
@@ -45,7 +45,7 @@ Espo.define('Views.User.Record.Detail', 'Views.Record.Detail', function (Dep) {
style: 'default'
});
}
if (this.model.id == this.getUser().id) {
this.buttons.push({
name: 'changePassword',
@@ -53,40 +53,64 @@ Espo.define('Views.User.Record.Detail', 'Views.Record.Detail', function (Dep) {
style: 'default'
});
}
if ((this.getAcl().check('EmailAccountScope') && this.model.id == this.getUser().id) || this.getUser().isAdmin()) {
this.buttons.push({
name: 'emailAccounts',
label: "Email Accounts",
style: 'default'
});
}
if (this.model.id == this.getUser().id) {
this.buttons.push({
name: 'externalAccounts',
label: 'External Accounts',
style: 'default'
});
}
}
if (this.model.id == this.getUser().id) {
this.listenTo(this.model, 'after:save', function () {
this.getUser().set(this.model.toJSON());
}.bind(this));
}
},
actionChangePassword: function () {
this.notify('Loading...');
this.createView('changePassword', 'Modals.ChangePassword', {
userId: this.model.id
}, function (view) {
view.render();
this.notify(false);
this.listenToOnce(view, 'changed', function () {
setTimeout(function () {
this.getBaseController().logout();
}.bind(this), 2000);
}, this);
}.bind(this));
},
actionPreferences: function () {
this.getRouter().navigate('#Preferences/edit/' + this.model.id, {trigger: true});
},
actionEmailAccounts: function () {
this.getRouter().navigate('#EmailAccount/list/userId=' + this.model.id, {trigger: true});
},
actionExternalAccounts: function () {
this.getRouter().navigate('#ExternalAccount', {trigger: true});
},
actionAccess: function () {
this.notify('Loading...');
$.ajax({
url: 'User/action/acl',
type: 'GET',
@@ -102,11 +126,11 @@ Espo.define('Views.User.Record.Detail', 'Views.Record.Detail', function (Dep) {
view.render();
}.bind(this));
}.bind(this));
},
});
});
+14 -1
View File
@@ -34,13 +34,26 @@ return array(
'name' => 'Check Inbound Emails',
'job' => 'CheckInboundEmails',
'status' => 'Active',
'scheduling' => '/10 * * * *',
'scheduling' => '/5 * * * *',
),
1 => array(
'name' => 'Check Personal Email Accounts',
'job' => 'CheckEmailAccounts',
'status' => 'Active',
'scheduling' => '/10 * * * *',
),
2 => array(
'name' => 'Send Email Reminders',
'job' => 'SendEmailReminders',
'status' => 'Active',
'scheduling' => '/2 * * * *',
),
3 => array(
'name' => 'Clean-up',
'job' => 'Cleanup',
'status' => 'Active',
'scheduling' => '1 1 * * 0',
),
),
);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "2.9.0",
"version": "2.9.2",
"description": "",
"main": "index.php",
"repository": {
+21 -21
View File
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -28,24 +28,24 @@ use tests\ReflectionHelper;
class CronManagerTest extends \PHPUnit_Framework_TestCase
{
protected $object;
protected $objects;
protected $filesPath= 'tests/testData/EntryPoints';
protected function setUp()
{
{
$this->objects['container'] = $this->getMockBuilder('\Espo\Core\Container')->disableOriginalConstructor()->getMock();
$this->objects['serviceFactory'] = $this->getMockBuilder('\Espo\Core\ServiceFactory')->disableOriginalConstructor()->getMock();
$this->objects['config'] = $this->getMockBuilder('\Espo\Core\Utils\Config')->disableOriginalConstructor()->getMock();
$this->objects['fileManager'] = $this->getMockBuilder('\Espo\Core\Utils\File\Manager')->disableOriginalConstructor()->getMock();
$this->objects['fileManager'] = $this->getMockBuilder('\Espo\Core\Utils\File\Manager')->disableOriginalConstructor()->getMock();
$map = array(
array('config', $this->objects['config']),
array('fileManager', $this->objects['fileManager']),
array('serviceFactory', $this->objects['serviceFactory']),
array('serviceFactory', $this->objects['serviceFactory']),
);
$this->objects['container']
@@ -55,7 +55,7 @@ class CronManagerTest extends \PHPUnit_Framework_TestCase
$this->object = new \Espo\Core\CronManager( $this->objects['container'] );
$this->reflection = new ReflectionHelper($this->object);
$this->reflection = new ReflectionHelper($this->object);
}
protected function tearDown()
@@ -68,50 +68,50 @@ class CronManagerTest extends \PHPUnit_Framework_TestCase
{
$this->objects['fileManager']
->expects($this->once())
->method('getContents')
->method('getPhpContents')
->will($this->returnValue(false));
$this->objects['config']
->expects($this->exactly(2))
->method('get')
->will($this->returnValue(50));
$this->assertTrue( $this->reflection->invokeMethod('checkLastRunTime', array()) );
}
$this->assertTrue( $this->reflection->invokeMethod('checkLastRunTime', array()) );
}
function testCheckLastRunTime()
{
$this->objects['fileManager']
->expects($this->once())
->method('getContents')
->method('getPhpContents')
->will($this->returnValue(time()-60));
$this->objects['config']
->expects($this->once())
->method('get')
->will($this->returnValue(50));
$this->assertTrue( $this->reflection->invokeMethod('checkLastRunTime', array()) );
}
$this->assertTrue( $this->reflection->invokeMethod('checkLastRunTime', array()) );
}
function testCheckLastRunTimeTooFrequency()
{
$this->objects['fileManager']
->expects($this->once())
->method('getContents')
->method('getPhpContents')
->will($this->returnValue(time()-49));
$this->objects['config']
->expects($this->once())
->method('get')
->will($this->returnValue(50));
$this->assertFalse( $this->reflection->invokeMethod('checkLastRunTime', array()) );
}
$this->assertFalse( $this->reflection->invokeMethod('checkLastRunTime', array()) );
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -150,7 +150,7 @@ class ConfigTest extends \PHPUnit_Framework_TestCase
public function testSystemConfigMerge()
{
$configDataWithoutSystem = $this->objects['fileManager']->getContents($this->configPath);
$configDataWithoutSystem = $this->objects['fileManager']->getPhpContents($this->configPath);
$this->assertArrayNotHasKey('systemItems', $configDataWithoutSystem);
$this->assertArrayNotHasKey('adminItems', $configDataWithoutSystem);
+3 -4
View File
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -89,7 +89,7 @@ class FieldManagerTest extends \PHPUnit_Framework_TestCase
->method('get')
->will($this->returnValue($data));
$this->assertTrue($this->object->update('name', $data, 'Account'));
$this->object->update('name', $data, 'Account');
}
public function testUpdateCustomFieldIsNotChanged()
@@ -138,10 +138,9 @@ class FieldManagerTest extends \PHPUnit_Framework_TestCase
"isCustom" => true,
);
$this->assertTrue($this->object->update('varName', $data, 'CustomEntity'));
$this->object->update('varName', $data, 'CustomEntity');
}
public function testRead()
{
$data = array(
+111 -11
View File
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -80,7 +80,6 @@ class LanguageTest extends \PHPUnit_Framework_TestCase
$this->object->setLanguage($originalLang);
}
public function testGetLangCacheFile()
{
$cacheFile = $this->cacheFile;
@@ -96,7 +95,6 @@ class LanguageTest extends \PHPUnit_Framework_TestCase
$this->object->setLanguage($originalLang);
}
public function testGetData()
{
$result = array (
@@ -142,7 +140,6 @@ class LanguageTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($result, $this->reflection->invokeMethod('getData', array()));
}
public function testGet()
{
$result = array (
@@ -204,7 +201,6 @@ class LanguageTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($result, $this->object->translate('language', 'options', 'Global', $requiredOptions));
}
public function testTranslateArray()
{
$input = array(
@@ -227,20 +223,124 @@ class LanguageTest extends \PHPUnit_Framework_TestCase
public function testSet()
{
$label = 'TEST';
$this->object->set('label', $label, 'fields', 'User');
$this->object->set('User', 'fields', 'label', $label);
$this->assertEquals($label, $this->object->translate('label', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
'label' => 'TEST',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$label2 = 'TEST2';
$this->object->set('name', $label2, 'fields', 'User');
$this->object->set('User', 'fields', 'name', $label2);
$this->assertEquals($label2, $this->object->translate('name', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
'label' => 'TEST',
'name' => 'TEST2',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$label3 = 'TEST3';
$this->object->set('name', $label3, 'fields', 'Account');
$this->object->set('Account', 'fields', 'name', $label3);
$this->assertEquals($label3, $this->object->translate('name', 'fields', 'Account'));
$this->reflection->invokeMethod('init', array(true));
$result = array(
'User' => array(
'fields' => array(
'label' => 'TEST',
'name' => 'TEST2',
),
),
'Account' => array(
'fields' => array(
'name' => 'TEST3',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$this->object->clearChanges();
$this->assertEquals(array(), $this->reflection->getProperty('changedData'));
$this->assertNotEquals('TEST', $this->object->get('User', 'fields', 'label'));
}
public function testDelete()
{
$this->object->delete('User', 'fields', 'label');
$this->assertNull($this->object->get('User.fields.label'));
$result = array(
'User' => array(
'fields' => array(
'label',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$this->object->delete('User', 'fields', 'name');
$this->assertNull($this->object->get('User.fields.name'));
$result = array(
'User' => array(
'fields' => array(
'label',
'name',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$this->object->clearChanges();
$this->assertNotNull($this->object->get('User.fields.label'));
$this->assertNotNull($this->object->get('User.fields.name'));
$this->assertEquals(array(), $this->reflection->getProperty('deletedData'));
}
public function testUndelete()
{
$this->object->delete('User', 'fields', 'label');
$this->assertNull($this->object->get('User.fields.label'));
$this->object->delete('User', 'fields', 'name');
$this->assertNull($this->object->get('User.fields.name'));
$label = 'TEST';
$this->object->set('User', 'fields', 'label', $label);
$this->assertEquals($label, $this->object->translate('label', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
1 => 'name',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$label2 = 'TEST2';
$this->object->set('User', 'fields', 'name', $label2);
$this->assertEquals($label2, $this->object->translate('name', 'fields', 'User'));
$result = array(
'User' => array(
'fields' => array(
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
}
}
?>
+176 -19
View File
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -24,52 +24,209 @@ namespace tests\Espo\Core\Utils;
use tests\ReflectionHelper;
class MetadataTest extends \PHPUnit_Framework_TestCase
{
protected $object;
protected $objects;
protected $reflection;
protected $defaultCacheFile = 'tests/testData/Utils/Metadata/metadata.php';
protected $cacheFile = 'tests/testData/cache/metadata.php';
protected $ormCacheFile = 'tests/testData/Utils/Metadata/ormMetadata.php';
protected function setUp()
{
{
/*copy defaultCacheFile file to cache*/
if (!file_exists($this->cacheFile)) {
copy($this->defaultCacheFile, $this->cacheFile);
}
$this->objects['config'] = $this->getMockBuilder('\Espo\Core\Utils\Config')->disableOriginalConstructor()->getMock();
$this->objects['fileManager'] = new \Espo\Core\Utils\File\Manager();
$this->objects['fileManager'] = new \Espo\Core\Utils\File\Manager();
$this->objects['Unifier'] = $this->getMockBuilder('\Espo\Core\Utils\File\Unifier')->disableOriginalConstructor()->getMock();
//set to use cache
$this->objects['config']
->expects($this->any())
->method('get')
->will($this->returnValue(true));
->will($this->returnValue(true));
$this->object = new \Espo\Core\Utils\Metadata($this->objects['config'], $this->objects['fileManager']);
$this->reflection = new ReflectionHelper($this->object);
$this->reflection->setProperty('cacheFile', 'tests/testData/Utils/Metadata/metadata.php');
$this->reflection->setProperty('ormCacheFile', 'tests/testData/Utils/Metadata/ormMetadata.php');
$this->reflection = new ReflectionHelper($this->object);
$this->reflection->setProperty('cacheFile', $this->cacheFile);
$this->reflection->setProperty('ormCacheFile', $this->ormCacheFile);
}
protected function tearDown()
{
$this->object = NULL;
}
function testGet()
{
public function testGet()
{
$result = 'System';
$this->assertEquals($result, $this->object->get('app.adminPanel.system.label'));
$this->assertEquals($result, $this->object->get('app.adminPanel.system.label'));
$result = 'fields';
$this->assertArrayHasKey($result, $this->object->get('entityDefs.User'));
}
$this->assertArrayHasKey($result, $this->object->get('entityDefs.User'));
}
public function testSet()
{
$data = array (
'fields' =>
array (
'name' =>
array (
'required' => false,
'maxLength' => 150,
'view' => 'Views.Test.Custom',
),
),
);
$this->object->set('entityDefs', 'Attachment', $data);
$this->assertEquals('Views.Test.Custom', $this->object->get('entityDefs.Attachment.fields.name.view'));
$this->assertEquals(150, $this->object->get('entityDefs.Attachment.fields.name.maxLength'));
$result = array(
'entityDefs' => array(
'Attachment' => $data
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
}
$data = array (
'fields' =>
array (
'name' =>
array (
'maxLength' => 200,
),
),
);
$this->object->set('entityDefs', 'Attachment', $data);
$this->assertEquals(200, $this->object->get('entityDefs.Attachment.fields.name.maxLength'));
$this->assertEquals('Views.Test.Custom', $this->object->get('entityDefs.Attachment.fields.name.view'));
?>
$result = array(
'entityDefs' => array(
'Attachment' => array (
'fields' =>
array (
'name' =>
array (
'required' => false,
'maxLength' => 200,
'view' => 'Views.Test.Custom',
),
),
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('changedData'));
$this->object->clearChanges();
$this->assertEquals(array(), $this->reflection->getProperty('changedData'));
$this->assertNull($this->object->get('entityDefs.Attachment.fields.name.view'));
}
public function testDelete()
{
$data = array (
'fields.name.type',
);
$this->object->delete('entityDefs', 'Attachment', $data);
$this->assertNull($this->object->get('entityDefs.Attachment.fields.name.type'));
$result = array(
'entityDefs' => array(
'Attachment' => array(
'fields.name.type',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$data = array (
'fields.name.required',
);
$this->object->delete('entityDefs', 'Attachment', $data);
$this->assertNull($this->object->get('entityDefs.Attachment.fields.name.required'));
$result = array(
'entityDefs' => array(
'Attachment' => array(
'fields.name.type',
'fields.name.required',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$this->object->init(false);
$this->assertNotNull($this->object->get('entityDefs.Attachment.fields.name.type'));
$this->assertNotNull($this->object->get('entityDefs.Attachment.fields.name.required'));
$this->object->clearChanges();
$this->assertEquals(array(), $this->reflection->getProperty('deletedData'));
}
public function testUndelete()
{
$data = array (
'fields.name.type',
'fields.name.required',
);
$this->object->delete('entityDefs', 'Attachment', $data);
$this->assertNull($this->object->get('entityDefs.Attachment.fields.name.type'));
$data = array (
'fields' =>
array (
'name' =>
array (
'type' => 'enum',
),
),
);
$this->object->set('entityDefs', 'Attachment', $data);
$this->assertEquals('enum', $this->object->get('entityDefs.Attachment.fields.name.type'));
$result = array(
'entityDefs' => array(
'Attachment' => array(
1 => 'fields.name.required',
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
$data = array (
'fields' =>
array (
'name' =>
array (
'required' => true,
),
),
);
$this->object->set('entityDefs', 'Attachment', $data);
$this->assertEquals(true, $this->object->get('entityDefs.Attachment.fields.name.required'));
$result = array(
'entityDefs' => array(
'Attachment' => array(
),
),
);
$this->assertEquals($result, $this->reflection->getProperty('deletedData'));
}
}