2011-04-26 18:22:10 +02:00
|
|
|
/**
|
2013-09-20 12:50:48 +02:00
|
|
|
* EGroupware - Addressbook - Javascript UI
|
2011-04-26 18:22:10 +02:00
|
|
|
*
|
|
|
|
* @link http://www.egroupware.org
|
2013-09-20 12:50:48 +02:00
|
|
|
* @package addressbook
|
|
|
|
* @author Hadi Nategh <hn-AT-stylite.de>
|
|
|
|
* @copyright (c) 2008-13 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
2013-11-04 21:54:23 +01:00
|
|
|
* @version $Id$
|
2011-04-26 18:22:10 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2013-09-20 12:50:48 +02:00
|
|
|
* UI for Addressbook
|
|
|
|
*
|
|
|
|
* @augments AppJS
|
2011-04-26 18:22:10 +02:00
|
|
|
*/
|
2013-11-04 21:54:23 +01:00
|
|
|
app.classes.addressbook = AppJS.extend(
|
2011-04-26 18:22:10 +02:00
|
|
|
{
|
2013-09-20 12:50:48 +02:00
|
|
|
appname: 'addressbook',
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
* @memberOf app.addressbook
|
|
|
|
*/
|
|
|
|
init: function()
|
|
|
|
{
|
|
|
|
// call parent
|
|
|
|
this._super.apply(this, arguments);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destructor
|
|
|
|
*/
|
|
|
|
destroy: function()
|
2011-04-26 18:22:10 +02:00
|
|
|
{
|
2013-09-20 12:50:48 +02:00
|
|
|
// call parent
|
|
|
|
this._super.apply(this, arguments);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function is called when the etemplate2 object is loaded
|
|
|
|
* and ready. If you must store a reference to the et2 object,
|
|
|
|
* make sure to clean it up in destroy().
|
|
|
|
*
|
2014-03-07 10:29:01 +01:00
|
|
|
* @param {etemplate2} et2 newly ready object
|
|
|
|
* @param {string} name
|
2013-09-20 12:50:48 +02:00
|
|
|
*/
|
2014-03-07 10:29:01 +01:00
|
|
|
et2_ready: function(et2, name)
|
2011-04-26 18:22:10 +02:00
|
|
|
{
|
2014-12-09 14:46:27 +01:00
|
|
|
// r49769 let's CRM view run under currentapp == "addressbook", which causes
|
|
|
|
// app.addressbook.et2_ready called before app.infolog.et2_ready and therefore
|
|
|
|
// app.addressbook.et2 would point to infolog template, if we not stop here
|
2015-09-28 19:05:35 +02:00
|
|
|
if (name.match(/^infolog|tracker\./)) return;
|
2014-12-09 14:46:27 +01:00
|
|
|
|
2013-09-20 12:50:48 +02:00
|
|
|
// call parent
|
|
|
|
this._super.apply(this, arguments);
|
|
|
|
|
2014-03-07 10:29:01 +01:00
|
|
|
switch (name)
|
2011-04-26 18:22:10 +02:00
|
|
|
{
|
2014-03-07 10:29:01 +01:00
|
|
|
case 'addressbook.edit':
|
|
|
|
var content = this.et2.getArrayMgr('content').data;
|
|
|
|
if (typeof content.showsearchbuttons == 'undefined' || !content.showsearchbuttons)
|
2014-01-31 17:04:05 +01:00
|
|
|
{
|
2016-06-02 16:51:15 +02:00
|
|
|
this.show_custom_country(jQuery('select[id*="adr_one_countrycode"]').get(0));
|
|
|
|
this.show_custom_country(jQuery('select[id*="adr_two_countrycode"]').get(0));
|
2014-03-07 10:29:01 +01:00
|
|
|
|
|
|
|
// Instanciate infolog JS too - wrong app, so it won't be done automatically
|
|
|
|
if(typeof window.app.infolog != 'object' && typeof window.app.classes['infolog'] == 'function')
|
|
|
|
{
|
|
|
|
window.app.infolog = new window.app.classes.infolog();
|
|
|
|
}
|
2014-01-31 17:04:05 +01:00
|
|
|
}
|
2014-10-13 15:33:10 +02:00
|
|
|
// Call check value if the AB got opened with presets
|
|
|
|
if (window.location.href.match(/&presets\[email\]/g) && content.presets_fields)
|
|
|
|
{
|
|
|
|
for(var i=0;i< content.presets_fields.length;i++)
|
|
|
|
{
|
|
|
|
this.check_value(this.et2.getWidgetById(content.presets_fields),0);
|
|
|
|
}
|
|
|
|
}
|
2014-03-07 10:29:01 +01:00
|
|
|
break;
|
2011-04-26 18:22:10 +02:00
|
|
|
}
|
|
|
|
|
2013-09-20 12:50:48 +02:00
|
|
|
jQuery('select[id*="adr_one_countrycode"]').each(function() {
|
|
|
|
app.addressbook.show_custom_country(this);
|
|
|
|
});
|
|
|
|
jQuery('select[id*="adr_two_countrycode"]').each(function() {
|
|
|
|
app.addressbook.show_custom_country(this);
|
|
|
|
});
|
|
|
|
},
|
2011-04-26 18:22:10 +02:00
|
|
|
|
2017-03-20 18:15:30 +01:00
|
|
|
/**
|
2016-04-11 22:38:12 +02:00
|
|
|
* Observer method receives update notifications from all applications
|
|
|
|
*
|
|
|
|
* App is responsible for only reacting to "messages" it is interested in!
|
|
|
|
*
|
|
|
|
* Addressbook checks for CRM view to update the displayed data if you edit
|
|
|
|
* that contact
|
|
|
|
*
|
|
|
|
* @param {string} _msg message (already translated) to show, eg. 'Entry deleted'
|
|
|
|
* @param {string} _app application name
|
|
|
|
* @param {(string|number)} _id id of entry to refresh or null
|
|
|
|
* @param {string} _type either 'update', 'edit', 'delete', 'add' or null
|
|
|
|
* - update: request just modified data from given rows. Sorting is not considered,
|
|
|
|
* so if the sort field is changed, the row will not be moved.
|
|
|
|
* - edit: rows changed, but sorting may be affected. Requires full reload.
|
|
|
|
* - delete: just delete the given rows clientside (no server interaction neccessary)
|
|
|
|
* - add: requires full reload for proper sorting
|
|
|
|
* @param {string} _msg_type 'error', 'warning' or 'success' (default)
|
|
|
|
* @param {object|null} _links app => array of ids of linked entries
|
|
|
|
* or null, if not triggered on server-side, which adds that info
|
|
|
|
* @return {false|*} false to stop regular refresh, thought all observers are run
|
|
|
|
*/
|
|
|
|
observer: function(_msg, _app, _id, _type, _msg_type, _links)
|
|
|
|
{
|
|
|
|
// Edit to the current entry
|
|
|
|
var state = this.getState();
|
|
|
|
if(_app === 'addressbook' && state && state.type && state.type === 'view' && state.id === _id)
|
|
|
|
{
|
|
|
|
var content = egw.dataGetUIDdata('addressbook::'+_id);
|
|
|
|
if (content.data)
|
|
|
|
{
|
|
|
|
var view = etemplate2.getById('addressbook-view');
|
|
|
|
if(view)
|
|
|
|
{
|
|
|
|
view.widgetContainer._children[0].set_value({content:content.data});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2016-05-26 01:28:59 +02:00
|
|
|
else if(_app === 'calendar')
|
|
|
|
{
|
|
|
|
// Event changed, update any [known] contacts participating
|
|
|
|
var content = egw.dataGetUIDdata(_app+'::'+_id);
|
2016-06-08 20:58:00 +02:00
|
|
|
if (content && content.data && content.data.participant_types && content.data.participant_types.c)
|
2016-05-26 01:28:59 +02:00
|
|
|
{
|
|
|
|
for(var contact in content.data.participant_types.c)
|
|
|
|
{
|
|
|
|
// Refresh handles checking to see if the contact is known,
|
|
|
|
// and updating it directly
|
|
|
|
egw.dataRefreshUID('addressbook::'+contact);
|
|
|
|
}
|
2016-06-14 19:04:16 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (!content)
|
|
|
|
{
|
|
|
|
// No data on the event, we'll have to reload if calendar column is visible
|
|
|
|
// to get the updated information
|
2016-06-30 10:14:19 +02:00
|
|
|
var nm = etemplate2.getById('addressbook-index').widgetContainer.getWidgetById('nm');
|
2016-06-14 19:04:16 +02:00
|
|
|
var pref = nm ? nm._getPreferences() : false;
|
|
|
|
if(pref && pref.visible.indexOf('calendar_calendar') > -1)
|
|
|
|
{
|
|
|
|
nm.refresh(null,'update');
|
|
|
|
}
|
2016-05-26 01:28:59 +02:00
|
|
|
}
|
|
|
|
}
|
2016-04-11 22:38:12 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
2014-03-12 14:29:52 +01:00
|
|
|
/**
|
|
|
|
* Open CRM view
|
|
|
|
*
|
|
|
|
* @param _action
|
|
|
|
* @param _senders
|
|
|
|
*/
|
|
|
|
view: function(_action, _senders)
|
|
|
|
{
|
|
|
|
var index = _senders[0]._index;
|
|
|
|
var id = _senders[0].id.split('::').pop();
|
2014-03-25 17:53:36 +01:00
|
|
|
var extras = {
|
|
|
|
index: index
|
|
|
|
};
|
2014-03-12 14:29:52 +01:00
|
|
|
|
2014-03-25 17:53:36 +01:00
|
|
|
// CRM list
|
|
|
|
if(_action.id != 'view')
|
|
|
|
{
|
|
|
|
extras.crm_list = _action.id.replace('view-','');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.egw.open(id, 'addressbook', 'view', extras, '_self', 'addressbook');
|
2014-03-12 14:29:52 +01:00
|
|
|
},
|
|
|
|
|
2014-03-12 16:21:01 +01:00
|
|
|
/**
|
2014-03-12 23:42:06 +01:00
|
|
|
* Set link filter for the already open & rendered list
|
2014-03-12 16:32:58 +01:00
|
|
|
*
|
2014-08-06 01:27:06 +02:00
|
|
|
* @param {Object} filter Object with key / value pairs of filters to set
|
2014-03-12 16:21:01 +01:00
|
|
|
*/
|
2014-08-06 01:27:06 +02:00
|
|
|
view_set_list: function(filter)
|
2014-03-12 16:21:01 +01:00
|
|
|
{
|
|
|
|
// Find the infolog list
|
2014-03-12 23:42:06 +01:00
|
|
|
var list = etemplate2.getById(
|
2016-06-02 16:51:15 +02:00
|
|
|
jQuery(this.et2.getInstanceManager().DOMContainer).nextAll('.et2_container').attr('id')
|
2014-03-12 16:21:01 +01:00
|
|
|
);
|
2014-03-28 17:56:35 +01:00
|
|
|
var nm = list ? list.widgetContainer.getWidgetById('nm') : null;
|
|
|
|
if(nm)
|
2014-03-12 16:21:01 +01:00
|
|
|
{
|
2014-03-12 23:42:06 +01:00
|
|
|
nm.applyFilters(filter);
|
2014-03-12 16:21:01 +01:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-03-07 10:29:01 +01:00
|
|
|
/**
|
|
|
|
* Run an action from CRM view toolbar
|
|
|
|
*
|
|
|
|
* @param {object} _action
|
|
|
|
*/
|
|
|
|
view_actions: function(_action)
|
|
|
|
{
|
|
|
|
var id = this.et2.getArrayMgr('content').data.id;
|
|
|
|
|
|
|
|
switch(_action.id)
|
|
|
|
{
|
2014-03-12 16:44:06 +01:00
|
|
|
case 'open':
|
2014-03-07 10:29:01 +01:00
|
|
|
this.egw.open(id, 'addressbook', 'edit');
|
|
|
|
break;
|
|
|
|
case 'copy':
|
|
|
|
this.egw.open(id, 'addressbook', 'edit', { makecp: 1});
|
|
|
|
break;
|
|
|
|
case 'cancel':
|
|
|
|
this.egw.open(null, 'addressbook', 'list', null, '_self', 'addressbook');
|
|
|
|
break;
|
2014-03-10 22:15:23 +01:00
|
|
|
default: // submit all other buttons back to server
|
|
|
|
this.et2._inst.submit();
|
2014-03-07 10:29:01 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-03-17 20:14:00 +01:00
|
|
|
/**
|
|
|
|
* Open the calender to view the selected contacts
|
|
|
|
* @param {egwAction} _action
|
|
|
|
* @param {egwActionObject[]} _senders
|
|
|
|
*/
|
|
|
|
view_calendar: function(_action, _senders)
|
|
|
|
{
|
|
|
|
var extras = {
|
|
|
|
filter: 'all',
|
2015-12-15 16:21:38 +01:00
|
|
|
cat_id: '',
|
2015-03-17 20:14:00 +01:00
|
|
|
owner: []
|
|
|
|
};
|
|
|
|
var orgs = [];
|
|
|
|
for(var i = 0; i < _senders.length; i++)
|
|
|
|
{
|
|
|
|
// Remove UID prefix for just contact_id
|
|
|
|
var ids = _senders[i].id.split('::');
|
|
|
|
ids.shift();
|
|
|
|
ids = ids.join('::');
|
|
|
|
|
|
|
|
// Orgs need to get all the contact IDs first
|
|
|
|
if (ids.substr(0,9) == 'org_name:')
|
|
|
|
{
|
|
|
|
orgs.push(ids);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-03-08 17:53:52 +01:00
|
|
|
// Check to see if this is a user account, we prefer to use
|
|
|
|
// account ID in calendar
|
|
|
|
var data = this.egw.dataGetUIDdata(_senders[i].id);
|
|
|
|
if(data && data.data && data.data.account_id)
|
|
|
|
{
|
|
|
|
extras.owner.push(data.data.account_id);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
extras.owner.push('c'+ids);
|
|
|
|
}
|
2015-03-17 20:14:00 +01:00
|
|
|
}
|
|
|
|
}
|
2016-04-29 09:51:13 +02:00
|
|
|
|
2015-03-17 20:14:00 +01:00
|
|
|
if(orgs.length > 0)
|
|
|
|
{
|
|
|
|
// Get organisation contacts, then show infolog list
|
|
|
|
this.egw.json('addressbook.addressbook_ui.ajax_organisation_contacts',
|
|
|
|
[orgs],
|
|
|
|
function(contacts) {
|
|
|
|
for(var i = 0; i < contacts.length; i++)
|
|
|
|
{
|
|
|
|
extras.owner.push('c'+contacts[i]);
|
|
|
|
}
|
|
|
|
extras.owner = extras.owner.join(',');
|
|
|
|
this.egw.open('','calendar','list',extras,'calendar');
|
|
|
|
},this,true,this
|
|
|
|
).sendRequest();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
extras.owner = extras.owner.join(',');
|
|
|
|
egw.open('', 'calendar', 'list', extras, 'calendar');
|
|
|
|
}
|
|
|
|
},
|
2013-09-20 12:50:48 +02:00
|
|
|
/**
|
|
|
|
* Add appointment or show calendar for selected contacts, call default nm_action after some checks
|
|
|
|
*
|
|
|
|
* @param _action
|
|
|
|
* @param _senders
|
|
|
|
*/
|
|
|
|
add_cal: function(_action, _senders)
|
|
|
|
{
|
2014-01-14 16:43:06 +01:00
|
|
|
if (!_senders[0].id.match(/^(?:addressbook::)?[0-9]+$/))
|
2013-09-20 12:50:48 +02:00
|
|
|
{
|
|
|
|
// send org-view requests to server
|
|
|
|
_action.data.nm_action = "submit";
|
2014-01-14 16:43:06 +01:00
|
|
|
nm_action(_action, _senders);
|
2013-09-20 12:50:48 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-06-14 19:11:05 +02:00
|
|
|
var ids = egw.user('account_id')+',';
|
2013-09-20 12:50:48 +02:00
|
|
|
for (var i = 0; i < _senders.length; i++)
|
|
|
|
{
|
2014-01-14 16:43:06 +01:00
|
|
|
// Remove UID prefix for just contact_id
|
|
|
|
var id = _senders[i].id.split('::');
|
|
|
|
ids += "c" + id[1] + ((i < _senders.length - 1) ? "," : "");
|
2011-04-26 18:22:10 +02:00
|
|
|
}
|
2014-01-14 16:43:06 +01:00
|
|
|
var extra = {};
|
2015-05-25 16:27:26 +02:00
|
|
|
extra[_action.data && _action.data.url && _action.data.url.indexOf('owner') > 0 ? 'owner' : 'participants'] = ids;
|
2014-01-14 16:43:06 +01:00
|
|
|
|
|
|
|
// Use framework to add calendar entry
|
|
|
|
egw.open('','calendar','add',extra);
|
2011-04-26 18:22:10 +02:00
|
|
|
}
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
2011-04-26 18:22:10 +02:00
|
|
|
|
2014-01-16 16:15:50 +01:00
|
|
|
/**
|
2014-03-18 20:20:29 +01:00
|
|
|
* View infolog entries linked to selected contact
|
2014-01-16 16:15:50 +01:00
|
|
|
* @param {egwAction} _action Select action
|
|
|
|
* @param {egwActionObject[]} _senders Selected contact(s)
|
|
|
|
*/
|
|
|
|
view_infolog: function(_action, _senders)
|
|
|
|
{
|
|
|
|
var extras = {
|
|
|
|
action: 'addressbook',
|
|
|
|
action_id: [],
|
|
|
|
action_title: _senders.length > 1 ? this.egw.lang('selected contacts') : ''
|
2014-02-13 14:54:52 +01:00
|
|
|
};
|
2014-03-18 20:20:29 +01:00
|
|
|
var orgs = [];
|
2014-03-13 20:04:47 +01:00
|
|
|
for(var i = 0; i < _senders.length; i++)
|
|
|
|
{
|
|
|
|
// Remove UID prefix for just contact_id
|
|
|
|
var ids = _senders[i].id.split('::');
|
|
|
|
ids.shift();
|
|
|
|
ids = ids.join('::');
|
|
|
|
|
2014-03-18 20:20:29 +01:00
|
|
|
// Orgs need to get all the contact IDs first
|
2014-03-13 20:04:47 +01:00
|
|
|
if (ids.substr(0,9) == 'org_name:')
|
|
|
|
{
|
2014-03-18 20:20:29 +01:00
|
|
|
orgs.push(ids);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
extras.action_id.push(ids);
|
2014-03-13 20:04:47 +01:00
|
|
|
}
|
|
|
|
}
|
2014-01-21 16:29:09 +01:00
|
|
|
|
2014-03-18 20:20:29 +01:00
|
|
|
if(orgs.length > 0)
|
|
|
|
{
|
|
|
|
// Get organisation contacts, then show infolog list
|
|
|
|
this.egw.json('addressbook.addressbook_ui.ajax_organisation_contacts',
|
|
|
|
[orgs],
|
|
|
|
function(contacts) {
|
|
|
|
extras.action_id = extras.action_id.concat(contacts);
|
|
|
|
this.egw.open('','infolog','list',extras,'infolog');
|
|
|
|
},this,true,this
|
|
|
|
).sendRequest();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
egw.open('', 'infolog', 'list', extras, 'infolog');
|
|
|
|
}
|
2014-01-16 16:15:50 +01:00
|
|
|
},
|
|
|
|
|
2013-09-20 12:50:48 +02:00
|
|
|
/**
|
|
|
|
* Add task for selected contacts, call default nm_action after some checks
|
|
|
|
*
|
|
|
|
* @param _action
|
|
|
|
* @param _senders
|
|
|
|
*/
|
|
|
|
add_task: function(_action, _senders)
|
|
|
|
{
|
|
|
|
if (!_senders[0].id.match(/^(addressbook::)?[0-9]+$/))
|
|
|
|
{
|
|
|
|
// send org-view requests to server
|
|
|
|
_action.data.nm_action = "submit";
|
2013-06-19 21:02:41 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-09-20 12:50:48 +02:00
|
|
|
// call nm_action's popup
|
|
|
|
_action.data.nm_action = "popup";
|
2013-06-19 21:02:41 +02:00
|
|
|
}
|
2013-09-20 12:50:48 +02:00
|
|
|
nm_action(_action, _senders);
|
|
|
|
},
|
|
|
|
|
2014-02-13 11:06:18 +01:00
|
|
|
/**
|
|
|
|
* [More...] in phones clicked: copy allways shown phone numbers to phone popup
|
|
|
|
*
|
|
|
|
* @param {jQuery.event} _event
|
|
|
|
* @param {et2_widget} _widget
|
|
|
|
*/
|
|
|
|
showphones: function(_event, _widget)
|
2013-09-20 12:50:48 +02:00
|
|
|
{
|
2014-02-13 11:06:18 +01:00
|
|
|
this._copyvalues({
|
|
|
|
tel_home: 'tel_home2',
|
|
|
|
tel_work: 'tel_work2',
|
|
|
|
tel_cell: 'tel_cell2',
|
2014-02-13 14:54:52 +01:00
|
|
|
tel_fax: 'tel_fax2'
|
2014-02-13 11:06:18 +01:00
|
|
|
});
|
|
|
|
jQuery('table.editphones').css('display','inline');
|
2013-09-20 12:50:48 +02:00
|
|
|
|
2014-02-13 11:06:18 +01:00
|
|
|
_event.stopPropagation();
|
|
|
|
return false;
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
2014-02-13 11:06:18 +01:00
|
|
|
/**
|
|
|
|
* [OK] in phone popup clicked: copy phone numbers back to always shown ones
|
|
|
|
*
|
|
|
|
* @param {jQuery.event} _event
|
|
|
|
* @param {et2_widget} _widget
|
|
|
|
*/
|
|
|
|
hidephones: function(_event, _widget)
|
2013-09-20 12:50:48 +02:00
|
|
|
{
|
2014-02-13 11:06:18 +01:00
|
|
|
this._copyvalues({
|
|
|
|
tel_home2: 'tel_home',
|
|
|
|
tel_work2: 'tel_work',
|
|
|
|
tel_cell2: 'tel_cell',
|
2014-02-13 14:54:52 +01:00
|
|
|
tel_fax2: 'tel_fax'
|
2014-02-13 11:06:18 +01:00
|
|
|
});
|
|
|
|
jQuery('table.editphones').css('display','none');
|
|
|
|
|
|
|
|
_event.stopPropagation();
|
|
|
|
return false;
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
2014-02-13 11:06:18 +01:00
|
|
|
/**
|
|
|
|
* Copy content of multiple fields
|
|
|
|
*
|
|
|
|
* @param {object} what object with src: dst pairs
|
|
|
|
*/
|
|
|
|
_copyvalues: function(what)
|
2013-09-20 12:50:48 +02:00
|
|
|
{
|
2014-02-13 11:06:18 +01:00
|
|
|
for(var name in what)
|
|
|
|
{
|
|
|
|
var src = this.et2.getWidgetById(name);
|
|
|
|
var dst = this.et2.getWidgetById(what[name]);
|
2014-02-13 18:29:44 +01:00
|
|
|
if (src && dst) dst.set_value(src.get_value ? src.get_value() : src.value);
|
2013-09-20 12:50:48 +02:00
|
|
|
}
|
2014-02-13 11:06:18 +01:00
|
|
|
// change tel_prefer according to what
|
|
|
|
var tel_prefer = this.et2.getWidgetById('tel_prefer');
|
2014-02-13 18:51:02 +01:00
|
|
|
if (tel_prefer)
|
|
|
|
{
|
|
|
|
var val = tel_prefer.get_value ? tel_prefer.get_value() : tel_prefer.value;
|
|
|
|
if (typeof what[val] != 'undefined') tel_prefer.set_value(what[val]);
|
|
|
|
}
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
2014-01-29 19:36:55 +01:00
|
|
|
/**
|
2014-01-31 16:37:44 +01:00
|
|
|
* Callback function to create confirm dialog for duplicates contacts
|
2014-01-29 19:36:55 +01:00
|
|
|
*
|
2014-01-31 16:37:44 +01:00
|
|
|
* @param {object} _data includes duplicates contacts information
|
2014-01-29 19:36:55 +01:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
_confirmdialog_callback: function(_data)
|
|
|
|
{
|
2014-02-13 14:54:52 +01:00
|
|
|
var confirmdialog = function(_title, _value, _buttons, _egw_or_appname)
|
|
|
|
{
|
|
|
|
return et2_createWidget("dialog",
|
|
|
|
{
|
|
|
|
callback: function(_buttons, _value)
|
|
|
|
{
|
|
|
|
if (_buttons == et2_dialog.OK_BUTTON)
|
|
|
|
{
|
|
|
|
var id = '';
|
|
|
|
var content = this.template.widgetContainer.getArrayMgr('content').data;
|
|
|
|
for (var row in _value.grid)
|
|
|
|
{
|
|
|
|
if (_value.grid[row].confirm == "true" && typeof content.grid !='undefined')
|
2014-01-31 16:37:44 +01:00
|
|
|
{
|
2014-02-13 14:54:52 +01:00
|
|
|
id = this.options.value.content.grid[row].confirm;
|
|
|
|
egw.open(id, 'addressbook');
|
2014-01-29 19:36:55 +01:00
|
|
|
|
2014-02-13 14:54:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
title: _title||egw.lang('Input required'),
|
|
|
|
buttons: _buttons||et2_dialog.BUTTONS_OK_CANCEL,
|
|
|
|
value: {
|
|
|
|
content: {
|
|
|
|
grid: _value
|
|
|
|
}
|
|
|
|
},
|
|
|
|
template: egw.webserverUrl+'/addressbook/templates/default/dupconfirmdialog.xet'
|
|
|
|
}, et2_dialog._create_parent(_egw_or_appname));
|
|
|
|
};
|
2014-01-29 19:36:55 +01:00
|
|
|
|
|
|
|
if (_data.msg && _data.doublicates)
|
|
|
|
{
|
|
|
|
var content = [];
|
|
|
|
|
|
|
|
for(var id in _data.doublicates)
|
|
|
|
{
|
|
|
|
content.push({"confirm":id,"name":_data.doublicates[id]});
|
|
|
|
}
|
2016-07-19 12:52:58 +02:00
|
|
|
confirmdialog(this.egw.lang('Duplicate warning'),content,et2_dialog.BUTTONs_OK_CANCEL);
|
2014-01-29 19:36:55 +01:00
|
|
|
}
|
2014-02-13 14:54:52 +01:00
|
|
|
if (typeof _data.fileas_options == 'object' && this.et2)
|
2014-01-29 19:36:55 +01:00
|
|
|
{
|
2014-02-13 14:54:52 +01:00
|
|
|
var selbox = this.et2.getWidgetById('fileas_type');
|
|
|
|
if (selbox)
|
2014-01-29 19:36:55 +01:00
|
|
|
{
|
|
|
|
selbox.set_select_options(_data.fileas_sel_options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2014-02-13 11:06:18 +01:00
|
|
|
|
2014-01-29 19:36:55 +01:00
|
|
|
/**
|
2014-02-13 14:54:52 +01:00
|
|
|
* Callback if certain fields get changed
|
2014-01-29 19:36:55 +01:00
|
|
|
*
|
|
|
|
* @param {widget} widget widget
|
|
|
|
* @param {string} own_id Current AB id
|
|
|
|
*/
|
|
|
|
check_value: function(widget, own_id)
|
2013-09-20 12:50:48 +02:00
|
|
|
{
|
2014-03-28 17:56:35 +01:00
|
|
|
// if we edit an account, call account_change to let it do it's stuff too
|
|
|
|
if (this.et2.getWidgetById('account_lid'))
|
|
|
|
{
|
|
|
|
this.account_change(null, widget);
|
|
|
|
}
|
|
|
|
|
2014-02-13 14:54:52 +01:00
|
|
|
var values = this.et2._inst.getValues(this.et2);
|
2013-09-20 12:50:48 +02:00
|
|
|
|
2014-01-29 19:36:55 +01:00
|
|
|
if (widget.id.match(/n_/))
|
2012-09-06 14:26:11 +02:00
|
|
|
{
|
2013-09-20 12:50:48 +02:00
|
|
|
var value = '';
|
|
|
|
if (values.n_prefix) value += values.n_prefix+" ";
|
|
|
|
if (values.n_given) value += values.n_given+" ";
|
|
|
|
if (values.n_middle) value += values.n_middle+" ";
|
|
|
|
if (values.n_family) value += values.n_family+" ";
|
|
|
|
if (values.n_suffix) value += values.n_suffix;
|
|
|
|
|
2014-02-13 15:08:21 +01:00
|
|
|
var name = this.et2.getWidgetById("n_fn");
|
2014-01-29 19:36:55 +01:00
|
|
|
if (typeof name != 'undefined') name.set_value(value);
|
2012-09-06 14:26:11 +02:00
|
|
|
}
|
2014-01-29 19:36:55 +01:00
|
|
|
egw.json('addressbook.addressbook_ui.ajax_check_values', [values, widget.id, own_id],this._confirmdialog_callback,this,true,this).sendRequest();
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
show_custom_country: function(selectbox)
|
|
|
|
{
|
|
|
|
if(!selectbox) return;
|
2013-11-27 00:37:48 +01:00
|
|
|
var custom_field_name = selectbox.id.replace("countrycode", "countryname");
|
2013-09-20 12:50:48 +02:00
|
|
|
var custom_field = document.getElementById(custom_field_name);
|
|
|
|
if(custom_field && selectbox.value == "-custom-") {
|
|
|
|
custom_field.style.display = "inline";
|
|
|
|
}
|
|
|
|
else if (custom_field)
|
|
|
|
{
|
|
|
|
if((selectbox.value == "" || selectbox.value == null) && custom_field.value != "")
|
|
|
|
{
|
|
|
|
selectbox.value = "-custom-";
|
|
|
|
// Chosen needs this to update
|
2016-06-02 16:51:15 +02:00
|
|
|
jQuery(selectbox).trigger("liszt:updated");
|
2013-09-20 12:50:48 +02:00
|
|
|
|
|
|
|
custom_field.style.display = "inline";
|
|
|
|
}
|
|
|
|
else
|
2013-06-19 21:02:41 +02:00
|
|
|
{
|
2013-09-20 12:50:48 +02:00
|
|
|
custom_field.style.display = "none";
|
2013-06-19 21:02:41 +02:00
|
|
|
}
|
2012-09-06 14:26:11 +02:00
|
|
|
}
|
2018-09-19 16:43:19 +02:00
|
|
|
var region = this.et2.getWidgetById(selectbox.name.replace('countrycode', 'region'));
|
|
|
|
if (region)
|
|
|
|
{
|
|
|
|
region.set_country_code(selectbox.value);
|
|
|
|
region.options.select_options = {};
|
|
|
|
region.transformAttributes(region.options);
|
|
|
|
}
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
2011-04-26 18:22:10 +02:00
|
|
|
|
2016-12-20 16:47:33 +01:00
|
|
|
/**
|
|
|
|
* Add a new mailing list. If any contacts are selected, they will be added.
|
|
|
|
*
|
|
|
|
* @param {egwAction} owner
|
|
|
|
* @param {egwActionObject[]} selected
|
|
|
|
*/
|
|
|
|
add_new_list: function(owner, selected)
|
2011-04-26 18:22:10 +02:00
|
|
|
{
|
2014-10-27 19:43:03 +01:00
|
|
|
if(!owner || typeof owner == 'object')
|
|
|
|
{
|
|
|
|
var filter = this.et2.getWidgetById('filter');
|
|
|
|
owner = filter.getValue()||egw.preference('add_default','addressbook');
|
|
|
|
}
|
2016-12-20 16:47:33 +01:00
|
|
|
var contacts = [];
|
2017-03-20 18:15:30 +01:00
|
|
|
if(selected && selected[0] && selected[0].getAllSelected())
|
|
|
|
{
|
2017-05-02 16:24:37 +02:00
|
|
|
// Action says all contacts selected, better ask the server for _all_ the IDs
|
|
|
|
var fetching = fetchAll(selected, this.et2.getWidgetById('nm'), jQuery.proxy(
|
2017-03-20 18:15:30 +01:00
|
|
|
function(contacts) {
|
|
|
|
this._add_new_list_prompt(owner, contacts);
|
|
|
|
}, this));
|
2017-05-02 16:24:37 +02:00
|
|
|
if (fetching) return;
|
2017-03-20 18:15:30 +01:00
|
|
|
}
|
2017-05-02 16:24:37 +02:00
|
|
|
if(selected && selected.length)
|
2016-12-20 16:47:33 +01:00
|
|
|
{
|
|
|
|
for(var i = 0; i < selected.length; i++)
|
|
|
|
{
|
|
|
|
// Remove UID prefix for just contact_id
|
|
|
|
var ids = selected[i].id.split('::');
|
|
|
|
ids.shift();
|
|
|
|
ids = ids.join('::');
|
|
|
|
contacts.push(ids);
|
|
|
|
}
|
|
|
|
}
|
2017-03-20 18:15:30 +01:00
|
|
|
this._add_new_list_prompt(owner, contacts);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ask the user for a name, then create a new list with the provided contacts
|
|
|
|
* in it.
|
|
|
|
*
|
|
|
|
* @param {int} owner
|
|
|
|
* @param {String[]} contacts
|
|
|
|
*/
|
|
|
|
_add_new_list_prompt: function(owner, contacts)
|
|
|
|
{
|
|
|
|
var lists = this.et2.getWidgetById('filter2');
|
2014-10-28 20:50:19 +01:00
|
|
|
et2_dialog.show_prompt(
|
|
|
|
function(button, name) {
|
|
|
|
if(button == et2_dialog.OK_BUTTON)
|
|
|
|
{
|
2016-12-20 16:47:33 +01:00
|
|
|
egw.json('addressbook.addressbook_ui.ajax_set_list',[0, name, owner, contacts],
|
2014-10-28 20:50:19 +01:00
|
|
|
function(result)
|
|
|
|
{
|
|
|
|
if(typeof result == 'object') return; // This response not for us
|
|
|
|
// Update list
|
|
|
|
if(result)
|
|
|
|
{
|
|
|
|
lists.options.select_options.unshift({value:result,label:name});
|
|
|
|
lists.set_select_options(lists.options.select_options);
|
|
|
|
|
|
|
|
// Set to new list so they can see it easily
|
|
|
|
lists.set_value(result);
|
2015-03-06 15:35:19 +01:00
|
|
|
// Call cahnge event manually after setting the value
|
|
|
|
// Not sure why our selectbox does not trigger change event
|
|
|
|
jQuery(lists.node).change();
|
2014-10-28 20:50:19 +01:00
|
|
|
}
|
2019-05-14 19:50:01 +02:00
|
|
|
// Add to actions
|
|
|
|
var addressbook_actions = egw_getActionManager('addressbook',false);
|
|
|
|
var dist_lists = null;
|
|
|
|
if(addressbook_actions && (dist_lists = addressbook_actions.getActionById('to_list')))
|
|
|
|
{
|
|
|
|
var id = 'to_list_' + result;
|
|
|
|
var action = dist_lists.addAction(
|
|
|
|
'popup',
|
|
|
|
id,
|
|
|
|
name
|
|
|
|
);
|
|
|
|
action.updateAction({group: 1});
|
|
|
|
}
|
2014-10-28 20:50:19 +01:00
|
|
|
}
|
|
|
|
).sendRequest(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
this.egw.lang('Name for the distribution list'),
|
2017-02-27 19:14:31 +01:00
|
|
|
this.egw.lang('Add a new list')
|
2014-10-28 20:50:19 +01:00
|
|
|
);
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
2014-10-27 19:43:03 +01:00
|
|
|
/**
|
|
|
|
* Rename the current distribution list selected in the nextmatch filter2
|
|
|
|
*
|
2014-10-28 20:50:19 +01:00
|
|
|
* Differences from add_new_list are in the dialog, parameters sent, and how the
|
|
|
|
* response is dealt with
|
|
|
|
*
|
2014-10-27 19:43:03 +01:00
|
|
|
* @param {egwAction} action Action selected in context menu (rename)
|
|
|
|
* @param {egwActionObject[]} selected The selected row(s). Not used for this.
|
|
|
|
*/
|
|
|
|
rename_list: function(action, selected)
|
|
|
|
{
|
|
|
|
var lists = this.et2.getWidgetById('filter2');
|
|
|
|
var list = lists.getValue() || 0;
|
|
|
|
var value = null;
|
|
|
|
for(var i = 0; i < lists.options.select_options.length; i++)
|
|
|
|
{
|
|
|
|
if(lists.options.select_options[i].value == list)
|
|
|
|
{
|
|
|
|
value = lists.options.select_options[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
et2_dialog.show_prompt(
|
|
|
|
function(button, name) {
|
|
|
|
if(button == et2_dialog.OK_BUTTON)
|
|
|
|
{
|
2014-10-28 20:50:19 +01:00
|
|
|
egw.json('addressbook.addressbook_ui.ajax_set_list',[list, name],
|
2014-10-27 19:43:03 +01:00
|
|
|
function(result)
|
|
|
|
{
|
|
|
|
if(typeof result == 'object') return; // This response not for us
|
|
|
|
// Update list
|
|
|
|
if(result)
|
|
|
|
{
|
|
|
|
value.label = name;
|
|
|
|
lists.set_select_options(lists.options.select_options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
).sendRequest(true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
this.egw.lang('Name for the distribution list'),
|
|
|
|
this.egw.lang('Rename list'),
|
|
|
|
value.label
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2017-01-23 16:16:56 +01:00
|
|
|
/**
|
|
|
|
* OnChange for distribution list selectbox
|
|
|
|
*/
|
2013-09-20 12:50:48 +02:00
|
|
|
filter2_onchange: function()
|
2011-04-26 18:22:10 +02:00
|
|
|
{
|
2017-01-23 16:16:56 +01:00
|
|
|
var filter = this.et2.getWidgetById('filter');
|
2013-09-20 12:50:48 +02:00
|
|
|
var filter2 = this.et2.getWidgetById('filter2');
|
|
|
|
var widget = this.et2.getWidgetById('nm');
|
2017-01-23 16:16:56 +01:00
|
|
|
var filter2_val = filter2.get_value();
|
2011-04-26 18:22:10 +02:00
|
|
|
|
2017-01-23 16:16:56 +01:00
|
|
|
if(filter2_val == 'add')
|
2013-09-20 12:50:48 +02:00
|
|
|
{
|
|
|
|
this.add_new_list(typeof widget == 'undefined' ? this.et2.getWidgetById('filter').value : widget.header.filter.get_value());
|
|
|
|
this.value='';
|
|
|
|
}
|
2017-01-24 16:43:57 +01:00
|
|
|
// automatic switch to accounts addressbook or all addressbooks depending on distribution list is a group
|
|
|
|
else if (filter2_val && (filter2_val < 0) !== (filter.get_value() === '0'))
|
|
|
|
{
|
|
|
|
// Change filter & filter2 at the same time
|
|
|
|
widget.applyFilters({
|
|
|
|
filter: filter2_val < 0 ? '0' : '',
|
|
|
|
filter2: filter2_val
|
|
|
|
});
|
|
|
|
// Don't get rows here, let applyFilters() do it
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-28 17:30:29 +01:00
|
|
|
|
2017-01-24 16:43:57 +01:00
|
|
|
return true;
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2014-02-13 14:54:52 +01:00
|
|
|
* Method to enable actions by comparing a field with given value
|
2013-09-20 12:50:48 +02:00
|
|
|
*/
|
|
|
|
nm_compare_field: function()
|
|
|
|
{
|
|
|
|
var field = this.et2.getWidgetById('filter2');
|
|
|
|
if (field) var val = field.get_value();
|
|
|
|
if (val)
|
|
|
|
{
|
|
|
|
return nm_compare_field;
|
2011-04-26 18:22:10 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-09-20 12:50:48 +02:00
|
|
|
return false;
|
2011-04-26 18:22:10 +02:00
|
|
|
}
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
2013-05-07 01:16:10 +02:00
|
|
|
|
2013-09-20 12:50:48 +02:00
|
|
|
/**
|
2014-09-08 20:47:40 +02:00
|
|
|
* Apply advanced search filters to index nextmatch
|
2014-10-16 11:32:13 +02:00
|
|
|
*
|
|
|
|
* @param {object} filters
|
2013-09-20 12:50:48 +02:00
|
|
|
*/
|
2014-09-08 20:47:40 +02:00
|
|
|
adv_search: function(filters)
|
|
|
|
{
|
|
|
|
var index = window.opener.etemplate2.getById('addressbook-index');
|
|
|
|
if(!index)
|
|
|
|
{
|
|
|
|
alert('Could not find index');
|
2014-12-12 15:21:04 +01:00
|
|
|
egw(window).close();
|
2014-09-08 20:47:40 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
var nm = index.widgetContainer.getWidgetById('nm');
|
|
|
|
if(!index)
|
|
|
|
{
|
|
|
|
window.opener.egw.message('Could not find list', 'error');
|
2014-12-12 15:21:04 +01:00
|
|
|
egw(window).close();
|
2014-09-08 20:47:40 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Reset filters first
|
|
|
|
nm.activeFilters = {};
|
|
|
|
nm.applyFilters(filters);
|
|
|
|
return false;
|
|
|
|
},
|
2013-09-20 12:50:48 +02:00
|
|
|
|
|
|
|
/**
|
2014-02-13 14:54:52 +01:00
|
|
|
* Mail vCard
|
2013-09-20 12:50:48 +02:00
|
|
|
*
|
2014-02-13 14:54:52 +01:00
|
|
|
* @param {object} _action
|
|
|
|
* @param {array} _elems
|
2013-09-20 12:50:48 +02:00
|
|
|
*/
|
|
|
|
adb_mail_vcard: function(_action, _elems)
|
2013-05-07 01:16:10 +02:00
|
|
|
{
|
2018-04-09 16:42:08 +02:00
|
|
|
var link = {'preset[type]':[], 'preset[file]':[]};
|
2018-07-11 14:39:28 +02:00
|
|
|
var content = {data:{files:{file:[], type:[]}}};
|
2018-04-09 16:42:08 +02:00
|
|
|
var nm = this.et2.getWidgetById('nm');
|
|
|
|
if(fetchAll(_elems, nm, jQuery.proxy(function(ids) {
|
|
|
|
this.adb_mail_vcard(_action, ids.map(function(num) {return {id:'addressbook::'+num};}));
|
|
|
|
}, this)))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-09-20 12:50:48 +02:00
|
|
|
for (var i = 0; i < _elems.length; i++)
|
|
|
|
{
|
2014-03-21 13:38:47 +01:00
|
|
|
var idToUse = _elems[i].id;
|
|
|
|
var idToUseArray = idToUse.split('::');
|
|
|
|
idToUse = idToUseArray[1];
|
2018-04-09 16:42:08 +02:00
|
|
|
link['preset[type]'].push("text/vcard; charset="+(egw.preference('vcard_charset', 'addressbook') || 'utf-8'));
|
|
|
|
link['preset[file]'].push("vfs://default/apps/addressbook/"+idToUse+"/.entry");
|
2018-07-11 14:39:28 +02:00
|
|
|
content.data.files.file.push("vfs://default/apps/addressbook/"+idToUse+"/.entry");
|
|
|
|
content.data.files.type.push("text/vcard; charset="+(egw.preference('vcard_charset', 'addressbook') || 'utf-8'));
|
2018-02-28 17:30:29 +01:00
|
|
|
}
|
2018-03-09 17:48:09 +01:00
|
|
|
egw.openWithinWindow("mail", "setCompose", content, link, /mail.mail_compose.compose/);
|
2018-06-07 14:45:42 +02:00
|
|
|
|
|
|
|
for (var index in content)
|
|
|
|
{
|
|
|
|
if (content[index].file.length > 0)
|
|
|
|
{
|
|
|
|
egw.message(egw.lang('%1 contact(s) added as %2', content[index].file.length, egw.lang(index)));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
2014-01-15 12:22:37 +01:00
|
|
|
/**
|
|
|
|
* Action function to set business or private mail checkboxes to user preferences
|
|
|
|
*
|
|
|
|
* @param {egwAction} action Action user selected.
|
|
|
|
*/
|
|
|
|
mailCheckbox: function(action)
|
|
|
|
{
|
|
|
|
var preferences = {
|
2014-02-13 14:54:52 +01:00
|
|
|
business: action.getManager().getActionById('email_business').checked ? true : false,
|
|
|
|
private: action.getManager().getActionById('email_home').checked ? true : false
|
|
|
|
};
|
|
|
|
this.egw.set_preference('addressbook','preferredMail', preferences);
|
2014-01-15 12:22:37 +01:00
|
|
|
},
|
2013-09-20 12:50:48 +02:00
|
|
|
|
2013-12-19 18:24:46 +01:00
|
|
|
/**
|
|
|
|
* Action function to add the email address (business or home) of the selected
|
|
|
|
* contacts to a compose email popup window.
|
|
|
|
*
|
|
|
|
* Uses the egw API to handle the opening of the popup.
|
|
|
|
*
|
|
|
|
* @param {egwAction} action Action user selected. Should have ID of either
|
|
|
|
* 'email_business' or 'email_home', from server side definition of actions.
|
|
|
|
* @param {egwActionObject[]} selected Selected rows
|
|
|
|
*/
|
|
|
|
addEmail: function(action, selected)
|
2013-09-20 12:50:48 +02:00
|
|
|
{
|
2014-10-16 11:32:13 +02:00
|
|
|
// Check for all selected.
|
2014-09-22 21:39:50 +02:00
|
|
|
var nm = this.et2.getWidgetById('nm');
|
|
|
|
if(fetchAll(selected, nm, jQuery.proxy(function(ids) {
|
|
|
|
// fetchAll() returns just the ID, no prefix, so map it to match normal selected
|
|
|
|
this.addEmail(action, ids.map(function(num) {return {id:'addressbook::'+num};}));
|
|
|
|
}, this)))
|
|
|
|
{
|
|
|
|
// Need more IDs, will use the above callback when they're ready.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-12-19 18:24:46 +01:00
|
|
|
// Go through selected & pull email addresses from data
|
|
|
|
var emails = [];
|
|
|
|
for(var i = 0; i < selected.length; i++)
|
|
|
|
{
|
|
|
|
// Pull data from global cache
|
|
|
|
var data = egw.dataGetUIDdata(selected[i].id) || {data:{}};
|
2014-01-13 18:40:36 +01:00
|
|
|
|
2014-01-15 10:11:32 +01:00
|
|
|
var email_business = data.data[action.getManager().getActionById('email_business').checked ? 'email' : ''];
|
|
|
|
var email = data.data[action.getManager().getActionById('email_home').checked ? 'email_home' : ''];
|
2014-12-05 13:37:40 +01:00
|
|
|
// prefix email with full name
|
|
|
|
var personal = data.data.n_fn || '';
|
|
|
|
if (personal.match(/[^a-z0-9. -]/i)) personal = '"'+personal.replace(/"/, '\\"')+'"';
|
|
|
|
|
2018-07-05 14:29:49 +02:00
|
|
|
//remove comma in personal as it will confilict with mail content comma seperator in the process
|
|
|
|
personal = personal.replace(/,/g,'');
|
2018-07-11 14:39:28 +02:00
|
|
|
|
2014-01-13 18:40:36 +01:00
|
|
|
if(email_business)
|
|
|
|
{
|
2014-12-05 13:37:40 +01:00
|
|
|
emails.push((personal?personal+' <':'')+email_business+(personal?'>':''));
|
2014-01-13 18:40:36 +01:00
|
|
|
}
|
2013-12-19 18:24:46 +01:00
|
|
|
if(email)
|
|
|
|
{
|
2014-12-05 13:37:40 +01:00
|
|
|
emails.push((personal?personal+' <':'')+email+(personal?'>':''));
|
2013-12-19 18:24:46 +01:00
|
|
|
}
|
|
|
|
}
|
2014-01-13 18:40:36 +01:00
|
|
|
switch (action.id)
|
2014-01-06 11:12:51 +01:00
|
|
|
{
|
2014-01-13 18:40:36 +01:00
|
|
|
case "add_to_to":
|
2018-07-05 11:29:46 +02:00
|
|
|
egw.open_link('mailto:' + emails.join(',').replace(/&/g, '__AMPERSAND__'));
|
2014-01-13 18:40:36 +01:00
|
|
|
break;
|
|
|
|
case "add_to_cc":
|
2018-07-05 11:29:46 +02:00
|
|
|
egw.open_link('mailto:' + '?cc=' + emails.join(',').replace(/&/g, '__AMPERSAND__'));
|
2014-01-13 18:40:36 +01:00
|
|
|
//egw.mailto('mailto:');
|
|
|
|
break;
|
|
|
|
case "add_to_bcc":
|
2018-07-05 11:29:46 +02:00
|
|
|
egw.open_link('mailto:' + '?bcc=' + emails.join(',').replace(/&/g, '__AMPERSAND__'));
|
2014-01-13 18:40:36 +01:00
|
|
|
break;
|
2014-01-06 11:12:51 +01:00
|
|
|
}
|
2014-01-13 18:40:36 +01:00
|
|
|
|
2013-12-19 18:24:46 +01:00
|
|
|
return false;
|
2013-09-20 12:50:48 +02:00
|
|
|
},
|
|
|
|
|
2017-01-19 17:36:38 +01:00
|
|
|
/**
|
|
|
|
* Merge the selected contacts into the target document.
|
|
|
|
*
|
|
|
|
* Normally we let the framework handle this, but in addressbook we want to
|
|
|
|
* interfere and customize things a little to ask about saving to infolog.
|
|
|
|
*
|
|
|
|
* @param {egwAction} action - The document they clicked
|
|
|
|
* @param {egwActionObject[]} selected - Rows selected
|
|
|
|
*/
|
|
|
|
merge_mail: function merge_mail(action, selected, target)
|
|
|
|
{
|
|
|
|
// Special processing for email documents - ask about infolog
|
|
|
|
if(action && action.data && selected.length > 1)
|
|
|
|
{
|
|
|
|
var callback = function(button, value) {
|
|
|
|
if(button == et2_dialog.OK_BUTTON)
|
|
|
|
{
|
|
|
|
var _action = jQuery.extend(true, {}, action);
|
|
|
|
if(value.infolog)
|
|
|
|
{
|
2017-01-20 16:37:08 +01:00
|
|
|
_action.data.menuaction += '&to_app=infolog&info_type='+value.info_type;
|
2017-01-19 17:36:38 +01:00
|
|
|
}
|
|
|
|
nm_action(_action, selected, target);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
et2_createWidget("dialog",{
|
|
|
|
callback: callback,
|
|
|
|
title: action.caption,
|
|
|
|
buttons: et2_dialog.BUTTONS_OK_CANCEL,
|
|
|
|
type: et2_dialog.QUESTION_MESSAGE,
|
2017-01-20 16:37:08 +01:00
|
|
|
template: egw.webserverUrl+'/addressbook/templates/default/mail_merge_dialog.xet',
|
|
|
|
value: {content: {info_type: 'email'}, sel_options: this.et2.getArrayMgr('sel_options').data}
|
2017-01-19 17:36:38 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Normal processing for only one contact selected
|
|
|
|
return nm_action(action, selected, target);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-12-07 00:13:33 +01:00
|
|
|
/**
|
|
|
|
* Retrieve the current state of the application for future restoration
|
|
|
|
*
|
|
|
|
* Overridden from parent to handle viewing a contact. In this case state
|
|
|
|
* will be {contact_id: #}
|
|
|
|
*
|
|
|
|
* @return {object} Application specific map representing the current state
|
|
|
|
*/
|
|
|
|
getState: function()
|
|
|
|
{
|
|
|
|
// Most likely we're in the list view
|
|
|
|
var state = this._super.apply(this, arguments);
|
|
|
|
|
|
|
|
if(jQuery.isEmptyObject(state))
|
|
|
|
{
|
|
|
|
// Not in a list view. Try to find contact ID
|
|
|
|
var etemplates = etemplate2.getByApplication('addressbook');
|
|
|
|
for(var i = 0; i < etemplates.length; i++)
|
|
|
|
{
|
|
|
|
var content = etemplates[i].widgetContainer.getArrayMgr("content");
|
|
|
|
if(content && content.getEntry('id'))
|
|
|
|
{
|
|
|
|
state = {app: 'addressbook', id: content.getEntry('id'), type: 'view'};
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the application's state to the given state.
|
|
|
|
*
|
|
|
|
* Overridden from parent to stop the contact view's infolog nextmatch from
|
|
|
|
* being changed.
|
|
|
|
*
|
|
|
|
* @param {{name: string, state: object}|string} state Object (or JSON string) for a state.
|
|
|
|
* Only state is required, and its contents are application specific.
|
|
|
|
*
|
|
|
|
* @return {boolean} false - Returns false to stop event propagation
|
|
|
|
*/
|
|
|
|
setState: function(state)
|
|
|
|
{
|
|
|
|
var current_state = this.getState();
|
2014-01-06 11:12:51 +01:00
|
|
|
|
2013-12-07 00:13:33 +01:00
|
|
|
// State should be an object, not a string, but we'll parse
|
|
|
|
if(typeof state == "string")
|
|
|
|
{
|
|
|
|
if(state.indexOf('{') != -1 || state =='null')
|
|
|
|
{
|
|
|
|
state = JSON.parse(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Redirect from view to list - parent would do this, but infolog nextmatch stops it
|
|
|
|
if(current_state.app && current_state.id && (typeof state.state == 'undefined' || typeof state.state.app == 'undefined'))
|
|
|
|
{
|
|
|
|
// Redirect to list
|
|
|
|
// 'blank' is the special name for no filters, send that instead of the nice translated name
|
|
|
|
var safe_name = jQuery.isEmptyObject(state) || jQuery.isEmptyObject(state.state||state.filter) ? 'blank' : state.name.replace(/[^A-Za-z0-9-_]/g, '_');
|
|
|
|
egw.open('',this.appname,'list',{'favorite': safe_name},this.appname);
|
|
|
|
return false;
|
|
|
|
}
|
2016-06-09 23:05:25 +02:00
|
|
|
else if (jQuery.isEmptyObject(state))
|
|
|
|
{
|
|
|
|
// Regular handling first to clear everything but advanced search
|
|
|
|
this._super.apply(this, arguments);
|
|
|
|
|
|
|
|
// Clear advanced search, which is in session and etemplate
|
|
|
|
egw.json('addressbook.addressbook_ui.ajax_clear_advanced_search',[], function() {
|
|
|
|
framework.setWebsiteTitle('addressbook','');
|
|
|
|
var index = etemplate2.getById('addressbook-index');
|
|
|
|
if(index && index.widgetContainer)
|
|
|
|
{
|
|
|
|
var nm = index.widgetContainer.getWidgetById('nm');
|
|
|
|
if(nm)
|
|
|
|
{
|
|
|
|
nm.applyFilters({
|
|
|
|
advanced_search: false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},this).sendRequest(true);
|
|
|
|
return false;
|
|
|
|
}
|
2017-05-26 22:08:45 +02:00
|
|
|
else if (state.state.grouped_view)
|
|
|
|
{
|
|
|
|
// Deal with grouped views that are not valid (not in list of options)
|
|
|
|
// by faking viewing that organisation
|
|
|
|
var index = etemplate2.getById('addressbook-index');
|
|
|
|
if(index && index.widgetContainer)
|
|
|
|
{
|
|
|
|
var grouped = index.widgetContainer.getWidgetById('grouped_view');
|
|
|
|
var options = false;
|
|
|
|
if(grouped && grouped.options && grouped.options.select_options)
|
|
|
|
{
|
|
|
|
options = grouped.options.select_options;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check to see if it's not there
|
2018-02-28 17:30:29 +01:00
|
|
|
if(options && (options.find &&
|
2017-07-26 23:06:37 +02:00
|
|
|
!options.find(function(e) {console.log(e); return e.value === state.state.grouped_view;}) ||
|
|
|
|
typeof options.find === 'undefined' && !options[state.state.grouped_view]
|
2017-05-26 22:08:45 +02:00
|
|
|
))
|
|
|
|
{
|
2017-07-26 23:06:37 +02:00
|
|
|
window.setTimeout(function() {
|
|
|
|
app.addressbook.setState(state);
|
|
|
|
}, 500);
|
2017-05-26 22:08:45 +02:00
|
|
|
var nm = index.widgetContainer.getWidgetById('nm');
|
|
|
|
var action = nm.controller._actionManager.getActionById('view_org');
|
|
|
|
var senders = [{_context: {_widget: nm}}];
|
|
|
|
return nm_action(action, senders, {}, {ids:[state.state.grouped_view]});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-13 16:57:11 +02:00
|
|
|
|
|
|
|
// Make sure advanced search is false if not set, this clears any
|
|
|
|
// currently set advanced search
|
|
|
|
if(typeof state.state.advanced_search === 'undefined')
|
|
|
|
{
|
|
|
|
state.state.advanced_search = false;
|
|
|
|
}
|
2016-06-14 03:18:11 +02:00
|
|
|
return this._super.apply(this, arguments);
|
2014-03-28 17:56:35 +01:00
|
|
|
},
|
2013-12-07 00:13:33 +01:00
|
|
|
|
2014-03-28 17:56:35 +01:00
|
|
|
/**
|
|
|
|
* Field changed, call server validation
|
|
|
|
*
|
|
|
|
* @param {jQuery.Event} _ev
|
|
|
|
* @param {et2_button} _widget
|
|
|
|
*/
|
|
|
|
account_change: function(_ev, _widget)
|
|
|
|
{
|
|
|
|
switch(_widget.id)
|
|
|
|
{
|
|
|
|
case 'account_lid':
|
|
|
|
case 'n_family':
|
|
|
|
case 'n_given':
|
2016-08-12 13:46:30 +02:00
|
|
|
case 'account_passwd':
|
2014-03-28 17:56:35 +01:00
|
|
|
case 'account_passwd_2':
|
|
|
|
var values = this.et2._inst.getValues(this.et2);
|
|
|
|
var data = {
|
|
|
|
account_id: this.et2.getArrayMgr('content').data.account_id,
|
|
|
|
account_lid: values.account_lid,
|
|
|
|
account_firstname: values.n_given,
|
|
|
|
account_lastname: values.n_family,
|
|
|
|
account_email: values.email,
|
|
|
|
account_passwd: values.account_passwd,
|
|
|
|
account_passwd_2: values.account_passwd_2
|
|
|
|
};
|
|
|
|
|
|
|
|
this.egw.message('');
|
2014-05-05 15:08:25 +02:00
|
|
|
this.egw.json('admin_account::ajax_check', [data, _widget.id], function(_msg)
|
2014-03-28 17:56:35 +01:00
|
|
|
{
|
|
|
|
if (_msg && typeof _msg == 'string')
|
|
|
|
{
|
|
|
|
egw(window).message(_msg, 'error'); // context get's lost :(
|
|
|
|
_widget.getDOMNode().focus();
|
|
|
|
}
|
|
|
|
}, this).sendRequest();
|
|
|
|
break;
|
|
|
|
}
|
2015-01-05 15:28:35 +01:00
|
|
|
},
|
2016-04-29 09:51:13 +02:00
|
|
|
|
2015-01-05 15:28:35 +01:00
|
|
|
/**
|
|
|
|
* Get title in order to set it as document title
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
|
|
|
getWindowTitle: function()
|
|
|
|
{
|
|
|
|
var widget = this.et2.getWidgetById('n_fn');
|
|
|
|
if(widget) return widget.options.value;
|
2016-05-20 15:50:26 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enable/Disable geolocation action items in contextmenu base on address availabilty
|
|
|
|
*
|
2016-06-30 10:14:19 +02:00
|
|
|
* @param {egwAction} _action
|
|
|
|
* @param {egwActionObject[]} _selected selected rows
|
2016-05-20 15:50:26 +02:00
|
|
|
* @returns {boolean} return false if no address found
|
|
|
|
*/
|
|
|
|
geoLocation_enabled: function(_action, _selected)
|
|
|
|
{
|
2016-05-25 12:50:18 +02:00
|
|
|
// multiple selection is not supported
|
|
|
|
if (_selected.length>1) return false;
|
|
|
|
|
2016-05-31 17:25:08 +02:00
|
|
|
var url = this.getGeolocationConfig();
|
2016-05-25 12:50:18 +02:00
|
|
|
|
|
|
|
// exit if no url or invalide url given
|
|
|
|
if (!url || typeof url === 'undefined' || typeof url !== 'string')
|
|
|
|
{
|
|
|
|
egw.debug('warn','no url or invalid url given as geoLocationUrl');
|
|
|
|
return false;
|
|
|
|
}
|
2016-05-20 15:50:26 +02:00
|
|
|
var content = egw.dataGetUIDdata(_selected[0].id);
|
2016-05-30 16:22:41 +02:00
|
|
|
|
2016-05-27 16:55:18 +02:00
|
|
|
// Selected, but data not found
|
|
|
|
if(!content || typeof content.data === 'undefined') return false;
|
|
|
|
|
2016-05-25 12:50:18 +02:00
|
|
|
var type = _action.id === 'business'?'one':'two';
|
|
|
|
var addrs = [
|
|
|
|
content.data['adr_'+type+'_street'],
|
|
|
|
content.data['adr_'+type+'_locality'],
|
|
|
|
content.data['adr_'+type+'_postalcode']
|
|
|
|
];
|
|
|
|
|
|
|
|
var fields = '';
|
|
|
|
// Replcae placeholders with acctual values
|
|
|
|
for (var i=0;i < addrs.length; i++)
|
|
|
|
{
|
|
|
|
fields += addrs[i] ? addrs[i] : '';
|
|
|
|
}
|
|
|
|
return (url !== '' && fields !== '')?true:false;
|
2016-05-20 15:50:26 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a geo location URL based on geolocation_url in
|
|
|
|
* site configuration
|
|
|
|
*
|
2016-06-30 10:14:19 +02:00
|
|
|
* @param {object} _dest_data
|
2016-05-25 12:50:18 +02:00
|
|
|
* @param {string} _dest_type type of destination address ('one'| 'two')
|
|
|
|
* @param {object} _src_data address data to be used as source contact data|coordination object
|
|
|
|
* @param {string} _src_type type of source address ('browser'|'one'|'two')
|
2016-05-20 15:50:26 +02:00
|
|
|
* @returns {Boolean|string} return url and return false if no address
|
|
|
|
*/
|
2016-05-25 12:50:18 +02:00
|
|
|
geoLocationUrl: function (_dest_data, _dest_type,_src_data, _src_type)
|
2016-05-20 15:50:26 +02:00
|
|
|
{
|
2016-05-25 12:50:18 +02:00
|
|
|
var dest_type = _dest_type || 'one';
|
2016-05-31 17:25:08 +02:00
|
|
|
var url = this.getGeolocationConfig();
|
2016-05-20 15:50:26 +02:00
|
|
|
|
|
|
|
// exit if no url or invalide url given
|
|
|
|
if (!url || typeof url === 'undefined' || typeof url !== 'string')
|
|
|
|
{
|
|
|
|
egw.debug('warn','no url or invalid url given as geoLocationUrl');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// array of placeholders with their representing values
|
2016-05-25 12:50:18 +02:00
|
|
|
var addrs = [
|
|
|
|
|
|
|
|
[ // source address
|
2016-05-30 16:22:41 +02:00
|
|
|
{id:'r0',val:_src_type === 'browser'?_src_data.latitude:_src_data['adr_'+_src_type+'_street']},
|
2016-05-25 12:50:18 +02:00
|
|
|
{id:'t0',val:_src_type === 'browser'?_src_data.longitude:_src_data['adr_'+_src_type+'_locality']},
|
|
|
|
{id:'c0',val:_src_type === 'browser'?'':_src_data['adr_'+_src_type+'_countrycode']},
|
|
|
|
{id:'z0',val:_src_type === 'browser'?'':_src_data['adr_'+_src_type+'_postalcode']}
|
|
|
|
],
|
|
|
|
[ // destination address
|
2016-05-30 16:22:41 +02:00
|
|
|
{id:'r1',val:_dest_data['adr_'+dest_type+'_street']},
|
2016-05-25 12:50:18 +02:00
|
|
|
{id:'t1',val:_dest_data['adr_'+dest_type+'_locality']},
|
|
|
|
{id:'c1',val:_dest_data['adr_'+dest_type+'_countrycode']},
|
|
|
|
{id:'z1',val:_dest_data['adr_'+dest_type+'_postalcode']}
|
|
|
|
]
|
2016-05-20 15:50:26 +02:00
|
|
|
];
|
|
|
|
|
2016-05-30 16:22:41 +02:00
|
|
|
var src_param = url.match(/{{%rs=.*%rs}}/ig);
|
2016-05-25 12:50:18 +02:00
|
|
|
if (src_param[0])
|
2016-05-20 15:50:26 +02:00
|
|
|
{
|
2016-05-30 16:22:41 +02:00
|
|
|
src_param = src_param[0].replace(/{{%rs=/,'');
|
|
|
|
src_param = src_param.replace(/%rs}}/,'');
|
2016-06-30 10:14:19 +02:00
|
|
|
url = url.replace(/{{%rs=.*%rs}}/, src_param);
|
2016-05-20 15:50:26 +02:00
|
|
|
}
|
|
|
|
|
2016-05-25 12:50:18 +02:00
|
|
|
var d_param = url.match(/{{%d=.*%d}}/ig);
|
|
|
|
if (d_param[0])
|
|
|
|
{
|
|
|
|
d_param = d_param[0].replace(/{{%d=/,'');
|
|
|
|
d_param = d_param.replace(/%d}}/,'');
|
2016-06-30 10:14:19 +02:00
|
|
|
url = url.replace(/{{%d=.*%d}}/, d_param);
|
2016-05-25 12:50:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Replcae placeholders with acctual values
|
|
|
|
for (var j=0;j<addrs.length;j++)
|
|
|
|
{
|
|
|
|
for (var i=0;i < addrs[j].length; i++)
|
|
|
|
{
|
|
|
|
url = url.replace('%'+addrs[j][i]['id'], addrs[j][i]['val']? addrs[j][i]['val'] : "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return url !== ''? url : false;
|
2016-05-20 15:50:26 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open a popup base on selected address in provided map
|
|
|
|
*
|
|
|
|
* @param {object} _action
|
|
|
|
* @param {object} _selected
|
|
|
|
*/
|
|
|
|
geoLocationExec: function (_action, _selected)
|
|
|
|
{
|
|
|
|
var content = egw.dataGetUIDdata(_selected[0].id);
|
2016-05-25 12:50:18 +02:00
|
|
|
var geolocation_src = egw.preference('geolocation_src','addressbook');
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
if (geolocation_src === 'browser' && navigator.geolocation)
|
|
|
|
{
|
|
|
|
navigator.geolocation.getCurrentPosition(function(position){
|
|
|
|
if (position && position.coords)
|
|
|
|
{
|
|
|
|
var url = self.geoLocationUrl(content.data,_action.id === 'business'?'one':'two', position.coords, 'browser');
|
|
|
|
window.open(url,'_blank');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
egw.json('addressbook.addressbook_ui.ajax_get_contact', [egw.user('account_id')],function(_data){
|
|
|
|
var url = self.geoLocationUrl(content.data,_action.id === 'business'?'one':'two', _data, geolocation_src === 'browser'?'one':geolocation_src);
|
|
|
|
window.open(url,'_blank');
|
|
|
|
}).sendRequest();
|
|
|
|
|
|
|
|
}
|
2016-05-31 17:25:08 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get geolocation_url stored in config|default url
|
|
|
|
*
|
|
|
|
* @returns {String}
|
|
|
|
*/
|
|
|
|
getGeolocationConfig: function()
|
|
|
|
{
|
|
|
|
// This default url should be identical to the first value of geolocation_url array
|
|
|
|
// defined in addressbook_hooks::config
|
|
|
|
var default_url = 'https://maps.here.com/directions/drive{{%rs=/%rs}}%r0,%t0,%z0,%c0{{%d=/%d}}%r1,%t1,%z1+%c1';
|
|
|
|
var geo_url = egw.config('geolocation_url');
|
|
|
|
if (geo_url) geo_url = geo_url[0];
|
|
|
|
return geo_url || default_url;
|
2018-06-12 21:49:59 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check to see if the selection contains at most one account
|
|
|
|
*
|
|
|
|
* @param {egwAction} action
|
|
|
|
* @param {egwActionObject[]} selected Selected rows
|
|
|
|
*/
|
|
|
|
can_merge: function(action, selected)
|
|
|
|
{
|
|
|
|
return selected.filter(function (row) {
|
|
|
|
var data = egw.dataGetUIDdata(row.id);
|
|
|
|
return data && data.data.account_id;
|
|
|
|
}).length <= 1;
|
2014-03-28 17:56:35 +01:00
|
|
|
}
|
2018-06-12 21:49:59 +02:00
|
|
|
|
2013-10-07 18:53:13 +02:00
|
|
|
});
|