external account 1

This commit is contained in:
Yuri Kuznetsov
2014-09-02 18:26:48 +03:00
parent 25aa0ec564
commit 1796db30fa
21 changed files with 744 additions and 20 deletions

View File

@@ -0,0 +1,104 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
namespace Espo\Controllers;
use \Espo\Core\Exceptions\Error;
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();
$arr = array();
foreach ($integrations as $entity) {
if ($entity->get('enabled') && $this->getMetadata()->get('integrations.' . $entity->id .'.allowUserAccounts')) {
$arr[] = array(
'id' => $entity->id
);
}
}
return array(
'list' => $arr
);
}
public function actionGetOAuthCredentials($params, $data, $request)
{
$id = $request->get('id');
list($integration, $userId) = explode('__', $id);
if (!$this->getUser()->isAdmin()) {
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'
);
}
}
public function actionRead($params, $data, $request)
{
list($integration, $userId) = explode('__', $params['id']);
if (!$this->getUser()->isAdmin()) {
if ($this->getUser()->id != $userId) {
throw new Forbidden();
}
}
$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()->isAdmin()) {
if ($this->getUser()->id != $userId) {
throw new Forbidden();
}
}
$entity = $this->getEntityManager()->getEntity('ExternalAccount', $params['id']);
$entity->set($data);
$this->getEntityManager()->saveEntity($entity);
return $entity->toArray();
}
}

View File

@@ -0,0 +1,28 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014 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\Entities;
class ExternalAccount extends Integration
{
}

View File

@@ -0,0 +1,39 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014 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\Repositories;
use Espo\ORM\Entity;
class ExternalAccount extends \Espo\Core\ORM\Repositories\RDB
{
public function get($id = null)
{
$entity = parent::get($id);
if (empty($entity) && !empty($id)) {
$entity = $this->get();
$entity->id = $id;
}
return $entity;
}
}

View File

@@ -0,0 +1,7 @@
{
"labels": {
"Connect": "Connect"
},
"help": {
}
}

View File

@@ -7,7 +7,8 @@
"EmailTemplate": "Email Template",
"EmailAccount": "Email Account",
"OutboundEmail": "Outbound Email",
"ScheduledJob": "Scheduled Job"
"ScheduledJob": "Scheduled Job",
"ExternalAccount": "External Account"
},
"scopeNamesPlural": {
"Email": "Emails",
@@ -17,7 +18,8 @@
"EmailTemplate": "Email Templates",
"EmailAccount": "Email Accounts",
"OutboundEmail": "Outbound Emails",
"ScheduledJob": "Scheduled Jobs"
"ScheduledJob": "Scheduled Jobs",
"ExternalAccount": "External Accounts"
},
"labels": {
"Misc": "Misc",

View File

@@ -2,7 +2,8 @@
"fields": {
"enabled": "Enabled",
"clientId": "Client ID",
"clientSecret": "Client Secret"
"clientSecret": "Client Secret",
"redirectUri": "Redirect URI"
},
"messages": {
"selectIntegration": "Select an integration in menu."

View File

@@ -0,0 +1,3 @@
{
"controller": "Controllers.ExternalAccount"
}

View File

@@ -0,0 +1,10 @@
{
"fields": {
"data": {
"type": "text"
},
"enabled": {
"type": "bool"
}
}
}

View File

@@ -13,10 +13,8 @@
},
"params": {
"url": "https://accounts.google.com/o/oauth2/auth",
"scope": "profile calendar contacts"
"scope": "https://www.googleapis.com/auth/calendar"
},
"allowUserAccounts": true,
"authMethod": "OAuth2",
"adminView": "Integrations.Google.Admin",
"userView": "Integrations.Google.User"
"authMethod": "OAuth2"
}

View File

@@ -0,0 +1,7 @@
{
"entity":true,
"layouts":false,
"tab":false,
"acl":false,
"customizable":false
}

View File

@@ -10,17 +10,17 @@
<div class="field field-enabled">{{{enabled}}}</div>
</div>
{{#each dataFieldList}}
<div class="cell cell-{{this}} form-group">
<div class="cell cell-{{./this}} form-group">
<label class="control-label field-label-{{./this}}">{{translate this scope='Integration' category='fields'}}</label>
<div class="field field-{{this}}">{{{var this ../this}}}</div>
</div>
{{/each}}
</div>
<div class="col-sm-6">
<div class="well">
{{#if helpText}}
{{{../helpText}}}
{{/if}}
{{#if helpText}}
<div class="well">
{{{../helpText}}}
</div>
{{/if}}
</div>
</div>

View File

@@ -0,0 +1,32 @@
<div class="button-container">
<button class="btn btn-primary" data-action="save">{{translate 'Save'}}</button>
<button class="btn btn-default" data-action="cancel">{{translate 'Cancel'}}</button>
</div>
<div class="row">
<div class="col-sm-6">
<div class="cell cell-enabled form-group">
<label class="control-label">{{translate 'enabled' scope='Integration' category='fields'}}</label>
<div class="field field-enabled">{{{enabled}}}</div>
</div>
{{#each dataFieldList}}
<div class="cell cell-{{./this}} form-group">
<label class="control-label field-label-{{./this}}">{{translate this scope='Integration' category='fields'}}</label>
<div class="field field-{{this}}">{{{var this ../this}}}</div>
</div>
{{/each}}
<div class="cell cell-enabled form-group">
<label class="control-label field-label-redirectUri">{{translate 'redirectUri' scope='Integration' category='fields'}}</label>
<div class="field field-enabled">
<input type="text" class="form-control" readonly value="{{redirectUri}}">
</div>
</div>
</div>
<div class="col-sm-6">
{{#if helpText}}
<div class="well">
{{{../helpText}}}
</div>
{{/if}}
</div>
</div>

View File

@@ -0,0 +1,22 @@
<div class="page-header"><h3>{{translate 'ExternalAccount' category='scopeNamesPlural'}}</h3></div>
<div class="row">
<div id="external-account-menu" class="col-sm-3">
<ul class="list-group">
{{#each externalAccountList}}
<li class="list-group-item"><a href="javascript:" class="external-account-link" data-id="{{id}}">{{id}}</a></li>
{{/each}}
</ul>
{{#unless externalAccountListCount}}
{{translate 'No Data'}}
{{/unless}}
</div>
<div id="external-account-panel" class="col-sm-9">
<h4 id="external-account-header" style="margin-top: 0px;"></h4>
<div id="external-account-content">
{{{content}}}
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
<div class="button-container">
<button class="btn btn-primary" data-action="save">{{translate 'Save'}}</button>
<button class="btn btn-default" data-action="cancel">{{translate 'Cancel'}}</button>
</div>
<div class="row">
<div class="col-sm-6">
<div>
<div class="cell cell-enabled form-group">
<label class="control-label">{{translate 'enabled' scope='Integration' category='fields'}}</label>
<div class="field field-enabled">{{{enabled}}}</div>
</div>
</div>
<div class="data-panel">
<button type="button" class="btn btn-danger" data-action="connect">{{translate 'Connect' scope='ExternalAccount'}}</button>
</div>
</div>
<div class="col-sm-6">
{{#if helpText}}
<div class="well">
{{{../helpText}}}
</div>
{{/if}}
</div>
</div>

View File

@@ -0,0 +1,53 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014 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.ExternalAccount', 'Controller', function (Dep) {
return Dep.extend({
defaultAction: 'list',
list: function (options) {
this.collectionFactory.create('ExternalAccount', function (collection) {
collection.once('sync', function () {
this.main('ExternalAccount.Index', {
collection: collection,
});
}, this);
collection.fetch();
}, this);
},
edit: function (options) {
var id = options.id;
this.collectionFactory.create('ExternalAccount', function (collection) {
collection.once('sync', function () {
this.main('ExternalAccount.Index', {
collection: collection,
id: id
});
}, this);
collection.fetch();
}, this);
},
});
});

View File

@@ -31,6 +31,16 @@ _.extend(Espo.Language.prototype, {
cache: null,
url: 'I18n',
has: function (name, category, scope) {
if (scope in this.data) {
if (category in this.data[scope]) {
if (name in this.data[scope][category]) {
return true;
}
}
}
},
get: function (scope, category, name) {
if (scope in this.data) {

View File

@@ -46,7 +46,7 @@ Espo.define('Views.Admin.Integrations.Edit', 'View', function (Dep) {
this.integration = this.options.integration;
this.helpText = false;
if (this.getLanguage().get(this.integration, 'help', 'Integration') != this.integration) {
if (this.getLanguage().has(this.integration, 'help', 'Integration')) {
this.helpText = this.translate(this.integration, 'help', 'Integration');
}
@@ -70,7 +70,7 @@ Espo.define('Views.Admin.Integrations.Edit', 'View', function (Dep) {
this.wait(true);
var fields = this.fields = this.getMetadata().get('integrations.' + this.integration + '.fields')
var fields = this.fields = this.getMetadata().get('integrations.' + this.integration + '.fields');
Object.keys(this.fields).forEach(function (name) {
this.model.defs.fields[name] = this.fields[name];
@@ -88,8 +88,7 @@ Espo.define('Views.Admin.Integrations.Edit', 'View', function (Dep) {
this.wait(false);
}, this);
this.model.fetch();
this.model.fetch();
},
hideField: function (name) {

View File

@@ -54,7 +54,6 @@ Espo.define('Views.Admin.Integrations.Index', 'View', function (Dep) {
this.renderDefaultPage();
} else {
this.openIntegration(this.integration);
}
});
},
@@ -64,9 +63,9 @@ Espo.define('Views.Admin.Integrations.Index', 'View', function (Dep) {
this.getRouter().navigate('#Admin/integrations/name=' + integration, {trigger: false});
// TODO get viewName
var viewName = 'Admin.Integrations.' + this.getMetadata().get('integrations.' + integration + '.authMethod');
this.notify('Loading...');
this.createView('content', 'Admin.Integrations.Edit', {
this.createView('content', viewName, {
el: '#integration-content',
integration: integration,
}, function (view) {
@@ -88,7 +87,6 @@ Espo.define('Views.Admin.Integrations.Index', 'View', function (Dep) {
return;
}
$('#integration-header').show().html(this.integration);
},
updatePageTitle: function () {

View File

@@ -0,0 +1,37 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014 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.Admin.Integrations.OAuth2', 'Views.Admin.Integrations.Edit', function (Dep) {
return Dep.extend({
template: 'admin.integrations.oauth2',
data: function () {
return _.extend({
redirectUri: this.getConfig().get('siteUrl') + '/oauthcallback'
}, Dep.prototype.data.call(this));
},
});
});

View File

@@ -0,0 +1,106 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014 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.ExternalAccount.Index', 'View', function (Dep) {
return Dep.extend({
template: 'external-account.index',
data: function () {
return {
externalAccountList: this.externalAccountList,
id: this.id,
externalAccountListCount: this.externalAccountList.length
};
},
events: {
'click #external-account-menu a.external-account-link': function (e) {
var id = $(e.currentTarget).data('id') + '__' + this.userId;
this.openExternalAccount(id);
},
},
setup: function () {
this.externalAccountList = this.collection.toJSON();
this.userId = this.getUser().id;
this.id = this.options.id || null;
if (this.id) {
this.userId = this.id.split('__')[1];
}
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.createView('content', viewName, {
el: '#external-account-content',
id: id,
integration: integration
}, function (view) {
this.renderHeader();
view.render();
this.notify(false);
$(window).scrollTop(0);
}.bind(this));
},
renderDefaultPage: function () {
$('#external-account-header').html('').hide();
$('#external-account-content').html('');
},
renderHeader: function () {
if (!this.id) {
$('#external-account-header').html('');
return;
}
$('#external-account-header').show().html(this.integration);
},
updatePageTitle: function () {
this.setPageTitle(this.translate('ExternalAccount', 'scopeNamesPlural'));
},
});
});

View File

@@ -0,0 +1,243 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014 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.ExternalAccount.OAuth2', 'View', function (Dep) {
return Dep.extend({
template: 'external-account.oauth2',
events: {
},
data: function () {
return {
integration: this.integration,
helpText: this.helpText
};
},
events: {
'click button[data-action="cancel"]': function () {
this.getRouter().navigate('#ExternalAccount', {trigger: true});
},
'click button[data-action="save"]': function () {
this.save();
},
'click [data-action="connect"]': function () {
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.model = new Espo.Model();
this.model.id = this.id;
this.model.name = 'ExternalAccount';
this.model.urlRoot = 'ExternalAccount';
this.model.defs = {
fields: {
enabled: {
required: true,
type: 'bool'
},
}
};
this.wait(true);
this.model.populateDefaults();
this.listenToOnce(this.model, 'sync', function () {
this.createFieldView('bool', 'enabled');
$.ajax({
url: 'ExternalAccount/action/getOAuthCredentials?id=' + this.id,
dataType: 'json'
}).done(function (respose) {
this.clientId = respose.clientId;
this.redirectUri = respose.redirectUri;
this.wait(false);
}.bind(this));
}, this);
this.model.fetch();
},
hideField: function (name) {
this.$el.find('label.field-label-' + name).addClass('hide');
this.$el.find('div.field-' + name).addClass('hide');
var view = this.getView(name);
if (view) {
view.enabled = false;
}
},
showField: function (name) {
this.$el.find('label.field-label-' + name).removeClass('hide');
this.$el.find('div.field-' + name).removeClass('hide');
var view = this.getView(name);
if (view) {
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');
} else {
this.$el.find('.data-panel').addClass('hidden');
}
}, this);
},
createFieldView: function (type, name, readOnly, params) {
this.createView(name, this.getFieldManager().getViewName(type), {
model: this.model,
el: this.options.el + ' .field-' + name,
defs: {
name: name,
params: params
},
mode: readOnly ? 'detail' : 'edit',
readOnly: readOnly,
});
this.fieldList.push(name);
},
save: function () {
this.fieldList.forEach(function (field) {
var view = this.getView(field);
if (!view.readOnly) {
view.fetchToModel();
}
}, 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.notify('Saved', 'success');
}, 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) {
if (params[name]) {
arr.push(name + '=' + encodeURI(params[name]));
}
}
path += '?' + arr.join('&');
var parseUrl = function (str) {
var accessToken = false;
var expires = false;
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 == 'access_token') {
accessToken = value;
}
if (name == 'expires') {
expires = value;
}
}, this);
if (accessToken) {
return {
accessToken: accessToken,
expires: expires
}
}
}
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());
callback.call(self, res.accessToken, res.expires);
popup.close();
}
}, 500);
},
connect: function () {
this.popup({
path: this.getMetadata().get('integrations.' + this.integration + '.params.url'),
params: {
client_id: this.clientId,
redirect_uri: this.redirectUri,
scope: this.getMetadata().get('integrations.' + this.integration + '.params.scope'),
response_type: 'token'
}
}, function (accessToken, expires) {
});
},
});
});