Compare commits

...

23 Commits

Author SHA1 Message Date
yuri a89872b785 3.0.1 2015-02-18 17:13:41 +02:00
yuri b6bc0471b8 Merge branch 'hotfix/3.0.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/3.0.1 2015-02-18 17:05:02 +02:00
Taras Machyshyn afd0f5a5ef Extensions: don't run afterUninstall script for updating an extension 2015-02-18 09:53:53 +02:00
yuri 533fff4d30 Merge branch 'hotfix/3.0.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/3.0.1 2015-02-17 16:49:03 +02:00
yuri 7b0b3d05ad fix zend mail 2015-02-17 16:46:13 +02:00
Taras Machyshyn e09c101f82 Upgrades: fixed an error 2015-02-17 16:45:21 +02:00
Taras Machyshyn 4e6b300477 Upgrades: improved restore functionality 2015-02-17 16:39:51 +02:00
Taras Machyshyn 7453eb583a impoved Uprades code 2015-02-17 15:53:35 +02:00
Taras Machyshyn b5d2eb93aa Upgrades: impoved beforeRunAction(), afterRunAction() 2015-02-17 15:53:27 +02:00
Taras Machyshyn b5b8ab4a8e added initialize(), finalize() methods to upgrade scripts 2015-02-17 15:53:18 +02:00
Taras Machyshyn b6010a7ad7 added a new fileManager test, fixes 2015-02-17 15:53:07 +02:00
Taras Machyshyn ebdb649bd7 Improved upgrade/install extenions feature 2015-02-17 15:52:53 +02:00
yuri 220d4d1b77 improve importer 2015-02-17 15:21:47 +02:00
yuri 2f8e1aeaf2 fix field iframe wysiwyg height 2015-02-17 14:57:17 +02:00
yuri 20db3b0bd2 improve email quick-edit 2015-02-17 12:25:39 +02:00
yuri 6d796c0b20 fix stream note header 2015-02-17 11:50:04 +02:00
yuri 100fec9409 increase notifications panel width 2015-02-17 11:15:54 +02:00
yuri 0b5beeba85 play sound when notification comes 2015-02-17 11:10:07 +02:00
yuri 909d31b9fe frontend: improve notification 2015-02-17 10:59:28 +02:00
yuri d9522cf555 fix navbar resize 2015-02-17 10:39:01 +02:00
yuri e42a877bf6 try catch for importing emails 2015-02-17 10:36:44 +02:00
yuri 8beeb64cbf log checkemail errors 2015-02-16 14:56:28 +02:00
yuri da3a18e766 Duplicate email check by messageId instead of messageIdInternal 2015-02-16 12:43:57 +02:00
41 changed files with 568 additions and 267 deletions
+3 -3
View File
@@ -61,7 +61,7 @@ class Extension extends \Espo\Core\Controllers\Record
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
$manager->install($data['id']);
$manager->install($data);
return true;
}
@@ -74,7 +74,7 @@ class Extension extends \Espo\Core\Controllers\Record
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
$manager->uninstall($data['id']);
$manager->uninstall($data);
return true;
}
@@ -103,7 +103,7 @@ class Extension extends \Espo\Core\Controllers\Record
{
$manager = new \Espo\Core\ExtensionManager($this->getContainer());
$manager->delete($params['id']);
$manager->delete($params);
return true;
}
@@ -28,7 +28,6 @@ class ExtensionManager extends Upgrades\Base
protected $params = array(
'packagePath' => 'data/upload/extensions',
'backupPath' => 'data/.backup/extensions',
'scriptNames' => array(
+49 -19
View File
@@ -24,6 +24,8 @@ namespace Espo\Core\Mail;
use \Zend\Mime\Mime as Mime;
use \Espo\ORM\Entity;
class Importer
{
private $entityManager;
@@ -74,9 +76,13 @@ class Importer
if (isset($message->from)) {
$email->set('fromName', $message->from);
}
$toArr = $this->getAddressListFromMessage($message, 'to');
$ccArr = $this->getAddressListFromMessage($message, 'cc');
$email->set('from', $fromArr[0]);
$email->set('to', implode(';', $this->getAddressListFromMessage($message, 'to')));
$email->set('cc', implode(';', $this->getAddressListFromMessage($message, 'cc')));
$email->set('to', implode(';', $toArr));
$email->set('cc', implode(';', $ccArr));
if (isset($message->messageId) && !empty($message->messageId)) {
$email->set('messageId', $message->messageId);
@@ -122,6 +128,8 @@ class Importer
$email->set('body', $body);
}
$parentFound = false;
if (isset($message->references) && !empty($message->references)) {
$reference = str_replace(array('/', '@'), " ", trim($message->references, '<>'));
$parentType = $parentId = null;
@@ -131,26 +139,19 @@ class Importer
if (!empty($parentType) && !empty($parentId)) {
$email->set('parentType', $parentType);
$email->set('parentId', $parentId);
$parentFound = true;
}
}
}
if (!$email->has('parentId')) {
if (!$parentFound) {
$from = $email->get('from');
if ($from) {
$contact = $this->getEntityManager()->getRepository('Contact')->where(array(
'emailAddress' => $from
))->findOne();
if ($contact) {
if (!$this->getConfig()->get('b2cMode')) {
if ($contact->get('accountId')) {
$email->set('parentType', 'Account');
$email->set('parentId', $contact->get('accountId'));
}
} else {
$email->set('parentType', 'Contact');
$email->set('parentId', $contact->id);
}
$parentFound = $this->findParent($email, $from);
}
if (!$parentFound) {
if (!empty($toArr)) {
$parentFound = $this->findParent($email, $toArr[0]);
}
}
}
@@ -162,11 +163,40 @@ class Importer
} catch (\Exception $e) {}
}
protected function checkIsDuplicate($email)
protected function findParent(Entity $email, $emailAddress)
{
if ($email->get('messageIdInternal')) {
$contact = $this->getEntityManager()->getRepository('Contact')->where(array(
'emailAddress' => $emailAddress
))->findOne();
if ($contact) {
if (!$this->getConfig()->get('b2cMode')) {
if ($contact->get('accountId')) {
$email->set('parentType', 'Account');
$email->set('parentId', $contact->get('accountId'));
return true;
}
} else {
$email->set('parentType', 'Contact');
$email->set('parentId', $contact->id);
return true;
}
} else {
$account = $this->getEntityManager()->getRepository('Account')->where(array(
'emailAddress' => $emailAddress
))->findOne();
if ($account) {
$email->set('parentType', 'Account');
$email->set('parentId', $account->id);
return true;
}
}
}
protected function checkIsDuplicate(Entity $email)
{
if ($email->get('messageId')) {
$duplicate = $this->getEntityManager()->getRepository('Email')->where(array(
'messageIdInternal' => $email->get('messageIdInternal')
'messageId' => $email->get('messageId')
))->findOne();
if ($duplicate) {
return true;
@@ -24,6 +24,15 @@ namespace Espo\Core\Mail\Storage;
class Message extends \Zend\Mail\Storage\Message
{
public function __isset($name)
{
$headers = $this->getHeaders();
if (empty($headers) || !is_object($headers)) {
return false;
}
return $this->getHeaders()->has($name);
}
public function isMultipart()
{
if (!isset($this->contentType)) {
+1
View File
@@ -30,6 +30,7 @@ class UpgradeManager extends Upgrades\Base
protected $params = array(
'packagePath' => 'data/upload/upgrades',
'backupPath' => 'data/.backup/upgrades',
'scriptNames' => array(
'before' => 'BeforeUpgrade',
@@ -304,7 +304,7 @@ abstract class Base
/**
* Get a list of files defined in manifest.json
*
* @return [type] [description]
* @return array
*/
protected function getDeleteFileList()
{
@@ -352,6 +352,18 @@ abstract class Base
return $this->data['fileList'];
}
protected function getRestoreFileList()
{
if (!isset($this->data['restoreFileList'])) {
$backupPath = $this->getPath('backupPath');
$backupFilePath = Util::concatPath($backupPath, self::FILES);
$this->data['restoreFileList'] = $this->getFileManager()->getFileList($backupFilePath, true, '', true, true);
}
return $this->data['restoreFileList'];
}
protected function copy($sourcePath, $destPath, $recursively = false, array $fileList = null, $copyOnlyFiles = false)
{
try {
@@ -478,18 +490,31 @@ abstract class Base
/**
* Execute an action. For ex., execute uninstall action in install
*
* @param [type] $actionName [description]
* @param [type] $data [description]
* @return [type] [description]
* @param string $actionName
* @param string $data
*
* @return void
*/
protected function executeAction($actionName, $data)
{
$currentAction = $this->getActionManager()->getAction();
$actionManager = $this->getActionManager();
$this->getActionManager()->setAction($actionName);
$this->getActionManager()->run($data);
$currentAction = $actionManager->getAction();
$actionManager->setAction($actionName);
$actionManager->run($data);
$actionManager->setAction($currentAction);
}
protected function initialize()
{
}
protected function finalize()
{
$this->getActionManager()->setAction($currentAction);
}
protected function beforeRunAction()
@@ -506,4 +531,23 @@ abstract class Base
{
return $this->getContainer()->get('dataManager')->clearCache();
}
protected function checkIsWritable()
{
$fullFileList = array_merge($this->getDeleteFileList(), $this->getCopyFileList());
$result = $this->getFileManager()->isWritableList($fullFileList);
if (!$result) {
$permissionDeniedList = $this->getFileManager()->getLastPermissionDeniedList();
throw new Error("Permission denied in <br>". implode(", <br>", $permissionDeniedList));
}
}
protected function backupExistingFiles()
{
$fullFileList = array_merge($this->getDeleteFileList(), $this->getCopyFileList());
$backupPath = $this->getPath('backupPath');
return $this->copy('', array($backupPath, self::FILES), false, $fullFileList);
}
}
@@ -24,14 +24,18 @@ namespace Espo\Core\Upgrades\Actions\Base;
class Delete extends \Espo\Core\Upgrades\Actions\Base
{
public function run($processId)
public function run($data)
{
$processId = $data['id'];
$GLOBALS['log']->debug('Delete package process ['.$processId.']: start run.');
if (empty($processId)) {
throw new Error('Delete package package ID was not specified.');
}
$this->initialize();
$this->setProcessId($processId);
$this->beforeRunAction();
@@ -41,6 +45,8 @@ class Delete extends \Espo\Core\Upgrades\Actions\Base
$this->afterRunAction();
$this->finalize();
$GLOBALS['log']->debug('Delete package process ['.$processId.']: end run.');
}
@@ -23,6 +23,7 @@
namespace Espo\Core\Upgrades\Actions\Base;
use Espo\Core\Exceptions\Error;
use Espo\Core\Utils\Util;
class Install extends \Espo\Core\Upgrades\Actions\Base
{
@@ -39,8 +40,10 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
* @param string $processId Upgrade/Extension ID, gotten in upload stage
* @return bool
*/
public function run($processId)
public function run($data)
{
$processId = $data['id'];
$GLOBALS['log']->debug('Installation process ['.$processId.']: start run.');
if (empty($processId)) {
@@ -49,6 +52,8 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
$this->setProcessId($processId);
$this->initialize();
$this->isCopied = false;
/** check if an archive is unzipped, if no then unzip */
@@ -58,22 +63,25 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
$this->isAcceptable();
}
//check permissions copied and deleted files
$this->checkIsWritable();
$this->backupExistingFiles();
$this->beforeRunAction();
/* run before install script */
$this->runScript('before');
/* remove files defined in a manifest */
if (!$this->deleteFiles()) {
$this->throwErrorAndRemovePackage('Permission denied to delete files.');
}
/* copy files from directory "Files" to EspoCRM files */
if (!$this->copyFiles()) {
$this->throwErrorAndRemovePackage('Cannot copy files.');
}
$this->isCopied = true;
/* remove files defined in a manifest */
$this->deleteFiles(true);
if (!$this->systemRebuild()) {
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
}
@@ -88,20 +96,32 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
/* delete unziped files */
$this->deletePackageFiles();
$this->finalize();
$GLOBALS['log']->debug('Installation process ['.$processId.']: end run.');
}
protected function restoreFiles()
{
$backupPath = $this->getPath('backupPath');
$res = true;
if ($this->isCopied) {
$res &= $this->copy(array($backupPath, self::FILES), '', true);
$GLOBALS['log']->info('Restore: copy back');
if (!$this->isCopied) {
return;
}
$res &= $this->getFileManager()->removeInDir($backupPath, true);
$GLOBALS['log']->info('Installer: Restore previous files.');
$backupPath = $this->getPath('backupPath');
$backupFilePath = Util::concatPath($backupPath, self::FILES);
$backupFileList = $this->getRestoreFileList();
$copyFileList = $this->getCopyFileList();
$deleteFileList = array_diff($copyFileList, $backupFileList);
$res = $this->copy($backupFilePath, '', true);
$res &= $this->getFileManager()->remove($deleteFileList, null, true);
if ($res) {
$this->getFileManager()->removeInDir($backupPath, true);
}
return $res;
}
@@ -22,13 +22,16 @@
namespace Espo\Core\Upgrades\Actions\Base;
use Espo\Core\Exceptions\Error,
Espo\Core\Utils\Util;
use Espo\Core\Exceptions\Error;
use Espo\Core\Utils\Util;
use Espo\Core\Utils\Json;
class Uninstall extends \Espo\Core\Upgrades\Actions\Base
{
public function run($processId)
public function run($data)
{
$processId = $data['id'];
$GLOBALS['log']->debug('Uninstallation process ['.$processId.']: start run.');
if (empty($processId)) {
@@ -37,31 +40,39 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
$this->setProcessId($processId);
$this->initialize();
$this->checkIsWritable();
$this->beforeRunAction();
/* run before install script */
$this->runScript('beforeUninstall');
if (!isset($data['isNotRunScriptBefore']) || !$data['isNotRunScriptBefore']) {
$this->runScript('beforeUninstall');
}
$backupPath = $this->getPath('backupPath');
if (file_exists($backupPath)) {
/* remove extension files, saved in fileList */
if (!$this->deleteFiles(true)) {
throw new Error('Permission denied to delete files.');
}
/* copy core files */
if (!$this->copyFiles()) {
throw new Error('Cannot copy files.');
throw new $this->throwErrorAndRemovePackage('Cannot copy files.');
}
/* remove extension files, saved in fileList */
if (!$this->deleteFiles(true)) {
throw new $this->throwErrorAndRemovePackage('Permission denied to delete files.');
}
}
if (!$this->systemRebuild()) {
throw new Error('Error occurred while EspoCRM rebuild.');
throw new $this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
}
/* run before install script */
$this->runScript('afterUninstall');
/* run after uninstall script */
if (!isset($data['isNotRunScriptAfter']) || !$data['isNotRunScriptAfter']) {
$this->runScript('afterUninstall');
}
$this->afterRunAction();
@@ -70,13 +81,9 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
/* delete backup files */
$this->deletePackageFiles();
$GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.');
}
$this->finalize();
protected function getDeleteFileList()
{
$extensionEntity = $this->getExtensionEntity();
return $extensionEntity->get('fileList');
$GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.');
}
protected function restoreFiles()
@@ -89,6 +96,13 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
}
$res = $this->copy($filesPath, '', true);
$manifestJson = $this->getFileManager()->getContents(array($packagePath, $this->manifestName));
$manifest = Json::decode($manifestJson, true);
if (!empty($manifest['delete'])) {
$res &= $this->getFileManager()->remove($manifest['delete'], null, true);
}
$res &= $this->getFileManager()->removeInDir($packagePath, true);
return $res;
@@ -131,4 +145,41 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
throw new Error($errorMessage);
}
protected function getCopyFileList()
{
if (!isset($this->data['fileList'])) {
$backupPath = $this->getPath('backupPath');
$filesPath = Util::concatPath($backupPath, self::FILES);
$this->data['fileList'] = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
}
return $this->data['fileList'];
}
protected function getRestoreFileList()
{
if (!isset($this->data['restoreFileList'])) {
$packagePath = $this->getPackagePath();
$filesPath = Util::concatPath($packagePath, self::FILES);
if (!file_exists($filesPath)) {
$this->unzipArchive($packagePath);
}
$this->data['restoreFileList'] = $this->getFileManager()->getFileList($filesPath, true, '', true, true);
}
return $this->data['restoreFileList'];
}
protected function getDeleteFileList()
{
$packageFileList = $this->getRestoreFileList();
$backupFileList = $this->getCopyFileList();
$deleteFileList = array_diff($packageFileList, $backupFileList);
return $deleteFileList;
}
}
@@ -38,6 +38,10 @@ class Upload extends \Espo\Core\Upgrades\Actions\Base
$GLOBALS['log']->debug('Installation process ['.$processId.']: start upload the package.');
$this->initialize();
$this->beforeRunAction();
$packagePath = $this->getPackagePath();
$packageArchivePath = $this->getPackagePath(true);
@@ -55,6 +59,10 @@ class Upload extends \Espo\Core\Upgrades\Actions\Base
$this->isAcceptable();
$this->afterRunAction();
$this->finalize();
$GLOBALS['log']->debug('Installation process ['.$processId.']: end upload the package.');
return $processId;
@@ -35,29 +35,15 @@ class Delete extends \Espo\Core\Upgrades\Actions\Base\Delete
*/
protected function getExtensionEntity()
{
return $this->extensionEntity;
}
/**
* Set Extension Entity
*
* @param \Espo\Entities\Extension $extensionEntity
*/
protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity)
{
$this->extensionEntity = $extensionEntity;
}
protected function beforeRunAction()
{
$processId = $this->getProcessId();
/** get extension entity */
$extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
if (!isset($extensionEntity)) {
throw new Error('Extension Entity not found.');
if (!isset($this->extensionEntity)) {
$processId = $this->getProcessId();
$this->extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
if (!isset($this->extensionEntity)) {
throw new Error('Extension Entity not found.');
}
}
$this->setExtensionEntity($extensionEntity);
return $this->extensionEntity;
}
protected function afterRunAction()
@@ -37,8 +37,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
$this->uninstallExtension();
$this->deleteExtension();
}
$this->copyExistingFiles();
}
protected function afterRunAction()
@@ -51,12 +49,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
*
* @return bool
*/
protected function copyExistingFiles()
protected function backupExistingFiles()
{
$fileList = $this->getCopyFileList();
$backupPath = $this->getPath('backupPath');
parent::backupExistingFiles();
$res = $this->copy('', array($backupPath, self::FILES), false, $fileList);
$backupPath = $this->getPath('backupPath');
/** copy scripts files */
$packagePath = $this->getPackagePath();
@@ -65,19 +62,6 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
return $res;
}
protected function restoreFiles()
{
$res = true;
if ($this->isCopied) {
$extensionFileList = $this->getCopyFileList();
$res &= $this->getFileManager()->remove($extensionFileList);
}
$res &= parent::restoreFiles();
return $res;
}
protected function isNew()
{
$extensionEntity = $this->getExtensionEntity();
@@ -208,7 +192,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
{
$extensionEntity = $this->getExtensionEntity();
$this->executeAction(ExtensionManager::UNINSTALL, $extensionEntity->get('id'));
$this->executeAction(ExtensionManager::UNINSTALL, array(
'id' => $extensionEntity->get('id'),
'isNotRunScriptAfter' => true,
)
);
}
/**
@@ -220,7 +208,7 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
{
$extensionEntity = $this->getExtensionEntity();
$this->executeAction(ExtensionManager::DELETE, $extensionEntity->get('id'));
$this->executeAction(ExtensionManager::DELETE, array('id' => $extensionEntity->get('id')));
}
@@ -35,29 +35,15 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall
*/
protected function getExtensionEntity()
{
return $this->extensionEntity;
}
/**
* Set Extension Entity
*
* @param \Espo\Entities\Extension $extensionEntity [description]
*/
protected function setExtensionEntity(\Espo\Entities\Extension $extensionEntity)
{
$this->extensionEntity = $extensionEntity;
}
protected function beforeRunAction()
{
$processId = $this->getProcessId();
/** get extension entity */
$extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
if (!isset($extensionEntity)) {
throw new Error('Extension Entity not found.');
if (!isset($this->extensionEntity)) {
$processId = $this->getProcessId();
$this->extensionEntity = $this->getEntityManager()->getEntity('Extension', $processId);
if (!isset($this->extensionEntity)) {
throw new Error('Extension Entity not found.');
}
}
$this->setExtensionEntity($extensionEntity);
return $this->extensionEntity;
}
protected function afterRunAction()
@@ -68,4 +54,14 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base\Uninstall
$extensionEntity->set('isInstalled', false);
$this->getEntityManager()->saveEntity($extensionEntity);
}
protected function getRestoreFileList()
{
if (!isset($this->data['restoreFileList'])) {
$extensionEntity = $this->getExtensionEntity();
$this->data['restoreFileList'] = $extensionEntity->get('fileList');
}
return $this->data['restoreFileList'];
}
}
@@ -24,17 +24,12 @@ namespace Espo\Core\Upgrades\Actions\Upgrade;
class Install extends \Espo\Core\Upgrades\Actions\Base\Install
{
protected function systemRebuild()
protected function finalize()
{
$manifest = $this->getManifest();
$res = $this->getConfig()->set('version', $manifest['version']);
if (method_exists($this->getConfig(), 'save')) {
$res = $this->getConfig()->save();
}
$res &= parent::systemRebuild();
return $res;
$this->getConfig()->set('version', $manifest['version']);
$this->getConfig()->save();
}
/**
@@ -29,6 +29,8 @@ class Manager
{
private $permission;
private $permissionDeniedList = array();
public function __construct(\Espo\Core\Utils\Config $config = null)
{
$params = null;
@@ -774,5 +776,73 @@ return '.var_export($content, true).';
?>';
}
/**
* Check if $paths are writable. Permission denied list are defined in getLastPermissionDeniedList()
*
* @param array $paths
*
* @return boolean
*/
public function isWritableList(array $paths)
{
$permissionDeniedList = array();
$result = true;
foreach ($paths as $path) {
$rowResult = $this->isWritable($path);
if (!$rowResult) {
$permissionDeniedList[] = $path;
}
$result &= $rowResult;
}
if (!empty($permissionDeniedList)) {
$this->permissionDeniedList = $this->getPermissionUtils()->arrangePermissionList($permissionDeniedList);
}
return (bool) $result;
}
/**
* Get last permission denied list
*
* @return array
*/
public function getLastPermissionDeniedList()
{
return $this->permissionDeniedList;
}
/**
* Check if $path is writable
*
* @param string | array $path
*
* @return boolean
*/
public function isWritable($path)
{
$existFile = $this->getExistsPath($path);
return is_writable($existFile);
}
/**
* Get exists path. Ex. if check /var/www/espocrm/custom/someFile.php and this file doesn't extist, result will be /var/www/espocrm/custom
*
* @param string | array $path
*
* @return string
*/
protected function getExistsPath($path)
{
$fullPath = $this->concatPaths($path);
if (!file_exists($fullPath)) {
$fullPath = $this->getExistsPath(pathinfo($fullPath, PATHINFO_DIRNAME));
}
return $fullPath;
}
}
@@ -94,6 +94,7 @@ return array (
'assignmentEmailNotifications' => false,
'assignmentEmailNotificationsEntityList' => array('Lead', 'Opportunity', 'Task', 'Case'),
'emailMessageMaxSize' => 10,
'notificationsCheckInterval' => 10,
'isInstalled' => false,
);
@@ -33,7 +33,9 @@ class CheckEmailAccounts extends \Espo\Core\Jobs\Base
foreach ($collection as $entity) {
try {
$service->fetchFromMailServer($entity);
} catch (\Exception $e) {}
} catch (\Exception $e) {
$GLOBALS['log']->error('Job CheckEmailAccounts: [' . $e->getCode() . '] ' .$e->getMessage());
}
}
return true;
@@ -33,9 +33,11 @@ class CheckInboundEmails extends \Espo\Core\Jobs\Base
foreach ($collection as $entity) {
try {
$service->fetchFromMailServer($entity);
} catch (\Exception $e) {}
} catch (\Exception $e) {
$GLOBALS['log']->error('Job CheckInboundEmails: [' . $e->getCode() . '] ' .$e->getMessage());
}
}
return true;
}
}
@@ -204,9 +204,13 @@ class InboundEmail extends \Espo\Services\Record
$message = $storage->getMessage($id);
$email = $importer->importMessage($message, $userId, array($teamId));
try {
$email = $importer->importMessage($message, $userId, array($teamId));
} catch (\Exception $e) {
$GLOBALS['log']->error('InboundEmail (Importing Message): [' . $e->getCode() . '] ' .$e->getMessage());
}
if ($email) {
if (!empty($email)) {
$this->noteAboutEmail($email);
if ($inboundEmail->get('createCase')) {
@@ -2,13 +2,13 @@
{
"label":"",
"rows":[
[{"name":"dateSent"}],
[{"name":"parent"}],
[{"name":"from"}],
[{"name":"dateSent"}, {"name":"assignedUser"}],
[{"name":"parent"}, {"name":"teams"}],
[{"name":"from"}, {"name":"cc"}],
[{"name":"to"}],
[{"name":"name"}],
[{"name":"body"}],
[{"name":"attachments"}]
[{"name":"name", "fullWidth": true}],
[{"name":"body", "fullWidth": true}],
[{"name":"attachments", "fullWidth": true}]
]
}
]
@@ -2,13 +2,13 @@
{
"label":"",
"rows":[
[{"name":"dateSent", "readOnly": true}],
[{"name":"parent"}],
[{"name":"from", "readOnly": true}],
[{"name":"dateSent", "readOnly": true}, {"name":"assignedUser"}],
[{"name":"parent"}, {"name":"teams"}],
[{"name":"from", "readOnly": true}, {"name":"cc", "readOnly": true}],
[{"name":"to", "readOnly": true}],
[{"name":"name", "readOnly": true}],
[{"name":"body", "readOnly": true}],
[{"name":"attachments", "readOnly": true}]
[{"name":"name", "readOnly": true, "fullWidth": true}],
[{"name":"body", "readOnly": true, "fullWidth": true}],
[{"name":"attachments", "readOnly": true, "fullWidth": true}]
]
}
]
+6 -2
View File
@@ -198,9 +198,13 @@ class EmailAccount extends Record
$message = $storage->getMessage($id);
$email = $importer->importMessage($message, $userId, array($teamId));
try {
$email = $importer->importMessage($message, $userId, array($teamId));
} catch (\Exception $e) {
$GLOBALS['log']->error('EmailAccount (Importing Message): [' . $e->getCode() . '] ' .$e->getMessage());
}
if ($email) {
if (!empty($email)) {
$this->noteAboutEmail($email);
}
Generated
+12 -5
View File
@@ -1592,11 +1592,18 @@
"time": "2014-01-07 13:28:54"
}
],
"aliases": [],
"aliases": [
],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": [
],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": []
"platform": [
],
"platform-dev": [
]
}
@@ -9,8 +9,12 @@
{{/unless}}
<div class="stream-head-container">
{{{avatar}}}
<span class="text-muted message">{{{message}}}</span>
<div class="pull-left">
{{{avatar}}}
</div>
<div class="stream-head-text-container">
<span class="text-muted message">{{{message}}}</span>
</div>
</div>
<div class="stream-date-container">
<span class="text-muted small">{{{createdAt}}}</span>
@@ -9,8 +9,12 @@
{{/unless}}
<div class="stream-head-container">
{{{avatar}}}
<span class="text-muted message">{{{message}}}</span>
<div class="pull-left">
{{{avatar}}}
</div>
<div class="stream-head-text-container">
<span class="text-muted message">{{{message}}}</span>
</div>
</div>
<div class="stream-date-container">
@@ -9,11 +9,15 @@
{{/unless}}
<div class="stream-head-container">
{{{avatar}}}
{{#if statusText}}
<span class="label label-{{statusStyle}}">{{statusText}}</span>
{{/if}}
<span class="text-muted message">{{{message}}}</span>
<div class="pull-left">
{{{avatar}}}
</div>
<div class="stream-head-text-container">
{{#if statusText}}
<span class="label label-{{statusStyle}}">{{statusText}}</span>
{{/if}}
<span class="text-muted message">{{{message}}}</span>
</div>
</div>
<div class="stream-date-container">
@@ -9,10 +9,14 @@
{{/unless}}
<div class="stream-head-container">
{{{avatar}}}
<span class="text-muted"><span class="glyphicon glyphicon-envelope "></span>
{{{message}}}
</span>
<div class="pull-left">
{{{avatar}}}
</div>
<div class="stream-head-text-container">
<span class="text-muted"><span class="glyphicon glyphicon-envelope "></span>
{{{message}}}
</span>
</div>
</div>
@@ -9,8 +9,12 @@
{{/unless}}
<div class="stream-head-container">
{{{avatar}}}
<span class="text-muted message">{{{message}}}</span>
<div class="pull-left">
{{{avatar}}}
</div>
<div class="stream-head-text-container">
<span class="text-muted message">{{{message}}}</span>
</div>
</div>
{{#if post}}
@@ -9,9 +9,13 @@
{{/unless}}
<div class="stream-head-container">
{{{avatar}}}
<span class="label label-{{style}}">{{statusText}}</span>
<span class="text-muted message">{{{message}}}</span>
<div class="pull-left">
{{{avatar}}}
</div>
<div class="stream-head-text-container">
<span class="label label-{{style}}">{{statusText}}</span>
<span class="text-muted message">{{{message}}}</span>
</div>
</div>
<div class="stream-date-container">
@@ -9,8 +9,12 @@
{{/unless}}
<div class="stream-head-container">
{{{avatar}}}
<span class="text-muted message">{{{message}}}</span> <a href="javascript:" data-action="expandDetails"><span class="glyphicon glyphicon-chevron-down"></span></a>
<div class="pull-left">
{{{avatar}}}
</div>
<div class="stream-head-text-container">
<span class="text-muted message">{{{message}}}</span> <a href="javascript:" data-action="expandDetails"><span class="glyphicon glyphicon-chevron-down"></span></a>
</div>
</div>
<div class="hidden details stream-details-container">
@@ -17,17 +17,16 @@
*
* 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.Email.Record.Compose', 'Views.Record.Edit', function (Dep) {
return Dep.extend({
isWide: true,
sideView: false,
});
});
@@ -17,34 +17,34 @@
*
* 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.Email.Record.Detail', 'Views.Record.Detail', function (Dep) {
return Dep.extend({
layoutNameConfigure: function () {
if (!this.model.isNew()) {
var isRestricted = false;
if (this.model.get('status') == 'Sent') {
this.layoutName += 'Restricted';
isRestricted = true;
}
if (this.model.get('status') == 'Archived' && this.model.get('createdById') == 'system') {
if (this.model.get('status') == 'Archived' && this.model.get('createdById') == 'system') {
this.layoutName += 'Restricted';
isRestricted = true;
}
}
},
init: function () {
Dep.prototype.init.call(this);
this.layoutNameConfigure();
},
});
});
@@ -17,19 +17,25 @@
*
* 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.Email.Record.EditQuick', 'Views.Email.Record.Edit', function (Dep, Detail) {
return Dep.extend({
isWide: true,
sideView: false,
init: function () {
Dep.prototype.init.call(this);
Dep.prototype.init.call(this);
this.columnCount = 2;
},
setup: function () {
Dep.prototype.setup.call(this);
},
});
});
@@ -17,20 +17,19 @@
*
* 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.Email.Record.Edit', ['Views.Record.Edit', 'Views.Email.Record.Detail'], function (Dep, Detail) {
return Dep.extend({
init: function () {
Dep.prototype.init.call(this);
Detail.prototype.layoutNameConfigure.call(this);
Detail.prototype.layoutNameConfigure.call(this);
},
});
});
+47 -42
View File
@@ -17,88 +17,93 @@
*
* 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.Fields.Wysiwyg', ['Views.Fields.Text', 'lib!Summernote'], function (Dep, Summernote) {
return Dep.extend({
type: 'wysiwyg',
return Dep.extend({
type: 'wysiwyg',
detailTemplate: 'fields.wysiwyg.detail',
editTemplate: 'fields.wysiwyg.edit',
setup: function () {
Dep.prototype.setup.call(this);
this.listenTo(this.model, 'change:isHtml', function (model) {
setup: function () {
Dep.prototype.setup.call(this);
this.listenTo(this.model, 'change:isHtml', function (model) {
if (!model.has('isHtml') || model.get('isHtml')) {
this.enableWysiwygMode();
} else {
this.disableWysiwygMode();
}
}.bind(this));
this.once('remove', function () {
$('body > .tooltip').remove();
});
});
},
afterRender: function () {
Dep.prototype.afterRender.call(this);
Dep.prototype.afterRender.call(this);
var language = this.getConfig().get('language');
if (!(language in $.summernote.lang)) {
$.summernote.lang[language] = this.getLanguage().translate('summernote', 'sets');
}
if (this.mode == 'edit') {
if (!this.model.has('isHtml') || this.model.get('isHtml')) {
this.enableWysiwygMode();
}
}
if (this.mode == 'detail') {
if (!this.model.has('isHtml') || this.model.get('isHtml')) {
this.$el.find('iframe').removeClass('hidden');
var iframe = this.$el.find('iframe').get(0);
var document = iframe.contentWindow.document;
var link = '<link href="client/css/iframe.css" rel="stylesheet" type="text/css"></link>'
document.open('text/html', 'replace');
var iframe = this.iframe = this.$el.find('iframe').get(0);
iframe.onload = function () {
var height = $(iframe).contents().find('html body').height();
iframe.style.height = height + 'px';
};
var doc = iframe.contentWindow.document;
var link = '<link href="client/css/iframe.css" rel="stylesheet" type="text/css"></link>';
doc.open('text/html', 'replace');
var body = this.model.get('body');
body += link;
document.write(body);
document.close();
iframe.style.height = document.body.scrollHeight + 'px';
doc.write(body);
doc.close();
} else {
this.$el.find('.plain').removeClass('hidden');
}
}
},
enableWysiwygMode: function () {
this.$summernote = this.$element.summernote({
height: 250,
lang: this.getConfig().get('language'),
onImageUpload: function (files, editor, welEditable) {
var file = files[0];
this.notify('Uploading...');
this.getModelFactory().create('Attachment', function (attachment) {
this.notify('Uploading...');
this.getModelFactory().create('Attachment', function (attachment) {
var fileReader = new FileReader();
fileReader.onload = function (e) {
fileReader.onload = function (e) {
$.ajax({
type: 'POST',
url: 'Attachment/action/upload',
data: e.target.result,
contentType: 'multipart/encrypted',
}).done(function (data) {
attachment.id = data.attachmentId;
}).done(function (data) {
attachment.id = data.attachmentId;
attachment.set('name', file.name);
attachment.set('type', file.type);
attachment.set('role', 'Inline Attachment');
@@ -108,12 +113,12 @@ Espo.define('Views.Fields.Wysiwyg', ['Views.Fields.Text', 'lib!Summernote'], fun
var url = '?entryPoint=attachment&id=' + attachment.id;
editor.insertImage(welEditable, url);
this.notify(false);
}, this);
}, this);
attachment.save();
}.bind(this));
}.bind(this);
fileReader.readAsDataURL(file);
}, this);
}.bind(this),
toolbar: [
@@ -121,23 +126,23 @@ Espo.define('Views.Fields.Wysiwyg', ['Views.Fields.Text', 'lib!Summernote'], fun
['style', ['bold', 'italic', 'underline', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
['height', ['height']],
['table', ['table', 'link', 'picture']],
['misc',['codeview']]
]
});
},
disableWysiwygMode: function () {
this.$element.destroy();
},
fetch: function () {
var data = {};
if (!this.model.has('isHtml') || this.model.get('isHtml')) {
data[this.name] = this.$element.code();
} else {
data[this.name] = this.$element.val();
data[this.name] = this.$element.val();
}
return data;
}
@@ -25,15 +25,20 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
template: 'notifications.badge',
updateFrequency: 10,
notificationsCheckInterval: 10,
timeout: null,
popupNotificationsData: null,
soundPath: 'client/sounds/pop_cork',
events: {
'click a[data-action="showNotifications"]': function (e) {
this.showNotifications();
setTimeout(function () {
this.checkUpdates();
}.bind(this), 100);
},
},
@@ -47,6 +52,8 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
}
}, this);
this.notificationsCheckInterval = this.getConfig().get('notificationsCheckInterval') || this.notificationsCheckInterval;
this.popupCheckIteration = 0;
this.lastId = 0;
this.shownNotificationIds = [];
@@ -74,7 +81,7 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
this.$badge = this.$el.find('.notifications-button');
this.$icon = this.$el.find('.notifications-button .icon');
this.checkUpdates();
this.runCheckUpdates(true);
this.$popupContainer = $('#popup-notifications-container');
if (!$(this.$popupContainer).size()) {
@@ -87,6 +94,17 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
}
},
playSound: function () {
var html = '' +
'<audio autoplay="autoplay">'+
'<source src="' + this.soundPath + '.mp3" type="audio/mpeg" />'+
'<source src="' + this.soundPath + '.ogg" type="audio/ogg" />'+
'<embed hidden="true" autostart="true" loop="false" src="' + this.soundPath +'.mp3" />'+
'</audio>';
$(html).get(0).volume = 0.3;
$(html).get(0).play();
},
showNotRead: function (count) {
this.$icon.addClass('warning');
this.$badge.attr('title', this.translate('New notifications') + ': ' + count);
@@ -97,18 +115,26 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
this.$badge.attr('title', '');
},
checkUpdates: function () {
checkUpdates: function (isFirstCheck) {
$.ajax('Notification/action/notReadCount').done(function (count) {
if (!isFirstCheck && count > this.unreadCount) {
this.playSound();
}
this.unreadCount = count;
if (count) {
this.showNotRead(count);
} else {
this.hideNotRead();
}
}.bind(this));
},
runCheckUpdates: function (isFirstCheck) {
this.checkUpdates(isFirstCheck);
this.timeout = setTimeout(function () {
this.checkUpdates();
}.bind(this), this.updateFrequency * 1000);
this.runCheckUpdates();
}.bind(this), this.notificationsCheckInterval * 1000);
},
checkPopupNotifications: function (name) {
@@ -185,7 +211,7 @@ Espo.define('Views.Notifications.Badge', 'View', function (Dep) {
var $container = $('<div>').attr('id', 'notifications-panel').css({
'position': 'absolute',
'width': '500px',
'width': '600px',
'z-index': 1001,
'right': 0,
'left': 'auto'
@@ -47,7 +47,6 @@ Espo.define('Views.PopupNotification', 'View', function (Dep) {
.addClass(className)
.addClass('popup-notification-' + this.style)
.appendTo('#popup-notifications-container');
this.setElement(containerSelector);
}, this);
@@ -60,7 +59,7 @@ Espo.define('Views.PopupNotification', 'View', function (Dep) {
this.once('after:render', function () {
this.onShow();
}.bind(this));
this.once('remove', function () {
$(containerSelector).remove();
});
+1 -5
View File
@@ -168,16 +168,12 @@ Espo.define('Views.Site.Navbar', 'View', function (Dep) {
var $navbar = $('#navbar .navbar');
var processMaxCount = 20;
var processCount = 0;
var processUpdateWidth = function () {
processCount++;
if (processCount > processMaxCount) return;
if ($navbar.height() > 45) {
updateWidth();
setTimeout(function () {
processUpdateWidth();
}, 400);
}, 200);
}
};
+4
View File
@@ -675,6 +675,10 @@ body > footer > p a:visited {
padding-bottom: 3px;
}
.stream-head-text-container {
padding-left: 26px;
}
.stream-head-container > img.avatar {
display: inline-block;
margin-top: -3px;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "3.0.0",
"version": "3.0.1",
"description": "",
"main": "index.php",
"repository": {
+18 -2
View File
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -389,7 +389,23 @@ class ManagerTest extends \PHPUnit_Framework_TestCase
}
}
public function existsPathSet()
{
return array(
array( 'application/Espo/Core/Application.php', 'application/Espo/Core/Application.php', ),
array( 'application/Espo/Core/NotRealApplication.php', 'application/Espo/Core'),
array( array('application', 'Espo/Core', 'NotRealApplication.php'), 'application/Espo/Core'),
array( 'application/NoEspo/Core/Application.php', 'application'),
array( 'notRealPath/Espo/Core/Application.php', '.'),
);
}
/**
* @dataProvider existsPathSet
*/
public function testGetExistsPath($input, $result)
{
$this->assertEquals($result, $this->reflection->invokeMethod('getExistsPath', array($input)) );
}
}
?>