diff --git a/README.md b/README.md
index 53c9182799..78aeb01a0a 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,13 @@ It's a web application with a frontend designed as a single page application bas
Download the latest release from our [website](http://www.espocrm.com).
+### Requirements
+
+* PHP 5.4 or above (with pdo, json, gd, mcrypt extensions);
+* MySQL 5.1 or above.
+
+For more information about server configuration see [this article](http://blog.espocrm.com/administration/server-configuration-for-espocrm/).
+
### How to report bug
Create an issue [here](https://github.com/espocrm/espocrm/issues) or post on our [forum](http://forum.espocrm.com/bug-reports?routestring=forum/bug-reports).
diff --git a/application/Espo/Controllers/EntityManager.php b/application/Espo/Controllers/EntityManager.php
index 6e89ce0ab9..359e62d46d 100644
--- a/application/Espo/Controllers/EntityManager.php
+++ b/application/Espo/Controllers/EntityManager.php
@@ -62,6 +62,12 @@ class EntityManager extends \Espo\Core\Controllers\Base
if (!empty($data['stream'])) {
$params['stream'] = $data['stream'];
}
+ if (!empty($data['sortBy'])) {
+ $params['sortBy'] = $data['sortBy'];
+ }
+ if (!empty($data['sortDirection'])) {
+ $params['asc'] = $data['sortDirection'] === 'asc';
+ }
$result = $this->getContainer()->get('entityManagerUtil')->create($name, $type, $params);
@@ -90,6 +96,10 @@ class EntityManager extends \Espo\Core\Controllers\Base
$name = $data['name'];
$name = filter_var($name, \FILTER_SANITIZE_STRING);
+ if (!empty($data['sortDirection'])) {
+ $data['asc'] = $data['sortDirection'] === 'asc';
+ }
+
$result = $this->getContainer()->get('entityManagerUtil')->update($name, $data);
if ($result) {
diff --git a/application/Espo/Core/Utils/EntityManager.php b/application/Espo/Core/Utils/EntityManager.php
index 7dcbcd00eb..9bd34a42bf 100644
--- a/application/Espo/Core/Utils/EntityManager.php
+++ b/application/Espo/Core/Utils/EntityManager.php
@@ -193,6 +193,16 @@ class EntityManager
$this->getLanguage()->set('Global', 'scopeNamesPlural', $name, $labelPlural);
}
+ if (isset($data['sortBy'])) {
+ $entityDefsData = array(
+ 'collection' => array(
+ 'sortBy' => $data['sortBy'],
+ 'asc' => !empty($data['asc'])
+ )
+ );
+ $this->getMetadata()->set('entityDefs', $name, $entityDefsData);
+ }
+
$this->getMetadata()->save();
$this->getLanguage()->save();
diff --git a/application/Espo/Hooks/Common/Stream.php b/application/Espo/Hooks/Common/Stream.php
index 4e341b60cd..ab1b1add58 100644
--- a/application/Espo/Hooks/Common/Stream.php
+++ b/application/Espo/Hooks/Common/Stream.php
@@ -184,6 +184,10 @@ class Stream extends \Espo\Core\Hooks\Base
$this->getStreamService()->noteCreate($entity);
}
+ if (in_array($this->getUser()->id, $userIdList)) {
+ $entity->set('isFollowed', true);
+ }
+
$autofollowUserIdList = $this->getAutofollowUserIdList($entity, $userIdList);
foreach ($autofollowUserIdList as $i => $userId) {
if (in_array($userId, $userIdList)) {
@@ -213,6 +217,10 @@ class Stream extends \Espo\Core\Hooks\Base
if (!empty($assignedUserId)) {
$this->getStreamService()->followEntity($entity, $assignedUserId);
$this->getStreamService()->noteAssign($entity);
+
+ if ($this->getUser()->id === $assignedUserId) {
+ $entity->set('isFollowed', true);
+ }
}
}
$this->getStreamService()->handleAudited($entity);
diff --git a/application/Espo/Modules/Crm/Services/Campaign.php b/application/Espo/Modules/Crm/Services/Campaign.php
index 2bf174ac9b..303f75c43b 100644
--- a/application/Espo/Modules/Crm/Services/Campaign.php
+++ b/application/Espo/Modules/Crm/Services/Campaign.php
@@ -33,7 +33,7 @@ class Campaign extends \Espo\Services\Record
$sentCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where(array(
'campaignId' => $entity->id,
- 'action' => 'Clicked'
+ 'action' => 'Sent'
))->count();
$entity->set('sentCount', $sentCount);
diff --git a/application/Espo/Resources/i18n/en_US/EmailAccount.json b/application/Espo/Resources/i18n/en_US/EmailAccount.json
index 801fe7da8a..c8ce60ad4f 100644
--- a/application/Espo/Resources/i18n/en_US/EmailAccount.json
+++ b/application/Espo/Resources/i18n/en_US/EmailAccount.json
@@ -8,7 +8,8 @@
"port": "Port",
"monitoredFolders": "Monitored Folders",
"ssl": "SSL",
- "fetchSince": "Fetch Since"
+ "fetchSince": "Fetch Since",
+ "emailAddress": "Email Address"
},
"links": {
},
diff --git a/application/Espo/Resources/i18n/en_US/EntityManager.json b/application/Espo/Resources/i18n/en_US/EntityManager.json
index d5fbf524fc..8219e246da 100644
--- a/application/Espo/Resources/i18n/en_US/EntityManager.json
+++ b/application/Espo/Resources/i18n/en_US/EntityManager.json
@@ -14,7 +14,9 @@
"entityForeign": "Foreign Entity",
"linkForeign": "Foreign Link",
"link": "Link",
- "labelForeign": "Foreign Label"
+ "labelForeign": "Foreign Label",
+ "sortBy": "Default Order (field)",
+ "sortDirection": "Default Order (direction)"
},
"options": {
"type": {
@@ -29,6 +31,10 @@
"manyToOne": "Many-to-One",
"parentToChildren": "Parent-to-Children",
"childrenToParent": "Children-to-Parent"
+ },
+ "sortDirection": {
+ "asc": "Ascending",
+ "desc": "Descending"
}
},
"messages": {
diff --git a/application/Espo/Resources/layouts/EmailAccount/detail.json b/application/Espo/Resources/layouts/EmailAccount/detail.json
index 86879947c7..8d6efe38f2 100644
--- a/application/Espo/Resources/layouts/EmailAccount/detail.json
+++ b/application/Espo/Resources/layouts/EmailAccount/detail.json
@@ -3,10 +3,11 @@
"label":"Main",
"rows":[
[
- {"name":"name"},
+ {"name":"emailAddress"},
{"name":"status"}
],
[
+ {"name":"name"},
{"name":"fetchSince"}
]
]
diff --git a/application/Espo/Resources/metadata/entityDefs/EmailAccount.json b/application/Espo/Resources/metadata/entityDefs/EmailAccount.json
index 38389e5e6e..fb98e658ae 100644
--- a/application/Espo/Resources/metadata/entityDefs/EmailAccount.json
+++ b/application/Espo/Resources/metadata/entityDefs/EmailAccount.json
@@ -4,6 +4,12 @@
"type": "varchar",
"required": true
},
+ "emailAddress": {
+ "type": "varchar",
+ "required": true,
+ "maxLength": 100,
+ "view": "EmailAccount.Fields.EmailAddress"
+ },
"status": {
"type": "enum",
"options": ["Active", "Inactive"]
diff --git a/frontend/client/res/templates/admin/entity-manager/modals/edit-entity.tpl b/frontend/client/res/templates/admin/entity-manager/modals/edit-entity.tpl
index f8b9d5a3b0..d902b4d3b7 100644
--- a/frontend/client/res/templates/admin/entity-manager/modals/edit-entity.tpl
+++ b/frontend/client/res/templates/admin/entity-manager/modals/edit-entity.tpl
@@ -26,13 +26,29 @@
+{{#if stream}}
- {{#if stream}}
- {{/if}}
+{{/if}}
+{{#if sortBy}}
+
+{{/if}}
diff --git a/frontend/client/src/views/admin/entity-manager/modals/edit-entity.js b/frontend/client/src/views/admin/entity-manager/modals/edit-entity.js
index 4e4b1474a6..03dfcb70bc 100644
--- a/frontend/client/src/views/admin/entity-manager/modals/edit-entity.js
+++ b/frontend/client/src/views/admin/entity-manager/modals/edit-entity.js
@@ -70,6 +70,9 @@ Espo.define('Views.Admin.EntityManager.Modals.EditEntity', 'Views.Modal', functi
this.model.set('labelPlural', this.translate(scope, 'scopeNamesPlural'));
this.model.set('type', this.getMetadata().get('scopes.' + scope + '.type') || '');
this.model.set('stream', this.getMetadata().get('scopes.' + scope + '.stream') || false);
+
+ this.model.set('sortBy', this.getMetadata().get('entityDefs.' + scope + '.collection.sortBy'));
+ this.model.set('sortDirection', this.getMetadata().get('entityDefs.' + scope + '.collection.asc') ? 'asc' : 'desc');
}
this.createView('type', 'Fields.Enum', {
@@ -131,6 +134,46 @@ Espo.define('Views.Admin.EntityManager.Modals.EditEntity', 'Views.Modal', functi
}
}
});
+
+ if (scope) {
+ var fieldDefs = this.getMetadata().get('entityDefs.' + scope + '.fields') || {}
+ var orderableFieldList = Object.keys(fieldDefs).filter(function (item) {
+ if (fieldDefs[item].notStorable) {
+ return false;
+ }
+ return true;
+ }, this);
+
+ var translatedOptions = {};
+ orderableFieldList.forEach(function (item) {
+ translatedOptions[item] = this.translate(item, 'fields', scope);
+ }, this);
+
+ this.createView('sortBy', 'Fields.Enum', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field-sortBy',
+ defs: {
+ name: 'sortBy',
+ params: {
+ options: orderableFieldList,
+ translatedOptions: translatedOptions
+ }
+ }
+ });
+
+ this.createView('sortDirection', 'Fields.Enum', {
+ model: model,
+ mode: 'edit',
+ el: this.options.el + ' .field-sortDirection',
+ defs: {
+ name: 'sortDirection',
+ params: {
+ options: ['asc', 'desc']
+ }
+ }
+ });
+ }
},
afterRender: function () {
@@ -162,6 +205,11 @@ Espo.define('Views.Admin.EntityManager.Modals.EditEntity', 'Views.Modal', functi
'stream'
];
+ if (this.scope) {
+ arr.push('sortBy');
+ arr.push('sortDirection');
+ }
+
var notValid = false;
arr.forEach(function (item) {
@@ -189,16 +237,23 @@ Espo.define('Views.Admin.EntityManager.Modals.EditEntity', 'Views.Modal', functi
var name = this.model.get('name');
+ var data = {
+ name: name,
+ labelSingular: this.model.get('labelSingular'),
+ labelPlural: this.model.get('labelPlural'),
+ type: this.model.get('type'),
+ stream: this.model.get('stream')
+ };
+
+ if (this.scope) {
+ data.sortBy = this.model.get('sortBy');
+ data.sortDirection = this.model.get('sortDirection');
+ }
+
$.ajax({
url: url,
type: 'POST',
- data: JSON.stringify({
- name: name,
- labelSingular: this.model.get('labelSingular'),
- labelPlural: this.model.get('labelPlural'),
- type: this.model.get('type'),
- stream: this.model.get('stream')
- }),
+ data: JSON.stringify(data),
error: function () {
this.$el.find('button[data-name="save"]').removeClass('disabled');
}.bind(this)
diff --git a/frontend/client/src/views/detail.js b/frontend/client/src/views/detail.js
index ad2b2cacec..552f1870b7 100644
--- a/frontend/client/src/views/detail.js
+++ b/frontend/client/src/views/detail.js
@@ -91,19 +91,11 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
if (this.getMetadata().get('scopes.' + this.scope + '.stream')) {
if (this.model.has('isFollowed')) {
this.handleFollowButton();
- } else {
- this.once('after:render', function () {
- if (this.model.has('isFollowed')) {
- this.handleFollowButton();
- } else {
- this.listenToOnce(this.model, 'sync', function () {
- if (this.model.has('isFollowed')) {
- this.handleFollowButton();
- }
- }, this);
- }
- }, this);
}
+
+ this.listenTo(this.model, 'change:isFollowed', function () {
+ this.handleFollowButton();
+ }, this);
}
},
@@ -144,7 +136,6 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
success: function () {
$el.remove();
this.model.set('isFollowed', true);
- this.handleFollowButton();
}.bind(this),
error: function () {
$el.removeClass('disabled');
@@ -161,7 +152,6 @@ Espo.define('Views.Detail', 'Views.Main', function (Dep) {
success: function () {
$el.remove();
this.model.set('isFollowed', false);
- this.handleFollowButton();
}.bind(this),
error: function () {
$el.removeClass('disabled');
diff --git a/frontend/client/src/views/email-account/fields/email-address.js b/frontend/client/src/views/email-account/fields/email-address.js
new file mode 100644
index 0000000000..9465fdb7c5
--- /dev/null
+++ b/frontend/client/src/views/email-account/fields/email-address.js
@@ -0,0 +1,36 @@
+/************************************************************************
+ * 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('Views.EmailAccount.Fields.EmailAddress', 'Views.Fields.Varchar', function (Dep) {
+
+ return Dep.extend({
+
+ setup: function () {
+ Dep.prototype.setup.call(this);
+
+ this.on('change', function () {
+ var emailAddress = this.model.get('emailAddress');
+ this.model.set('name', emailAddress);
+ }, this);
+ },
+
+ });
+});
diff --git a/frontend/client/src/views/fields/enum.js b/frontend/client/src/views/fields/enum.js
index f5a8c0509c..7de656b7bd 100644
--- a/frontend/client/src/views/fields/enum.js
+++ b/frontend/client/src/views/fields/enum.js
@@ -49,6 +49,10 @@ Espo.define('Views.Fields.Enum', ['Views.Fields.Base'], function (Dep) {
}
}
+ if ('translatedOptions' in this.params) {
+ this.translatedOptions = this.params.translatedOptions;
+ }
+
if (this.params.translation) {
var data = this.getLanguage().data;
var arr = this.params.translation.split('.');