mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-26 00:29:38 +01:00
complete push implementation for timesheet incl. ACL check
This commit is contained in:
parent
76a5793a0a
commit
e9c4d3f07e
@ -417,6 +417,7 @@
|
||||
egw_script.getAttribute('data-websocket-url'),
|
||||
JSON.parse(egw_script.getAttribute('data-websocket-tokens'))
|
||||
);
|
||||
egw.set_grants(JSON.parse(egw_script.getAttribute('data-grants') || "{}"));
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
@ -440,7 +441,7 @@
|
||||
// get TypeScript modules working with our loader
|
||||
function require(_file)
|
||||
{
|
||||
return { EgwApp: window.EgwApp};
|
||||
return window.exports;
|
||||
}
|
||||
var exports = {};
|
||||
|
||||
|
@ -135,6 +135,9 @@ var EgwApp = /** @class */ (function () {
|
||||
/**
|
||||
* Handle a push notification about entry changes from the websocket
|
||||
*
|
||||
* Get's called for data of all apps, but should only handle data of apps it displays,
|
||||
* which is by default only it's own, but can be for multiple apps eg. for calendar.
|
||||
*
|
||||
* @param pushData
|
||||
* @param {string} pushData.app application name
|
||||
* @param {(string|number)} pushData.id id of entry to refresh or null
|
||||
@ -148,9 +151,40 @@ var EgwApp = /** @class */ (function () {
|
||||
* @param {number} pushData.account_id User that caused the notification
|
||||
*/
|
||||
EgwApp.prototype.push = function (pushData) {
|
||||
// don't care about other apps data, reimplement if your app does care eg. calendar
|
||||
if (pushData.app !== this.appname)
|
||||
return;
|
||||
// only handle delete by default, for simple case of uid === "$app::$id"
|
||||
if (pushData.type === 'delete') {
|
||||
egw.dataStoreUID(pushData.app + '::' + pushData.id, null);
|
||||
egw.dataStoreUID(this.uid(pushData), null);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Get (possible) app-specific uid
|
||||
*
|
||||
* @param {object} pushData see push method for individual attributes
|
||||
*/
|
||||
EgwApp.prototype.uid = function (pushData) {
|
||||
return pushData.app + '::' + pushData.id;
|
||||
};
|
||||
/**
|
||||
* Method called after apps push implementation checked visibility
|
||||
*
|
||||
* @param {et2_nextmatch} nm
|
||||
* @param pushData see push method for individual attributes
|
||||
* @todo implement better way to update nextmatch widget without disturbing the user / state
|
||||
* @todo show indicator that an update has happend
|
||||
* @todo rate-limit update frequency
|
||||
*/
|
||||
EgwApp.prototype.updateList = function (nm, pushData) {
|
||||
switch (pushData.type) {
|
||||
case 'add':
|
||||
case 'unknown':
|
||||
nm.applyFilters();
|
||||
break;
|
||||
default:
|
||||
egw.dataRefreshUID(this.uid(pushData));
|
||||
break;
|
||||
}
|
||||
};
|
||||
/**
|
||||
@ -1634,3 +1668,4 @@ var EgwApp = /** @class */ (function () {
|
||||
return EgwApp;
|
||||
}());
|
||||
exports.EgwApp = EgwApp;
|
||||
//# sourceMappingURL=egw_app.js.map
|
@ -15,6 +15,19 @@ import 'jqueryui';
|
||||
import '../jsapi/egw_global';
|
||||
import '../etemplate/et2_types';
|
||||
|
||||
/**
|
||||
* Type for push-message
|
||||
*/
|
||||
export interface PushData
|
||||
{
|
||||
type: "add"|"edit"|"update"|"delete"|"unknown";
|
||||
app: string; // app-name, can include a subtype eg. "projectmanager-element"
|
||||
id: string | number;
|
||||
acl?: any; // app-specific acl data, eg. the owner, or array of participants
|
||||
account_id: number; // user that caused the change
|
||||
[propName:string]: any; // arbitrary more parameters
|
||||
}
|
||||
|
||||
/**
|
||||
* Common base class for application javascript
|
||||
* Each app should extend as needed.
|
||||
@ -206,6 +219,9 @@ export abstract class EgwApp
|
||||
/**
|
||||
* Handle a push notification about entry changes from the websocket
|
||||
*
|
||||
* Get's called for data of all apps, but should only handle data of apps it displays,
|
||||
* which is by default only it's own, but can be for multiple apps eg. for calendar.
|
||||
*
|
||||
* @param pushData
|
||||
* @param {string} pushData.app application name
|
||||
* @param {(string|number)} pushData.id id of entry to refresh or null
|
||||
@ -218,12 +234,49 @@ export abstract class EgwApp
|
||||
* @param {object|null} pushData.acl Extra data for determining relevance. eg: owner or responsible to decide if update is necessary
|
||||
* @param {number} pushData.account_id User that caused the notification
|
||||
*/
|
||||
push(pushData)
|
||||
push(pushData : PushData)
|
||||
{
|
||||
// don't care about other apps data, reimplement if your app does care eg. calendar
|
||||
if (pushData.app !== this.appname) return;
|
||||
|
||||
// only handle delete by default, for simple case of uid === "$app::$id"
|
||||
if (pushData.type === 'delete')
|
||||
{
|
||||
egw.dataStoreUID(pushData.app + '::' + pushData.id, null);
|
||||
egw.dataStoreUID(this.uid(pushData), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get (possible) app-specific uid
|
||||
*
|
||||
* @param {object} pushData see push method for individual attributes
|
||||
*/
|
||||
uid(pushData)
|
||||
{
|
||||
return pushData.app + '::' + pushData.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called after apps push implementation checked visibility
|
||||
*
|
||||
* @param {et2_nextmatch} nm
|
||||
* @param pushData see push method for individual attributes
|
||||
* @todo implement better way to update nextmatch widget without disturbing the user / state
|
||||
* @todo show indicator that an update has happend
|
||||
* @todo rate-limit update frequency
|
||||
*/
|
||||
updateList(nm, pushData : PushData)
|
||||
{
|
||||
switch (pushData.type)
|
||||
{
|
||||
case 'add':
|
||||
case 'unknown':
|
||||
nm.applyFilters();
|
||||
break;
|
||||
|
||||
default:
|
||||
egw.dataRefreshUID(this.uid(pushData));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ egw.extend('preferences', egw.MODULE_GLOBAL, function()
|
||||
* @access: private, use egw.preferences() or egw.set_perferences()
|
||||
*/
|
||||
var prefs = {};
|
||||
var grants = {};
|
||||
|
||||
// Return the actual extension
|
||||
return {
|
||||
@ -171,6 +172,50 @@ egw.extend('preferences', egw.MODULE_GLOBAL, function()
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Setting prefs for an app or 'common'
|
||||
*
|
||||
* @param {object} _data
|
||||
* @param {string} _app application name or undefined to set grants of all apps at once
|
||||
* and therefore will be inaccessible in IE, after that window is closed
|
||||
*/
|
||||
set_grants: function(_data, _app)
|
||||
{
|
||||
if (_app)
|
||||
{
|
||||
grants[_app] = jQuery.extend(true, {}, _data);
|
||||
}
|
||||
else
|
||||
{
|
||||
grants = jQuery.extend(true, {}, _data);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Query an EGroupware user preference
|
||||
*
|
||||
* We currently load grants from all apps in egw.js, so no need for a callback or promise.
|
||||
*
|
||||
* @param {string} _app app-name
|
||||
* @param {function|false|undefined} _callback optional callback, if preference needs loading first
|
||||
* if false given and preference is not loaded, undefined is return and no (synchronious) request is send to server
|
||||
* @param {object} _context context for callback
|
||||
* @return {object|undefined|false} grant object, false if not (yet) loaded and no callback or undefined
|
||||
*/
|
||||
grants: function( _app) //, _callback, _context)
|
||||
{
|
||||
/* we currently load grants from all apps in egw.js, so no need for a callback or promise
|
||||
if (typeof grants[_app] == 'undefined')
|
||||
{
|
||||
if (_callback === false) return undefined;
|
||||
var request = this.json('EGroupware\\Api\\Framework::ajax_get_preference', [_app], _callback, _context);
|
||||
request.sendRequest(typeof _callback == 'function', 'GET'); // use synchronous (cachable) GET request
|
||||
if (typeof grants[_app] == 'undefined') grants[_app] = {};
|
||||
if (typeof _callback == 'function') return false;
|
||||
}*/
|
||||
return typeof grants[_app] === 'object' ? jQuery.extend({}, grants[_app]) : grants[_app];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -718,6 +718,38 @@ class Acl
|
||||
return $grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get grants for a single app
|
||||
*
|
||||
* We use a "get-grants" hook, in case an app need more then what Acl::get_grants returns (with default parameters!).
|
||||
*
|
||||
* @param string $_app app-name
|
||||
* @return array
|
||||
*/
|
||||
function ajax_get_grants($_app=null)
|
||||
{
|
||||
if (!($grants = Hooks::single('get-grants', $_app)))
|
||||
{
|
||||
$grants = $this->get_grants($_app);
|
||||
}
|
||||
return $grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get grants for all apps
|
||||
*
|
||||
* @return array with app => array of grants pairs
|
||||
*/
|
||||
function ajax_get_all_grants()
|
||||
{
|
||||
$app_grants = [];
|
||||
foreach(array_keys($GLOBALS['egw_info']['user']['apps']) as $app)
|
||||
{
|
||||
$app_grants[$app] = $this->get_grants($app);
|
||||
}
|
||||
return $app_grants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all ACL entries for an account (user or group)
|
||||
*
|
||||
|
@ -677,6 +677,7 @@ class Link extends Link\Storage
|
||||
*/
|
||||
static function unlink2($link_id,$app,&$id,$owner=0,$app2='',$id2='',$hold_for_purge=false)
|
||||
{
|
||||
error_log(__METHOD__."($link_id, '$app', $id, ...)");
|
||||
if (self::DEBUG)
|
||||
{
|
||||
echo "<p>Link::unlink('$link_id','$app',".array2string($id).",'$owner','$app2','$id2', $hold_for_purge)</p>\n";
|
||||
@ -1479,8 +1480,9 @@ class Link extends Link\Storage
|
||||
* @param string $app name of app in which the updated happend
|
||||
* @param string $id id in $app of the updated entry
|
||||
* @param array $data =null updated data of changed entry, as the read-method of the BO-layer would supply it
|
||||
* @param string $type ="unknown" type of update: "add", "edit", "update" or default "unknown"
|
||||
*/
|
||||
static function notify_update($app,$id,$data=null)
|
||||
static function notify_update($app,$id,$data=null,$type='unknown')
|
||||
{
|
||||
self::delete_cache($app,$id);
|
||||
//error_log(__METHOD__."('$app', $id, $data)");
|
||||
@ -1497,7 +1499,7 @@ class Link extends Link\Storage
|
||||
// in case "someone" interested in all changes (used eg. for push)
|
||||
Hooks::process([
|
||||
'location' => 'notify-all',
|
||||
'type' => 'edit',
|
||||
'type' => !empty($data[Link::OLD_LINK_TITLE]) ? 'update' : $type,
|
||||
'app' => $app,
|
||||
'id' => $id,
|
||||
'data' => $data,
|
||||
|
@ -23,7 +23,7 @@ $replace = array(
|
||||
"\n});" => "\n}\n\napp.classes.$matches[1] = ".ucfirst($matches[1])."App;"
|
||||
]);
|
||||
},
|
||||
"/^\tappname:\s*'([^']+)',/m" => "\treadonly appname: '$1';",
|
||||
"/^\tappname:\s*'([^']+)',/m" => "\treadonly appname = '$1';",
|
||||
"/^\t([^: ,;(]+):\s*([^()]+),/m" => "\t\$1: $2;",
|
||||
"/^\t([^:\n]+):\s*function\s*\(.*this._super.(apply|call)\(/msU" =>
|
||||
function($matches) {
|
||||
|
@ -640,10 +640,14 @@ class timesheet_bo extends Api\Storage
|
||||
}
|
||||
}
|
||||
|
||||
$type = !isset($old) ? 'add' :
|
||||
($new['ts_status'] == self::DELETED_STATUS ? 'delete' : 'update');
|
||||
|
||||
// Check for restore of deleted contact, restore held links
|
||||
if($old && $old['ts_status'] == self::DELETED_STATUS && $new['ts_status'] != self::DELETED_STATUS)
|
||||
{
|
||||
Link::restore(TIMESHEET_APP, $new['ts_id']);
|
||||
$type = 'add';
|
||||
}
|
||||
|
||||
if (!($err = parent::save()))
|
||||
@ -659,7 +663,7 @@ class timesheet_bo extends Api\Storage
|
||||
return implode(', ',$this->tracking->errors);
|
||||
}
|
||||
// notify the link-class about the update, as other apps may be subscribt to it
|
||||
Link::notify_update(TIMESHEET_APP,$this->data['ts_id'],$this->data);
|
||||
Link::notify_update(TIMESHEET_APP, $this->data['ts_id'], $this->data, $type);
|
||||
}
|
||||
|
||||
return $err;
|
||||
|
@ -38,7 +38,9 @@ var egw_app_1 = require("../../api/js/jsapi/egw_app");
|
||||
var TimesheetApp = /** @class */ (function (_super) {
|
||||
__extends(TimesheetApp, _super);
|
||||
function TimesheetApp() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||
_this.appname = 'timesheet';
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* This function is called when the etemplate2 object is loaded
|
||||
@ -170,6 +172,46 @@ var TimesheetApp = /** @class */ (function (_super) {
|
||||
if (widget)
|
||||
return widget.options.value;
|
||||
};
|
||||
/**
|
||||
* Handle a push notification about entry changes from the websocket
|
||||
*
|
||||
* @param pushData
|
||||
* @param {string} pushData.app application name
|
||||
* @param {(string|number)} pushData.id id of entry to refresh or null
|
||||
* @param {string} pushData.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 {object|null} pushData.acl Extra data for determining relevance. eg: owner or responsible to decide if update is necessary
|
||||
* @param {number} pushData.account_id User that caused the notification
|
||||
*/
|
||||
TimesheetApp.prototype.push = function (pushData) {
|
||||
var _a, _b, _c;
|
||||
// timesheed does NOT care about other apps data
|
||||
if (pushData.app !== this.appname)
|
||||
return;
|
||||
if (pushData.type === 'delete') {
|
||||
return _super.prototype.push.call(this, pushData);
|
||||
}
|
||||
// all other cases (add, edit, update) are handled identical
|
||||
// check visibility
|
||||
if (typeof this._grants === 'undefined') {
|
||||
this._grants = egw.grants(this.appname);
|
||||
}
|
||||
if (typeof this._grants[pushData.acl] === 'undefined')
|
||||
return;
|
||||
// check if we might not see it because of an owner filter
|
||||
var nm = (_a = this.et2) === null || _a === void 0 ? void 0 : _a.getWidgetById('nm');
|
||||
var nm_value = (_b = nm) === null || _b === void 0 ? void 0 : _b.getValue();
|
||||
if (nm && nm_value && typeof ((_c = nm_value.col_filter) === null || _c === void 0 ? void 0 : _c.ts_owner) !== 'undefined') {
|
||||
if (!nm_value.col_filter.ts_owner || nm_value.col_filter.ts_owner == pushData.acl) {
|
||||
this.updateList(nm, pushData);
|
||||
}
|
||||
}
|
||||
};
|
||||
return TimesheetApp;
|
||||
}(egw_app_1.EgwApp));
|
||||
app.classes.timesheet = TimesheetApp;
|
||||
//# sourceMappingURL=app.js.map
|
@ -26,7 +26,7 @@ import { EgwApp } from '../../api/js/jsapi/egw_app';
|
||||
*/
|
||||
class TimesheetApp extends EgwApp
|
||||
{
|
||||
readonly appname: 'timesheet';
|
||||
readonly appname = 'timesheet';
|
||||
|
||||
/**
|
||||
* This function is called when the etemplate2 object is loaded
|
||||
@ -191,6 +191,53 @@ class TimesheetApp extends EgwApp
|
||||
var widget = this.et2.getWidgetById('ts_title');
|
||||
if(widget) return widget.options.value;
|
||||
}
|
||||
|
||||
private _grants : any;
|
||||
|
||||
/**
|
||||
* Handle a push notification about entry changes from the websocket
|
||||
*
|
||||
* @param pushData
|
||||
* @param {string} pushData.app application name
|
||||
* @param {(string|number)} pushData.id id of entry to refresh or null
|
||||
* @param {string} pushData.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 {object|null} pushData.acl Extra data for determining relevance. eg: owner or responsible to decide if update is necessary
|
||||
* @param {number} pushData.account_id User that caused the notification
|
||||
*/
|
||||
push(pushData)
|
||||
{
|
||||
// timesheed does NOT care about other apps data
|
||||
if (pushData.app !== this.appname) return;
|
||||
|
||||
if (pushData.type === 'delete')
|
||||
{
|
||||
return super.push(pushData);
|
||||
}
|
||||
|
||||
// all other cases (add, edit, update) are handled identical
|
||||
// check visibility
|
||||
if (typeof this._grants === 'undefined')
|
||||
{
|
||||
this._grants = egw.grants(this.appname);
|
||||
}
|
||||
if (typeof this._grants[pushData.acl] === 'undefined') return;
|
||||
|
||||
// check if we might not see it because of an owner filter
|
||||
let nm = this.et2?.getWidgetById('nm');
|
||||
let nm_value = nm?.getValue();
|
||||
if (nm && nm_value && typeof nm_value.col_filter?.ts_owner !== 'undefined')
|
||||
{
|
||||
if (!nm_value.col_filter.ts_owner || nm_value.col_filter.ts_owner == pushData.acl)
|
||||
{
|
||||
this.updateList(nm, pushData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.classes.timesheet = TimesheetApp;
|
||||
|
Loading…
Reference in New Issue
Block a user