mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-21 23:43:17 +01:00
Merge branch 'master' into web-components
This commit is contained in:
commit
538e483499
@ -17,6 +17,7 @@
|
||||
|
||||
import {egw} from "../jsapi/egw_global";
|
||||
import {et2_IDOMNode} from "./et2_core_interfaces";
|
||||
import {et2_form_name} from "./et2_core_common";
|
||||
|
||||
export function et2_compileLegacyJS(_code, _widget, _context)
|
||||
{
|
||||
@ -144,7 +145,8 @@ function js_pseudo_funcs(_val,widget)
|
||||
{
|
||||
// et2_form_name doesn't care about ][, just [
|
||||
var _cname = widget.getPath() ? widget.getPath().join("[") : false;
|
||||
_val = _val.replace(/form::name\(/g, "'"+widget.getRoot()._inst.uniqueId+"_'+"+(_cname ? "et2_form_name('"+_cname+"'," : '('));
|
||||
document.et2_form_name = et2_form_name;
|
||||
_val = _val.replace(/form::name\(/g, "'"+widget.getRoot()._inst.uniqueId+"_'+"+(_cname ? "document.et2_form_name('"+_cname+"'," : '('));
|
||||
}
|
||||
|
||||
if (_val.indexOf('egw::lang(') != -1)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -519,9 +519,9 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
|
||||
cell.nm_id = node.getAttribute('id');
|
||||
}
|
||||
// Apply widget's class to td, for backward compatability
|
||||
if(node.getAttribute("class"))
|
||||
if (node.getAttribute("class"))
|
||||
{
|
||||
cell.class += (cell.class ? " " : "") + node.getAttribute("class");
|
||||
cell.class += (cell.class ? " " : "") + this.getArrayMgr("content").expandName(node.getAttribute("class"));
|
||||
}
|
||||
|
||||
// Create the element
|
||||
|
@ -641,7 +641,7 @@ export class et2_avatar extends et2_image
|
||||
}
|
||||
).sendRequest(true);
|
||||
}
|
||||
if (this.options.crop)
|
||||
if (this.options.crop && !this.options.readonly)
|
||||
{
|
||||
jQuery(this.image).cropper({
|
||||
aspectRatio: 1/1,
|
||||
|
@ -639,7 +639,6 @@ export class et2_link_entry extends et2_inputWidget
|
||||
protected search: JQuery;
|
||||
protected clear: JQuery;
|
||||
protected link_button: JQuery;
|
||||
private response: any;
|
||||
private request: any;
|
||||
private last_search: string;
|
||||
processing: boolean = false;
|
||||
@ -1041,6 +1040,13 @@ export class et2_link_entry extends et2_inputWidget
|
||||
};
|
||||
}
|
||||
}
|
||||
// display a search query, not a selected entry
|
||||
else if (_value !== null && typeof _value === 'object' && typeof _value.query === 'string')
|
||||
{
|
||||
this.options.value = { app: _value.app || this.options.only_app, id: null };
|
||||
this.search.val(_value.query);
|
||||
return;
|
||||
}
|
||||
this._oldValue = this.options.value;
|
||||
if (!_value || _value.length == 0 || _value == null || jQuery.isEmptyObject(_value))
|
||||
{
|
||||
@ -1183,18 +1189,29 @@ export class et2_link_entry extends et2_inputWidget
|
||||
return response(this.cache[request.term]);
|
||||
}
|
||||
|
||||
// Remember callback
|
||||
this.response = response;
|
||||
|
||||
this.search.addClass("loading");
|
||||
// Remove specific display and revert to CSS file
|
||||
// show() would use inline, should be inline-block
|
||||
this.clear.css('display', '');
|
||||
this.request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_search",
|
||||
[this.app_select.val(), '', request.term, request.options],
|
||||
this._results,
|
||||
this, true, this
|
||||
).sendRequest();
|
||||
|
||||
this.request = egw.request("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_search",
|
||||
[this.app_select.val(), '', request.term, request.options]);
|
||||
|
||||
this.request.then((data) =>
|
||||
{
|
||||
if (this.request)
|
||||
{
|
||||
this.request = null;
|
||||
}
|
||||
this.search.removeClass("loading");
|
||||
let result = [];
|
||||
for (var id in data)
|
||||
{
|
||||
result.push({"value": id, "label": data[id]});
|
||||
}
|
||||
this.cache[this.search.val()] = result;
|
||||
response(result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1239,27 +1256,6 @@ export class et2_link_entry extends et2_inputWidget
|
||||
}, event.data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Server found some results
|
||||
*
|
||||
* @param {Array} data
|
||||
*/
|
||||
_results(data)
|
||||
{
|
||||
if (this.request)
|
||||
{
|
||||
this.request = null;
|
||||
}
|
||||
this.search.removeClass("loading");
|
||||
var result = [];
|
||||
for (var id in data)
|
||||
{
|
||||
result.push({"value": id, "label": data[id]});
|
||||
}
|
||||
this.cache[this.search.val()] = result;
|
||||
this.response(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a link using the current internal values
|
||||
*
|
||||
|
@ -19,7 +19,7 @@ import {ClassWithAttributes} from "./et2_core_inheritance";
|
||||
import {et2_no_init} from "./et2_core_common";
|
||||
import {egw} from "../jsapi/egw_global";
|
||||
import {et2_IInput} from "./et2_core_interfaces";
|
||||
|
||||
import {date} from "./lib/date.js";
|
||||
/**
|
||||
* Class which implements the "button-timestamper" XET-Tag
|
||||
*
|
||||
|
@ -96,6 +96,18 @@ export class et2_video extends et2_baseWidget implements et2_IDOMNode
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Defines if the video should be played repeatedly"
|
||||
},
|
||||
"volume": {
|
||||
"name": "Video volume",
|
||||
"type": "float",
|
||||
"default": 0,
|
||||
"description": "Set video's volume"
|
||||
},
|
||||
"playbackrate": {
|
||||
"name": "Video playBackRate",
|
||||
"type": "float",
|
||||
"default": 1,
|
||||
"description": "Set video's playBackRate"
|
||||
}
|
||||
};
|
||||
|
||||
@ -264,6 +276,105 @@ export class et2_video extends et2_baseWidget implements et2_IDOMNode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set volume
|
||||
* @param _value
|
||||
*/
|
||||
set_volume(_value: number)
|
||||
{
|
||||
let value = _value>100?100:_value;
|
||||
if (value>= 0)
|
||||
{
|
||||
if (this._isYoutube() && this.youtube)
|
||||
{
|
||||
this.youtube.setVolume(value);
|
||||
}
|
||||
else if(!this._isYoutube())
|
||||
{
|
||||
this.video[0].volume = value/100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get volume
|
||||
*/
|
||||
get_volume()
|
||||
{
|
||||
if (this._isYoutube() && this.youtube)
|
||||
{
|
||||
return this.youtube.getVolume();
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.video[0].volume * 100;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method to set playBackRate
|
||||
* @param _value
|
||||
*/
|
||||
set_playBackRate(_value: number)
|
||||
{
|
||||
let value = _value>16?16:_value;
|
||||
if (value>= 0)
|
||||
{
|
||||
if (this._isYoutube() && this.youtube)
|
||||
{
|
||||
this.youtube.setPlaybackRate(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.video[0].playbackRate = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get playBackRate
|
||||
*/
|
||||
get_playBackRate()
|
||||
{
|
||||
if (this._isYoutube() && this.youtube)
|
||||
{
|
||||
return this.youtube.getPlaybackRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.video[0].playbackRate;
|
||||
}
|
||||
}
|
||||
|
||||
set_mute(_value)
|
||||
{
|
||||
if (this._isYoutube() && this.youtube) {
|
||||
if (_value)
|
||||
{
|
||||
this.youtube.mute();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.youtube.unMute();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.video[0].muted = _value;
|
||||
}
|
||||
}
|
||||
|
||||
get_mute()
|
||||
{
|
||||
if (this._isYoutube() && this.youtube)
|
||||
{
|
||||
return this.youtube.isMuted();
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.video[0].muted;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set poster attribute in order to specify
|
||||
* an image to be shown while video is loading or before user play it
|
||||
|
@ -782,18 +782,21 @@ export abstract class EgwApp
|
||||
})
|
||||
.addClass("ui-helper-clearfix");
|
||||
|
||||
let el = document.getElementById('favorite_sidebox_'+this.appname).getElementsByTagName('ul')[0];
|
||||
let sortablejs = Sortable.create(el, {
|
||||
ghostClass: 'ui-fav-sortable-placeholder',
|
||||
draggable: 'li:not([data-id$="add"])',
|
||||
delay: 25,
|
||||
dataIdAttr:'data-id',
|
||||
onSort: function(event){
|
||||
let favSortedList = sortablejs.toArray();
|
||||
self.egw.set_preference(self.appname,'fav_sort_pref',favSortedList);
|
||||
self._refresh_fav_nm();
|
||||
}
|
||||
});
|
||||
let el = document.getElementById('favorite_sidebox_'+this.appname)?.getElementsByTagName('ul')[0];
|
||||
if (el && el instanceof HTMLElement)
|
||||
{
|
||||
let sortablejs = Sortable.create(el, {
|
||||
ghostClass: 'ui-fav-sortable-placeholder',
|
||||
draggable: 'li:not([data-id$="add"])',
|
||||
delay: 25,
|
||||
dataIdAttr:'data-id',
|
||||
onSort: function(event){
|
||||
let favSortedList = sortablejs.toArray();
|
||||
self.egw.set_preference(self.appname,'fav_sort_pref',favSortedList);
|
||||
self._refresh_fav_nm();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bind favorite de-select
|
||||
var egw_fw = egw_getFramework();
|
||||
|
@ -127,16 +127,16 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
||||
}.bind(this);
|
||||
|
||||
this.websocket = new WebSocket(url);
|
||||
this.websocket.onopen = jQuery.proxy(function(e)
|
||||
this.websocket.onopen = (e) =>
|
||||
{
|
||||
check_timer = window.setTimeout(check, check_interval);
|
||||
this.websocket.send(JSON.stringify({
|
||||
subscribe: tokens,
|
||||
account_id: parseInt(account_id)
|
||||
}));
|
||||
}, this);
|
||||
};
|
||||
|
||||
this.websocket.onmessage = jQuery.proxy(function(event)
|
||||
this.websocket.onmessage = (event) =>
|
||||
{
|
||||
reconnect_time = min_reconnect_time;
|
||||
console.log(event);
|
||||
@ -148,18 +148,18 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
||||
{
|
||||
this.handleResponse({ response: [data]});
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
this.websocket.onerror = jQuery.proxy(function(error)
|
||||
this.websocket.onerror = (error) =>
|
||||
{
|
||||
reconnect_time *= 2;
|
||||
if (reconnect_time > max_reconnect_time) reconnect_time = max_reconnect_time;
|
||||
|
||||
console.log(error);
|
||||
(error||this.handleError({}, error));
|
||||
}, this);
|
||||
};
|
||||
|
||||
this.websocket.onclose = jQuery.proxy(function(event)
|
||||
this.websocket.onclose = (event) =>
|
||||
{
|
||||
if (event.wasClean)
|
||||
{
|
||||
@ -176,9 +176,9 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
||||
console.log('[close] Connection died --> reconnect in '+reconnect_time+'ms');
|
||||
if (check_timer) window.clearTimeout(check_timer);
|
||||
check_timer = null;
|
||||
window.setTimeout(jQuery.proxy(this.openWebSocket, this, url, tokens, account_id, error, reconnect_time), reconnect_time);
|
||||
window.setTimeout(() => this.openWebSocket(url, tokens, account_id, error, reconnect_time), reconnect_time);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
@ -189,6 +189,7 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
||||
* @param {function} error option error callback(_xmlhttp, _err) used instead our default this.error
|
||||
*
|
||||
* @return {Promise|boolean} Promise or for async==="keepalive" boolean is returned
|
||||
* Promise.abort() allows to abort the pending request
|
||||
*/
|
||||
json_request.prototype.sendRequest = function(async, method, error)
|
||||
{
|
||||
@ -231,7 +232,9 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
||||
let promise;
|
||||
if (this.async)
|
||||
{
|
||||
promise = (this.egw.window?this.egw.window:window).fetch(url, init)
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
promise = (this.egw.window?this.egw.window:window).fetch(url, {...init, ...signal})
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw response;
|
||||
@ -242,6 +245,9 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
||||
.catch((_err) => {
|
||||
(error || this.handleError).call(this, _err)
|
||||
});
|
||||
|
||||
// offering a simple abort mechanism and compatibility with jQuery.ajax
|
||||
promise.abort = () => controller.abort();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -496,45 +502,40 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
||||
* @param {string} _menuaction
|
||||
* @param {any[]} _parameters
|
||||
*
|
||||
* @return Promise
|
||||
* @return Promise resolving to data part (not full response, which can contain other parts)
|
||||
* Promise.abort() allows to abort the pending request
|
||||
*/
|
||||
request: function(_menuaction, _parameters)
|
||||
{
|
||||
let request = new json_request(_menuaction, _parameters, null, this, true, this, this);
|
||||
let ajax_promise = request.sendRequest();
|
||||
|
||||
// This happens first, immediately
|
||||
let resolvePromise = function(resolve, reject) {
|
||||
// Bind to ajax response - this is called _after_ any other handling
|
||||
ajax_promise.always(function(response, status, p) {
|
||||
if(status !== "success") reject();
|
||||
|
||||
// The ajax request has completed, get just the data & pass it on
|
||||
if(response && response.response)
|
||||
const request = new json_request(_menuaction, _parameters, null, this, true, this, this);
|
||||
const response = request.sendRequest();
|
||||
let promise = response.then(function(response)
|
||||
{
|
||||
// The ajax request has completed, get just the data & pass it on
|
||||
if(response && response.response)
|
||||
{
|
||||
for(let value of response.response)
|
||||
{
|
||||
for(let value of response.response)
|
||||
if(value.type && value.type === "data" && typeof value.data !== "undefined")
|
||||
{
|
||||
if(value.type && value.type === "data" && typeof value.data !== "undefined")
|
||||
{
|
||||
// Data was packed in response
|
||||
resolve(value.data);
|
||||
}
|
||||
else if (value && typeof value.type === "undefined" && typeof value.data === "undefined")
|
||||
{
|
||||
// Just raw data
|
||||
resolve(value);
|
||||
}
|
||||
// Data was packed in response
|
||||
return value.data;
|
||||
}
|
||||
else if (value && typeof value.type === "undefined" && typeof value.data === "undefined")
|
||||
{
|
||||
// Just raw data
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// No data? Resolve the promise with nothing
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
const myPromise = new Promise(resolvePromise);
|
||||
|
||||
return myPromise;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// pass abort method to returned response
|
||||
if (typeof response.abort === 'function')
|
||||
{
|
||||
promise.abort = response.abort;
|
||||
}
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,7 @@
|
||||
/* Basic information about this app */
|
||||
$setup_info['api']['name'] = 'api';
|
||||
$setup_info['api']['title'] = 'EGroupware API';
|
||||
$setup_info['api']['version'] = '21.1';
|
||||
$setup_info['api']['version'] = '21.1.001';
|
||||
$setup_info['api']['versions']['current_header'] = '1.29';
|
||||
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
|
||||
$setup_info['api']['versions']['maintenance_release'] = '21.1.20210723';
|
||||
|
@ -789,5 +789,18 @@ function api_upgrade20_1_002()
|
||||
*/
|
||||
function api_upgrade20_1_003()
|
||||
{
|
||||
return $GLOBALS['setup_info']['activesync']['currentver'] = '21.1';
|
||||
return $GLOBALS['setup_info']['api']['currentver'] = '21.1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove non-email addresses from egw_history_log.share_email
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function api_upgrade21_1()
|
||||
{
|
||||
$GLOBALS['egw_setup']->db->query("UPDATE egw_history_log SET share_email=NULL, history_timestamp=history_timestamp".
|
||||
" WHERE share_email is NOT NULL AND share_email NOT LIKE '%@%'", __LINE__, __FILE__);
|
||||
|
||||
return $GLOBALS['setup_info']['api']['currentver'] = '21.1.001';
|
||||
}
|
||||
|
@ -923,9 +923,10 @@ class Widget
|
||||
|
||||
/**
|
||||
* Checks if a widget is readonly:
|
||||
* - readonly attribute set
|
||||
* - $readonlys[__ALL__] set and $readonlys[$form_name] !== false
|
||||
* - $readonlys[$form_name] evaluates to true
|
||||
* 1. $readonlys set to true for $form_name:
|
||||
* a) $readonlys[$form_name] is set to true (flat array)
|
||||
* b) self::get_array($readonlys, $form_name) is set to true (hierarchical)
|
||||
* 2. ($readonlys[__ALL__] or widget readonly attribute) is true AND NOT $readonlys set to false for $form_name
|
||||
*
|
||||
* @param string $cname =''
|
||||
* @param string $form_name =null form_name, to not calculate him again
|
||||
@ -940,13 +941,12 @@ class Widget
|
||||
);
|
||||
$form_name = self::form_name($cname, $this->id, $expand);
|
||||
}
|
||||
$readonly = $this->attrs['readonly'] || self::$request->readonlys[$form_name] ||
|
||||
self::get_array(self::$request->readonlys,$form_name) === true ||
|
||||
isset(self::$request->readonlys['__ALL__']) && (
|
||||
// Exceptions to all
|
||||
self::$request->readonlys[$form_name] !== false &&
|
||||
self::get_array(self::$request->readonlys,$form_name) !== false
|
||||
);
|
||||
// readonlys can either be set / used as flat array with complete form-name, hierarchical
|
||||
$readonlys = self::$request->readonlys[$form_name] ?? self::get_array(self::$request->readonlys,$form_name);
|
||||
|
||||
$readonly = $readonlys === true ||
|
||||
// exception to __ALL__ or readonly="true" attribute by setting $readonlys[$from_name] === false
|
||||
($this->attrs['readonly'] || isset(self::$request->readonlys['__ALL__'])) && $readonlys !== false;
|
||||
|
||||
//error_log(__METHOD__."('$cname') this->id='$this->id' --> form_name='$form_name': attrs[readonly]=".array2string($this->attrs['readonly']).", readonlys['$form_name']=".array2string(self::$request->readonlys[$form_name]).", readonlys[$form_name]=".array2string(self::get_array(self::$request->readonlys,$form_name)).", readonlys['__ALL__']=".array2string(self::$request->readonlys['__ALL__'])." returning ".array2string($readonly));
|
||||
return $readonly;
|
||||
|
@ -513,6 +513,9 @@ tinymce.init({
|
||||
language: language_code["'. $GLOBALS['egw_info']['user']['preferences']['common']['lang'].'"],
|
||||
language_url: egw.webserverUrl+"/api/js/tinymce/langs/"+language_code[egw.preference("lang", "common")]+".js",
|
||||
browser_spellcheck: true,
|
||||
images_upload_url: imageUpload,
|
||||
paste_data_images: true,
|
||||
paste_filter_drop: true,
|
||||
contextmenu: false,
|
||||
file_picker_callback: function(_callback, _value, _meta){
|
||||
var callback = _callback;
|
||||
|
@ -867,7 +867,7 @@ class Link extends Link\Storage
|
||||
if ($id && is_null($title)) // $app,$id has been deleted ==> unlink all links to it
|
||||
{
|
||||
static $unlinking = array();
|
||||
// check if we are already trying to unlink the entry, to avoid an infinit recursion
|
||||
// check if we are already trying to unlink the entry, to avoid an infinite recursion
|
||||
if (!isset($unlinking[$app]) || !isset($unlinking[$app][$id]))
|
||||
{
|
||||
$unlinking[$app][$id] = true;
|
||||
@ -1344,10 +1344,16 @@ class Link extends Link\Storage
|
||||
Storage\History::static_add($link['app2'],$link['id2'],$GLOBALS['egw_info']['user']['account_id'],'~file~','', Vfs::basename($url));
|
||||
}
|
||||
}
|
||||
if (($Ok = !file_exists($url) || Vfs::remove($url,true)) && ((int)$app > 0 || $fname))
|
||||
{
|
||||
// try removing the dir, in case it's empty
|
||||
if (($dir = Vfs::dirname($url))) @Vfs::rmdir($dir);
|
||||
try {
|
||||
if (($Ok = !file_exists($url) || Vfs::remove($url,true)) && ((int)$app > 0 || $fname))
|
||||
{
|
||||
// try removing the dir, in case it's empty
|
||||
if (($dir = Vfs::dirname($url))) @Vfs::rmdir($dir);
|
||||
}
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// ignore SQL error caused by only virtual directories with non-integer (hash) fs_id
|
||||
$Ok = false;
|
||||
}
|
||||
if (!is_null($current_is_root))
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ use EGroupware\Api;
|
||||
/**
|
||||
* Record history logging service
|
||||
*
|
||||
* This class need to be instanciated for EACH app, which wishes to use it!
|
||||
* This class need to be instantiated for EACH app, which wishes to use it!
|
||||
*/
|
||||
class History
|
||||
{
|
||||
@ -57,12 +57,12 @@ class History
|
||||
*
|
||||
* @param string $appname app name this instance operates on
|
||||
*/
|
||||
function __construct($appname='',$user=null)
|
||||
function __construct($appname = '', $user = null)
|
||||
{
|
||||
$this->appname = $appname ? $appname : $GLOBALS['egw_info']['flags']['currentapp'];
|
||||
$this->user = !is_null($user) ? $user : $GLOBALS['egw_info']['user']['account_id'];
|
||||
$this->appname = $appname ?: $GLOBALS['egw_info']['flags']['currentapp'];
|
||||
$this->user = $user ?: $GLOBALS['egw_info']['user']['account_id'];
|
||||
|
||||
if (is_object($GLOBALS['egw_setup']->db))
|
||||
if(is_object($GLOBALS['egw_setup']->db))
|
||||
{
|
||||
$this->db = $GLOBALS['egw_setup']->db;
|
||||
}
|
||||
@ -82,11 +82,11 @@ class History
|
||||
{
|
||||
$where = array('history_appname' => $this->appname);
|
||||
|
||||
if (is_array($record_id) || is_numeric($record_id))
|
||||
if(is_array($record_id) || is_numeric($record_id))
|
||||
{
|
||||
$where['history_record_id'] = $record_id;
|
||||
}
|
||||
$this->db->delete(self::TABLE,$where,__LINE__,__FILE__);
|
||||
$this->db->delete(self::TABLE, $where, __LINE__, __FILE__);
|
||||
|
||||
return $this->db->affected_rows();
|
||||
}
|
||||
@ -100,14 +100,14 @@ class History
|
||||
{
|
||||
$where = array(
|
||||
'history_appname' => $this->appname,
|
||||
'history_status' => $status
|
||||
'history_status' => $status
|
||||
);
|
||||
|
||||
if (is_array($record_id) || is_numeric($record_id))
|
||||
if(is_array($record_id) || is_numeric($record_id))
|
||||
{
|
||||
$where['history_record_id'] = $record_id;
|
||||
}
|
||||
$this->db->delete(self::TABLE,$where,__LINE__,__FILE__);
|
||||
$this->db->delete(self::TABLE, $where, __LINE__, __FILE__);
|
||||
|
||||
return $this->db->affected_rows();
|
||||
}
|
||||
@ -120,16 +120,13 @@ class History
|
||||
* @param string $new_value new value
|
||||
* @param string $old_value old value
|
||||
*/
|
||||
function add($status,$record_id,$new_value,$old_value)
|
||||
function add($status, $record_id, $new_value, $old_value)
|
||||
{
|
||||
if ($new_value != $old_value)
|
||||
if($new_value != $old_value)
|
||||
{
|
||||
$share_with = '';
|
||||
foreach(isset($GLOBALS['egw']->sharing) ? $GLOBALS['egw']->sharing : [] as $token => $share_obj)
|
||||
{
|
||||
$share_with .= $share_obj->get_share_with();
|
||||
}
|
||||
$this->db->insert(self::TABLE,array(
|
||||
$share_with = static::get_share_with($this->appname, $record_id);
|
||||
|
||||
$this->db->insert(self::TABLE, array(
|
||||
'history_record_id' => $record_id,
|
||||
'history_appname' => $this->appname,
|
||||
'history_owner' => $this->user,
|
||||
@ -137,9 +134,9 @@ class History
|
||||
'history_new_value' => $new_value,
|
||||
'history_old_value' => $old_value,
|
||||
'history_timestamp' => time(),
|
||||
'sessionid' => $GLOBALS['egw']->session->sessionid_access_log,
|
||||
'sessionid' => $GLOBALS['egw']->session->sessionid_access_log,
|
||||
'share_email' => $share_with,
|
||||
),false,__LINE__,__FILE__);
|
||||
), false, __LINE__, __FILE__);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,14 +145,11 @@ class History
|
||||
*/
|
||||
public static function static_add($appname, $id, $user, $field_code, $new_value, $old_value = '')
|
||||
{
|
||||
if ($new_value != $old_value)
|
||||
if($new_value != $old_value)
|
||||
{
|
||||
$share_with = '';
|
||||
foreach(isset($GLOBALS['egw']->sharing) ? $GLOBALS['egw']->sharing : [] as $token => $share_obj)
|
||||
{
|
||||
$share_with .= $share_obj->get_share_with();
|
||||
}
|
||||
$GLOBALS['egw']->db->insert(self::TABLE,array(
|
||||
$share_with = static::get_share_with($appname, $id);
|
||||
|
||||
$GLOBALS['egw']->db->insert(self::TABLE, array(
|
||||
'history_record_id' => $id,
|
||||
'history_appname' => $appname,
|
||||
'history_owner' => (int)$user,
|
||||
@ -163,12 +157,38 @@ class History
|
||||
'history_new_value' => $new_value,
|
||||
'history_old_value' => $old_value,
|
||||
'history_timestamp' => time(),
|
||||
'sessionid' => $GLOBALS['egw']->session->sessionid_access_log,
|
||||
'sessionid' => $GLOBALS['egw']->session->sessionid_access_log,
|
||||
'share_email' => $share_with,
|
||||
),false,__LINE__,__FILE__);
|
||||
), false, __LINE__, __FILE__);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a record was accessed via a share, we want to record who the entry was shared with, rather than the current
|
||||
* user. Since multiple shares can be active at once, and they might not be for the current entry, we check to
|
||||
* see if the given entry was accessed via a share, and which share was used.
|
||||
* The share's share_with is recorded into the history for some hope of tracking who made the change.
|
||||
* share_with is a list of email addresses, and may be empty.
|
||||
*
|
||||
* @param $appname
|
||||
* @param $id
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
static function get_share_with($appname, $id)
|
||||
{
|
||||
$share_with = null;
|
||||
foreach(isset($GLOBALS['egw']->sharing) ? $GLOBALS['egw']->sharing : [] as $token => $share_obj)
|
||||
{
|
||||
// Make sure share is of the correct type to access an entry, and it is the correct entry
|
||||
if($share_obj instanceof Api\Link\Sharing && "$appname::$id" === $share_obj['share_path'])
|
||||
{
|
||||
$share_with .= $share_obj->get_share_with();
|
||||
}
|
||||
}
|
||||
return $share_with;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search history-log
|
||||
*
|
||||
@ -177,13 +197,16 @@ class History
|
||||
* @param string $sort ='DESC'
|
||||
* @param int $limit =null only return this many entries
|
||||
* @return array of arrays with keys id, record_id, appname, owner (account_id), status, new_value, old_value,
|
||||
* timestamp (Y-m-d H:i:s in servertime), user_ts (timestamp in user-time)
|
||||
* timestamp (Y-m-d H:i:s in servertime), user_ts (timestamp in user-time)
|
||||
*/
|
||||
function search($filter,$order='history_id',$sort='DESC',$limit=null)
|
||||
function search($filter, $order = 'history_id', $sort = 'DESC', $limit = null)
|
||||
{
|
||||
if (!is_array($filter)) $filter = is_numeric($filter) ? array('history_record_id' => $filter) : array();
|
||||
if(!is_array($filter))
|
||||
{
|
||||
$filter = is_numeric($filter) ? array('history_record_id' => $filter) : array();
|
||||
}
|
||||
|
||||
if (!$order || !preg_match('/^[a-z0-9_]+$/i',$order) || !preg_match('/^(asc|desc)?$/i',$sort))
|
||||
if(!$order || !preg_match('/^[a-z0-9_]+$/i', $order) || !preg_match('/^(asc|desc)?$/i', $sort))
|
||||
{
|
||||
$orderby = 'ORDER BY history_id DESC';
|
||||
}
|
||||
@ -193,23 +216,30 @@ class History
|
||||
}
|
||||
foreach($filter as $col => $value)
|
||||
{
|
||||
if (!is_numeric($col) && substr($col,0,8) != 'history_')
|
||||
if(!is_numeric($col) && substr($col, 0, 8) != 'history_')
|
||||
{
|
||||
$filter['history_'.$col] = $value;
|
||||
$filter['history_' . $col] = $value;
|
||||
unset($filter[$col]);
|
||||
}
|
||||
}
|
||||
if (!isset($filter['history_appname'])) $filter['history_appname'] = $this->appname;
|
||||
if(!isset($filter['history_appname']))
|
||||
{
|
||||
$filter['history_appname'] = $this->appname;
|
||||
}
|
||||
|
||||
// do not try to read all history entries of an app
|
||||
if (!$filter['history_record_id']) return array();
|
||||
if(!$filter['history_record_id'])
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
foreach($this->db->select(self::TABLE, '*', $filter, __LINE__, __FILE__,
|
||||
isset($limit) ? 0 : false, $orderby, 'phpgwapi', $limit) as $row)
|
||||
isset($limit) ? 0 : false, $orderby, 'phpgwapi', $limit
|
||||
) as $row)
|
||||
{
|
||||
$row['user_ts'] = $this->db->from_timestamp($row['history_timestamp']) + 3600 * $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'];
|
||||
$rows[] = Api\Db::strip_array_keys($row,'history_');
|
||||
$rows[] = Api\Db::strip_array_keys($row, 'history_');
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
@ -227,54 +257,58 @@ class History
|
||||
$rows = array();
|
||||
$filter['history_appname'] = $query['appname'];
|
||||
$filter['history_record_id'] = $query['record_id'];
|
||||
if(is_array($query['colfilter'])) {
|
||||
foreach($query['colfilter'] as $column => $value) {
|
||||
if(is_array($query['colfilter']))
|
||||
{
|
||||
foreach($query['colfilter'] as $column => $value)
|
||||
{
|
||||
$filter[$column] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// filter out private (or no longer defined) custom fields
|
||||
if ($filter['history_appname'])
|
||||
if($filter['history_appname'])
|
||||
{
|
||||
$to_or[] = "history_status NOT LIKE '#%'";
|
||||
// explicitly allow "##" used to store iCal/vCard X-attributes
|
||||
if (in_array($filter['history_appname'], array('calendar','infolog','addressbook')))
|
||||
if(in_array($filter['history_appname'], array('calendar', 'infolog', 'addressbook')))
|
||||
{
|
||||
$to_or[] = "history_status LIKE '##%'";
|
||||
}
|
||||
if (($cfs = Customfields::get($filter['history_appname'])))
|
||||
if(($cfs = Customfields::get($filter['history_appname'])))
|
||||
{
|
||||
$to_or[] = 'history_status IN ('.implode(',', array_map(function($str)
|
||||
{
|
||||
return $GLOBALS['egw']->db->quote('#'.$str);
|
||||
}, array_keys($cfs))).')';
|
||||
$to_or[] = 'history_status IN (' . implode(',', array_map(function ($str)
|
||||
{
|
||||
return $GLOBALS['egw']->db->quote('#' . $str);
|
||||
}, array_keys($cfs))
|
||||
) . ')';
|
||||
}
|
||||
$filter[] = '('.implode(' OR ', $to_or).')';
|
||||
$filter[] = '(' . implode(' OR ', $to_or) . ')';
|
||||
}
|
||||
$_query = array(array(
|
||||
'table' => self::TABLE,
|
||||
'cols' => array(
|
||||
'history_id',
|
||||
'history_record_id',
|
||||
'history_appname',
|
||||
'history_owner',
|
||||
'history_status',
|
||||
'history_new_value',
|
||||
'history_timestamp',
|
||||
'history_old_value',
|
||||
'share_email'
|
||||
),
|
||||
'where' => $filter,
|
||||
));
|
||||
'table' => self::TABLE,
|
||||
'cols' => array(
|
||||
'history_id',
|
||||
'history_record_id',
|
||||
'history_appname',
|
||||
'history_owner',
|
||||
'history_status',
|
||||
'history_new_value',
|
||||
'history_timestamp',
|
||||
'history_old_value',
|
||||
'share_email'
|
||||
),
|
||||
'where' => $filter,
|
||||
));
|
||||
|
||||
// Add in files, if possible
|
||||
if($GLOBALS['egw_info']['user']['apps']['filemanager'] &&
|
||||
($sqlfs_sw = new Api\Vfs\Sqlfs\StreamWrapper()) &&
|
||||
($file = $sqlfs_sw->url_stat("/apps/{$query['appname']}/{$query['record_id']}",STREAM_URL_STAT_LINK)))
|
||||
($file = $sqlfs_sw->url_stat("/apps/{$query['appname']}/{$query['record_id']}", STREAM_URL_STAT_LINK)))
|
||||
{
|
||||
$_query[] = array(
|
||||
'table' => Api\Vfs\Sqlfs\StreamWrapper::TABLE,
|
||||
'cols' =>array('fs_id', 'fs_dir', "'filemanager'",'COALESCE(fs_modifier,fs_creator)',"'~file~'",'fs_name','fs_modified', 'fs_mime', '"" AS share_email'),
|
||||
'cols' => array('fs_id', 'fs_dir', "'filemanager'", 'COALESCE(fs_modifier,fs_creator)', "'~file~'",
|
||||
'fs_name', 'fs_modified', 'fs_mime', '"" AS share_email'),
|
||||
'where' => array('fs_dir' => $file['ino'])
|
||||
);
|
||||
}
|
||||
@ -290,20 +324,21 @@ class History
|
||||
$row['user_ts'] = $GLOBALS['egw']->db->from_timestamp($row['history_timestamp']) + 3600 * $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'];
|
||||
|
||||
// Explode multi-part values
|
||||
foreach(array('history_new_value','history_old_value') as $field)
|
||||
foreach(array('history_new_value', 'history_old_value') as $field)
|
||||
{
|
||||
if(strpos($row[$field],Tracking::ONE2N_SEPERATOR) !== false)
|
||||
if(strpos($row[$field], Tracking::ONE2N_SEPERATOR) !== false)
|
||||
{
|
||||
$row[$field] = explode(Tracking::ONE2N_SEPERATOR,$row[$field]);
|
||||
$row[$field] = explode(Tracking::ONE2N_SEPERATOR, $row[$field]);
|
||||
}
|
||||
}
|
||||
if ($row['history_old_value'] !== Tracking::DIFF_MARKER && (
|
||||
static::needs_diff($row['history_status'], $row['history_old_value']) ||
|
||||
static::needs_diff($row['history_status'], $row['history_old_value'])
|
||||
))
|
||||
if($row['history_old_value'] !== Tracking::DIFF_MARKER && (
|
||||
static::needs_diff($row['history_status'], $row['history_old_value']) ||
|
||||
static::needs_diff($row['history_status'], $row['history_old_value'])
|
||||
))
|
||||
{
|
||||
// Larger text stored with full old / new value - calculate diff and just send that
|
||||
$diff = new \Horde_Text_Diff('auto', array(explode("\n",$row['history_old_value']), explode("\n",$row['history_new_value'])));
|
||||
$diff = new \Horde_Text_Diff('auto', array(explode("\n", $row['history_old_value']),
|
||||
explode("\n", $row['history_new_value'])));
|
||||
$renderer = new \Horde_Text_Diff_Renderer_Unified();
|
||||
$row['history_new_value'] = $renderer->render($diff);
|
||||
$row['history_old_value'] = Tracking::DIFF_MARKER;
|
||||
@ -330,18 +365,26 @@ class History
|
||||
$rows[$new_version]['old_value'] = $row['history_new_value'];
|
||||
}
|
||||
}
|
||||
$rows[] = Api\Db::strip_array_keys($row,'history_');
|
||||
|
||||
// TODO: This is just here to hide bad values before we clean them with an update. If you're here, remove this IF block
|
||||
// Clear invalid share_email values
|
||||
if($row['share_email'] && stripos($row['share_email'], '@') === false)
|
||||
{
|
||||
$row['share_email'] = '';
|
||||
}
|
||||
|
||||
$rows[] = Api\Db::strip_array_keys($row, 'history_');
|
||||
}
|
||||
$total = $GLOBALS['egw']->db->union($_query,__LINE__,__FILE__)->NumRows();
|
||||
$total = $GLOBALS['egw']->db->union($_query, __LINE__, __FILE__)->NumRows();
|
||||
|
||||
// allow to hook into get_rows of other apps
|
||||
Api\Hooks::process(array(
|
||||
'hook_location' => 'etemplate2_history_get_rows',
|
||||
'get_rows' => __METHOD__,
|
||||
'value' => &$query,
|
||||
'rows' => &$rows,
|
||||
'total' => &$total,
|
||||
), array(), true); // true = no permission check
|
||||
'hook_location' => 'etemplate2_history_get_rows',
|
||||
'get_rows' => __METHOD__,
|
||||
'value' => &$query,
|
||||
'rows' => &$rows,
|
||||
'total' => &$total,
|
||||
), array(), true); // true = no permission check
|
||||
|
||||
return $total;
|
||||
}
|
||||
@ -364,9 +407,9 @@ class History
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return $name == 'note' || // Addressbook
|
||||
strpos($name, 'description') !== false || // Calendar, Records, Timesheet, ProjectManager, Resources
|
||||
$name == 'De' || // Tracker, InfoLog
|
||||
return $name == 'note' || // Addressbook
|
||||
strpos($name, 'description') !== false || // Calendar, Records, Timesheet, ProjectManager, Resources
|
||||
$name == 'De' || // Tracker, InfoLog
|
||||
($value && (strlen($value) > 200 || strstr($value, "\n") !== FALSE));
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class Base
|
||||
* @var array
|
||||
*/
|
||||
protected static $fstab = array(
|
||||
'/' => 'sqlfs://$host/',
|
||||
'/' => 'sqlfs://$host/',
|
||||
'/apps' => 'links://$host/apps',
|
||||
);
|
||||
|
||||
@ -75,73 +75,88 @@ class Base
|
||||
* @param string $url =null url of the filesystem to mount, eg. oldvfs://default/
|
||||
* @param string $path =null path to mount the filesystem in the vfs, eg. /
|
||||
* @param boolean $check_url =null check if url is an existing directory, before mounting it
|
||||
* default null only checks if url does not contain a $ as used in $user or $pass
|
||||
* default null only checks if url does not contain a $ as used in $user or $pass
|
||||
* @param boolean|int $persistent_mount =true create a persitent mount, or only a temprary for current request,
|
||||
* or integer account_id to mount persistent for a given user or group
|
||||
* or integer account_id to mount persistent for a given user or group
|
||||
* @param boolean $clear_fstab =false true clear current fstab, false (default) only add given mount
|
||||
* @return array|boolean array with fstab, if called without parameter or true on successful mount
|
||||
*/
|
||||
static function mount($url=null, $path=null, $check_url=null, $persistent_mount=true, $clear_fstab=false)
|
||||
static function mount($url = null, $path = null, $check_url = null, $persistent_mount = true, $clear_fstab = false)
|
||||
{
|
||||
if (is_null($check_url)) $check_url = strpos($url,'$') === false;
|
||||
if(is_null($check_url))
|
||||
{
|
||||
$check_url = strpos($url, '$') === false;
|
||||
}
|
||||
|
||||
if (!isset($GLOBALS['egw_info']['server']['vfs_fstab'])) // happens eg. in setup
|
||||
if(!isset($GLOBALS['egw_info']['server']['vfs_fstab'])) // happens eg. in setup
|
||||
{
|
||||
$api_config = Config::read('phpgwapi');
|
||||
if (isset($api_config['vfs_fstab']) && is_array($api_config['vfs_fstab']))
|
||||
if(isset($api_config['vfs_fstab']) && is_array($api_config['vfs_fstab']))
|
||||
{
|
||||
self::$fstab = $api_config['vfs_fstab'];
|
||||
}
|
||||
else
|
||||
{
|
||||
self::$fstab = array(
|
||||
'/' => 'sqlfs://$host/',
|
||||
'/' => 'sqlfs://$host/',
|
||||
'/apps' => 'links://$host/apps',
|
||||
);
|
||||
}
|
||||
unset($api_config);
|
||||
}
|
||||
if (is_null($url) || is_null($path))
|
||||
if(is_null($url) || is_null($path))
|
||||
{
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns '.array2string(self::$fstab));
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($url) . ',' . array2string($path) . ') returns ' . array2string(self::$fstab));
|
||||
}
|
||||
return self::$fstab;
|
||||
}
|
||||
if (!Vfs::$is_root)
|
||||
if(!Vfs::$is_root)
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') permission denied, you are NOT root!');
|
||||
return false; // only root can mount
|
||||
if(self::LOG_LEVEL > 0)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($url) . ',' . array2string($path) . ') permission denied, you are NOT root!');
|
||||
}
|
||||
return false; // only root can mount
|
||||
}
|
||||
if ($clear_fstab)
|
||||
if($clear_fstab)
|
||||
{
|
||||
self::$fstab = array();
|
||||
}
|
||||
if (isset(self::$fstab[$path]) && self::$fstab[$path] === $url)
|
||||
if(isset(self::$fstab[$path]) && self::$fstab[$path] === $url)
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') already mounted.');
|
||||
return true; // already mounted
|
||||
if(self::LOG_LEVEL > 0)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($url) . ',' . array2string($path) . ') already mounted.');
|
||||
}
|
||||
return true; // already mounted
|
||||
}
|
||||
self::load_wrapper(Vfs::parse_url($url,PHP_URL_SCHEME));
|
||||
self::load_wrapper(Vfs::parse_url($url, PHP_URL_SCHEME));
|
||||
|
||||
if ($check_url && (!file_exists($url) || opendir($url) === false))
|
||||
if($check_url && (!file_exists($url) || opendir($url) === false))
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') url does NOT exist!');
|
||||
return false; // url does not exist
|
||||
if(self::LOG_LEVEL > 0)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($url) . ',' . array2string($path) . ') url does NOT exist!');
|
||||
}
|
||||
return false; // url does not exist
|
||||
}
|
||||
self::$fstab[$path] = $url;
|
||||
|
||||
uksort(self::$fstab, function($a, $b)
|
||||
uksort(self::$fstab, function ($a, $b)
|
||||
{
|
||||
return strlen($a) - strlen($b);
|
||||
});
|
||||
|
||||
if ($persistent_mount)
|
||||
if($persistent_mount)
|
||||
{
|
||||
if ($persistent_mount === true)
|
||||
if($persistent_mount === true)
|
||||
{
|
||||
Config::save_value('vfs_fstab',self::$fstab,'phpgwapi');
|
||||
Config::save_value('vfs_fstab', self::$fstab, 'phpgwapi');
|
||||
$GLOBALS['egw_info']['server']['vfs_fstab'] = self::$fstab;
|
||||
// invalidate session cache
|
||||
if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited
|
||||
if(method_exists($GLOBALS['egw'], 'invalidate_session_cache')) // egw object in setup is limited
|
||||
{
|
||||
$GLOBALS['egw']->invalidate_session_cache();
|
||||
}
|
||||
@ -154,10 +169,13 @@ class Base
|
||||
$prefs->save_repository();
|
||||
// also save for current session
|
||||
$GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'][$path] =
|
||||
$_SESSION[Api\Session::EGW_INFO_CACHE]['user']['preferences']['common']['vfs_fstab'][$path] = $url;
|
||||
$_SESSION[Api\Session::EGW_INFO_CACHE]['user']['preferences']['common']['vfs_fstab'][$path] = $url;
|
||||
}
|
||||
}
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($url).','.array2string($path).') returns true (successful new mount).');
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($url) . ',' . array2string($path) . ') returns true (successful new mount).');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -168,18 +186,24 @@ class Base
|
||||
*/
|
||||
static function umount($path)
|
||||
{
|
||||
if (!Vfs::$is_root)
|
||||
if(!Vfs::$is_root)
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).','.array2string($path).') permission denied, you are NOT root!');
|
||||
return false; // only root can mount
|
||||
if(self::LOG_LEVEL > 0)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($path) . ',' . array2string($path) . ') permission denied, you are NOT root!');
|
||||
}
|
||||
return false; // only root can mount
|
||||
}
|
||||
if (!isset(self::$fstab[$path]) && ($path = array_search($path,self::$fstab)) === false)
|
||||
if(!isset(self::$fstab[$path]) && ($path = array_search($path, self::$fstab)) === false)
|
||||
{
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__.'('.array2string($path).') NOT mounted!');
|
||||
return false; // $path not mounted
|
||||
if(self::LOG_LEVEL > 0)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($path) . ') NOT mounted!');
|
||||
}
|
||||
return false; // $path not mounted
|
||||
}
|
||||
unset(self::$fstab[$path], $GLOBALS['egw_info']['server']['vfs_fstab'][$path]);
|
||||
Config::save_value('vfs_fstab', $GLOBALS['egw_info']['server']['vfs_fstab'],'phpgwapi');
|
||||
Config::save_value('vfs_fstab', $GLOBALS['egw_info']['server']['vfs_fstab'], 'phpgwapi');
|
||||
|
||||
unset($GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'][$path]);
|
||||
unset($_SESSION[Api\Session::EGW_INFO_CACHE]['server']['vfs_fstab'][$path]);
|
||||
@ -190,11 +214,14 @@ class Base
|
||||
$prefs->save_repository();
|
||||
|
||||
// invalidate session cache
|
||||
if (method_exists($GLOBALS['egw'],'invalidate_session_cache')) // egw object in setup is limited
|
||||
if(method_exists($GLOBALS['egw'], 'invalidate_session_cache')) // egw object in setup is limited
|
||||
{
|
||||
$GLOBALS['egw']->invalidate_session_cache();
|
||||
}
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__.'('.array2string($path).') returns true (successful unmount).');
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . '(' . array2string($path) . ') returns true (successful unmount).');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -204,12 +231,12 @@ class Base
|
||||
* @param string $fullurl full url returned by resolve_url
|
||||
* @return string|NULL mount url or null if not found
|
||||
*/
|
||||
static function mount_url($fullurl, &$mounted=null)
|
||||
static function mount_url($fullurl, &$mounted = null)
|
||||
{
|
||||
foreach(array_reverse(self::$fstab) as $mounted => $url)
|
||||
{
|
||||
list($url_no_query) = explode('?',$url);
|
||||
if (substr($fullurl,0,1+strlen($url_no_query)) === $url_no_query.'/')
|
||||
list($url_no_query) = explode('?', $url);
|
||||
if(substr($fullurl, 0, 1 + strlen($url_no_query)) === $url_no_query . '/')
|
||||
{
|
||||
return $url;
|
||||
}
|
||||
@ -237,71 +264,99 @@ class Base
|
||||
* @param ?string &$mounted =null on return mount-point of resolved url, IF $_path is a path or vfs-url, other urls return NULL!
|
||||
* @return string|boolean false if the url cant be resolved, should not happen if fstab has a root entry
|
||||
*/
|
||||
static function resolve_url($_path,$do_symlink=true,$use_symlinkcache=true,$replace_user_pass_host=true,$fix_url_query=false, &$mounted=null)
|
||||
static function resolve_url($_path, $do_symlink = true, $use_symlinkcache = true, $replace_user_pass_host = true, $fix_url_query = false, &$mounted = null)
|
||||
{
|
||||
$path = self::get_path($_path);
|
||||
|
||||
// we do some caching here
|
||||
if (isset(self::$resolve_url_cache[$path]) && $replace_user_pass_host)
|
||||
if(isset(self::$resolve_url_cache[$path]) && $replace_user_pass_host)
|
||||
{
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '".self::$resolve_url_cache[$path]."' (from cache)");
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . "('$path') = '" . self::$resolve_url_cache[$path] . "' (from cache)");
|
||||
}
|
||||
$mounted = self::$resolve_url_cache[$path]['mounted'];
|
||||
return self::$resolve_url_cache[$path]['url'];
|
||||
}
|
||||
// check if we can already resolve path (or a part of it) with a known symlinks
|
||||
if ($use_symlinkcache)
|
||||
if($use_symlinkcache)
|
||||
{
|
||||
$path = self::symlinkCache_resolve($path,$do_symlink);
|
||||
$path = self::symlinkCache_resolve($path, $do_symlink);
|
||||
}
|
||||
// setting default user, passwd and domain, if it's not contained int the url
|
||||
$defaults = array(
|
||||
'user' => $GLOBALS['egw_info']['user']['account_lid'],
|
||||
'pass' => urlencode($GLOBALS['egw_info']['user']['passwd']),
|
||||
'host' => $GLOBALS['egw_info']['user']['domain'],
|
||||
'home' => str_replace(array('\\\\','\\'),array('','/'),$GLOBALS['egw_info']['user']['homedirectory']),
|
||||
'home' => str_replace(array('\\\\', '\\'), array('', '/'), $GLOBALS['egw_info']['user']['homedirectory']),
|
||||
);
|
||||
$parts = array_merge(Vfs::parse_url($_path),Vfs::parse_url($path),$defaults);
|
||||
if (!$parts['host']) $parts['host'] = 'default'; // otherwise we get an invalid url (scheme:///path/to/something)!
|
||||
|
||||
if (!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME)
|
||||
$parts = array_merge(Vfs::parse_url($_path), Vfs::parse_url($path), $defaults);
|
||||
if(!$parts['host'])
|
||||
{
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$path' (path is already an url)");
|
||||
return $path; // path is already a non-vfs url --> nothing to do
|
||||
// otherwise we get an invalid url (scheme:///path/to/something)!
|
||||
$parts['host'] = 'default';
|
||||
}
|
||||
|
||||
if(!empty($parts['scheme']) && $parts['scheme'] != self::SCHEME)
|
||||
{
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . "('$path') = '$path' (path is already an url)");
|
||||
}
|
||||
return $path; // path is already a non-vfs url --> nothing to do
|
||||
}
|
||||
if(empty($parts['path']))
|
||||
{
|
||||
$parts['path'] = '/';
|
||||
}
|
||||
if (empty($parts['path'])) $parts['path'] = '/';
|
||||
|
||||
foreach(array_reverse(self::$fstab) as $mounted => $url)
|
||||
{
|
||||
if ($mounted == '/' || $mounted == $parts['path'] || $mounted.'/' == substr($parts['path'],0,strlen($mounted)+1))
|
||||
if($mounted == '/' || $mounted == $parts['path'] || $mounted . '/' == substr($parts['path'], 0, strlen($mounted) + 1))
|
||||
{
|
||||
$scheme = Vfs::parse_url($url,PHP_URL_SCHEME);
|
||||
if (is_null(self::$wrappers) || !in_array($scheme,self::$wrappers))
|
||||
$scheme = Vfs::parse_url($url, PHP_URL_SCHEME);
|
||||
if(is_null(self::$wrappers) || !in_array($scheme, self::$wrappers))
|
||||
{
|
||||
self::load_wrapper($scheme);
|
||||
}
|
||||
if (($relative = substr($parts['path'],strlen($mounted))))
|
||||
if(($relative = substr($parts['path'], strlen($mounted))))
|
||||
{
|
||||
$url = Vfs::concat($url, $relative);
|
||||
}
|
||||
// if url contains url parameter, eg. from filesystem streamwrapper, we need to append relative path here too
|
||||
$matches = null;
|
||||
if ($fix_url_query && preg_match('|([?&]url=)([^&]+)|', $url, $matches))
|
||||
if($fix_url_query && preg_match('|([?&]url=)([^&]+)|', $url, $matches))
|
||||
{
|
||||
$url = str_replace($matches[0], $matches[1].Vfs::concat($matches[2], substr($parts['path'],strlen($mounted))), $url);
|
||||
$url = str_replace($matches[0], $matches[1] . Vfs::concat($matches[2], substr($parts['path'], strlen($mounted))), $url);
|
||||
}
|
||||
|
||||
if ($replace_user_pass_host)
|
||||
if($replace_user_pass_host)
|
||||
{
|
||||
$url = str_replace(array('$user','$pass','$host','$home'),array($parts['user'],$parts['pass'],$parts['host'],$parts['home']),$url);
|
||||
$url = str_replace(array('$user',
|
||||
'$pass',
|
||||
'$host',
|
||||
'$home'), array($parts['user'],
|
||||
$parts['pass'],
|
||||
$parts['host'],
|
||||
$parts['home']), $url);
|
||||
}
|
||||
if ($parts['query']) $url .= '?'.$parts['query'];
|
||||
if ($parts['fragment']) $url .= '#'.$parts['fragment'];
|
||||
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."('$path') = '$url'");
|
||||
|
||||
if (($class = self::scheme2class($scheme)) && is_callable([$class, 'replace']))
|
||||
if($parts['query'])
|
||||
{
|
||||
if (!($replace = call_user_func([$class, 'replace'], $url)))
|
||||
$url .= '?' . $parts['query'];
|
||||
}
|
||||
if($parts['fragment'])
|
||||
{
|
||||
$url .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . "('$path') = '$url'");
|
||||
}
|
||||
|
||||
if(($class = self::scheme2class($scheme)) && is_callable([$class, 'replace']))
|
||||
{
|
||||
if(!($replace = call_user_func([$class, 'replace'], $url)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -310,16 +365,17 @@ class Base
|
||||
|
||||
// Make sure we don't cache anything with a link anywhere in the url, since it fails with eg: /apps/InfoLog/Open$/2021$.
|
||||
// is_link() is not always right here
|
||||
$is_link = is_link($url) || (self::symlinkCache_resolve(Vfs::parse_url($url,PHP_URL_PATH)) !== Vfs::parse_url($url,PHP_URL_PATH));
|
||||
$is_link = is_link($url) || (self::symlinkCache_resolve(Vfs::parse_url($url, PHP_URL_PATH)) !== Vfs::parse_url($url, PHP_URL_PATH));
|
||||
if($is_link && $do_symlink)
|
||||
{
|
||||
$old_url = $url;
|
||||
$_url = self::symlinkCache_resolve($url);
|
||||
$url = @readlink($url) ?: (Vfs::parse_url($_url,PHP_URL_PATH) != $parts['path'] ?
|
||||
str_replace([$parts['path'],Vfs::parse_url($old_url,PHP_URL_SCHEME)],[$_url,Vfs::parse_url(Vfs::resolve_url($_url),PHP_URL_SCHEME)],$url) : null) ?:$url;
|
||||
$url = @readlink($url) ?: (Vfs::parse_url($_url, PHP_URL_PATH) != $parts['path'] ?
|
||||
str_replace([$parts['path'], Vfs::parse_url($old_url, PHP_URL_SCHEME)], [$_url,
|
||||
Vfs::parse_url(Vfs::resolve_url($_url), PHP_URL_SCHEME)], $url) : null) ?: $url;
|
||||
$is_link = $old_url == $url;
|
||||
}
|
||||
if ($replace_user_pass_host && !$is_link)
|
||||
if($replace_user_pass_host && !$is_link)
|
||||
{
|
||||
self::$resolve_url_cache[$path] = ['url' => $url, 'mounted' => $mounted];
|
||||
}
|
||||
@ -327,8 +383,11 @@ class Base
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
if (self::LOG_LEVEL > 0) error_log(__METHOD__."('$path') can't resolve path!\n");
|
||||
trigger_error(__METHOD__."($path) can't resolve path!\n",E_USER_WARNING);
|
||||
if(self::LOG_LEVEL > 0)
|
||||
{
|
||||
error_log(__METHOD__ . "('$path') can't resolve path!\n");
|
||||
}
|
||||
trigger_error(__METHOD__ . "($path) can't resolve path!\n", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -345,22 +404,37 @@ class Base
|
||||
* @param string $_path vfs path
|
||||
* @param string $target target path
|
||||
*/
|
||||
static protected function symlinkCache_add($_path,$target)
|
||||
static protected function symlinkCache_add($_path, $target)
|
||||
{
|
||||
$path = self::get_path($_path);
|
||||
|
||||
if (isset(self::$symlink_cache[$path])) return; // nothing to do
|
||||
|
||||
if ($target[0] != '/') $target = Vfs::parse_url($target,PHP_URL_PATH);
|
||||
if(isset(self::$symlink_cache[$path]))
|
||||
{
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
if($target[0] != '/')
|
||||
{
|
||||
$query = Vfs::parse_url($target, PHP_URL_QUERY);
|
||||
$target = Vfs::parse_url($target, PHP_URL_PATH);
|
||||
if($query)
|
||||
{
|
||||
// Don't cache without query, some StreamWrappers need those parameters
|
||||
$target = "?$query";
|
||||
}
|
||||
}
|
||||
|
||||
self::$symlink_cache[$path] = $target;
|
||||
|
||||
// sort longest path first
|
||||
uksort(self::$symlink_cache, function($b, $a)
|
||||
uksort(self::$symlink_cache, function ($b, $a)
|
||||
{
|
||||
return strlen($a) - strlen($b);
|
||||
});
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path,$target) cache now ".array2string(self::$symlink_cache));
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . "($path,$target) cache now " . array2string(self::$symlink_cache));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,7 +447,10 @@ class Base
|
||||
$path = self::get_path($_path);
|
||||
|
||||
unset(self::$symlink_cache[$path]);
|
||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__."($path) cache now ".array2string(self::$symlink_cache));
|
||||
if(self::LOG_LEVEL > 1)
|
||||
{
|
||||
error_log(__METHOD__ . "($path) cache now " . array2string(self::$symlink_cache));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -385,7 +462,7 @@ class Base
|
||||
* @param boolean $do_symlink =true is a direct match allowed, default yes (must be false for a lstat or readlink!)
|
||||
* @return string target or path, if path not found
|
||||
*/
|
||||
public static function symlinkCache_resolve($_path, $do_symlink=true)
|
||||
public static function symlinkCache_resolve($_path, $do_symlink = true)
|
||||
{
|
||||
// remove vfs scheme, but no other schemes (eg. filesystem!)
|
||||
$path = self::get_path($_path);
|
||||
@ -396,18 +473,24 @@ class Base
|
||||
{
|
||||
if (($strlen_p = strlen($p)) > $strlen_path) continue; // $path can NOT start with $p
|
||||
|
||||
if ($path == $p)
|
||||
if($path == $p)
|
||||
{
|
||||
if ($do_symlink) $target = $t;
|
||||
if($do_symlink)
|
||||
{
|
||||
$target = $t;
|
||||
}
|
||||
break;
|
||||
}
|
||||
elseif (substr($path,0,$strlen_p+1) == $p.'/')
|
||||
elseif(substr($path, 0, $strlen_p + 1) == $p . '/')
|
||||
{
|
||||
$target = $t . substr($path,$strlen_p);
|
||||
$target = $t . substr($path, $strlen_p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (self::LOG_LEVEL > 1 && isset($target)) error_log(__METHOD__."($path) = $target");
|
||||
if(self::LOG_LEVEL > 1 && isset($target))
|
||||
{
|
||||
error_log(__METHOD__ . "($path) = $target");
|
||||
}
|
||||
return isset($target) ? $target : $path;
|
||||
}
|
||||
|
||||
@ -429,7 +512,7 @@ class Base
|
||||
*/
|
||||
static function load_wrapper($scheme)
|
||||
{
|
||||
if (!in_array($scheme,self::get_wrappers()))
|
||||
if(!in_array($scheme, self::get_wrappers()))
|
||||
{
|
||||
switch($scheme)
|
||||
{
|
||||
@ -440,16 +523,16 @@ class Base
|
||||
self::$wrappers[] = 'webdavs';
|
||||
break;
|
||||
case '':
|
||||
break; // default file, always loaded
|
||||
break; // default file, always loaded
|
||||
default:
|
||||
// check if scheme is buildin in php or one of our own stream wrappers
|
||||
if (in_array($scheme,stream_get_wrappers()) || class_exists(self::scheme2class($scheme)))
|
||||
if(in_array($scheme, stream_get_wrappers()) || class_exists(self::scheme2class($scheme)))
|
||||
{
|
||||
self::$wrappers[] = $scheme;
|
||||
}
|
||||
else
|
||||
{
|
||||
trigger_error("Can't load stream-wrapper for scheme '$scheme'!",E_USER_WARNING);
|
||||
trigger_error("Can't load stream-wrapper for scheme '$scheme'!", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -464,7 +547,7 @@ class Base
|
||||
*/
|
||||
static function get_wrappers()
|
||||
{
|
||||
if (is_null(self::$wrappers))
|
||||
if(is_null(self::$wrappers))
|
||||
{
|
||||
self::$wrappers = stream_get_wrappers();
|
||||
}
|
||||
@ -482,19 +565,23 @@ class Base
|
||||
*/
|
||||
static function scheme2class($scheme)
|
||||
{
|
||||
if ($scheme === self::SCHEME)
|
||||
if($scheme === self::SCHEME)
|
||||
{
|
||||
return __CLASS__;
|
||||
}
|
||||
list($app, $app_scheme) = explode('.', $scheme);
|
||||
foreach(array(
|
||||
empty($app_scheme) ? 'EGroupware\\Api\\Vfs\\'.ucfirst($scheme).'\\StreamWrapper' : // streamwrapper in Api\Vfs
|
||||
'EGroupware\\'.ucfirst($app).'\\Vfs\\'.ucfirst($app_scheme).'\\StreamWrapper', // streamwrapper in $app\Vfs
|
||||
str_replace('.','_',$scheme).'_stream_wrapper', // old (flat) name
|
||||
empty($app_scheme) ? 'EGroupware\\Api\\Vfs\\' . ucfirst($scheme) . '\\StreamWrapper' : // streamwrapper in Api\Vfs
|
||||
'EGroupware\\' . ucfirst($app) . '\\Vfs\\' . ucfirst($app_scheme) . '\\StreamWrapper',
|
||||
// streamwrapper in $app\Vfs
|
||||
str_replace('.', '_', $scheme) . '_stream_wrapper', // old (flat) name
|
||||
) as $class)
|
||||
{
|
||||
//error_log(__METHOD__."('$scheme') class_exists('$class')=".array2string(class_exists($class)));
|
||||
if (class_exists($class)) return $class;
|
||||
if(class_exists($class))
|
||||
{
|
||||
return $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -505,18 +592,18 @@ class Base
|
||||
* @param string $only_remove_scheme =self::SCHEME if given only that scheme get's removed
|
||||
* @return string path without training slash
|
||||
*/
|
||||
static protected function get_path($path,$only_remove_scheme=self::SCHEME)
|
||||
static protected function get_path($path, $only_remove_scheme = self::SCHEME)
|
||||
{
|
||||
if ($path[0] != '/' && (!$only_remove_scheme || Vfs::parse_url($path, PHP_URL_SCHEME) == $only_remove_scheme))
|
||||
if($path[0] != '/' && (!$only_remove_scheme || Vfs::parse_url($path, PHP_URL_SCHEME) == $only_remove_scheme))
|
||||
{
|
||||
$path = Vfs::parse_url($path, PHP_URL_PATH);
|
||||
}
|
||||
// remove trailing slashes eg. added by WebDAV, but do NOT remove / from "sqlfs://default/"!
|
||||
if ($path != '/')
|
||||
if($path != '/')
|
||||
{
|
||||
while (mb_substr($path, -1) == '/' && $path != '/' && ($path[0] == '/' || Vfs::parse_url($path, PHP_URL_PATH) != '/'))
|
||||
while(mb_substr($path, -1) == '/' && $path != '/' && ($path[0] == '/' || Vfs::parse_url($path, PHP_URL_PATH) != '/'))
|
||||
{
|
||||
$path = mb_substr($path,0,-1);
|
||||
$path = mb_substr($path, 0, -1);
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
@ -532,7 +619,7 @@ class Base
|
||||
{
|
||||
static $cache = array();
|
||||
$ret =& $cache[$url];
|
||||
if (!isset($ret))
|
||||
if(!isset($ret))
|
||||
{
|
||||
$matches = null;
|
||||
$ret = preg_match('/\?(.*&)?ro=([^&]+)/', $url, $matches) && $matches[2];
|
||||
@ -548,52 +635,58 @@ class Base
|
||||
* @param string $name
|
||||
* @param array $params first param has to be the path, otherwise we can not determine the correct wrapper
|
||||
* @param boolean|"null" $fail_silent =false should only false be returned if function is not supported by the backend,
|
||||
* or should an E_USER_WARNING error be triggered (default), or "null": return NULL
|
||||
* or should an E_USER_WARNING error be triggered (default), or "null": return NULL
|
||||
* @param int $path_param_key =0 key in params containing the path, default 0
|
||||
* @param boolean $instanciate =false true: instanciate the class to call method $name, false: static call
|
||||
* @return mixed return value of backend or false if function does not exist on backend
|
||||
*/
|
||||
protected static function _call_on_backend($name, array $params, $fail_silent=false, $path_param_key=0, $instanciate=false)
|
||||
protected static function _call_on_backend($name, array $params, $fail_silent = false, $path_param_key = 0, $instanciate = false)
|
||||
{
|
||||
$pathes = $params[$path_param_key];
|
||||
|
||||
$scheme2urls = array();
|
||||
foreach(is_array($pathes) ? $pathes : array($pathes) as $path)
|
||||
{
|
||||
if (!($url = Vfs::resolve_url_symlinks($path,false,false)))
|
||||
if(!($url = Vfs::resolve_url_symlinks($path, false, false)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
$k=(string)Vfs::parse_url($url,PHP_URL_SCHEME);
|
||||
if (!(is_array($scheme2urls[$k]))) $scheme2urls[$k] = array();
|
||||
$k = (string)Vfs::parse_url($url, PHP_URL_SCHEME);
|
||||
if(!(is_array($scheme2urls[$k])))
|
||||
{
|
||||
$scheme2urls[$k] = array();
|
||||
}
|
||||
$scheme2urls[$k][$path] = $url;
|
||||
}
|
||||
$ret = array();
|
||||
foreach($scheme2urls as $scheme => $urls)
|
||||
{
|
||||
if ($scheme)
|
||||
if($scheme)
|
||||
{
|
||||
if (!class_exists($class = Vfs\StreamWrapper::scheme2class($scheme)) || !method_exists($class,$name))
|
||||
if(!class_exists($class = Vfs\StreamWrapper::scheme2class($scheme)) || !method_exists($class, $name))
|
||||
{
|
||||
if (!$fail_silent) trigger_error("Can't $name for scheme $scheme!\n",E_USER_WARNING);
|
||||
if(!$fail_silent)
|
||||
{
|
||||
trigger_error("Can't $name for scheme $scheme!\n", E_USER_WARNING);
|
||||
}
|
||||
return $fail_silent === 'null' ? null : false;
|
||||
}
|
||||
$callback = [$instanciate ? new $class($url) : $class, $name];
|
||||
if (!is_array($pathes))
|
||||
if(!is_array($pathes))
|
||||
{
|
||||
$params[$path_param_key] = $url;
|
||||
|
||||
return call_user_func_array($callback, $params);
|
||||
}
|
||||
$params[$path_param_key] = $urls;
|
||||
if (!is_array($r = call_user_func_array($callback, $params)))
|
||||
if(!is_array($r = call_user_func_array($callback, $params)))
|
||||
{
|
||||
return $r;
|
||||
}
|
||||
// we need to re-translate the urls to pathes, as they can eg. contain symlinks
|
||||
foreach($urls as $path => $url)
|
||||
{
|
||||
if (isset($r[$url]) || isset($r[$url=Vfs::parse_url($url,PHP_URL_PATH)]))
|
||||
if(isset($r[$url]) || isset($r[$url = Vfs::parse_url($url, PHP_URL_PATH)]))
|
||||
{
|
||||
$ret[$path] = $r[$url];
|
||||
}
|
||||
@ -607,7 +700,7 @@ class Base
|
||||
else
|
||||
{
|
||||
$time = null;
|
||||
return $name($url,$time);
|
||||
return $name($url, $time);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
|
@ -2264,7 +2264,6 @@ div.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button {
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
background-position: 3px;
|
||||
padding-left: 15px;
|
||||
outline: none;
|
||||
}
|
||||
div.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button.right {
|
||||
|
@ -645,8 +645,7 @@ class InfologApp extends EgwApp
|
||||
*/
|
||||
getWindowTitle()
|
||||
{
|
||||
var widget = this.et2.getWidgetById('info_subject');
|
||||
if(widget) return widget.options.value;
|
||||
return this.et2.getValueById("info_subject");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,7 +75,7 @@
|
||||
</row>
|
||||
<row class="$row_cont[info_cat] $row_cont[class]" valign="top">
|
||||
<hbox align="center" class="infolog_CompletedClmn">
|
||||
<image label="$row_cont[info_type]" src="infolog/${row}[info_type]" default_src="infolog/navbar"/>
|
||||
<image label="$row_cont[info_type]" src="infolog/$row_cont[info_type]" default_src="infolog/navbar"/>
|
||||
<image label="$row_cont[info_status_label]" id="edit_status[$row_cont[info_id]]" href="javascript:egw.open($row_cont[info_id],'infolog');" src="infolog/$row_cont[info_status_label]" default_src="status"/>
|
||||
<image label="$row_cont[info_percent]" id="edit_percent[$row_cont[info_id]]" href="javascript:egw.open($row_cont[info_id],'infolog');" src="$row_cont[info_percent]"/>
|
||||
<progress label="$row_cont[info_percent]" id="{$row}[info_percent2]" href="javascript:egw.open($row_cont[info_id],'infolog');"/>
|
||||
|
@ -326,6 +326,11 @@ class mail_integration {
|
||||
}
|
||||
}
|
||||
|
||||
//consider all addresses in the header
|
||||
foreach (['TO','CC','BCC', 'FROM'] as $h)
|
||||
{
|
||||
$mailcontent['mailaddress'] .= ','.$headers[$h];
|
||||
}
|
||||
// Convert addresses to email and personal
|
||||
$addresses = imap_rfc822_parse_adrlist($mailcontent['mailaddress'],'');
|
||||
foreach ($addresses as $address)
|
||||
|
Loading…
Reference in New Issue
Block a user