egroupware_official/api/js/jsapi/egw_user.js
nathan 235ff7a2cf Et2SelectAccount improvements
- Fix always including all accounts
- Limit included accounts to 100 to avoid breaking
- Let repeat() cache
- Improved "more results" count
- Reduce repeats & iteration needed
2023-10-06 11:37:46 -06:00

441 lines
11 KiB
JavaScript

/**
* EGroupware clientside API object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link http://www.egroupware.org
* @author Andreas Stöckel (as AT stylite.de)
* @author Ralf Becker <RalfBecker@outdoor-training.de>
*/
/*egw:uses
egw_core;
*/
import './egw_core.js';
egw.extend('user', egw.MODULE_GLOBAL, function()
{
"use strict";
/**
* Data about current user
*
* @access: private, use egw.user(_field) or egw.app(_app)
*/
let userData = {apps: {}};
/**
* Client side cache of accounts user has access to
* Used by account select widgets
*/
let accountStore = {
// Filled by AJAX when needed
//accounts: {},
//groups: {},
//owngroups: {}
};
/**
* Clientside cache for accountData calls
*/
let accountData = {};
let resolveGroup = {};
// Hold in-progress request to avoid making more
let request = null;
return {
/**
* Set data of current user
*
* @param {object} _data
* @param {boolean} _need_clone _data need to be cloned, as it is from different window context
* and therefore will be inaccessible in IE, after that window is closed
*/
set_user: function (_data, _need_clone)
{
userData = _need_clone ? jQuery.extend(true, {}, _data) : _data;
},
/**
* Get data about current user
*
* @param {string} _field
* - 'account_id','account_lid','person_id','account_status','memberships'
* - 'account_firstname','account_lastname','account_email','account_fullname','account_phone'
* - 'apps': object with app => data pairs the user has run-rights for
* @return {string|array|null}
*/
user: function (_field)
{
return userData[_field];
},
/**
* Return data of apps the user has rights to run
*
* Can be used the check of run rights like: if (egw.app('addressbook')) { do something if user has addressbook rights }
*
* @param {string} _app
* @param {string} _name attribute to return, default return whole app-data-object
* @return object|string|null null if not found
*/
app: function(_app, _name)
{
return typeof _name == 'undefined' || typeof userData.apps[_app] == 'undefined' ?
userData.apps[_app] : userData.apps[_app][_name];
},
/**
* Get a list of accounts the user has access to
* The list is filtered by type, one of 'accounts','groups','both', 'owngroups'
*
* @param {string} type
* @returns {Promise<{value:string,label:string,icon?:string}[]>}
*/
accounts: function (type)
{
if (typeof type === 'undefined')
{
type = 'accounts';
}
if (request !== null)
{
return request.then(() =>
{
return this.accounts(type)
});
}
if (jQuery.isEmptyObject(accountStore))
{
const cache_it = data =>
{
let types = ["accounts", "groups", "owngroups"];
for (let t of types)
{
if (typeof data[t] === "object")
{
accountStore[t] = (Array.isArray(data[t]) ? data[t]:Object.values(data[t]) ?? []).map(a => {a.value = ""+a.value; return a});
}
}
}
request = egw.request("EGroupware\\Api\\Framework::ajax_user_list", []).then(_data =>
{
cache_it(_data);
request = null;
return this.accounts(type);
});
return request;
}
let result = [];
if (type === 'both')
{
result = [...Object.values(accountStore.accounts), ...Object.values(accountStore.groups)];
}
else
{
result = [...Object.values(accountStore[type])];
}
return Promise.resolve(result);
},
/**
* Get account-infos for given numerical _account_ids
*
* @param {int|int[]} _account_ids
* @param {string} _field default 'account_email'
* @param {boolean} _resolve_groups true: return attribute for all members, false: return attribute of group
* @param {function|undefined} _callback deprecated, use egw.accountDate(...).then(data => _callback.bind(_context)(data))
* @param {object|undefined} _context deprecated, see _context
* @return {Promise} resolving to object { account_id => value, ... }
*/
accountData: function(_account_ids, _field, _resolve_groups, _callback, _context)
{
if (!_field) _field = 'account_email';
if (!Array.isArray(_account_ids)) _account_ids = [_account_ids];
// check our cache or current user first
const data = {};
let pending = false;
for(let i=0; i < _account_ids.length; ++i)
{
const account_id = _account_ids[i];
if (account_id == userData.account_id)
{
data[account_id] = userData[_field];
}
else if ((!_resolve_groups || account_id > 0) && typeof accountData[account_id] !== 'undefined' &&
typeof accountData[account_id][_field] !== 'undefined')
{
data[account_id] = accountData[account_id][_field];
pending = pending || data[account_id] instanceof Promise;
}
else if (_resolve_groups && account_id < 0 && typeof resolveGroup[account_id] !== 'undefined' &&
typeof resolveGroup[account_id][_field] != 'undefined')
{
// Groups are resolved on the server, but then the response
// is cached, so we can re-resolve it locally
for(let id in resolveGroup[account_id][_field])
{
data[id] = resolveGroup[account_id][_field][id];
pending = pending || data[id] instanceof Promise;
}
}
else
{
continue;
}
_account_ids.splice(i--, 1);
}
let promise;
// something not found in cache --> ask server
if (_account_ids.length)
{
promise = egw.request('EGroupware\\Api\\Framework::ajax_account_data',[_account_ids, _field, _resolve_groups]).then(_data =>
{
for(let account_id in _data)
{
if (typeof accountData[account_id] === 'undefined')
{
accountData[account_id] = {};
}
data[account_id] = accountData[account_id][_field] = _data[account_id];
}
// If resolving for 1 group, cache the whole answer too
// (More than 1 group, we can't split to each group)
if(_resolve_groups && _account_ids.length === 1 && _account_ids[0] < 0)
{
const group_id = _account_ids[0];
if (typeof resolveGroup[group_id] === 'undefined')
{
resolveGroup[group_id] = {};
}
resolveGroup[group_id][_field] = _data;
}
return data;
});
// store promise, in case someone asks while the request is pending, to not query the server again
_account_ids.forEach(account_id =>
{
if (_resolve_groups && account_id < 0) return; // we must NOT cache the promise for account_id!
if (typeof accountData[account_id] === 'undefined')
{
accountData[account_id] = {};
}
accountData[account_id][_field] = promise.then(function(_data)
{
const result = {};
result[this.account_id] = _data[this.account_id];
return result;
}.bind({ account_id: account_id }));
});
if (_resolve_groups && _account_ids.length === 1 && _account_ids[0] < 0)
{
resolveGroup[_account_ids[0]] = promise;
}
}
else
{
promise = Promise.resolve(data);
}
// if we have any pending promises, we need to resolve and merge them
if (pending)
{
promise = promise.then(_data =>
{
const promises = [];
for (let account_id in _data)
{
if (_data[account_id] instanceof Promise)
{
promises.push(_data[account_id]);
}
}
return Promise.all(promises).then(_results =>
{
_results.forEach(result =>
{
for (let account_id in result)
{
_data[account_id] = result[account_id];
}
});
return _data;
});
});
}
// if deprecated callback is given, call it with then
if (typeof _callback === 'function')
{
promise = promise.then(_data =>
{
_callback.bind(_context)(_data);
return _data;
});
}
return promise;
},
/**
* Set account data. This one can be called from the server to pre-fill the cache.
*
* @param {Array} _data
* @param {String} _field
*/
set_account_cache: function(_data, _field)
{
for(let account_id in _data)
{
if (typeof accountData[account_id] === 'undefined')
{
accountData[account_id] = {};
}
accountData[account_id][_field] = _data[account_id];
}
},
/**
* Set specified account-data of selected user in an other widget
*
* Used eg. in template as: onchange="egw.set_account_data(widget, 'target', 'account_email')"
*
* @param {et2_widget} _src_widget widget to select the user
* @param {string} _target_name name of widget to set the data
* @param {string} _field name of data to set eg. "account_email" or "{account_fullname} <{account_email}>"
*/
set_account_data: function(_src_widget, _target_name, _field)
{
const user = _src_widget.get_value();
const target = _src_widget.getRoot().getWidgetById(_target_name);
const field = _field;
if (user && target)
{
egw.accountData(user, _field, false, function(_data)
{
let data;
if (field.indexOf('{') == -1)
{
data = _data[user];
target.set_value(data);
}
else
{
data = field;
/**
* resolve given data whilst the condition met
*/
const resolveData = function(_d, condition, action) {
const whilst = function (_d) {
return condition(_d) ? action(condition(_d)).then(whilst) : Promise.resolve(_d);
}
return whilst(_d);
};
/**
* get data promise
*/
const getData = function(_match)
{
const match = _match;
return new Promise(function(resolve)
{
egw.accountData(user, match, false, function(_d)
{
data = data.replace(/{([^}]+)}/, _d[user]);
resolve(data);
});
});
};
// run resolve data
resolveData(data, function(_d) {
const r = _d.match(/{([^}]+)}/);
return r && r.length > 0 ? r[1] : r;
},
getData).then(function(data){
target.set_value(data)
});
}
});
}
},
/**
* Invalidate client-side account cache
*
* For _type == "add" we invalidate the whole cache currently.
*
* @param {number} _id nummeric account_id, !_id will invalidate whole cache
* @param {string} _type "add", "delete", "update" or "edit"
*/
invalidate_account: function(_id, _type)
{
if (_id)
{
delete accountData[_id];
delete resolveGroup[_id];
}
else
{
accountData = {};
resolveGroup = {};
}
if (jQuery.isEmptyObject(accountStore)) return;
switch(_type)
{
case 'delete':
case 'edit':
case 'update':
if (_id)
{
const store = _id < 0 ? accountStore.groups : accountStore.accounts;
for(let i=0; i < store.length; ++i)
{
if (store && typeof store[i] != 'undefined' && _id == store[i].value)
{
if (_type === 'delete')
{
delete(store[i]);
}
else
{
this.link_title('api-accounts', _id, function(_label)
{
store[i].label = _label;
if (_id < 0)
{
for(let j=0; j < accountStore.owngroups.length; ++j)
{
if (_id == accountStore.owngroups[j].value)
{
accountStore.owngroups[j].label = _label;
break;
}
}
}
}, this, true); // true = force reload
}
break;
}
}
break;
}
// fall through
default:
accountStore = {};
break;
}
}
};
});