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}}
{{{stream}}}
- {{/if}}
+{{/if}} +{{#if sortBy}} +
+
+ +
+ {{{sortBy}}} +
+
+
+ +
+ {{{sortDirection}}} +
+
+
+{{/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('.');