mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-07 08:34:42 +01:00
Merge branch 'master' into web-components
This commit is contained in:
commit
fac1dfb8d4
2
.gitignore
vendored
2
.gitignore
vendored
@ -65,6 +65,8 @@ status/
|
|||||||
smallpart/
|
smallpart/
|
||||||
swoolepush/
|
swoolepush/
|
||||||
webauthn/
|
webauthn/
|
||||||
|
*/js/*.map
|
||||||
|
*/js/app.min.js
|
||||||
addressbook/js/app.js
|
addressbook/js/app.js
|
||||||
admin/js/app.js
|
admin/js/app.js
|
||||||
api/js/etemplate/*.js
|
api/js/etemplate/*.js
|
||||||
|
16
Gruntfile.js
16
Gruntfile.js
@ -41,7 +41,7 @@ module.exports = function (grunt) {
|
|||||||
files: {
|
files: {
|
||||||
"pixelegg/css/pixelegg.min.css": [
|
"pixelegg/css/pixelegg.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
@ -59,7 +59,7 @@ module.exports = function (grunt) {
|
|||||||
],
|
],
|
||||||
"pixelegg/css/mobile.min.css": [
|
"pixelegg/css/mobile.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
@ -77,7 +77,7 @@ module.exports = function (grunt) {
|
|||||||
],
|
],
|
||||||
"pixelegg/mobile/fw_mobile.min.css": [
|
"pixelegg/mobile/fw_mobile.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
@ -94,7 +94,7 @@ module.exports = function (grunt) {
|
|||||||
],
|
],
|
||||||
"pixelegg/css/monochrome.min.css": [
|
"pixelegg/css/monochrome.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
@ -112,7 +112,7 @@ module.exports = function (grunt) {
|
|||||||
],
|
],
|
||||||
"pixelegg/css/modern.min.css": [
|
"pixelegg/css/modern.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
@ -134,7 +134,7 @@ module.exports = function (grunt) {
|
|||||||
files: {
|
files: {
|
||||||
"jdots/css/high-contrast.min.css": [
|
"jdots/css/high-contrast.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
@ -155,7 +155,7 @@ module.exports = function (grunt) {
|
|||||||
],
|
],
|
||||||
"jdots/css/jdots.min.css": [
|
"jdots/css/jdots.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
@ -175,7 +175,7 @@ module.exports = function (grunt) {
|
|||||||
],
|
],
|
||||||
"jdots/css/orange-green.min.css": [
|
"jdots/css/orange-green.min.css": [
|
||||||
"api/js/jquery/chosen/chosen.css",
|
"api/js/jquery/chosen/chosen.css",
|
||||||
"vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css",
|
"node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css",
|
||||||
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
"vendor/egroupware/magicsuggest/magicsuggest.css",
|
||||||
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
"api/js/jquery/jquery-ui-timepicker-addon.css",
|
||||||
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
"api/js/jquery/blueimp/css/blueimp-gallery.min.css",
|
||||||
|
31
README.md
31
README.md
@ -1,24 +1,29 @@
|
|||||||
# EGroupware
|
# EGroupware
|
||||||
| Branch | Status | Tools | Usage |
|
|
||||||
| ------ | ------ | ----- | ----- |
|
|
||||||
| master | [![Build Status](https://travis-ci.org/EGroupware/egroupware.svg?branch=master)](https://travis-ci.org/EGroupware/egroupware) | <img src="https://travis-ci.com/images/logos/TravisCI-Full-Color.png" width="108" alt="Travis CI"/> | runs unit-tests after each commit |
|
|
||||||
| 20.1 | [![Build Status](https://travis-ci.org/EGroupware/egroupware.svg?branch=20.1)](https://travis-ci.org/EGroupware/egroupware) | [![Scrutinizer CI](https://scrutinizer-ci.com/images/logo.png) scrutinizer](https://scrutinizer-ci.com/g/EGroupware/egroupware/) | runs static analysis on our codebase |
|
|
||||||
| 19.1 | [![Build Status](https://travis-ci.org/EGroupware/egroupware.svg?branch=19.1)](https://travis-ci.org/EGroupware/egroupware) | <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQ2scF5HUwLnJVnk2UhYwWpUXHmLQYNXM5yBw&usqp=CAU" width="110" alt="BrowserStack" /> | manual testing with unusual browser versions or platforms |
|
|
||||||
|
|
||||||
### Default and prefered installation method for EGroupware is via your package manager:
|
| Tools | Usage |
|
||||||
|
| ----- | ----- |
|
||||||
|
| <img src="https://travis-ci.com/images/logos/TravisCI-Full-Color.png" width="108" alt="Travis CI"/> | runs unit-tests after each commit |
|
||||||
|
| [![Scrutinizer CI](https://scrutinizer-ci.com/images/logo.png) scrutinizer](https://scrutinizer-ci.com/g/EGroupware/egroupware/) | runs static analysis on our codebase |
|
||||||
|
| <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn%3AANd9GcQ2scF5HUwLnJVnk2UhYwWpUXHmLQYNXM5yBw&usqp=CAU" width="110" alt="BrowserStack" /> | manual testing with unusual browser versions or platforms |
|
||||||
|
|
||||||
https://software.opensuse.org/download.html?project=server%3AeGroupWare&package=egroupware-epl
|
### Default and prefered installation method for EGroupware is via your Linux package manager:
|
||||||
|
|
||||||
### Installing EGroupware 20.1 via Docker:
|
* [Installation & Update instructions](https://github.com/EGroupware/egroupware/wiki/Installation-using-egroupware-docker-RPM-DEB-package)
|
||||||
EGroupware 20.1 can be installed via Docker, in fact the DEB/RPM packages also does that. Instructions on how to run EGroupware in Docker are in [doc/docker](https://github.com/EGroupware/egroupware/tree/20.1/doc/docker) subdirectory.
|
* [Distribution specific instructions](https://github.com/EGroupware/egroupware/wiki/Distribution-specific-instructions)
|
||||||
|
|
||||||
### Installing EGroupware 19.1 via Docker:
|
> Every other method (including a developer installation by cloning the repo) is way more complicated AND does not include all features, as part's of EGroupware are running in different containers, eg. the push-server!
|
||||||
EGroupware 19.1 can be installed via Docker, in fact the DEB/RPM packages also does that. Instructions on how to run EGroupware in Docker are in [doc/docker](https://github.com/EGroupware/egroupware/tree/19.1/doc/docker) subdirectory.
|
|
||||||
|
|
||||||
|
### Installing EGroupware 21.1 via Docker for non-Linux environments or not supported Linux distros:
|
||||||
|
EGroupware 21.1 can be installed via Docker, in fact the DEB/RPM packages also does that. Instructions on how to run EGroupware in Docker are in our [Wiki](https://github.com/EGroupware/egroupware/wiki/Docker-compose-installation) and in [doc/docker](https://github.com/EGroupware/egroupware/tree/21.1/doc/docker) subdirectory.
|
||||||
|
|
||||||
### Installing EGroupware development version:
|
### Installing EGroupware development version via Docker:
|
||||||
|
* this is the prefered developer installation, as it contains eg. a push-server container
|
||||||
|
* https://github.com/EGroupware/egroupware/tree/master/doc/docker/development
|
||||||
|
|
||||||
|
### Deprecated EGroupware development installation:
|
||||||
* install composer.phar from https://getcomposer.org/download/
|
* install composer.phar from https://getcomposer.org/download/
|
||||||
* optional: for minified JavaScript and CSS install nodejs and grunt
|
* for JavaScript dependencies and build install nodejs and npm
|
||||||
|
* optional: for minified CSS install grunt
|
||||||
```
|
```
|
||||||
apt/yum/zypper install nodejs
|
apt/yum/zypper install nodejs
|
||||||
npm install -g grunt-cli
|
npm install -g grunt-cli
|
||||||
|
@ -1528,6 +1528,9 @@ class AddressbookApp extends EgwApp
|
|||||||
*/
|
*/
|
||||||
private videoconference_isUserOnline(_action, _selected)
|
private videoconference_isUserOnline(_action, _selected)
|
||||||
{
|
{
|
||||||
|
// ATM we're not supporting status in mobile theme
|
||||||
|
if (egwIsMobile()) return false;
|
||||||
|
|
||||||
let list = app.status ? app.status.getEntireList() : {};
|
let list = app.status ? app.status.getEntireList() : {};
|
||||||
for (let sel in _selected)
|
for (let sel in _selected)
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
egw_action_common;
|
egw_action_common;
|
||||||
egw_action_popup;
|
egw_action_popup;
|
||||||
vendor.bower-asset.jquery.dist.jquery;
|
vendor.bower-asset.jquery.dist.jquery;
|
||||||
/vendor/bower-asset/jquery-ui/jquery-ui.js;
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {egwAction,egwActionImplementation} from "./egw_action.js";
|
import {egwAction,egwActionImplementation} from "./egw_action.js";
|
||||||
|
@ -12,14 +12,12 @@
|
|||||||
/*egw:uses
|
/*egw:uses
|
||||||
vendor.bower-asset.jquery.dist.jquery;
|
vendor.bower-asset.jquery.dist.jquery;
|
||||||
egw_menu;
|
egw_menu;
|
||||||
/api/js/jquery/jquery-tap-and-hold/jquery.tapandhold.js;
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {egwAction, egwActionImplementation, egwActionObject} from './egw_action.js';
|
import {egwAction, egwActionImplementation, egwActionObject} from './egw_action.js';
|
||||||
import {egwFnct} from './egw_action_common.js';
|
import {egwFnct} from './egw_action_common.js';
|
||||||
import {egwMenu, _egw_active_menu} from "./egw_menu.js";
|
import {egwMenu, _egw_active_menu} from "./egw_menu.js";
|
||||||
import {EGW_KEY_ENTER, EGW_KEY_MENU} from "./egw_action_constants.js";
|
import {EGW_KEY_ENTER, EGW_KEY_MENU} from "./egw_action_constants.js";
|
||||||
import "../jquery/jquery-tap-and-hold/jquery.tapandhold.js";
|
|
||||||
|
|
||||||
if (typeof window._egwActionClasses == "undefined")
|
if (typeof window._egwActionClasses == "undefined")
|
||||||
window._egwActionClasses = {};
|
window._egwActionClasses = {};
|
||||||
@ -280,7 +278,39 @@ export function egwPopupActionImplementation()
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
ai._handleTapHold = function (_node, _callback)
|
||||||
|
{
|
||||||
|
let holdTimer = 600;
|
||||||
|
let maxDistanceAllowed = 40;
|
||||||
|
let tapTimeout = null;
|
||||||
|
let startx = 0;
|
||||||
|
let starty = 0;
|
||||||
|
|
||||||
|
//TODO (todo-jquery): ATM we need to convert the possible given jquery dom node object into DOM Element, this
|
||||||
|
// should be no longer neccessary after removing jQuery nodes.
|
||||||
|
if (_node instanceof jQuery)
|
||||||
|
{
|
||||||
|
_node = _node[0];
|
||||||
|
}
|
||||||
|
_node.addEventListener('touchstart', function(e){
|
||||||
|
|
||||||
|
tapTimeout = setTimeout(function(event){
|
||||||
|
_callback(e);
|
||||||
|
}, holdTimer);
|
||||||
|
startx = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
|
||||||
|
starty = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
|
||||||
|
});
|
||||||
|
_node.addEventListener('touchend', function(){
|
||||||
|
clearTimeout(tapTimeout);
|
||||||
|
});
|
||||||
|
_node.addEventListener('touchmove', function(_event){
|
||||||
|
if (tapTimeout == null) return;
|
||||||
|
let e = _event.originalEvent;
|
||||||
|
let x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
|
||||||
|
let y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
|
||||||
|
if (Math.sqrt((x-startx)*(x-startx) + (y-starty)+(y-starty)) > maxDistanceAllowed) clearTimeout(tapTimeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Registers the handler for the context menu
|
* Registers the handler for the context menu
|
||||||
*
|
*
|
||||||
@ -292,14 +322,6 @@ export function egwPopupActionImplementation()
|
|||||||
ai._registerContext = function(_node, _callback, _context)
|
ai._registerContext = function(_node, _callback, _context)
|
||||||
{
|
{
|
||||||
var contextHandler = function(e) {
|
var contextHandler = function(e) {
|
||||||
if(egwIsMobile())
|
|
||||||
{
|
|
||||||
if (e.originalEvent.which == 3)
|
|
||||||
{
|
|
||||||
// Enable onhold trigger till we define a better handler for tree contextmenu
|
|
||||||
// return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Obtain the event object
|
//Obtain the event object
|
||||||
if (!e)
|
if (!e)
|
||||||
@ -327,7 +349,7 @@ export function egwPopupActionImplementation()
|
|||||||
};
|
};
|
||||||
// Safari still needs the taphold to trigger contextmenu
|
// Safari still needs the taphold to trigger contextmenu
|
||||||
// Chrome has default event on touch and hold which acts like right click
|
// Chrome has default event on touch and hold which acts like right click
|
||||||
jQuery(_node).bind('taphold', contextHandler);
|
this._handleTapHold(_node, contextHandler);
|
||||||
jQuery(_node).on('contextmenu', contextHandler);
|
jQuery(_node).on('contextmenu', contextHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ import {et2_template} from "./et2_widget_template";
|
|||||||
import {egw} from "../jsapi/egw_global";
|
import {egw} from "../jsapi/egw_global";
|
||||||
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
|
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
|
||||||
import {egwIsMobile} from "../egw_action/egw_action_common.js";
|
import {egwIsMobile} from "../egw_action/egw_action_common.js";
|
||||||
|
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';
|
||||||
|
|
||||||
//import {et2_selectAccount} from "./et2_widget_SelectAccount";
|
//import {et2_selectAccount} from "./et2_widget_SelectAccount";
|
||||||
|
|
||||||
@ -2078,30 +2079,15 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
|
|||||||
self.selectPopup = null;
|
self.selectPopup = null;
|
||||||
};
|
};
|
||||||
const $select = jQuery(select.getDOMNode());
|
const $select = jQuery(select.getDOMNode());
|
||||||
$select.find('.ui-multiselect-checkboxes').sortable({
|
|
||||||
placeholder:'ui-fav-sortable-placeholder',
|
let sortablejs = Sortable.create(select.getDOMNode().getElementsByClassName('ui-multiselect-checkboxes')[0], {
|
||||||
items:'li[class^="selcolumn_sortable_col"]',
|
ghostClass: 'ui-fav-sortable-placeholder',
|
||||||
cancel: 'li[class^="selcolumn_sortable_#"]',
|
draggable: 'li[class^="selcolumn_sortable_col"]',
|
||||||
cursor: "move",
|
filter: 'li[class^="selcolumn_sortable_#"]',
|
||||||
tolerance: "pointer",
|
direction: 'vertical',
|
||||||
axis: 'y',
|
delay: 25,
|
||||||
containment: "parent",
|
|
||||||
delay: 250, //(millisecond) delay before the sorting should start
|
|
||||||
beforeStop: function(event, ui) {
|
|
||||||
jQuery('li[class^="selcolumn_sortable_#"]', this).css({
|
|
||||||
opacity: 1
|
|
||||||
});
|
|
||||||
},
|
|
||||||
start: function(event, ui){
|
|
||||||
jQuery('li[class^="selcolumn_sortable_#"]', this).css({
|
|
||||||
opacity: 0.5
|
|
||||||
});
|
|
||||||
},
|
|
||||||
sort: function (event, ui)
|
|
||||||
{
|
|
||||||
jQuery( this ).sortable("refreshPositions" );
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$select.disableSelection();
|
$select.disableSelection();
|
||||||
$select.find('li[class^="selcolumn_sortable_"]').each(function(i,v){
|
$select.find('li[class^="selcolumn_sortable_"]').each(function(i,v){
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
/*egw:uses
|
/*egw:uses
|
||||||
/vendor/bower-asset/jquery/dist/jquery.js;
|
/vendor/bower-asset/jquery/dist/jquery.js;
|
||||||
/vendor/bower-asset/jquery-ui/jquery-ui.js;
|
|
||||||
et2_core_inputWidget;
|
et2_core_inputWidget;
|
||||||
et2_core_valueWidget;
|
et2_core_valueWidget;
|
||||||
*/
|
*/
|
||||||
|
@ -19,6 +19,7 @@ import {et2_INextmatchHeader} from "./et2_extension_nextmatch";
|
|||||||
import {et2_dropdown_button} from "./et2_widget_dropdown_button";
|
import {et2_dropdown_button} from "./et2_widget_dropdown_button";
|
||||||
import {ClassWithAttributes} from "./et2_core_inheritance";
|
import {ClassWithAttributes} from "./et2_core_inheritance";
|
||||||
import {egw, egw_getFramework} from "../jsapi/egw_global";
|
import {egw, egw_getFramework} from "../jsapi/egw_global";
|
||||||
|
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Favorites widget, designed for use with a nextmatch widget
|
* Favorites widget, designed for use with a nextmatch widget
|
||||||
@ -177,18 +178,19 @@ export class et2_favorites extends et2_dropdown_button implements et2_INextmatch
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//Add Sortable handler to nm fav. menu
|
/**
|
||||||
jQuery(this.menu).sortable({
|
* todo (@todo-jquery-ui): the sorting does not work at the moment becuase of jquery-ui menu being used in order to create dropdown
|
||||||
|
* buttons menu. Once we replace the et2_widget_dropdown_button with web component this should be adapted
|
||||||
items:'li:not([data-id$="add"])',
|
* and working again.
|
||||||
placeholder:'ui-fav-sortable-placeholder',
|
**/
|
||||||
delay: 250, //(millisecond) delay before the sorting should start
|
let sortablejs = Sortable.create(this.menu[0], {
|
||||||
update: function ()
|
ghostClass: 'ui-fav-sortable-placeholder',
|
||||||
{
|
draggable: 'li:not([data-id$="add"])',
|
||||||
self.favSortedList = jQuery(this).sortable('toArray', {attribute:'data-id'});
|
delay: 25,
|
||||||
|
dataIdAttr:'data-id',
|
||||||
self.egw().set_preference(self.options.app,'fav_sort_pref',self.favSortedList);
|
onSort: function(event){
|
||||||
|
self.favSortedList = sortablejs.toArray();
|
||||||
|
self.egw.set_preference(self.options.app,'fav_sort_pref', self.favSortedList );
|
||||||
sideBoxDOMNodeSort(self.favSortedList);
|
sideBoxDOMNodeSort(self.favSortedList);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -23,6 +23,7 @@ import {et2_action_object_impl, et2_DOMWidget} from "./et2_core_DOMWidget";
|
|||||||
import {egw_getAppObjectManager, egwActionObject} from '../egw_action/egw_action.js';
|
import {egw_getAppObjectManager, egwActionObject} from '../egw_action/egw_action.js';
|
||||||
import {et2_directChildrenByTagName, et2_filteredNodeIterator, et2_readAttrWithDefault} from "./et2_core_xml";
|
import {et2_directChildrenByTagName, et2_filteredNodeIterator, et2_readAttrWithDefault} from "./et2_core_xml";
|
||||||
import {egw} from "../jsapi/egw_global";
|
import {egw} from "../jsapi/egw_global";
|
||||||
|
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,6 +118,7 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
|
|||||||
private wrapper = null;
|
private wrapper = null;
|
||||||
private lastRowNode: null;
|
private lastRowNode: null;
|
||||||
|
|
||||||
|
private sortablejs : Sortable = null;
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -943,55 +945,49 @@ export class et2_grid extends et2_DOMWidget implements et2_IDetachedDOM, et2_IAl
|
|||||||
*/
|
*/
|
||||||
set_sortable(sortable: boolean | Function)
|
set_sortable(sortable: boolean | Function)
|
||||||
{
|
{
|
||||||
const $node = jQuery(this.getDOMNode());
|
const self = this;
|
||||||
if(!sortable)
|
let tbody = this.getDOMNode().getElementsByTagName('tbody')[0];
|
||||||
|
|
||||||
|
if(!sortable && this.sortablejs)
|
||||||
{
|
{
|
||||||
$node.sortable("destroy");
|
this.sortablejs.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure rows have IDs, so sortable has something to return
|
for (let i =0; i < tbody.children.length; i++)
|
||||||
jQuery('tr', this.tbody).each(function(index) {
|
{
|
||||||
const $this = jQuery(this);
|
if (!tbody.children[i].classList.contains('th') && !tbody.children[i].id)
|
||||||
|
{
|
||||||
|
tbody.children[i].setAttribute('id', i.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Header does not participate in sorting
|
this.sortablejs = new Sortable(tbody,{
|
||||||
if($this.hasClass('th')) return;
|
group: this.options.sortable_connectWith,
|
||||||
|
draggable: "tr:not(.th)",
|
||||||
// If row doesn't have an ID, assign the index as ID
|
filter: this.options.sortable_cancel,
|
||||||
if(!$this.attr("id")) $this.attr("id", index);
|
ghostClass: this.options.sortable_placeholder,
|
||||||
});
|
dataIdAttr: 'id',
|
||||||
|
onAdd:function (event) {
|
||||||
const self = this;
|
if (typeof self.options.sortable_recieveCallback == 'function') {
|
||||||
|
self.options.sortable_recieveCallback.call(self, event, this, self.id);
|
||||||
// Set up sortable
|
}
|
||||||
$node.sortable({
|
},
|
||||||
// Header does not participate in sorting
|
onStart: function (event, ui) {
|
||||||
items: "> tbody > tr:not(.th)",
|
if (typeof self.options.sortable_startCallback == 'function') {
|
||||||
distance: 15,
|
self.options.sortable_startCallback.call(self, event, this, self.id);
|
||||||
cancel: this.options.sortable_cancel,
|
}
|
||||||
placeholder: this.options.sortable_placeholder,
|
},
|
||||||
containment: this.options.sortable_containment,
|
onSort: function (event) {
|
||||||
connectWith: this.options.sortable_connectWith,
|
|
||||||
update: function(event, ui) {
|
|
||||||
self.egw().json(sortable,[
|
self.egw().json(sortable,[
|
||||||
self.getInstanceManager().etemplate_exec_id,
|
self.getInstanceManager().etemplate_exec_id,
|
||||||
$node.sortable("toArray"),
|
self.sortablejs.toArray(),
|
||||||
self.id],
|
self.id],
|
||||||
null,
|
null,
|
||||||
self,
|
self,
|
||||||
true
|
true
|
||||||
).sendRequest();
|
).sendRequest();
|
||||||
},
|
},
|
||||||
receive: function (event, ui) {
|
|
||||||
if (typeof self.sortable_recieveCallback == 'function') {
|
|
||||||
self.sortable_recieveCallback.call(self, event, ui, self.id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
start: function (event, ui) {
|
|
||||||
if (typeof self.options.sortable_startCallback == 'function') {
|
|
||||||
self.options.sortable_startCallback.call(self, event, ui, self.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -638,7 +638,7 @@ export class et2_taglist extends et2_selectbox implements et2_IResizeable
|
|||||||
});
|
});
|
||||||
|
|
||||||
// if value has already been set, re-set it by it's id(s)
|
// if value has already been set, re-set it by it's id(s)
|
||||||
if (this.options.select_options.length && this.options.value.length) {
|
if (this.options.select_options.length && this.options.value?.length) {
|
||||||
this.set_value(this.options.value.map((v) => v.id));
|
this.set_value(this.options.value.map((v) => v.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import './fw_browser.js';
|
|||||||
import './fw_ui.js';
|
import './fw_ui.js';
|
||||||
import './fw_classes.js';
|
import './fw_classes.js';
|
||||||
import '../jsapi/egw_inheritance.js';
|
import '../jsapi/egw_inheritance.js';
|
||||||
|
import "sortablejs/Sortable.min.js";
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DOMWindow} window
|
* @param {DOMWindow} window
|
||||||
@ -48,31 +48,16 @@ import '../jsapi/egw_inheritance.js';
|
|||||||
init: function()
|
init: function()
|
||||||
{
|
{
|
||||||
this._super.apply(this,arguments);
|
this._super.apply(this,arguments);
|
||||||
|
let self = this;
|
||||||
this.setBottomLine(this.parent.entries);
|
this.setBottomLine(this.parent.entries);
|
||||||
//Make the base Div sortable. Set all elements with the style "egw_fw_ui_sidemenu_entry_header"
|
|
||||||
//as handle
|
this.elemDiv.classList.add('ui-sortable')
|
||||||
if(jQuery(this.elemDiv).data('uiSortable'))
|
Sortable.create(this.elemDiv,{
|
||||||
{
|
onSort: function (evt) {
|
||||||
jQuery(this.elemDiv).sortable("destroy");
|
self.parent.isDraged = true;
|
||||||
}
|
self.parent.refreshSort();
|
||||||
jQuery(this.elemDiv).sortable({
|
|
||||||
handle: ".egw_fw_ui_sidemenu_entry_header",
|
|
||||||
distance: 15,
|
|
||||||
start: function(event, ui)
|
|
||||||
{
|
|
||||||
var parent = ui.item.context._parent;
|
|
||||||
parent.isDraged = true;
|
|
||||||
parent.parent.startDrag.call(parent.parent);
|
|
||||||
},
|
},
|
||||||
stop: function(event, ui)
|
direction: 'vertical'
|
||||||
{
|
|
||||||
var parent = ui.item.context._parent;
|
|
||||||
parent.parent.stopDrag.call(parent.parent);
|
|
||||||
parent.parent.refreshSort.call(parent.parent);
|
|
||||||
},
|
|
||||||
opacity: 0.7,
|
|
||||||
axis: 'y'
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -107,31 +92,6 @@ import '../jsapi/egw_inheritance.js';
|
|||||||
this._super.apply(this,arguments);
|
this._super.apply(this,arguments);
|
||||||
this.sortCallback = _sortCallback;
|
this.sortCallback = _sortCallback;
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
startDrag: function()
|
|
||||||
{
|
|
||||||
if (this.activeEntry)
|
|
||||||
{
|
|
||||||
jQuery(this.activeEntry.marker).show();
|
|
||||||
jQuery(this.elemDiv).sortable("refresh");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {undefined}
|
|
||||||
*/
|
|
||||||
stopDrag: function()
|
|
||||||
{
|
|
||||||
if (this.activeEntry)
|
|
||||||
{
|
|
||||||
jQuery(this.activeEntry.marker).hide();
|
|
||||||
jQuery(this.elemDiv).sortable("refresh");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the sidemenu elements whenever they were sorted. An array containing
|
* Called by the sidemenu elements whenever they were sorted. An array containing
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
jQuery - Tap and Hold
|
|
||||||
=====================
|
|
||||||
|
|
||||||
This jQuery plugin lets you detect a tap and hold event on touch interfaces.
|
|
||||||
|
|
||||||
How to use it?
|
|
||||||
|
|
||||||
1) Add the jQuery Tap and Hold plugin into your HTML
|
|
||||||
|
|
||||||
<script src="jquery.tapandhold.js" type="text/javascript"></script>
|
|
||||||
|
|
||||||
2) Bind a tap and hold handler function to the tap and hold event of an element.
|
|
||||||
|
|
||||||
$("#myDiv").bind("taphold", function(event){
|
|
||||||
alert("This is a tap and hold!");
|
|
||||||
});
|
|
||||||
|
|
||||||
You can check a working example in examples/example1.html
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>jQuery - Tap and Hold</title>
|
|
||||||
<script src="http://code.jquery.com/jquery-1.7.min.js" type="text/javascript"></script>
|
|
||||||
<script src="https://raw.github.com/zaubersoftware/jquery-tap-and-hold/master/jquery.tapandhold.js" type="text/javascript"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function(){
|
|
||||||
$(".tap").bind("taphold", function(){
|
|
||||||
alert("Hello Tap and Hold World!");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="tap" style="width:200px; height:200px; background-color: blue; margin: 100px 300px;"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,136 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2011 Zauber S.A. <http://www.zaubersoftware.com/>
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
* @author Guido Marucci Blas - guido@zaubersoftware.com
|
|
||||||
* @description Adds a handler for a custom event 'taphold' that handles a
|
|
||||||
* tap and hold on touch interfaces.
|
|
||||||
*/
|
|
||||||
(function($) {
|
|
||||||
var TAP_AND_HOLD_TRIGGER_TIMER = 600;
|
|
||||||
var MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT = 40;
|
|
||||||
var TOUCHSTART = "touchstart";
|
|
||||||
var TOUCHEND = "touchend";
|
|
||||||
var TOUCHMOVE = "touchmove";
|
|
||||||
|
|
||||||
// For debugging only
|
|
||||||
// var TOUCHSTART = "mousedown";
|
|
||||||
// var TOUCHEND = "mouseup";
|
|
||||||
// var TOUCHMOVE = "mousemove";
|
|
||||||
|
|
||||||
var tapAndHoldTimer = null;
|
|
||||||
|
|
||||||
function calculateEuclideanDistance(x1, y1, x2, y2) {
|
|
||||||
var diffX = (x2 - x1);
|
|
||||||
var diffY = (y2 - y1);
|
|
||||||
return Math.sqrt((diffX * diffX) + (diffY * diffY));
|
|
||||||
};
|
|
||||||
|
|
||||||
function onTouchStart(event) {
|
|
||||||
var e = event.originalEvent;
|
|
||||||
|
|
||||||
// Only start detector if and only if one finger is over the widget
|
|
||||||
if (!e.touches || (e.targetTouches.length === 1 && e.touches.length === 1)) {
|
|
||||||
startTapAndHoldDetector.call(this, event)
|
|
||||||
var element = $(this);
|
|
||||||
element.bind(TOUCHMOVE, onTouchMove);
|
|
||||||
element.bind(TOUCHEND, onTouchEnd);
|
|
||||||
} else {
|
|
||||||
stopTapAndHoldDetector.call(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function onTouchMove(event) {
|
|
||||||
if (tapAndHoldTimer == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var e = event.originalEvent;
|
|
||||||
var x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
|
|
||||||
var y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
|
|
||||||
|
|
||||||
var tapAndHoldPoint = $(this).data("taphold.point");
|
|
||||||
var euclideanDistance = calculateEuclideanDistance(tapAndHoldPoint.x, tapAndHoldPoint.y, x, y);
|
|
||||||
|
|
||||||
if (euclideanDistance > MAX_DISTANCE_ALLOWED_IN_TAP_AND_HOLD_EVENT) {
|
|
||||||
stopTapAndHoldDetector.call(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function onTouchEnd(event) {
|
|
||||||
stopTapAndHoldDetector.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
function onTapAndHold(event) {
|
|
||||||
clear.call(this);
|
|
||||||
$(this).data("taphold.handler").call(this, event);
|
|
||||||
};
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
tapAndHoldTimer = null;
|
|
||||||
$(this).unbind(TOUCHMOVE, onTouchMove);
|
|
||||||
$(this).unbind(TOUCHEND, onTouchEnd);
|
|
||||||
};
|
|
||||||
|
|
||||||
function startTapAndHoldDetector(event) {
|
|
||||||
if (tapAndHoldTimer != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var self = this;
|
|
||||||
tapAndHoldTimer = setTimeout(function(){
|
|
||||||
onTapAndHold.call(self, event)
|
|
||||||
}, TAP_AND_HOLD_TRIGGER_TIMER);
|
|
||||||
|
|
||||||
// Stores tap x & y
|
|
||||||
var e = event.originalEvent;
|
|
||||||
var tapAndHoldPoint = {};
|
|
||||||
tapAndHoldPoint.x = (e.changedTouches) ? e.changedTouches[0].pageX: e.pageX;
|
|
||||||
tapAndHoldPoint.y = (e.changedTouches) ? e.changedTouches[0].pageY: e.pageY;
|
|
||||||
$(this).data("taphold.point", tapAndHoldPoint);
|
|
||||||
};
|
|
||||||
|
|
||||||
function stopTapAndHoldDetector() {
|
|
||||||
clearTimeout(tapAndHoldTimer);
|
|
||||||
clear.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
$.event.special["taphold"] = {
|
|
||||||
setup: function() {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
add: function(handleObj) {
|
|
||||||
$(this).data("taphold.handler", handleObj.handler);
|
|
||||||
if (handleObj.data) {
|
|
||||||
$(this).bind(TOUCHSTART, handleObj.data, onTouchStart);
|
|
||||||
} else {
|
|
||||||
$(this).bind(TOUCHSTART, onTouchStart);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
remove: function(handleObj) {
|
|
||||||
stopTapAndHoldDetector.call(this);
|
|
||||||
if (handleObj.data) {
|
|
||||||
$(this).unbind(TOUCHSTART, handleObj.data, onTouchStart);
|
|
||||||
} else {
|
|
||||||
$(this).unbind(TOUCHSTART, onTouchStart);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
teardown: function() {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(jQuery);
|
|
@ -78,7 +78,7 @@ window.app = {classes: {}};
|
|||||||
window.egw_appName = egw_script.getAttribute('data-app');
|
window.egw_appName = egw_script.getAttribute('data-app');
|
||||||
|
|
||||||
// split includes in legacy js and modules
|
// split includes in legacy js and modules
|
||||||
const legacy_js_regexp = /\/dhtmlx|jquery-ui/;
|
const legacy_js_regexp = /\/dhtmlx|jquery-ui-dist/;
|
||||||
|
|
||||||
// check if egw object was injected by window open
|
// check if egw object was injected by window open
|
||||||
if (typeof window.egw == 'undefined')
|
if (typeof window.egw == 'undefined')
|
||||||
|
@ -17,6 +17,7 @@ import {et2_dialog} from "../etemplate/et2_widget_dialog";
|
|||||||
import {et2_createWidget} from "../etemplate/et2_core_widget";
|
import {et2_createWidget} from "../etemplate/et2_core_widget";
|
||||||
import {et2_favorites} from "../etemplate/et2_widget_favorites";
|
import {et2_favorites} from "../etemplate/et2_widget_favorites";
|
||||||
import type {IegwAppLocal} from "./egw_global";
|
import type {IegwAppLocal} from "./egw_global";
|
||||||
|
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type for push-message
|
* Type for push-message
|
||||||
@ -781,32 +782,18 @@ export abstract class EgwApp
|
|||||||
})
|
})
|
||||||
.addClass("ui-helper-clearfix");
|
.addClass("ui-helper-clearfix");
|
||||||
|
|
||||||
//Add Sortable handler to sideBox fav. menu
|
let el = document.getElementById('favorite_sidebox_'+this.appname).getElementsByTagName('ul')[0];
|
||||||
jQuery('ul','#favorite_sidebox_'+this.appname).sortable({
|
let sortablejs = Sortable.create(el, {
|
||||||
items:'li:not([data-id$="add"])',
|
ghostClass: 'ui-fav-sortable-placeholder',
|
||||||
placeholder:'ui-fav-sortable-placeholder',
|
draggable: 'li:not([data-id$="add"])',
|
||||||
delay:250, //(millisecond) delay before the sorting should start
|
delay: 25,
|
||||||
helper: function(event, item : any) {
|
dataIdAttr:'data-id',
|
||||||
// We'll need to know which app this is for
|
onSort: function(event){
|
||||||
item.attr('data-appname',self.appname);
|
let favSortedList = sortablejs.toArray();
|
||||||
// Create custom helper so it can be dragged to Home
|
self.egw.set_preference(self.appname,'fav_sort_pref',favSortedList);
|
||||||
var h_parent = item.parent().parent().clone();
|
self._refresh_fav_nm();
|
||||||
h_parent.find('li').not('[data-id="'+item.attr('data-id')+'"]').remove();
|
}
|
||||||
h_parent.appendTo('body');
|
});
|
||||||
return h_parent;
|
|
||||||
},
|
|
||||||
// @ts-ignore
|
|
||||||
refreshPositions: true,
|
|
||||||
update: function (event, ui)
|
|
||||||
{
|
|
||||||
// @ts-ignore
|
|
||||||
var favSortedList = jQuery(this).sortable('toArray', {attribute:'data-id'});
|
|
||||||
|
|
||||||
self.egw.set_preference(self.appname,'fav_sort_pref',favSortedList);
|
|
||||||
|
|
||||||
self._refresh_fav_nm();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Bind favorite de-select
|
// Bind favorite de-select
|
||||||
var egw_fw = egw_getFramework();
|
var egw_fw = egw_getFramework();
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*egw:uses
|
/*egw:uses
|
||||||
/vendor/bower-asset/jquery-ui/jquery-ui.js;
|
|
||||||
jquery.jquery-ui-timepicker-addon;
|
jquery.jquery-ui-timepicker-addon;
|
||||||
|
|
||||||
egw_core;
|
egw_core;
|
||||||
@ -19,8 +18,6 @@
|
|||||||
egw_css;
|
egw_css;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "../../../vendor/bower-asset/jquery/dist/jquery.min.js";
|
|
||||||
//import "../../../vendor/bower-asset/jquery-ui/jquery-ui.js";
|
|
||||||
import "../jquery/jquery.noconflict.js";
|
import "../jquery/jquery.noconflict.js";
|
||||||
//import "../jquery/jquery-ui-timepicker-addon.js";
|
//import "../jquery/jquery-ui-timepicker-addon.js";
|
||||||
import './egw_core.js';
|
import './egw_core.js';
|
||||||
|
@ -20,6 +20,13 @@ egw.extend('jsonq', egw.MODULE_GLOBAL, function()
|
|||||||
{
|
{
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicit registered push callbacks
|
||||||
|
*
|
||||||
|
* @type {Function[]}
|
||||||
|
*/
|
||||||
|
let push_callbacks = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queued json requests (objects with attributes menuaction, parameters, context, callback, sender and callbeforesend)
|
* Queued json requests (objects with attributes menuaction, parameters, context, callback, sender and callbeforesend)
|
||||||
*
|
*
|
||||||
@ -149,6 +156,34 @@ egw.extend('jsonq', egw.MODULE_GLOBAL, function()
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
return uid;
|
return uid;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback to receive push broadcasts eg. in a popup or iframe
|
||||||
|
*
|
||||||
|
* It's also used internally by egw_message's push method to dispatch to the registered callbacks.
|
||||||
|
*
|
||||||
|
* @param {Function|PushData} data callback (with bound context) or PushData to dispatch to callbacks
|
||||||
|
*/
|
||||||
|
registerPush: function(data)
|
||||||
|
{
|
||||||
|
if (typeof data === "function")
|
||||||
|
{
|
||||||
|
push_callbacks.push(data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (let n in push_callbacks)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
push_callbacks[n].call(this, data);
|
||||||
|
}
|
||||||
|
// if we get an exception, we assume the callback is no longer available and remove it
|
||||||
|
catch (ex) {
|
||||||
|
push_callbacks.splice(n, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -459,6 +459,9 @@ egw.extend('message', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
|||||||
app_obj.push(pushData);
|
app_obj.push(pushData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// call the global registered push callbacks
|
||||||
|
this.registerPush(pushData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ $setup_info['api']['title'] = 'EGroupware API';
|
|||||||
$setup_info['api']['version'] = '21.1';
|
$setup_info['api']['version'] = '21.1';
|
||||||
$setup_info['api']['versions']['current_header'] = '1.29';
|
$setup_info['api']['versions']['current_header'] = '1.29';
|
||||||
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
|
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
|
||||||
$setup_info['api']['versions']['maintenance_release'] = '21.1.20210629';
|
$setup_info['api']['versions']['maintenance_release'] = '21.1.20210723';
|
||||||
$setup_info['api']['enable'] = 3;
|
$setup_info['api']['enable'] = 3;
|
||||||
$setup_info['api']['app_order'] = 1;
|
$setup_info['api']['app_order'] = 1;
|
||||||
$setup_info['api']['license'] = 'GPL';
|
$setup_info['api']['license'] = 'GPL';
|
||||||
|
@ -425,7 +425,7 @@ class Asyncservice
|
|||||||
{
|
{
|
||||||
// checking / setting up egw_info/user
|
// checking / setting up egw_info/user
|
||||||
//
|
//
|
||||||
if ($GLOBALS['egw_info']['user']['account_id'] != $job['account_id'])
|
//if ($GLOBALS['egw_info']['user']['account_id'] != $job['account_id'])
|
||||||
{
|
{
|
||||||
// run notifications, before changing account_id of enviroment
|
// run notifications, before changing account_id of enviroment
|
||||||
Link::run_notifies();
|
Link::run_notifies();
|
||||||
|
@ -71,15 +71,18 @@ class Applications
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* populate array with a list of installed apps
|
* Populate array with a list of installed apps
|
||||||
*
|
*
|
||||||
|
* egw_applications.app_enabled = -1 is NOT installed, but an uninstalled autoinstall app!
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
*/
|
*/
|
||||||
function read_installed_apps()
|
function read_installed_apps()
|
||||||
{
|
{
|
||||||
$GLOBALS['egw_info']['apps'] = Api\Cache::getInstance(__CLASS__, 'apps', function()
|
$GLOBALS['egw_info']['apps'] = Api\Cache::getInstance(__CLASS__, 'apps', function()
|
||||||
{
|
{
|
||||||
$apps = array();
|
$apps = array();
|
||||||
foreach($this->db->select($this->table_name,'*',false,__LINE__,__FILE__,false,'ORDER BY app_order ASC') as $row)
|
foreach($this->db->select($this->table_name,'*', ['app_enabled != -1'],__LINE__,__FILE__,false,'ORDER BY app_order ASC') as $row)
|
||||||
{
|
{
|
||||||
$apps[$row['app_name']] = Array(
|
$apps[$row['app_name']] = Array(
|
||||||
'title' => $row['app_name'],
|
'title' => $row['app_name'],
|
||||||
|
@ -1003,7 +1003,7 @@ abstract class Framework extends Framework\Extra
|
|||||||
self::includeCSS('/api/js/jquery/chosen/chosen.css');
|
self::includeCSS('/api/js/jquery/chosen/chosen.css');
|
||||||
|
|
||||||
// eTemplate2 uses jQueryUI, so load it first so et2 can override if needed
|
// eTemplate2 uses jQueryUI, so load it first so et2 can override if needed
|
||||||
self::includeCSS("/vendor/bower-asset/jquery-ui/themes/redmond/jquery-ui.css");
|
self::includeCSS("/node_modules/jquery-ui-themes/themes/redmond/jquery-ui.css");
|
||||||
|
|
||||||
// eTemplate2 - load in top so sidebox has styles too
|
// eTemplate2 - load in top so sidebox has styles too
|
||||||
self::includeCSS('/api/templates/default/etemplate2.css');
|
self::includeCSS('/api/templates/default/etemplate2.css');
|
||||||
@ -1074,8 +1074,8 @@ abstract class Framework extends Framework\Extra
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
// manually load old legacy javascript dhtmlx & jQuery-UI via script tag
|
// manually load old legacy javascript dhtmlx & jQuery-UI via script tag
|
||||||
self::includeJS('/vendor/bower-asset/jquery-ui/jquery-ui.js');
|
self::includeJS('/node_modules/jquery-ui-dist/jquery-ui.min.js');
|
||||||
self::includeJS('/api/js/jquery/jquery-ui-timepicker-addon.js');
|
self::includeJS('/node_modules/jquery-ui-timepicker-addon/dist/jquery-ui-timepicker-addon.min.js');
|
||||||
self::includeJS('/api/js/dhtmlxtree/codebase/dhtmlxcommon.js');
|
self::includeJS('/api/js/dhtmlxtree/codebase/dhtmlxcommon.js');
|
||||||
self::includeJS('/api/js/dhtmlxMenu/sources/dhtmlxmenu.js');
|
self::includeJS('/api/js/dhtmlxMenu/sources/dhtmlxmenu.js');
|
||||||
self::includeJS('/api/js/dhtmlxMenu/sources/ext/dhtmlxmenu_ext.js');
|
self::includeJS('/api/js/dhtmlxMenu/sources/ext/dhtmlxmenu_ext.js');
|
||||||
|
@ -241,7 +241,7 @@ class Bundle
|
|||||||
// generate api bundle
|
// generate api bundle
|
||||||
$inc_mgr->include_js_file('/vendor/bower-asset/jquery/dist/jquery.js');
|
$inc_mgr->include_js_file('/vendor/bower-asset/jquery/dist/jquery.js');
|
||||||
$inc_mgr->include_js_file('/api/js/jquery/jquery.noconflict.js');
|
$inc_mgr->include_js_file('/api/js/jquery/jquery.noconflict.js');
|
||||||
$inc_mgr->include_js_file('/vendor/bower-asset/jquery-ui/jquery-ui.js');
|
$inc_mgr->include_js_file('/node_modules/jquery-ui-dist/jquery-ui.min.js');
|
||||||
$inc_mgr->include_js_file('/api/js/jsapi/jsapi.js');
|
$inc_mgr->include_js_file('/api/js/jsapi/jsapi.js');
|
||||||
$inc_mgr->include_js_file('/api/js/egw_json.js');
|
$inc_mgr->include_js_file('/api/js/egw_json.js');
|
||||||
$inc_mgr->include_js_file('/api/js/jsapi/egw.js');
|
$inc_mgr->include_js_file('/api/js/jsapi/egw.js');
|
||||||
|
@ -2333,14 +2333,19 @@ abstract class Merge
|
|||||||
/**
|
/**
|
||||||
* Merge the selected IDs into the given document, save it to the VFS, then
|
* Merge the selected IDs into the given document, save it to the VFS, then
|
||||||
* either open it in the editor or have the browser download the file.
|
* either open it in the editor or have the browser download the file.
|
||||||
|
*
|
||||||
|
* @param String[]|null $ids Allows extending classes to process IDs in their own way. Leave null to pull from request.
|
||||||
|
* @param Merge|null $document_merge Already instantiated Merge object to do the merge.
|
||||||
|
* @throws Api\Exception
|
||||||
|
* @throws Api\Exception\AssertionFailed
|
||||||
*/
|
*/
|
||||||
public static function merge_entries()
|
public static function merge_entries(array $ids = null, Merge &$document_merge = null)
|
||||||
{
|
{
|
||||||
if (class_exists($_REQUEST['merge']) && is_subclass_of($_REQUEST['merge'], 'EGroupware\\Api\\Storage\\Merge'))
|
if (is_null($document_merge) && class_exists($_REQUEST['merge']) && is_subclass_of($_REQUEST['merge'], 'EGroupware\\Api\\Storage\\Merge'))
|
||||||
{
|
{
|
||||||
$document_merge = new $_REQUEST['merge']();
|
$document_merge = new $_REQUEST['merge']();
|
||||||
}
|
}
|
||||||
else
|
elseif (is_null($document_merge))
|
||||||
{
|
{
|
||||||
$document_merge = new Api\Contacts\Merge();
|
$document_merge = new Api\Contacts\Merge();
|
||||||
}
|
}
|
||||||
@ -2351,13 +2356,16 @@ abstract class Merge
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = is_string($_REQUEST['id']) && strpos($_REQUEST['id'],'[') === FALSE ? explode(',',$_REQUEST['id']) : json_decode($_REQUEST['id'],true);
|
if(is_null(($ids)))
|
||||||
|
{
|
||||||
|
$ids = is_string($_REQUEST['id']) && strpos($_REQUEST['id'], '[') === FALSE ? explode(',', $_REQUEST['id']) : json_decode($_REQUEST['id'], true);
|
||||||
|
}
|
||||||
if($_REQUEST['select_all'] === 'true')
|
if($_REQUEST['select_all'] === 'true')
|
||||||
{
|
{
|
||||||
$ids = self::get_all_ids($document_merge);
|
$ids = self::get_all_ids($document_merge);
|
||||||
}
|
}
|
||||||
|
|
||||||
$filename = '';
|
$filename = $document_merge->get_filename($_REQUEST['document']);
|
||||||
$result = $document_merge->merge_file($_REQUEST['document'], $ids, $filename, '', $header);
|
$result = $document_merge->merge_file($_REQUEST['document'], $ids, $filename, '', $header);
|
||||||
|
|
||||||
if(!is_file($result) || !is_readable($result))
|
if(!is_file($result) || !is_readable($result))
|
||||||
@ -2412,6 +2420,17 @@ abstract class Merge
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a filename for the merged file
|
||||||
|
*
|
||||||
|
* Default is just the name of the template
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function get_filename($document) : string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all ids for when they try to do 'Select All', then merge into document
|
* Get all ids for when they try to do 'Select All', then merge into document
|
||||||
*
|
*
|
||||||
|
@ -82,7 +82,10 @@ class Vfs extends Vfs\Base
|
|||||||
*/
|
*/
|
||||||
static $is_root = false;
|
static $is_root = false;
|
||||||
/**
|
/**
|
||||||
* Current user id, in case we ever change if away from $GLOBALS['egw_info']['user']['account_id']
|
* Current Vfs user id, set from $GLOBALS['egw_info']['user']['account_id'] by self::init_static()
|
||||||
|
*
|
||||||
|
* Should be protected and moved to Vfs\Base plus a getter and setter method added for public access,
|
||||||
|
* as after setting it in 21.1+, Api\Vfs\StreamWrapper::init_static() need to be called to set the default user context!
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
@ -749,22 +752,22 @@ class Vfs extends Vfs\Base
|
|||||||
* @return boolean
|
* @return boolean
|
||||||
* @todo deprecated or even remove $user parameter and code
|
* @todo deprecated or even remove $user parameter and code
|
||||||
*/
|
*/
|
||||||
static function check_access($path, $check, $stat=null, $user=null)
|
static function check_access($path, $check, $stat=null, int $user=null)
|
||||||
{
|
{
|
||||||
static $vfs = null;
|
static $vfs = null;
|
||||||
|
|
||||||
if (is_null($stat) && $user && $user != self::$user)
|
if (is_null($stat) && $user && $user !== self::$user)
|
||||||
{
|
{
|
||||||
static $path_user_stat = array();
|
static $path_user_stat = array();
|
||||||
|
|
||||||
$backup_user = self::$user;
|
$backup_user = self::$user;
|
||||||
self::$user = $user;
|
self::$user = $user;
|
||||||
|
Vfs\StreamWrapper::init_static();
|
||||||
|
self::clearstatcache($path);
|
||||||
|
|
||||||
if (!isset($path_user_stat[$path]) || !isset($path_user_stat[$path][$user]))
|
if (!isset($path_user_stat[$path]) || !isset($path_user_stat[$path][$user]))
|
||||||
{
|
{
|
||||||
self::clearstatcache($path);
|
$vfs = new Vfs\StreamWrapper();
|
||||||
|
|
||||||
if (!isset($vfs)) $vfs = new Vfs\StreamWrapper();
|
|
||||||
$path_user_stat[$path][$user] = $vfs->url_stat($path, 0);
|
$path_user_stat[$path][$user] = $vfs->url_stat($path, 0);
|
||||||
|
|
||||||
self::clearstatcache($path); // we need to clear the stat-cache after the call too, as the next call might be the regular user again!
|
self::clearstatcache($path); // we need to clear the stat-cache after the call too, as the next call might be the regular user again!
|
||||||
@ -786,6 +789,8 @@ class Vfs extends Vfs\Base
|
|||||||
$ret = false; // no access, if we can not stat the file
|
$ret = false; // no access, if we can not stat the file
|
||||||
}
|
}
|
||||||
self::$user = $backup_user;
|
self::$user = $backup_user;
|
||||||
|
Vfs\StreamWrapper::init_static();
|
||||||
|
$vfs = null;
|
||||||
|
|
||||||
// we need to clear stat-cache again, after restoring original user, as eg. eACL is stored in session
|
// we need to clear stat-cache again, after restoring original user, as eg. eACL is stored in session
|
||||||
self::clearstatcache($path);
|
self::clearstatcache($path);
|
||||||
|
@ -254,10 +254,11 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
|||||||
{
|
{
|
||||||
$umaskbefore = umask();
|
$umaskbefore = umask();
|
||||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__." about to call mkdir for $fs_dir # Present UMASK:".decoct($umaskbefore)." called from:".function_backtrace());
|
if (self::LOG_LEVEL > 1) error_log(__METHOD__." about to call mkdir for $fs_dir # Present UMASK:".decoct($umaskbefore)." called from:".function_backtrace());
|
||||||
self::mkdir_recursive($fs_dir,0700,true);
|
// if running as root eg. via (docker exec) filemanager/cli.php do NOT create dirs not readable by webserver
|
||||||
|
self::mkdir_recursive($fs_dir,function_exists('posix_getuid') && !posix_getuid() ? 0777 : 0700,true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if opend file is a directory
|
// check if opened file is a directory
|
||||||
elseif($stat && ($stat['mode'] & self::MODE_DIR) == self::MODE_DIR)
|
elseif($stat && ($stat['mode'] & self::MODE_DIR) == self::MODE_DIR)
|
||||||
{
|
{
|
||||||
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) Is a directory!");
|
if (self::LOG_LEVEL) error_log(__METHOD__."($url,$mode,$options) Is a directory!");
|
||||||
@ -308,6 +309,11 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
|
|||||||
if ($this->operation == self::STORE2FS)
|
if ($this->operation == self::STORE2FS)
|
||||||
{
|
{
|
||||||
if (self::LOG_LEVEL > 1) error_log(__METHOD__." fopen (may create a directory? mkdir) ($this->opened_fs_id,$mode,$options)");
|
if (self::LOG_LEVEL > 1) error_log(__METHOD__." fopen (may create a directory? mkdir) ($this->opened_fs_id,$mode,$options)");
|
||||||
|
// if creating a new file as root eg. via (docker exec) filemanager/cli.php do NOT create files unreadable by webserver
|
||||||
|
if ($new_file && function_exists('posix_getuid') && !posix_getuid())
|
||||||
|
{
|
||||||
|
umask(0666);
|
||||||
|
}
|
||||||
if (!($this->opened_stream = fopen(self::_fs_path($this->opened_fs_id),$mode)) && $new_file)
|
if (!($this->opened_stream = fopen(self::_fs_path($this->opened_fs_id),$mode)) && $new_file)
|
||||||
{
|
{
|
||||||
// delete db entry again, if we are not able to open a new(!) file
|
// delete db entry again, if we are not able to open a new(!) file
|
||||||
|
@ -972,6 +972,7 @@ class StreamWrapper extends Base implements StreamWrapperIface
|
|||||||
/**
|
/**
|
||||||
* Init our static properties and register this wrapper
|
* Init our static properties and register this wrapper
|
||||||
*
|
*
|
||||||
|
* Must be called when Vfs::$user is changed!
|
||||||
*/
|
*/
|
||||||
static function init_static()
|
static function init_static()
|
||||||
{
|
{
|
||||||
@ -984,16 +985,31 @@ class StreamWrapper extends Base implements StreamWrapperIface
|
|||||||
{
|
{
|
||||||
self::$fstab = $fstab;
|
self::$fstab = $fstab;
|
||||||
}
|
}
|
||||||
if (!empty($GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab']) &&
|
|
||||||
is_array($GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab']))
|
// get the user Vfs is currently using, might be different from $GLOBALS['egw_info']['user']['account_id']
|
||||||
|
if (!isset(Vfs::$user))
|
||||||
{
|
{
|
||||||
self::$fstab += $GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'];
|
Vfs::init_static();
|
||||||
|
}
|
||||||
|
if (Vfs::$user != $GLOBALS['egw_info']['user']['account_id'])
|
||||||
|
{
|
||||||
|
$prefs = new Api\Preferences(Vfs::$user);
|
||||||
|
$vfs_fstab = $prefs->data['common']['vfs_fstab'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$vfs_fstab = $GLOBALS['egw_info']['user']['preferences']['common']['vfs_fstab'];
|
||||||
|
}
|
||||||
|
if (!empty($vfs_fstab) && is_array($vfs_fstab))
|
||||||
|
{
|
||||||
|
self::$fstab += $vfs_fstab;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set default context for our schema ('vfs') with current user
|
// set default context for our schema ('vfs') with current user
|
||||||
if (!($context = stream_context_get_options(stream_context_get_default())) || empty($context[self::SCHEME]['user']))
|
if (!($context = stream_context_get_options(stream_context_get_default())) || empty($context[self::SCHEME]['user']) ||
|
||||||
|
$context[self::SCHEME]['user'] !== (int)Vfs::$user)
|
||||||
{
|
{
|
||||||
$context[self::SCHEME]['user'] = (int)$GLOBALS['egw_info']['user']['account_id'];
|
$context[self::SCHEME]['user'] = (int)Vfs::$user;
|
||||||
stream_context_set_default($context);
|
stream_context_set_default($context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -85,7 +85,7 @@
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
background-image: url(../../../vendor/bower-asset/jquery-ui/themes/redmond/images/ui-icons_469bdd_256x240.png);
|
background-image: url(../../../node_modules/jquery-ui-themes/themes/redmond/images/ui-icons_469bdd_256x240.png);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
background-image: url(../../../vendor/bower-asset/jquery-ui/themes/redmond/images/ui-icons_469bdd_256x240.png);
|
background-image: url(../../../node_modules/jquery-ui-themes/themes/redmond/images/ui-icons_469bdd_256x240.png);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@
|
|||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
background-image: url(../../../vendor/bower-asset/jquery-ui/themes/redmond/images/ui-icons_469bdd_256x240.png);
|
background-image: url(../../../node_modules/jquery-ui-themes/themes/redmond/images/ui-icons_469bdd_256x240.png);
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,13 @@
|
|||||||
"issuses": "https://my.egroupware.org"
|
"issuses": "https://my.egroupware.org"
|
||||||
},
|
},
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
"type": "pear",
|
"type": "composer",
|
||||||
"url": "https://pear.horde.org"
|
"url": "https://asset-packagist.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "pear",
|
||||||
|
"url": "https://pear.horde.org"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "vcs",
|
"type": "vcs",
|
||||||
@ -51,6 +55,9 @@
|
|||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.3"
|
"php": "7.3"
|
||||||
},
|
},
|
||||||
|
"fxp-asset": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
"sort-packages": true
|
"sort-packages": true
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@ -70,14 +77,12 @@
|
|||||||
"bower-asset/fastclick": "1.0.*",
|
"bower-asset/fastclick": "1.0.*",
|
||||||
"bower-asset/jquery": "^1.12.4",
|
"bower-asset/jquery": "^1.12.4",
|
||||||
"bower-asset/jquery-touchswipe": "1.6.*",
|
"bower-asset/jquery-touchswipe": "1.6.*",
|
||||||
"bower-asset/jquery-ui": "=1.11.2",
|
|
||||||
"egroupware/activesync": "self.version",
|
"egroupware/activesync": "self.version",
|
||||||
"egroupware/adodb-php": "self.version",
|
"egroupware/adodb-php": "self.version",
|
||||||
"egroupware/bookmarks": "self.version",
|
"egroupware/bookmarks": "self.version",
|
||||||
"egroupware/collabora": "self.version",
|
"egroupware/collabora": "self.version",
|
||||||
"egroupware/guzzlestream": "dev-master",
|
"egroupware/guzzlestream": "dev-master",
|
||||||
"egroupware/icalendar": "^2.1.9",
|
"egroupware/icalendar": "^2.1.9",
|
||||||
"egroupware/imap-client": "^2.30.2",
|
|
||||||
"egroupware/magicsuggest": "^2.1",
|
"egroupware/magicsuggest": "^2.1",
|
||||||
"egroupware/news_admin": "self.version",
|
"egroupware/news_admin": "self.version",
|
||||||
"egroupware/openid": "self.version",
|
"egroupware/openid": "self.version",
|
||||||
@ -90,13 +95,13 @@
|
|||||||
"egroupware/tracker": "self.version",
|
"egroupware/tracker": "self.version",
|
||||||
"egroupware/webdav": "dev-master",
|
"egroupware/webdav": "dev-master",
|
||||||
"egroupware/z-push-dev": "^2.5",
|
"egroupware/z-push-dev": "^2.5",
|
||||||
"fxp/composer-asset-plugin": "^1.2.2",
|
|
||||||
"giggsey/libphonenumber-for-php": "^8.12",
|
"giggsey/libphonenumber-for-php": "^8.12",
|
||||||
"npm-asset/as-jqplot": "1.0.*",
|
"npm-asset/as-jqplot": "1.0.*",
|
||||||
"npm-asset/gridster": "0.5.*",
|
"npm-asset/gridster": "0.5.*",
|
||||||
"oomphinc/composer-installers-extender": "^1.1",
|
"oomphinc/composer-installers-extender": "^1.1",
|
||||||
"pear-pear.horde.org/horde_compress": "^2.0.8",
|
"pear-pear.horde.org/horde_compress": "^2.0.8",
|
||||||
"pear-pear.horde.org/horde_crypt": "^2.7.9",
|
"pear-pear.horde.org/horde_crypt": "^2.7.9",
|
||||||
|
"pear-pear.horde.org/horde_imap_client": "^2.30.3",
|
||||||
"pear-pear.horde.org/horde_mail": "^2.1.2",
|
"pear-pear.horde.org/horde_mail": "^2.1.2",
|
||||||
"pear-pear.horde.org/horde_managesieve": "^1.0.2",
|
"pear-pear.horde.org/horde_managesieve": "^1.0.2",
|
||||||
"pear-pear.horde.org/horde_mapi": "^1.0.9",
|
"pear-pear.horde.org/horde_mapi": "^1.0.9",
|
||||||
|
910
composer.lock
generated
910
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -64,7 +64,7 @@ else \
|
|||||||
RESULT=$?; \
|
RESULT=$?; \
|
||||||
fi; \
|
fi; \
|
||||||
rm composer-setup.php; \
|
rm composer-setup.php; \
|
||||||
composer.phar self-update 1.8.6; \
|
composer.phar self-update 1.10.22; \
|
||||||
exit $RESULT' \
|
exit $RESULT' \
|
||||||
# disable certificate checks for LDAP as most LDAP and AD servers have no "valid" cert
|
# disable certificate checks for LDAP as most LDAP and AD servers have no "valid" cert
|
||||||
&& echo "TLS_REQCERT never" >> /etc/ldap/ldap.conf
|
&& echo "TLS_REQCERT never" >> /etc/ldap/ldap.conf
|
||||||
|
@ -69,7 +69,7 @@ fi; \
|
|||||||
rm composer-setup.php; \
|
rm composer-setup.php; \
|
||||||
exit $RESULT' \
|
exit $RESULT' \
|
||||||
# build EGroupware
|
# build EGroupware
|
||||||
&& composer.phar self-update 1.8.6 \
|
&& composer.phar self-update 1.10.22 \
|
||||||
&& cd /usr/share \
|
&& cd /usr/share \
|
||||||
&& [ $PHP_VERSION = "8.0" ] && COMPOSER_EXTRA=--ignore-platform-reqs || true \
|
&& [ $PHP_VERSION = "8.0" ] && COMPOSER_EXTRA=--ignore-platform-reqs || true \
|
||||||
&& composer.phar create-project $COMPOSER_EXTRA --prefer-dist --no-scripts --no-dev egroupware/egroupware:$VERSION \
|
&& composer.phar create-project $COMPOSER_EXTRA --prefer-dist --no-scripts --no-dev egroupware/egroupware:$VERSION \
|
||||||
|
36
doc/ldif2sql.php
Executable file
36
doc/ldif2sql.php
Executable file
@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
if (!$_SERVER['argc'])
|
||||||
|
{
|
||||||
|
echo "cat test.ldif | ".basename($_SERVER['argv'][0])."attr1[, attr2[, ...]]\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$attrs = array_slice($_SERVER['argv'], 1);
|
||||||
|
|
||||||
|
$values = $rows = [];
|
||||||
|
while(!feof(STDIN))
|
||||||
|
{
|
||||||
|
$line = trim(fgets(STDIN));
|
||||||
|
if (empty($line) || $line[0] === '#' ||
|
||||||
|
!preg_match('/^([^:]+): (.*)$/', $line, $matches))
|
||||||
|
{
|
||||||
|
$values = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($matches[1] === 'dn') $values = [];
|
||||||
|
|
||||||
|
$values[$matches[1]] = $matches[2];
|
||||||
|
|
||||||
|
if (count(array_intersect(array_keys($values), $attrs)) === count($attrs))
|
||||||
|
{
|
||||||
|
$cols = [];
|
||||||
|
foreach($attrs as $attr)
|
||||||
|
{
|
||||||
|
$cols[$attr] = "'".addslashes($values[$attr])."'";
|
||||||
|
}
|
||||||
|
$cols = '('.implode(', ', $cols).')';
|
||||||
|
if (!in_array($cols, $rows)) $rows[] = $cols;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo implode(",\n", $rows)."\n";
|
@ -1,3 +1,14 @@
|
|||||||
|
egroupware-docker (21.1.20210723) hardy; urgency=low
|
||||||
|
|
||||||
|
* Security Update: all 21.1 users should upgrade ASAP, 20.1 and below is not affected
|
||||||
|
* Filemanager/VFS: when creating a new file as root eg. via (docker exec) filemanager/cli.php do NOT create files unreadable by webserver
|
||||||
|
* Collabora: Fix editing files in mounted share
|
||||||
|
* Kanban/PostgreSQL: fix installation of example board under PostgreSQL
|
||||||
|
* smallPART/PostgreSQL: fix multiple SQL errors
|
||||||
|
* smallPART/PostgreSQL: fix installation under PostgreSQL
|
||||||
|
|
||||||
|
-- Ralf Becker <rb@egroupware.org> Fri, 23 Jul 2021 08:09:49 +0200
|
||||||
|
|
||||||
egroupware-docker (21.1.20210629) hardy; urgency=low
|
egroupware-docker (21.1.20210629) hardy; urgency=low
|
||||||
|
|
||||||
* Mail/Admin: fix not working mail wizard
|
* Mail/Admin: fix not working mail wizard
|
||||||
|
@ -75,8 +75,8 @@
|
|||||||
</row>
|
</row>
|
||||||
<row class="$row_cont[info_cat] $row_cont[class]" valign="top">
|
<row class="$row_cont[info_cat] $row_cont[class]" valign="top">
|
||||||
<hbox align="center" class="infolog_CompletedClmn">
|
<hbox align="center" class="infolog_CompletedClmn">
|
||||||
<image label="$row_cont[info_type]" src="${row}[info_type]" default_src="infolog/navbar"/>
|
<image label="$row_cont[info_type]" src="infolog/${row}[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="$row_cont[info_status_label]" default_src="status"/>
|
<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]"/>
|
<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');"/>
|
<progress label="$row_cont[info_percent]" id="{$row}[info_percent2]" href="javascript:egw.open($row_cont[info_id],'infolog');"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
|
@ -176,6 +176,24 @@ class mail_hooks
|
|||||||
'always_display' => lang('always show html emails'),
|
'always_display' => lang('always show html emails'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$contactLabelOptions = array (
|
||||||
|
'n_prefix' => array (
|
||||||
|
'id' => 'n_prefix',
|
||||||
|
'label' => lang('Prefix'),
|
||||||
|
),
|
||||||
|
'n_given' => array (
|
||||||
|
'id' => 'n_given',
|
||||||
|
'label' => lang('First name')
|
||||||
|
),
|
||||||
|
'n_family' => array(
|
||||||
|
'id' => 'n_family',
|
||||||
|
'label' => lang('Last name')
|
||||||
|
),
|
||||||
|
'org_name' => array(
|
||||||
|
'id' => 'org_name',
|
||||||
|
'label' => lang('Organisation')
|
||||||
|
)
|
||||||
|
);
|
||||||
// otherwise we get warnings during setup
|
// otherwise we get warnings during setup
|
||||||
if (!is_array($folderList)) $folderList = array();
|
if (!is_array($folderList)) $folderList = array();
|
||||||
|
|
||||||
@ -457,29 +475,12 @@ class mail_hooks
|
|||||||
'label' => 'Contact label',
|
'label' => 'Contact label',
|
||||||
'help' => 'Defines what to show as contact label for added contact into To/Cc/Bcc when composing an email. Default is firstname lastname and empty means include eveything available.',
|
'help' => 'Defines what to show as contact label for added contact into To/Cc/Bcc when composing an email. Default is firstname lastname and empty means include eveything available.',
|
||||||
'name' => 'contactLabel',
|
'name' => 'contactLabel',
|
||||||
'values' => '',
|
'values' => $contactLabelOptions,
|
||||||
'attributes' => array(
|
'attributes' => array(
|
||||||
'allowFreeEntries' => false,
|
'allowFreeEntries' => false,
|
||||||
'editModeEnabled' => false,
|
'editModeEnabled' => false,
|
||||||
'autocomplete_url' => ' ',
|
'autocomplete_url' => ' ',
|
||||||
'select_options' => array (
|
'select_options' => $contactLabelOptions
|
||||||
'n_prefix' => array (
|
|
||||||
'id' => 'n_prefix',
|
|
||||||
'label' => lang('Prefix'),
|
|
||||||
),
|
|
||||||
'n_given' => array (
|
|
||||||
'id' => 'n_given',
|
|
||||||
'label' => lang('First name')
|
|
||||||
),
|
|
||||||
'n_family' => array(
|
|
||||||
'id' => 'n_family',
|
|
||||||
'label' => lang('Last name')
|
|
||||||
),
|
|
||||||
'org_name' => array(
|
|
||||||
'id' => 'org_name',
|
|
||||||
'label' => lang('Organisation')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
'default' => ['n_given','n_family']
|
'default' => ['n_given','n_family']
|
||||||
)
|
)
|
||||||
|
2335
package-lock.json
generated
2335
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@ -45,13 +45,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lion/button": "^0.14.1",
|
"@andxor/jquery-ui-touch-punch-fix": "^1.0.2",
|
||||||
"@lion/core": "^0.18.1",
|
"jquery-ui-dist": "^1.12.1",
|
||||||
"@lion/input": "^0.15.3",
|
"jquery-ui-themes": "^1.12.0",
|
||||||
"carbon-components": "^10.37.0",
|
"jquery-ui-timepicker-addon": "^1.6.3",
|
||||||
"carbon-web-components": "^1.14.1",
|
|
||||||
"lit-element": "^2.5.1",
|
"lit-element": "^2.5.1",
|
||||||
"lit-html": "^1.4.1"
|
"lit-html": "^1.4.1",
|
||||||
|
"sortablejs": "^1.14.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
|
@ -23,7 +23,7 @@ import '../../api/js/framework/fw_browser.js';
|
|||||||
import '../../api/js/framework/fw_ui.js';
|
import '../../api/js/framework/fw_ui.js';
|
||||||
import '../../api/js/framework/fw_classes.js';
|
import '../../api/js/framework/fw_classes.js';
|
||||||
import '../../api/js/jsapi/egw_inheritance.js';
|
import '../../api/js/jsapi/egw_inheritance.js';
|
||||||
|
import '@andxor/jquery-ui-touch-punch-fix/jquery.ui.touch-punch.js';
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DOMWindow} window
|
* @param {DOMWindow} window
|
||||||
|
@ -226,6 +226,9 @@ if (!empty($detail = $_GET['detail']))
|
|||||||
{
|
{
|
||||||
switch($key)
|
switch($key)
|
||||||
{
|
{
|
||||||
|
case 'autoinstall':
|
||||||
|
$val = json_encode($val);
|
||||||
|
break;
|
||||||
case 'title':
|
case 'title':
|
||||||
continue 2;
|
continue 2;
|
||||||
case 'tables':
|
case 'tables':
|
||||||
|
@ -516,16 +516,17 @@ class setup
|
|||||||
),False,__LINE__,__FILE__);
|
),False,__LINE__,__FILE__);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$this->db->insert($this->applications_table,array(
|
$this->db->insert($this->applications_table, [
|
||||||
'app_name' => $appname,
|
'app_enabled' => $enable,
|
||||||
'app_enabled' => $enable,
|
'app_order' => $setup_info[$appname]['app_order'],
|
||||||
'app_order' => $setup_info[$appname]['app_order'],
|
'app_tables' => (string)$tables, // app_tables is NOT NULL
|
||||||
'app_tables' => (string)$tables, // app_tables is NOT NULL
|
'app_version' => $setup_info[$appname]['version'],
|
||||||
'app_version' => $setup_info[$appname]['version'],
|
'app_index' => $setup_info[$appname]['index'],
|
||||||
'app_index' => $setup_info[$appname]['index'],
|
'app_icon' => $setup_info[$appname]['icon'],
|
||||||
'app_icon' => $setup_info[$appname]['icon'],
|
'app_icon_app' => $setup_info[$appname]['icon_app'],
|
||||||
'app_icon_app' => $setup_info[$appname]['icon_app'],
|
], [
|
||||||
),False,__LINE__,__FILE__);
|
'app_name' => $appname,
|
||||||
|
], __LINE__, __FILE__);
|
||||||
}
|
}
|
||||||
catch (Api\Db\Exception\InvalidSql $e)
|
catch (Api\Db\Exception\InvalidSql $e)
|
||||||
{
|
{
|
||||||
@ -548,7 +549,7 @@ class setup
|
|||||||
* Check if an application has info in the db
|
* Check if an application has info in the db
|
||||||
*
|
*
|
||||||
* @param $appname Application 'name' with a matching $setup_info[$appname] array slice
|
* @param $appname Application 'name' with a matching $setup_info[$appname] array slice
|
||||||
* @param $enabled optional, set to False to not enable this app
|
* @return boolean|null null: autoinstalled app which got uninstalled
|
||||||
*/
|
*/
|
||||||
function app_registered($appname)
|
function app_registered($appname)
|
||||||
{
|
{
|
||||||
@ -563,13 +564,13 @@ class setup
|
|||||||
// _debug_array($setup_info[$appname]);
|
// _debug_array($setup_info[$appname]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->db->select($this->applications_table,'COUNT(*)',array('app_name' => $appname),__LINE__,__FILE__)->fetchColumn())
|
if (($enabled = $this->db->select($this->applications_table, 'app_enabled', ['app_name' => $appname], __LINE__,__FILE__)->fetchColumn()) !== false)
|
||||||
{
|
{
|
||||||
if(@$GLOBALS['DEBUG'])
|
if(@$GLOBALS['DEBUG'])
|
||||||
{
|
{
|
||||||
echo '... app previously registered.';
|
echo '... app previously registered.';
|
||||||
}
|
}
|
||||||
return True;
|
return $enabled <= -1 ? null : true;
|
||||||
}
|
}
|
||||||
if(@$GLOBALS['DEBUG'])
|
if(@$GLOBALS['DEBUG'])
|
||||||
{
|
{
|
||||||
@ -676,10 +677,35 @@ class setup
|
|||||||
$this->db->delete(Api\Config::TABLE, array('config_app'=>$appname),__LINE__,__FILE__);
|
$this->db->delete(Api\Config::TABLE, array('config_app'=>$appname),__LINE__,__FILE__);
|
||||||
}
|
}
|
||||||
//echo 'DELETING application: ' . $appname;
|
//echo 'DELETING application: ' . $appname;
|
||||||
$this->db->delete($this->applications_table,array('app_name'=>$appname),__LINE__,__FILE__);
|
|
||||||
|
// when uninstalling an autoinstall app, we must mark it deleted in the DB, otherwise it will install again the next update
|
||||||
|
if (file_exists($file = EGW_SERVER_ROOT.'/'.$appname.'/setup/setup.inc.php'))
|
||||||
|
{
|
||||||
|
$setup_info = [];
|
||||||
|
include($file);
|
||||||
|
}
|
||||||
|
if (!empty($setup_info[$appname]['autoinstall']) && $setup_info[$appname]['autoinstall'] === true)
|
||||||
|
{
|
||||||
|
$this->db->update($this->applications_table, [
|
||||||
|
'app_enabled' => -1,
|
||||||
|
'app_tables' => '',
|
||||||
|
'app_version' => 'uninstalled',
|
||||||
|
'app_index' => null,
|
||||||
|
], [
|
||||||
|
'app_name' => $appname,
|
||||||
|
], __LINE__, __FILE__);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->db->delete($this->applications_table, ['app_name' => $appname], __LINE__, __FILE__);
|
||||||
|
}
|
||||||
|
|
||||||
Api\Egw\Applications::invalidate();
|
Api\Egw\Applications::invalidate();
|
||||||
|
|
||||||
|
// unregister hooks, before removing links
|
||||||
|
unset($GLOBALS['egw_info']['apps'][$appname]);
|
||||||
|
Api\Hooks::read(true);
|
||||||
|
|
||||||
// Remove links to the app
|
// Remove links to the app
|
||||||
Link::unlink(0, $appname);
|
Link::unlink(0, $appname);
|
||||||
}
|
}
|
||||||
@ -1219,6 +1245,8 @@ class setup
|
|||||||
{
|
{
|
||||||
static $table_names = False;
|
static $table_names = False;
|
||||||
|
|
||||||
|
if(!is_object($this->db)) $this->loaddb();
|
||||||
|
|
||||||
if (!$table_names || $force_refresh) $table_names = $this->db->table_names();
|
if (!$table_names || $force_refresh) $table_names = $this->db->table_names();
|
||||||
|
|
||||||
if (!$table_names) return false;
|
if (!$table_names) return false;
|
||||||
|
@ -62,7 +62,7 @@ class setup_detection
|
|||||||
/* one of these tables exists. checking for post/pre beta version */
|
/* one of these tables exists. checking for post/pre beta version */
|
||||||
if($GLOBALS['egw_setup']->applications_table != 'applications')
|
if($GLOBALS['egw_setup']->applications_table != 'applications')
|
||||||
{
|
{
|
||||||
foreach($GLOBALS['egw_setup']->db->select($GLOBALS['egw_setup']->applications_table,'*',false,__LINE__,__FILE__) as $row)
|
foreach($GLOBALS['egw_setup']->db->select($GLOBALS['egw_setup']->applications_table, '*', 'app_enabled != -1', __LINE__, __FILE__) as $row)
|
||||||
{
|
{
|
||||||
$app = $row['app_name'];
|
$app = $row['app_name'];
|
||||||
if (!isset($setup_info[$app])) // app source no longer there
|
if (!isset($setup_info[$app])) // app source no longer there
|
||||||
|
@ -618,10 +618,10 @@ class setup_process
|
|||||||
foreach($setup_info as $appname => &$appdata)
|
foreach($setup_info as $appname => &$appdata)
|
||||||
{
|
{
|
||||||
// check if app is NOT installed
|
// check if app is NOT installed
|
||||||
if(!$GLOBALS['egw_setup']->app_registered($appname))
|
if (!($registered = $GLOBALS['egw_setup']->app_registered($appname)))
|
||||||
{
|
{
|
||||||
// check if app wants to be automatically installed on update to version x or allways
|
// check if app wants to be automatically installed on update to version x or always (unless uninstalled prior)
|
||||||
if (isset($appdata['autoinstall']) && ($appdata['autoinstall'] === true ||
|
if (isset($appdata['autoinstall']) && ($appdata['autoinstall'] === true && $registered !== null ||
|
||||||
$appdata['autoinstall'] === $this->api_version_target))
|
$appdata['autoinstall'] === $this->api_version_target))
|
||||||
{
|
{
|
||||||
$info_c = $this->current(array($appname => $appdata), $DEBUG);
|
$info_c = $this->current(array($appname => $appdata), $DEBUG);
|
||||||
|
Loading…
Reference in New Issue
Block a user