mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-22 07:53:39 +01:00
merge master
This commit is contained in:
commit
0cc7ce12d0
52
.travis.yml
52
.travis.yml
@ -6,32 +6,22 @@ php:
|
|||||||
# none of our dependencies allow 8.0
|
# none of our dependencies allow 8.0
|
||||||
# - master
|
# - master
|
||||||
|
|
||||||
matrix:
|
os: linux
|
||||||
|
|
||||||
|
jobs:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
# allow_failures:
|
# allow_failures:
|
||||||
# - php: master
|
# - php: master
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- memcached
|
- memcached
|
||||||
- mysql #we use mariadb instead installed via addons below
|
- mysql
|
||||||
# - postgres
|
# - postgres
|
||||||
|
|
||||||
#addons:
|
|
||||||
# mariadb: '10.0'
|
|
||||||
|
|
||||||
sudo: required
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -y libpcre3-dev
|
- sudo apt-get install -y libpcre3-dev apache2 libapache2-mod-fastcgi
|
||||||
- case $(phpenv version-name) in
|
- case $(phpenv version-name) in
|
||||||
"5.6")
|
|
||||||
yes "" | pecl install memcache;
|
|
||||||
yes "" | pecl install apcu-4.0.11;
|
|
||||||
yes "" | pecl install igbinary;
|
|
||||||
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
|
||||||
phpenv config-rm xdebug.ini;
|
|
||||||
;;
|
|
||||||
"7"|"7.0"|"7.1"|"7.2")
|
"7"|"7.0"|"7.1"|"7.2")
|
||||||
yes "" | pecl install apcu;
|
yes "" | pecl install apcu;
|
||||||
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
||||||
@ -41,17 +31,19 @@ before_script:
|
|||||||
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini;
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
- case $(phpenv version-name) in
|
# enable apache with php-fpm see https://docs.travis-ci.com/user/languages/php/#apache--php
|
||||||
"5.6")
|
- sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
|
||||||
composer require 'phpunit/phpunit:~5.7';
|
- sudo a2enmod rewrite actions fastcgi alias
|
||||||
;;
|
- echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||||
"7"|"7.0")
|
- sudo sed -i -e "s,www-data,travis,g" /etc/apache2/envvars
|
||||||
composer require 'phpunit/phpunit:~6';
|
- sudo chown -R travis:travis /var/lib/apache2/fastcgi
|
||||||
;;
|
- ~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm
|
||||||
*)
|
# configure apache virtual hosts
|
||||||
composer require --ignore-platform-reqs 'phpunit/phpunit:~7';
|
- sudo cp -f doc/travis-ci-apache.conf /etc/apache2/sites-available/000-default.conf
|
||||||
;;
|
- sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/000-default.conf
|
||||||
esac
|
# remove .htaccess as it is read by Apache, but content is for mod_php
|
||||||
|
- rm -f .htaccess
|
||||||
|
- sudo service apache2 restart
|
||||||
- php -m
|
- php -m
|
||||||
- php -i
|
- php -i
|
||||||
- php install-cli.php --ignore-platform-reqs
|
- php install-cli.php --ignore-platform-reqs
|
||||||
@ -64,7 +56,7 @@ script:
|
|||||||
# install egroupware using MariaDB as domain "default"
|
# install egroupware using MariaDB as domain "default"
|
||||||
# and add an admin user "demo" with password "guest"
|
# and add an admin user "demo" with password "guest"
|
||||||
- php doc/rpm-build/post_install.php --domain default
|
- php doc/rpm-build/post_install.php --domain default
|
||||||
--source_dir `pwd` --start_db '' --autostart_db '' --start_webserver '' --webserver_user ''
|
--source_dir `pwd` --start_db '' --autostart_db '' --start_webserver '' --autostart_webserver '' --webserver_user ''
|
||||||
--admin_user demo --admin_passwd guest --admin_email noreply@example.com
|
--admin_user demo --admin_passwd guest --admin_email noreply@example.com
|
||||||
# disable PostgreSQL install as it fails in Travis with Fatal error: Call to unimplemented native function pg_set_client_encoding
|
# disable PostgreSQL install as it fails in Travis with Fatal error: Call to unimplemented native function pg_set_client_encoding
|
||||||
# install egroupware using PostgreSQL as domain "pgsql", need some specific handling we can not create users via sql
|
# install egroupware using PostgreSQL as domain "pgsql", need some specific handling we can not create users via sql
|
||||||
@ -79,8 +71,12 @@ script:
|
|||||||
# --source_dir `pwd` --start_db '' --autostart_db '' --start_webserver '' --webserver_user ''
|
# --source_dir `pwd` --start_db '' --autostart_db '' --start_webserver '' --webserver_user ''
|
||||||
# Ubuntu has problems with #!/usr/bin/env php -dapc.enable=1, it stalls forever
|
# Ubuntu has problems with #!/usr/bin/env php -dapc.enable=1, it stalls forever
|
||||||
- vendor/bin/phpunit -c doc -dapc.enable_cli=1
|
- vendor/bin/phpunit -c doc -dapc.enable_cli=1
|
||||||
|
# output Apache error.log to diagnose PHP errors in requests send by unit-tests
|
||||||
|
- echo "travis_fold:start:SCRIPT folding starts"
|
||||||
|
- sudo cat /var/log/apache2/error.log
|
||||||
# do not run syntax check for hhvm, as it always fails / get terminated after 10m
|
# do not run syntax check for hhvm, as it always fails / get terminated after 10m
|
||||||
- test $(phpenv version-name) = 'hhvm' || ./doc/php_syntax_check.sh
|
- ./doc/php_syntax_check.sh
|
||||||
|
- echo "travis_fold:start:SCRIPT folding ends"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
@ -10,9 +10,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use EGroupware\Api;
|
use EGroupware\Api;
|
||||||
use EGroupware\Api\Framework;
|
|
||||||
use EGroupware\Api\Acl;
|
use EGroupware\Api\Acl;
|
||||||
use EGroupware\Api\Etemplate;
|
use EGroupware\Api\Etemplate;
|
||||||
|
use EGroupware\Api\Framework;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI for admin: edit/add account
|
* UI for admin: edit/add account
|
||||||
@ -28,6 +28,13 @@ class admin_account
|
|||||||
'delete' => true,
|
'delete' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Copying account uses addressbook fields, but we explicitly clear these
|
||||||
|
protected static $copy_clear_fields = array(
|
||||||
|
'account_firstname','account_lastname','account_fullname', 'person_id',
|
||||||
|
'account_id','account_lid',
|
||||||
|
'account_lastlogin','accountlastloginfrom','account_lastpwd_change'
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to edit account data via "Account" tab in addressbook edit dialog
|
* Hook to edit account data via "Account" tab in addressbook edit dialog
|
||||||
*
|
*
|
||||||
@ -104,18 +111,24 @@ class admin_account
|
|||||||
}
|
}
|
||||||
$readonlys['account_passwd'] = $readonlys['account_passwd2'] = true;
|
$readonlys['account_passwd'] = $readonlys['account_passwd2'] = true;
|
||||||
}
|
}
|
||||||
|
// save old values to only trigger save, if one of the following values change (contact data get saved anyway)
|
||||||
|
$preserve = empty($content['id']) ? array() :
|
||||||
|
array('old_account' => array_intersect_key($account, array_flip(array(
|
||||||
|
'account_lid', 'account_status', 'account_groups', 'anonymous', 'changepassword',
|
||||||
|
'mustchangepassword', 'account_primary_group', 'homedirectory', 'loginshell',
|
||||||
|
'account_expires', 'account_firstname', 'account_lastname', 'account_email'))),
|
||||||
|
'deny_edit' => $deny_edit);
|
||||||
|
|
||||||
|
if($content && $_GET['copy'])
|
||||||
|
{
|
||||||
|
$this->copy($content, $account, $preserve);
|
||||||
|
}
|
||||||
return array(
|
return array(
|
||||||
'name' => 'admin.account',
|
'name' => 'admin.account',
|
||||||
'prepend' => true,
|
'prepend' => true,
|
||||||
'label' => 'Account',
|
'label' => 'Account',
|
||||||
'data' => $account,
|
'data' => $account,
|
||||||
// save old values to only trigger save, if one of the following values change (contact data get saved anyway)
|
'preserve' => $preserve,
|
||||||
'preserve' => empty($content['id']) ? array() :
|
|
||||||
array('old_account' => array_intersect_key($account, array_flip(array(
|
|
||||||
'account_lid', 'account_status', 'account_groups', 'anonymous', 'changepassword',
|
|
||||||
'mustchangepassword', 'account_primary_group', 'homedirectory', 'loginshell',
|
|
||||||
'account_expires', 'account_firstname', 'account_lastname', 'account_email'))),
|
|
||||||
'deny_edit' => $deny_edit),
|
|
||||||
'readonlys' => $readonlys,
|
'readonlys' => $readonlys,
|
||||||
'pre_save_callback' => $deny_edit ? null : 'admin_account::addressbook_pre_save',
|
'pre_save_callback' => $deny_edit ? null : 'admin_account::addressbook_pre_save',
|
||||||
);
|
);
|
||||||
@ -243,6 +256,35 @@ class admin_account
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function copy(array &$content, array &$account, array &$preserve)
|
||||||
|
{
|
||||||
|
// We skipped the addressbook copy, call it now
|
||||||
|
$ab_ui = new addressbook_ui();
|
||||||
|
$ab_ui->copy_contact($content, true);
|
||||||
|
|
||||||
|
// copy_contact() reset the owner, fix it
|
||||||
|
$content['owner'] = '0';
|
||||||
|
|
||||||
|
// Explicitly, always clear these
|
||||||
|
static $clear_content = Array(
|
||||||
|
'n_family','n_given','n_middle','n_suffix','n_fn','n_fileas',
|
||||||
|
'account_id','contact_id','id','etag','carddav_name','uid'
|
||||||
|
);
|
||||||
|
foreach($clear_content as $field)
|
||||||
|
{
|
||||||
|
$account[$field] ='';
|
||||||
|
$preserve[$field] = '';
|
||||||
|
}
|
||||||
|
$account['link_to']['to_id'] = 0;
|
||||||
|
unset($preserve['old_account']);
|
||||||
|
|
||||||
|
// Never copy these on an account
|
||||||
|
foreach(static::$copy_clear_fields as $field)
|
||||||
|
{
|
||||||
|
unset($account[$field]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete an account
|
* Delete an account
|
||||||
*
|
*
|
||||||
|
@ -139,7 +139,10 @@ class admin_config
|
|||||||
$_POST = array('newsettings' => &$_content['newsettings']);
|
$_POST = array('newsettings' => &$_content['newsettings']);
|
||||||
|
|
||||||
// Remove actual files (cleanup) of deselected urls from login_background_file
|
// Remove actual files (cleanup) of deselected urls from login_background_file
|
||||||
$this->remove_anon_images(array_diff((array)$c->config_data['login_background_file'], $_content['newsettings']['login_background_file']));
|
if (!empty($c->config_data['login_background_file']))
|
||||||
|
{
|
||||||
|
$this->remove_anon_images(array_diff((array)$c->config_data['login_background_file'], (array)$_content['newsettings']['login_background_file']));
|
||||||
|
}
|
||||||
|
|
||||||
/* Load hook file with functions to validate each config (one/none/all) */
|
/* Load hook file with functions to validate each config (one/none/all) */
|
||||||
$errors = Api\Hooks::single(array(
|
$errors = Api\Hooks::single(array(
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use EGroupware\Api;
|
use EGroupware\Api;
|
||||||
use EGroupware\Api\Link;
|
|
||||||
use EGroupware\Api\Egw;
|
use EGroupware\Api\Egw;
|
||||||
use EGroupware\Api\Etemplate;
|
use EGroupware\Api\Etemplate;
|
||||||
use EGroupware\Api\Etemplate\Widget\Tree;
|
use EGroupware\Api\Etemplate\Widget\Tree;
|
||||||
|
use EGroupware\Api\Link;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI for admin
|
* UI for admin
|
||||||
@ -154,6 +154,13 @@ class admin_ui
|
|||||||
'onExecute' => 'javaScript:app.admin.account',
|
'onExecute' => 'javaScript:app.admin.account',
|
||||||
'group' => $group,
|
'group' => $group,
|
||||||
),
|
),
|
||||||
|
'copy' => array(
|
||||||
|
'caption' => 'Copy',
|
||||||
|
'url' => 'menuaction=addressbook.addressbook_ui.edit&makecp=1&contact_id=$id',
|
||||||
|
'onExecute' => 'javaScript:app.admin.account',
|
||||||
|
'allowOnMultiple' => false,
|
||||||
|
'icon' => 'copy',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// generate urls for add/edit accounts via addressbook
|
// generate urls for add/edit accounts via addressbook
|
||||||
$edit = Link::get_registry('addressbook', 'edit');
|
$edit = Link::get_registry('addressbook', 'edit');
|
||||||
|
@ -760,11 +760,17 @@ var AdminApp = /** @class */ (function (_super) {
|
|||||||
AdminApp.prototype.account = function (_action, _senders) {
|
AdminApp.prototype.account = function (_action, _senders) {
|
||||||
var params = jQuery.extend({}, this.egw.link_get_registry('addressbook', 'edit'));
|
var params = jQuery.extend({}, this.egw.link_get_registry('addressbook', 'edit'));
|
||||||
var popup = this.egw.link_get_registry('addressbook', 'edit_popup');
|
var popup = this.egw.link_get_registry('addressbook', 'edit_popup');
|
||||||
if (_action.id == 'add') {
|
switch (_action.id) {
|
||||||
params.owner = '0';
|
case 'add':
|
||||||
}
|
params.owner = '0';
|
||||||
else {
|
break;
|
||||||
params.account_id = _senders[0].id.split('::').pop(); // get last :: separated part
|
case 'copy':
|
||||||
|
params.owner = '0';
|
||||||
|
params.copy = true;
|
||||||
|
// Fall through
|
||||||
|
default:
|
||||||
|
params.account_id = _senders[0].id.split('::').pop(); // get last :: separated part
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this.egw.open_link(this.egw.link('/index.php', params), 'admin', popup);
|
this.egw.open_link(this.egw.link('/index.php', params), 'admin', popup);
|
||||||
};
|
};
|
||||||
|
@ -888,14 +888,20 @@ class AdminApp extends EgwApp
|
|||||||
var params = jQuery.extend({}, this.egw.link_get_registry('addressbook', 'edit'));
|
var params = jQuery.extend({}, this.egw.link_get_registry('addressbook', 'edit'));
|
||||||
var popup = <string>this.egw.link_get_registry('addressbook', 'edit_popup');
|
var popup = <string>this.egw.link_get_registry('addressbook', 'edit_popup');
|
||||||
|
|
||||||
if (_action.id == 'add')
|
switch(_action.id)
|
||||||
{
|
{
|
||||||
params.owner = '0';
|
case 'add':
|
||||||
}
|
params.owner = '0';
|
||||||
else
|
break;
|
||||||
{
|
case 'copy':
|
||||||
params.account_id = _senders[0].id.split('::').pop(); // get last :: separated part
|
params.owner = '0';
|
||||||
|
params.copy = true;
|
||||||
|
// Fall through
|
||||||
|
default:
|
||||||
|
params.account_id = _senders[0].id.split('::').pop(); // get last :: separated part
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.egw.open_link(this.egw.link('/index.php', params), 'admin', popup);
|
this.egw.open_link(this.egw.link('/index.php', params), 'admin', popup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,58 +3,85 @@
|
|||||||
<!-- $Id$ -->
|
<!-- $Id$ -->
|
||||||
<overlay>
|
<overlay>
|
||||||
<template id="admin.account.delete.delete" template="" lang="" group="0" version="18.1.001">
|
<template id="admin.account.delete.delete" template="" lang="" group="0" version="18.1.001">
|
||||||
<vbox class="admin_account_delete">
|
<grid width="100%" height="100%">
|
||||||
<description value="Who would you like to transfer records owned by the deleted user to?" class="dialogHeader2"/>
|
<columns>
|
||||||
<select-account id="new_owner" empty_label="Delete all records" class="dialogHeader3"/>
|
<column width="60%"/>
|
||||||
<description value="Automatically transfer entries owned by the user:"/>
|
<column/>
|
||||||
<select id="delete_apps" rows="6" multiple="true" span="2"/>
|
</columns>
|
||||||
<description value="Please manually deal with entries owned by the user:"/>
|
<rows>
|
||||||
<grid id="counts" disabled="!@counts">
|
<row>
|
||||||
<columns>
|
<description value="Who would you like to transfer records owned by the deleted user to?" class="dialogHeader2"/>
|
||||||
<column width="150"/>
|
<select-account id="new_owner" empty_label="Delete all records" class="dialogHeader3"/>
|
||||||
<column/>
|
</row>
|
||||||
</columns>
|
<row>
|
||||||
<rows>
|
<vbox>
|
||||||
<row>
|
<description value="Automatically transfer entries owned by the user:"/>
|
||||||
<select-app id="${row}[app]" readonly="true"/>
|
<select id="delete_apps" rows="6" multiple="true" span="2"/>
|
||||||
<description id="${row}[count]"/>
|
</vbox>
|
||||||
</row>
|
</row>
|
||||||
</rows>
|
<row>
|
||||||
</grid>
|
<vbox>
|
||||||
<grid>
|
<description value="Please manually deal with entries owned by the user:"/>
|
||||||
<columns>
|
<grid id="counts" disabled="!@counts">
|
||||||
<column width="150"/>
|
<columns>
|
||||||
<column/>
|
<column width="150"/>
|
||||||
</columns>
|
<column/>
|
||||||
<rows>
|
</columns>
|
||||||
<row>
|
<rows>
|
||||||
<select-app value="filemanager" readonly="true"/>
|
<row>
|
||||||
<description value="Change owner of found files to the new user, and move the home folder to /home/new-user/old-home-username."/>
|
<select-app id="${row}[app]" readonly="true"/>
|
||||||
</row>
|
<description id="${row}[count]"/>
|
||||||
<row>
|
</row>
|
||||||
<select-app value="mail" readonly="true"/>
|
</rows>
|
||||||
<description value="Please check email. It gets automatically deleted if email integration is used."/>
|
</grid>
|
||||||
</row>
|
<grid>
|
||||||
</rows>
|
<columns>
|
||||||
</grid>
|
<column width="150"/>
|
||||||
<description value="If you delete the user without selecting an account to move the data to, all entries get deleted!" font_style="b"/>
|
<column/>
|
||||||
</vbox>
|
</columns>
|
||||||
|
<rows>
|
||||||
|
<row>
|
||||||
|
<select-app value="filemanager" readonly="true"/>
|
||||||
|
<description value="Change owner of found files to the new user, and move the home folder to /home/new-user/old-home-username."/>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<select-app value="mail" readonly="true"/>
|
||||||
|
<description value="Please check email. It gets automatically deleted if email integration is used."/>
|
||||||
|
</row>
|
||||||
|
</rows>
|
||||||
|
</grid>
|
||||||
|
<description value="If you delete the user without selecting an account to move the data to, all entries get deleted!" font_style="b"/>
|
||||||
|
</vbox>
|
||||||
|
</row>
|
||||||
|
</rows>
|
||||||
|
</grid>
|
||||||
</template>
|
</template>
|
||||||
<template id="admin.account.delete" template="" lang="" group="0" version="18.1.001">
|
<template id="admin.account.delete" template="" lang="" group="0" version="18.1.001">
|
||||||
<box class="dialogHeader">
|
<grid width="100%">
|
||||||
<select-account id="account_id" readonly="true" label="Delete" onchange="var apps = widget.getRoot().getWidgetById('delete_apps'); apps.set_enabled(widget.getValue());"/>
|
<columns>
|
||||||
</box>
|
<column width="100%"/>
|
||||||
<tabbox id="tabs" width="99%" tab_height="400px">
|
</columns>
|
||||||
<tabs>
|
<rows>
|
||||||
<tab id="main" label="Delete"/>
|
<row class="dialogHeader">
|
||||||
</tabs>
|
<select-account id="account_id" readonly="true" label="Delete" onchange="var apps = widget.getRoot().getWidgetById('delete_apps'); apps.set_enabled(widget.getValue());"/>
|
||||||
<tabpanels>
|
</row>
|
||||||
<template template="admin.account.delete.delete" width="99%"/>
|
<row>
|
||||||
</tabpanels>
|
<tabbox id="tabs" width="100%" tab_height="400px">
|
||||||
</tabbox>
|
<tabs>
|
||||||
<hbox class="dialogFooterToolbar">
|
<tab id="main" label="Delete"/>
|
||||||
<button id="delete" label="Delete"/>
|
</tabs>
|
||||||
<button id="cancel" label="Cancel" onclick="window.close()"/>
|
<tabpanels>
|
||||||
</hbox>
|
<template template="admin.account.delete.delete" width="100%"/>
|
||||||
|
</tabpanels>
|
||||||
|
</tabbox>
|
||||||
|
</row>
|
||||||
|
<row class="dialogFooterToolbar">
|
||||||
|
<hbox>
|
||||||
|
<button id="delete" label="Delete"/>
|
||||||
|
<button id="cancel" label="Cancel" onclick="window.close()"/>
|
||||||
|
</hbox>
|
||||||
|
</row>
|
||||||
|
</rows>
|
||||||
|
</grid>
|
||||||
</template>
|
</template>
|
||||||
</overlay>
|
</overlay>
|
||||||
|
@ -58,10 +58,7 @@ td.admin_userAgent span {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.admin_account_delete > * {
|
#admin-account-delete .dialogHeader label.et2_label {
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
form#admin-account-delete div.dialogHeader > label {
|
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
/* Global Category classes*/
|
/* Global Category classes*/
|
||||||
|
@ -66,10 +66,7 @@ td.admin_userAgent span {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.admin_account_delete > * {
|
#admin-account-delete .dialogHeader label.et2_label {
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
form#admin-account-delete div.dialogHeader > label {
|
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
}
|
}
|
||||||
/* Global Category classes*/
|
/* Global Category classes*/
|
||||||
@ -249,4 +246,7 @@ Admin command
|
|||||||
filter: initial;
|
filter: initial;
|
||||||
/* IE 6-9 */
|
/* IE 6-9 */
|
||||||
}
|
}
|
||||||
|
#admin-account-delete .dialogFooterToolbar .et2_button_delete {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,4 +59,8 @@
|
|||||||
/*filter grey*/
|
/*filter grey*/
|
||||||
.img_filter_none;
|
.img_filter_none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#admin-account-delete .dialogFooterToolbar .et2_button_delete {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class AclCommandTest extends CommandBase {
|
|||||||
/**
|
/**
|
||||||
* Create accounts for testing
|
* Create accounts for testing
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class AclCommandTest extends CommandBase {
|
|||||||
$this->assertNotEmpty($this->account_id, 'Did not create test user account');
|
$this->assertNotEmpty($this->account_id, 'Did not create test user account');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
// Delete the accounts we created
|
// Delete the accounts we created
|
||||||
if($this->group_id)
|
if($this->group_id)
|
||||||
|
@ -23,7 +23,7 @@ class ConfigCommandTest extends CommandBase
|
|||||||
// If we add a config, make sure we can delete it for clean up
|
// If we add a config, make sure we can delete it for clean up
|
||||||
protected $config_name = 'test_config';
|
protected $config_name = 'test_config';
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
if($this->config_name)
|
if($this->config_name)
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,7 @@ class DeleteAccountCommandTest extends CommandBase {
|
|||||||
'account_lastname' => 'Test'
|
'account_lastname' => 'Test'
|
||||||
);
|
);
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
if(($account_id = $GLOBALS['egw']->accounts->name2id($this->account['account_lid'])))
|
if(($account_id = $GLOBALS['egw']->accounts->name2id($this->account['account_lid'])))
|
||||||
{
|
{
|
||||||
@ -44,7 +44,7 @@ class DeleteAccountCommandTest extends CommandBase {
|
|||||||
$this->assertNotEmpty($this->account_id, 'Did not create test user account');
|
$this->assertNotEmpty($this->account_id, 'Did not create test user account');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
if($this->account_id && ($GLOBALS['egw']->accounts->id2name($this->account_id)))
|
if($this->account_id && ($GLOBALS['egw']->accounts->id2name($this->account_id)))
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ class GroupCommandTest extends CommandBase {
|
|||||||
'account_members' => array()
|
'account_members' => array()
|
||||||
);
|
);
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
// Can't set this until now - value is not available
|
// Can't set this until now - value is not available
|
||||||
$this->group['account_members'] = array($GLOBALS['egw_info']['user']['account_id']);
|
$this->group['account_members'] = array($GLOBALS['egw_info']['user']['account_id']);
|
||||||
@ -39,7 +39,7 @@ class GroupCommandTest extends CommandBase {
|
|||||||
$GLOBALS['egw']->accounts->delete($account_id);
|
$GLOBALS['egw']->accounts->delete($account_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
// Delete the accounts we created
|
// Delete the accounts we created
|
||||||
if($this->group_id)
|
if($this->group_id)
|
||||||
|
@ -23,13 +23,13 @@ class PreferencesCommandTest extends CommandBase
|
|||||||
// If we add a preference, make sure we can delete it for clean up
|
// If we add a preference, make sure we can delete it for clean up
|
||||||
protected $preference_name = 'test_preference';
|
protected $preference_name = 'test_preference';
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
Api\Cache::unsetInstance(Api\Preferences::class, 'forced');
|
Api\Cache::unsetInstance(Api\Preferences::class, 'forced');
|
||||||
Api\Cache::unsetInstance(Api\Preferences::class, 'default');
|
Api\Cache::unsetInstance(Api\Preferences::class, 'default');
|
||||||
Api\Cache::unsetInstance(Api\Preferences::class, $GLOBALS['egw_info']['user']['account_id']);
|
Api\Cache::unsetInstance(Api\Preferences::class, $GLOBALS['egw_info']['user']['account_id']);
|
||||||
}
|
}
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
if($this->preference_name)
|
if($this->preference_name)
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,7 @@ class UserCommandTest extends CommandBase {
|
|||||||
'account_lastname' => 'Test'
|
'account_lastname' => 'Test'
|
||||||
);
|
);
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
if($this->account_id)
|
if($this->account_id)
|
||||||
{
|
{
|
||||||
|
@ -114,7 +114,7 @@ var et2_htmlarea = /** @class */ (function (_super) {
|
|||||||
valid_children: this.options.valid_children,
|
valid_children: this.options.valid_children,
|
||||||
plugins: [
|
plugins: [
|
||||||
"print searchreplace autolink directionality ",
|
"print searchreplace autolink directionality ",
|
||||||
"visualblocks visualchars image link media template ",
|
"visualblocks visualchars image link media template fullscreen",
|
||||||
"codesample table charmap hr pagebreak nonbreaking anchor toc ",
|
"codesample table charmap hr pagebreak nonbreaking anchor toc ",
|
||||||
"insertdatetime advlist lists textcolor wordcount imagetools ",
|
"insertdatetime advlist lists textcolor wordcount imagetools ",
|
||||||
"colorpicker textpattern help paste code searchreplace tabfocus"
|
"colorpicker textpattern help paste code searchreplace tabfocus"
|
||||||
@ -440,14 +440,14 @@ var et2_htmlarea = /** @class */ (function (_super) {
|
|||||||
*/
|
*/
|
||||||
et2_htmlarea.TOOLBAR_EXTENDED = "fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | " +
|
et2_htmlarea.TOOLBAR_EXTENDED = "fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | " +
|
||||||
"link | alignleft aligncenter alignright alignjustify | numlist " +
|
"link | alignleft aligncenter alignright alignjustify | numlist " +
|
||||||
"bullist outdent indent | removeformat | image";
|
"bullist outdent indent | removeformat | image | fullscreen";
|
||||||
/**
|
/**
|
||||||
* arranged toolbars as advanced mode
|
* arranged toolbars as advanced mode
|
||||||
* @constant
|
* @constant
|
||||||
*/
|
*/
|
||||||
et2_htmlarea.TOOLBAR_ADVANCED = "undo redo| formatselect | fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | " +
|
et2_htmlarea.TOOLBAR_ADVANCED = "undo redo| formatselect | fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | " +
|
||||||
"link | alignleft aligncenter alignright alignjustify | numlist " +
|
"link | alignleft aligncenter alignright alignjustify | numlist " +
|
||||||
"bullist outdent indent ltr rtl | removeformat code| image | searchreplace";
|
"bullist outdent indent ltr rtl | removeformat code| image | searchreplace | fullscreen";
|
||||||
/**
|
/**
|
||||||
* font size formats
|
* font size formats
|
||||||
* @constant
|
* @constant
|
||||||
|
@ -110,7 +110,7 @@ class et2_htmlarea extends et2_editableWidget implements et2_IResizeable
|
|||||||
*/
|
*/
|
||||||
public static readonly TOOLBAR_EXTENDED : string = "fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+
|
public static readonly TOOLBAR_EXTENDED : string = "fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+
|
||||||
"link | alignleft aligncenter alignright alignjustify | numlist "+
|
"link | alignleft aligncenter alignright alignjustify | numlist "+
|
||||||
"bullist outdent indent | removeformat | image";
|
"bullist outdent indent | removeformat | image | fullscreen";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* arranged toolbars as advanced mode
|
* arranged toolbars as advanced mode
|
||||||
@ -118,7 +118,7 @@ class et2_htmlarea extends et2_editableWidget implements et2_IResizeable
|
|||||||
*/
|
*/
|
||||||
public static readonly TOOLBAR_ADVANCED : string = "undo redo| formatselect | fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+
|
public static readonly TOOLBAR_ADVANCED : string = "undo redo| formatselect | fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | "+
|
||||||
"link | alignleft aligncenter alignright alignjustify | numlist "+
|
"link | alignleft aligncenter alignright alignjustify | numlist "+
|
||||||
"bullist outdent indent ltr rtl | removeformat code| image | searchreplace";
|
"bullist outdent indent ltr rtl | removeformat code| image | searchreplace | fullscreen";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* font size formats
|
* font size formats
|
||||||
@ -232,7 +232,7 @@ class et2_htmlarea extends et2_editableWidget implements et2_IResizeable
|
|||||||
valid_children : this.options.valid_children,
|
valid_children : this.options.valid_children,
|
||||||
plugins: [
|
plugins: [
|
||||||
"print searchreplace autolink directionality ",
|
"print searchreplace autolink directionality ",
|
||||||
"visualblocks visualchars image link media template ",
|
"visualblocks visualchars image link media template fullscreen",
|
||||||
"codesample table charmap hr pagebreak nonbreaking anchor toc ",
|
"codesample table charmap hr pagebreak nonbreaking anchor toc ",
|
||||||
"insertdatetime advlist lists textcolor wordcount imagetools ",
|
"insertdatetime advlist lists textcolor wordcount imagetools ",
|
||||||
"colorpicker textpattern help paste code searchreplace tabfocus"
|
"colorpicker textpattern help paste code searchreplace tabfocus"
|
||||||
|
@ -266,8 +266,8 @@ var etemplate2 = /** @class */ (function () {
|
|||||||
etemplate2.prototype.bind_unload = function () {
|
etemplate2.prototype.bind_unload = function () {
|
||||||
if (this._etemplate_exec_id) {
|
if (this._etemplate_exec_id) {
|
||||||
this.destroy_session = jQuery.proxy(function (ev) {
|
this.destroy_session = jQuery.proxy(function (ev) {
|
||||||
var request = egw.json("EGroupware\\Api\\Etemplate::ajax_destroy_session", [this._etemplate_exec_id], null, null, false);
|
// need to use async === "keepalive" to run via beforeunload
|
||||||
request.sendRequest();
|
egw.json("EGroupware\\Api\\Etemplate::ajax_destroy_session", [this._etemplate_exec_id], null, null, "keepalive").sendRequest();
|
||||||
}, this);
|
}, this);
|
||||||
if (!window.onbeforeunload) {
|
if (!window.onbeforeunload) {
|
||||||
window.onbeforeunload = this.destroy_session;
|
window.onbeforeunload = this.destroy_session;
|
||||||
|
@ -335,9 +335,9 @@ export class etemplate2
|
|||||||
{
|
{
|
||||||
this.destroy_session = jQuery.proxy(function (ev)
|
this.destroy_session = jQuery.proxy(function (ev)
|
||||||
{
|
{
|
||||||
const request = egw.json("EGroupware\\Api\\Etemplate::ajax_destroy_session",
|
// need to use async === "keepalive" to run via beforeunload
|
||||||
[this._etemplate_exec_id], null, null, false);
|
egw.json("EGroupware\\Api\\Etemplate::ajax_destroy_session",
|
||||||
request.sendRequest();
|
[this._etemplate_exec_id], null, null, "keepalive").sendRequest();
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
if (!window.onbeforeunload)
|
if (!window.onbeforeunload)
|
||||||
|
11
api/js/jsapi/egw_global.d.ts
vendored
11
api/js/jsapi/egw_global.d.ts
vendored
@ -673,13 +673,14 @@ declare class JsonRequest
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Sends the assembled request to the server
|
* Sends the assembled request to the server
|
||||||
* @param {boolean} [async=false] Overrides async provided in constructor to give an easy way to make simple async requests
|
* @param {boolean|"keepalive"} _async true: asynchronious request, false: synchronious request,
|
||||||
|
* "keepalive": async. request with keepalive===true / sendBeacon, to be used in beforeunload event
|
||||||
* @param {string} method ='POST' allow to eg. use a (cachable) 'GET' request instead of POST
|
* @param {string} method ='POST' allow to eg. use a (cachable) 'GET' request instead of POST
|
||||||
* @param {function} error option error callback(_xmlhttp, _err) used instead our default this.error
|
* @param {function} error option error callback(_xmlhttp, _err) used instead our default this.error
|
||||||
*
|
*
|
||||||
* @return {jqXHR} jQuery jqXHR request object
|
* @return {jqXHR} jQuery jqXHR request object
|
||||||
*/
|
*/
|
||||||
sendRequest(async? : boolean, method? : "POST"|"GET", error? : Function)
|
sendRequest(async? : boolean|"keepalive", method? : "POST"|"GET", error? : Function)
|
||||||
/**
|
/**
|
||||||
* Open websocket to push server (and keeps it open)
|
* Open websocket to push server (and keeps it open)
|
||||||
*
|
*
|
||||||
@ -721,14 +722,14 @@ declare interface IegwWndLocal extends IegwGlobal
|
|||||||
* which handles the actual request. If the menuaction is a full featured
|
* which handles the actual request. If the menuaction is a full featured
|
||||||
* url, this one will be used instead.
|
* url, this one will be used instead.
|
||||||
* @param _parameters which should be passed to the menuaction function.
|
* @param _parameters which should be passed to the menuaction function.
|
||||||
* @param _async specifies whether the request should be asynchronous or
|
* @param {boolean|"keepalive"} _async true: asynchronious request, false: synchronious request,
|
||||||
* not.
|
* "keepalive": async. request with keepalive===true / sendBeacon, to be used in beforeunload event
|
||||||
* @param _callback specifies the callback function which should be
|
* @param _callback specifies the callback function which should be
|
||||||
* called, once the request has been sucessfully executed.
|
* called, once the request has been sucessfully executed.
|
||||||
* @param _context is the context which will be used for the callback function
|
* @param _context is the context which will be used for the callback function
|
||||||
* @param _sender is a parameter being passed to the _callback function
|
* @param _sender is a parameter being passed to the _callback function
|
||||||
*/
|
*/
|
||||||
json(_menuaction : string, _parameters? : any[], _callback? : Function, _context? : object, _async? : boolean, _sender?) : JsonRequest;
|
json(_menuaction : string, _parameters? : any[], _callback? : Function, _context? : object, _async? : boolean|"keepalive", _sender?) : JsonRequest;
|
||||||
/**
|
/**
|
||||||
* Registers a new handler plugin.
|
* Registers a new handler plugin.
|
||||||
*
|
*
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
* @link http://www.egroupware.org
|
* @link http://www.egroupware.org
|
||||||
* @author Andreas Stöckel (as AT stylite.de)
|
* @author Andreas Stöckel (as AT stylite.de)
|
||||||
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
||||||
* @version $Id$
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*egw:uses
|
/*egw:uses
|
||||||
@ -53,7 +52,8 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
|||||||
* @param {array} _parameters
|
* @param {array} _parameters
|
||||||
* @param {function} _callback
|
* @param {function} _callback
|
||||||
* @param {object} _context
|
* @param {object} _context
|
||||||
* @param {boolean} _async
|
* @param {boolean|"keepalive"} _async true: asynchronious request, false: synchronious request,
|
||||||
|
* "keepalive": async. request with keepalive===true / sendBeacon, to be used in boforeunload event
|
||||||
* @param {object} _sender
|
* @param {object} _sender
|
||||||
* @param {egw} _egw
|
* @param {egw} _egw
|
||||||
*/
|
*/
|
||||||
@ -154,11 +154,12 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the assembled request to the server
|
* Sends the assembled request to the server
|
||||||
* @param {boolean} [async=false] Overrides async provided in constructor to give an easy way to make simple async requests
|
* @param {boolean|"keepalive"} _async Overrides async provided in constructor: true: asynchronious request,
|
||||||
|
* false: synchronious request, "keepalive": async. request with keepalive===true / sendBeacon, to be used in beforeunload event
|
||||||
* @param {string} method ='POST' allow to eg. use a (cachable) 'GET' request instead of POST
|
* @param {string} method ='POST' allow to eg. use a (cachable) 'GET' request instead of POST
|
||||||
* @param {function} error option error callback(_xmlhttp, _err) used instead our default this.error
|
* @param {function} error option error callback(_xmlhttp, _err) used instead our default this.error
|
||||||
*
|
*
|
||||||
* @return {jqXHR} jQuery jqXHR request object
|
* @return {jqXHR|boolean} jQuery jqXHR request object or for async==="keepalive" boolean is returned
|
||||||
*/
|
*/
|
||||||
json_request.prototype.sendRequest = function(async, method, error)
|
json_request.prototype.sendRequest = function(async, method, error)
|
||||||
{
|
{
|
||||||
@ -176,6 +177,15 @@ egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// send with keepalive===true or sendBeacon to be used in beforeunload event
|
||||||
|
if (this.async === "keepalive" && typeof navigator.sendBeacon !== "undefined")
|
||||||
|
{
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('json_data', request_obj);
|
||||||
|
//(window.opener||window).console.log("navigator.sendBeacon", this.url, request_obj, data.getAll('json_data'));
|
||||||
|
return navigator.sendBeacon(this.url, data);
|
||||||
|
}
|
||||||
|
|
||||||
// Send the request via AJAX using the jquery ajax function
|
// Send the request via AJAX using the jquery ajax function
|
||||||
// we need to use jQuery of window of egw object, as otherwise the one from main window is used!
|
// we need to use jQuery of window of egw object, as otherwise the one from main window is used!
|
||||||
// (causing eg. apply from server with app.$app.method to run in main window instead of popup)
|
// (causing eg. apply from server with app.$app.method to run in main window instead of popup)
|
||||||
|
@ -983,7 +983,7 @@ class Accounts
|
|||||||
if (!is_array($app_users))
|
if (!is_array($app_users))
|
||||||
{
|
{
|
||||||
self::setup_cache();
|
self::setup_cache();
|
||||||
$cache = &self::$cache['account_split'][$app_user];
|
$cache = &self::$cache['account_split'][$app_users];
|
||||||
|
|
||||||
if (is_array($cache))
|
if (is_array($cache))
|
||||||
{
|
{
|
||||||
|
@ -125,7 +125,7 @@ class Ldap
|
|||||||
*
|
*
|
||||||
* @var Api\Accounts
|
* @var Api\Accounts
|
||||||
*/
|
*/
|
||||||
private $frontend;
|
protected $frontend;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instance of the ldap class
|
* Instance of the ldap class
|
||||||
|
@ -769,9 +769,10 @@ class Cache
|
|||||||
if (is_null(Cache::$default_provider))
|
if (is_null(Cache::$default_provider))
|
||||||
{
|
{
|
||||||
Cache::$default_provider =
|
Cache::$default_provider =
|
||||||
function_exists('apcu_fetch') && Cache\Apcu::available() ? 'EGroupware\Api\Cache\Apcu' :
|
PHP_SAPI === 'cli' ? 'EGroupware\Api\Cache\Files' :
|
||||||
(function_exists('apc_fetch') && Cache\Apc::available() ? 'EGroupware\Api\Cache\Apc' :
|
(function_exists('apcu_fetch') && Cache\Apcu::available() ? 'EGroupware\Api\Cache\Apcu' :
|
||||||
'EGroupware\Api\Cache\Files');
|
(function_exists('apc_fetch') && Cache\Apc::available() ? 'EGroupware\Api\Cache\Apc' :
|
||||||
|
'EGroupware\Api\Cache\Files'));
|
||||||
}
|
}
|
||||||
|
|
||||||
//error_log('Cache::$default_provider='.array2string(Cache::$default_provider));
|
//error_log('Cache::$default_provider='.array2string(Cache::$default_provider));
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
|
|
||||||
namespace EGroupware\Api\Cache;
|
namespace EGroupware\Api\Cache;
|
||||||
|
|
||||||
|
// fix warning in tests, if memcache extension not available
|
||||||
|
if (!defined('MEMCACHE_COMPRESSED')) define('MEMCACHE_COMPRESSED', 2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caching provider storing data in memcached via PHP's memcache extension
|
* Caching provider storing data in memcached via PHP's memcache extension
|
||||||
*
|
*
|
||||||
|
@ -548,13 +548,13 @@ abstract class Framework extends Framework\Extra
|
|||||||
$var['logo_header'] = self::get_login_logo_or_bg_url('login_logo_header', 'logo');
|
$var['logo_header'] = self::get_login_logo_or_bg_url('login_logo_header', 'logo');
|
||||||
}
|
}
|
||||||
|
|
||||||
$var['logo_url'] = $GLOBALS['egw_info']['server']['login_logo_url']?$GLOBALS['egw_info']['server']['login_logo_url']:'http://www.eGroupWare.org';
|
$var['logo_url'] = $GLOBALS['egw_info']['server']['login_logo_url']?$GLOBALS['egw_info']['server']['login_logo_url']:'http://www.egroupware.org';
|
||||||
|
|
||||||
if (substr($var['logo_url'],0,4) != 'http')
|
if (substr($var['logo_url'],0,4) != 'http')
|
||||||
{
|
{
|
||||||
$var['logo_url'] = 'http://'.$var['logo_url'];
|
$var['logo_url'] = 'http://'.$var['logo_url'];
|
||||||
}
|
}
|
||||||
$var['logo_title'] = $GLOBALS['egw_info']['server']['login_logo_title']?$GLOBALS['egw_info']['server']['login_logo_title']:'www.eGroupWare.org';
|
$var['logo_title'] = $GLOBALS['egw_info']['server']['login_logo_title']?$GLOBALS['egw_info']['server']['login_logo_title']:'www.egroupware.org';
|
||||||
|
|
||||||
return $var;
|
return $var;
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,9 @@ class Sieve extends Horde\ManageSieve
|
|||||||
//'logger' => new \admin_mail_logger('/tmp/sieve.log'),
|
//'logger' => new \admin_mail_logger('/tmp/sieve.log'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// try "PLAIN" first, in case IMAP wrongly reports some digest, it does not (correctly) implement
|
||||||
|
array_unshift($this->supportedAuthMethods, self::AUTH_PLAIN);
|
||||||
|
|
||||||
parent::__construct($params);
|
parent::__construct($params);
|
||||||
|
|
||||||
$this->displayCharset = Translation::charset();
|
$this->displayCharset = Translation::charset();
|
||||||
|
@ -252,7 +252,13 @@ class Session
|
|||||||
$config->value('num_unsuccessful_ip',$GLOBALS['egw_info']['server']['num_unsuccessful_ip']);
|
$config->value('num_unsuccessful_ip',$GLOBALS['egw_info']['server']['num_unsuccessful_ip']);
|
||||||
$config->value('install_id',$GLOBALS['egw_info']['server']['install_id']);
|
$config->value('install_id',$GLOBALS['egw_info']['server']['install_id']);
|
||||||
$config->value('max_history',$GLOBALS['egw_info']['server']['max_history']);
|
$config->value('max_history',$GLOBALS['egw_info']['server']['max_history']);
|
||||||
$config->save_repository();
|
try
|
||||||
|
{
|
||||||
|
$config->save_repository();
|
||||||
|
}
|
||||||
|
catch (Db\Exception $e) {
|
||||||
|
_egw_log_exception($e); // ignore exception, as it blocks session creation, if database is not writable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self::set_cookiedomain();
|
self::set_cookiedomain();
|
||||||
@ -544,9 +550,12 @@ class Session
|
|||||||
// --> allows this stateless protocolls which use basic auth to use sessions!
|
// --> allows this stateless protocolls which use basic auth to use sessions!
|
||||||
if (($this->sessionid = self::get_sessionid(true)))
|
if (($this->sessionid = self::get_sessionid(true)))
|
||||||
{
|
{
|
||||||
session_id($this->sessionid);
|
if (session_status() !== PHP_SESSION_ACTIVE) // gives warning including password
|
||||||
|
{
|
||||||
|
session_id($this->sessionid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
elseif (!headers_sent()) // only gives warnings, nothing we can do
|
||||||
{
|
{
|
||||||
self::cache_control();
|
self::cache_control();
|
||||||
session_start();
|
session_start();
|
||||||
@ -557,6 +566,10 @@ class Session
|
|||||||
}
|
}
|
||||||
$this->sessionid = session_id();
|
$this->sessionid = session_id();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->sessionid = session_id() ?: Auth::randomstring(24);
|
||||||
|
}
|
||||||
$this->kp3 = Auth::randomstring(24);
|
$this->kp3 = Auth::randomstring(24);
|
||||||
|
|
||||||
$GLOBALS['egw_info']['user'] = $this->read_repositories();
|
$GLOBALS['egw_info']['user'] = $this->read_repositories();
|
||||||
@ -635,26 +648,30 @@ class Session
|
|||||||
}
|
}
|
||||||
$GLOBALS['egw']->db->transaction_commit();
|
$GLOBALS['egw']->db->transaction_commit();
|
||||||
|
|
||||||
if ($GLOBALS['egw_info']['server']['usecookies'] && !$no_session)
|
if (!headers_sent())
|
||||||
{
|
{
|
||||||
self::egw_setcookie(self::EGW_SESSION_NAME,$this->sessionid);
|
if ($GLOBALS['egw_info']['server']['usecookies'] && !$no_session)
|
||||||
self::egw_setcookie('kp3',$this->kp3);
|
{
|
||||||
self::egw_setcookie('domain',$this->account_domain);
|
self::egw_setcookie(self::EGW_SESSION_NAME, $this->sessionid);
|
||||||
}
|
self::egw_setcookie('kp3', $this->kp3);
|
||||||
if ($GLOBALS['egw_info']['server']['usecookies'] && !$no_session || isset($_COOKIE['last_loginid']))
|
self::egw_setcookie('domain', $this->account_domain);
|
||||||
{
|
}
|
||||||
self::egw_setcookie('last_loginid', $this->account_lid ,$now+1209600); /* For 2 weeks */
|
if ($GLOBALS['egw_info']['server']['usecookies'] && !$no_session || isset($_COOKIE['last_loginid']))
|
||||||
self::egw_setcookie('last_domain',$this->account_domain,$now+1209600);
|
{
|
||||||
}
|
self::egw_setcookie('last_loginid', $this->account_lid, $now + 1209600); /* For 2 weeks */
|
||||||
|
self::egw_setcookie('last_domain', $this->account_domain, $now + 1209600);
|
||||||
|
}
|
||||||
|
|
||||||
// set new remember me token/cookie, if requested and necessary
|
// set new remember me token/cookie, if requested and necessary
|
||||||
$expiration = null;
|
$expiration = null;
|
||||||
if (($token = $this->checkSetRememberMeToken($remember_me, $_COOKIE[self::REMEMBER_ME_COOKIE], $expiration)))
|
if (($token = $this->checkSetRememberMeToken($remember_me, $_COOKIE[self::REMEMBER_ME_COOKIE], $expiration)))
|
||||||
{
|
{
|
||||||
self::egw_setcookie(self::REMEMBER_ME_COOKIE, $token, $expiration);
|
self::egw_setcookie(self::REMEMBER_ME_COOKIE, $token, $expiration);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__."($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) successfull sessionid=$this->sessionid");
|
if (self::ERROR_LOG_DEBUG) error_log(__METHOD__ . "($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) successfull sessionid=$this->sessionid");
|
||||||
|
}
|
||||||
|
elseif (self::ERROR_LOG_DEBUG) error_log(__METHOD__ . "($this->login,$this->passwd,$this->passwd_type,$no_session,$auth_check) could NOT set session cookies, headers already sent");
|
||||||
|
|
||||||
// hook called once session is created
|
// hook called once session is created
|
||||||
Hooks::process(array(
|
Hooks::process(array(
|
||||||
@ -1649,6 +1666,8 @@ class Session
|
|||||||
*/
|
*/
|
||||||
private static function set_cookiedomain()
|
private static function set_cookiedomain()
|
||||||
{
|
{
|
||||||
|
if (PHP_SAPI === "cli") return; // gives warnings and has no benefit
|
||||||
|
|
||||||
if ($GLOBALS['egw_info']['server']['cookiedomain'])
|
if ($GLOBALS['egw_info']['server']['cookiedomain'])
|
||||||
{
|
{
|
||||||
// Admin set domain, eg. .domain.com to allow egw.domain.com and www.domain.com
|
// Admin set domain, eg. .domain.com to allow egw.domain.com and www.domain.com
|
||||||
@ -1957,6 +1976,7 @@ class Session
|
|||||||
case PHP_SESSION_DISABLED:
|
case PHP_SESSION_DISABLED:
|
||||||
throw new \ErrorException('EGroupware requires PHP session extension!');
|
throw new \ErrorException('EGroupware requires PHP session extension!');
|
||||||
case PHP_SESSION_NONE:
|
case PHP_SESSION_NONE:
|
||||||
|
if (headers_sent()) return false; // only gives warnings
|
||||||
ini_set('session.use_cookies',0); // disable the automatic use of cookies, as it uses the path / by default
|
ini_set('session.use_cookies',0); // disable the automatic use of cookies, as it uses the path / by default
|
||||||
session_name(self::EGW_SESSION_NAME);
|
session_name(self::EGW_SESSION_NAME);
|
||||||
if (($sessionid = self::get_sessionid()))
|
if (($sessionid = self::get_sessionid()))
|
||||||
|
@ -207,7 +207,7 @@ class Sharing
|
|||||||
header('WWW-Authenticate: Basic realm="'.$realm.'"');
|
header('WWW-Authenticate: Basic realm="'.$realm.'"');
|
||||||
return static::share_fail(
|
return static::share_fail(
|
||||||
'401 Unauthorized',
|
'401 Unauthorized',
|
||||||
"<html>\n<head>\n<title>401 Unauthorized</title>\n<body>\nAuthorization failed.\n</body>\n</html>\n"
|
"Authorization failed."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,7 +395,11 @@ class Translation
|
|||||||
static function &load_app($app,$lang)
|
static function &load_app($app,$lang)
|
||||||
{
|
{
|
||||||
//$start = microtime(true);
|
//$start = microtime(true);
|
||||||
if (is_null(self::$db)) self::init(false);
|
if (!isset(self::$db))
|
||||||
|
{
|
||||||
|
self::init(false);
|
||||||
|
if (!isset(self::$db)) return;
|
||||||
|
}
|
||||||
$loaded = array();
|
$loaded = array();
|
||||||
foreach(self::$db->select(self::LANG_TABLE,'message_id,content',array(
|
foreach(self::$db->select(self::LANG_TABLE,'message_id,content',array(
|
||||||
'lang' => $lang,
|
'lang' => $lang,
|
||||||
|
@ -90,9 +90,8 @@ class Sharing extends \EGroupware\Api\Sharing
|
|||||||
*/
|
*/
|
||||||
public static function setup_share($keep_session, &$share)
|
public static function setup_share($keep_session, &$share)
|
||||||
{
|
{
|
||||||
|
|
||||||
// need to reset fs_tab, as resolve_url does NOT work with just share mounted
|
// need to reset fs_tab, as resolve_url does NOT work with just share mounted
|
||||||
if (count($GLOBALS['egw_info']['server']['vfs_fstab']) <= 1)
|
if (empty($GLOBALS['egw_info']['server']['vfs_fstab']) || count($GLOBALS['egw_info']['server']['vfs_fstab']) <= 1)
|
||||||
{
|
{
|
||||||
unset($GLOBALS['egw_info']['server']['vfs_fstab']); // triggers reset of fstab in mount()
|
unset($GLOBALS['egw_info']['server']['vfs_fstab']); // triggers reset of fstab in mount()
|
||||||
$GLOBALS['egw_info']['server']['vfs_fstab'] = Vfs::mount();
|
$GLOBALS['egw_info']['server']['vfs_fstab'] = Vfs::mount();
|
||||||
|
@ -367,7 +367,7 @@ function function_backtrace($remove=0)
|
|||||||
return $_GET['menuaction'] ? $_GET['menuaction'] : str_replace(EGW_SERVER_ROOT,'',$_SERVER['SCRIPT_FILENAME']);
|
return $_GET['menuaction'] ? $_GET['menuaction'] : str_replace(EGW_SERVER_ROOT,'',$_SERVER['SCRIPT_FILENAME']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!function_exists('lang') || defined('NO_LANG')) // setup declares an own version
|
if (!function_exists('lang') && !defined('NO_LANG')) // setup declares an own version
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* function to handle multilanguage support
|
* function to handle multilanguage support
|
||||||
|
377
api/tests/CalDAVTest.php
Normal file
377
api/tests/CalDAVTest.php
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CalDAV tests base class
|
||||||
|
*
|
||||||
|
* @link http://www.egroupware.org
|
||||||
|
* @author Ralf Becker
|
||||||
|
* @package api
|
||||||
|
* @subpackage caldav
|
||||||
|
* @copyright (c) 2020 by Ralf Becker <rb@egroupware.org>
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace EGroupware\Api;
|
||||||
|
|
||||||
|
// so tests can run standalone
|
||||||
|
require_once __DIR__.'/../src/loader/common.php'; // autoloader
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use GuzzleHttp\Client, GuzzleHttp\RequestOptions;
|
||||||
|
use Horde_Icalendar, Horde_Icalendar_Exception;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract CalDAVTest using GuzzleHttp\Client against EGroupware CalDAV/CardDAV server
|
||||||
|
*
|
||||||
|
* @see http://docs.guzzlephp.org/en/v6/quickstart.html
|
||||||
|
*
|
||||||
|
* @package EGroupware\Api
|
||||||
|
*/
|
||||||
|
abstract class CalDAVTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Base URL of CalDAV server
|
||||||
|
*/
|
||||||
|
const CALDAV_BASE = 'http://localhost/egroupware/groupdav.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get full URL for a CalDAV path
|
||||||
|
*
|
||||||
|
* @param string $path CalDAV path
|
||||||
|
* @return string URL
|
||||||
|
*/
|
||||||
|
protected function url($path='/')
|
||||||
|
{
|
||||||
|
$base = self::CALDAV_BASE;
|
||||||
|
if (!empty($GLOBALS['EGW_DOMAIN']) && $GLOBALS['EGW_DOMAIN'] !== 'default')
|
||||||
|
{
|
||||||
|
$base = str_replace('localhost', $GLOBALS['EGW_DOMAIN'], $base);
|
||||||
|
}
|
||||||
|
return $base.$path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default options for GuzzleHttp\Client
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @see http://docs.guzzlephp.org/en/v6/request-options.html
|
||||||
|
*/
|
||||||
|
protected $client_options = [
|
||||||
|
RequestOptions::HTTP_ERRORS => false, // return all HTTP status, not throwing exceptions
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Cookie' => 'XDEBUG_SESSION=PHPSTORM',
|
||||||
|
//'User-Agent' => 'CalDAVSynchronizer',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get HTTP client for tests
|
||||||
|
*
|
||||||
|
* It will use by default the always existing user "demo" with password "guest" (use [] to NOT authenticate).
|
||||||
|
* Additional users need to be created with $this->createUser("name").
|
||||||
|
*
|
||||||
|
* @param string|array $user_or_options ='demo' string with account_lid of user for authentication or array of options
|
||||||
|
* @return Client
|
||||||
|
* @see http://docs.guzzlephp.org/en/v6/request-options.html
|
||||||
|
* @see http://docs.guzzlephp.org/en/v6/quickstart.html
|
||||||
|
*/
|
||||||
|
protected function getClient($user_or_options='demo')
|
||||||
|
{
|
||||||
|
if (!is_array($user_or_options))
|
||||||
|
{
|
||||||
|
$user_or_options = $this->auth($user_or_options);
|
||||||
|
}
|
||||||
|
return new Client(array_merge($this->client_options, $user_or_options));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a number of users with optional ACL rights too
|
||||||
|
*
|
||||||
|
* Example with boss granting secretary full rights on his calendar, plus one other user:
|
||||||
|
*
|
||||||
|
* $users = [
|
||||||
|
* 'boss' => [],
|
||||||
|
* 'secretary' => ['rights' => ['boss' => Acl::READ|Acl::ADD|Acl::EDIT|Acl::DELETE]],
|
||||||
|
* 'other' => [],
|
||||||
|
* ];
|
||||||
|
* self::createUsersACL($users);
|
||||||
|
*
|
||||||
|
* @param array& $users $account_lid => array with values for keys with (defaults) "firstname" ($_acount_lid), "lastname" ("User"),
|
||||||
|
* "email" ("$_account_lid@example.org"), "password" (random string), "primary_group" ("NoGroups" to not set rights)
|
||||||
|
* "rights" array with $grantee => $rights pairs (need to be created before!)
|
||||||
|
* @param string $app app to create the rights for, default "calendar"
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected function createUsersACL(array &$users, $app='calendar')
|
||||||
|
{
|
||||||
|
foreach($users as $user => $data)
|
||||||
|
{
|
||||||
|
$data['id'] = self::createUser($user, $data);
|
||||||
|
|
||||||
|
foreach($data['rights'] ?? [] as $grantee => $rights)
|
||||||
|
{
|
||||||
|
self::addAcl('calendar', $data['id'], $grantee, $rights);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track created users for tearDown and authentication
|
||||||
|
*
|
||||||
|
* @var array $account_lid => array with other data pairs
|
||||||
|
*/
|
||||||
|
private static $created_users = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a user
|
||||||
|
*
|
||||||
|
* Created users are automatic deleted in tearDown() and can be passed to auth() or getClient() methods.
|
||||||
|
* Users have random passwords to force new/different sessions!
|
||||||
|
*
|
||||||
|
* @param string $_account_lid
|
||||||
|
* @param array& $data =[] values for keys with (defaults) "firstname" ($_acount_lid), "lastname" ("User"),
|
||||||
|
* "email" ("$_account_lid@example.org"), "password" (random string), "primary_group" ("NoGroups" to not set rights)
|
||||||
|
* on return: with defaults set
|
||||||
|
* @return int account_id of created user
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
protected static function createUser($_account_lid, array &$data=[])
|
||||||
|
{
|
||||||
|
// add some defaults
|
||||||
|
$data = array_merge([
|
||||||
|
'firstname' => ucfirst($_account_lid),
|
||||||
|
'lastname' => 'User',
|
||||||
|
'email' => $_account_lid.'@example.org',
|
||||||
|
'password' => 'secret',//Auth::randomstring(12),
|
||||||
|
'primary_group' => 'NoGroup',
|
||||||
|
], $data);
|
||||||
|
|
||||||
|
$data['id'] = self::getSetup()->add_account($_account_lid, $data['firstname'], $data['lastname'],
|
||||||
|
$data['password'], $data['primary_group'], false, $data['email']);
|
||||||
|
|
||||||
|
// give use run rights for CalDAV apps, as NoGroup does NOT!
|
||||||
|
self::addAcl(['groupdav','calendar','infolog','addressbook'], 'run', $data['id']);
|
||||||
|
|
||||||
|
self::$created_users[$_account_lid] = $data;
|
||||||
|
|
||||||
|
return $data['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get authentication information for given user to use
|
||||||
|
*
|
||||||
|
* @param string $_account_lid ='demo'
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function auth($_account_lid='demo')
|
||||||
|
{
|
||||||
|
if ($_account_lid === 'demo')
|
||||||
|
{
|
||||||
|
$password = 'guest';
|
||||||
|
}
|
||||||
|
elseif (!isset(self::$created_users[$_account_lid]))
|
||||||
|
{
|
||||||
|
throw new \InvalidArgumentException("No user '$_account_lid' exist, need to create it with createUser('$_account_lid')");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$password = self::$created_users[$_account_lid]['password'];
|
||||||
|
}
|
||||||
|
return [RequestOptions::AUTH => [$_account_lid, $password]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tear down:
|
||||||
|
* - delete users created by createUser() incl. their ACL and data
|
||||||
|
*
|
||||||
|
* @ToDo: implement eg. with admin_cmd_delete_user to also delete ACL and data
|
||||||
|
*/
|
||||||
|
public static function tearDownAfterClass() : void
|
||||||
|
{
|
||||||
|
$setup = self::getSetup();
|
||||||
|
|
||||||
|
foreach(self::$created_users as $account_lid => $data)
|
||||||
|
{
|
||||||
|
// if ($id) $setup->accounts->delete($data['id']);
|
||||||
|
unset(self::$created_users[$account_lid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add ACL rights
|
||||||
|
*
|
||||||
|
* @param string|array $apps app-names
|
||||||
|
* @param string $location eg. "run"
|
||||||
|
* @param int|string $account accountid or account_lid
|
||||||
|
* @param int $rights rights to set, default 1
|
||||||
|
*/
|
||||||
|
protected static function addAcl($apps, $location, $account, $rights=1)
|
||||||
|
{
|
||||||
|
return self::getSetup()->add_acl($apps, $location, $account, $rights);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return instance of setup object eg. to create users
|
||||||
|
*
|
||||||
|
* @return \setup
|
||||||
|
*/
|
||||||
|
private static function getSetup()
|
||||||
|
{
|
||||||
|
static $setup=null;
|
||||||
|
if (!isset($setup))
|
||||||
|
{
|
||||||
|
if (!isset($_REQUEST['domain']))
|
||||||
|
{
|
||||||
|
$_REQUEST['domain'] = $GLOBALS['EGW_DOMAIN'] ?? 'default';
|
||||||
|
}
|
||||||
|
$_REQUEST['ConfigDomain'] = $_REQUEST['domain'];
|
||||||
|
|
||||||
|
$GLOBALS['egw_info'] = array(
|
||||||
|
'flags' => array(
|
||||||
|
'noheader' => True,
|
||||||
|
'nonavbar' => True,
|
||||||
|
'currentapp' => 'setup',
|
||||||
|
'noapi' => True
|
||||||
|
));
|
||||||
|
if (file_exists(__DIR__ . '/../../header.inc.php'))
|
||||||
|
{
|
||||||
|
include_once(__DIR__ . '/../../header.inc.php');
|
||||||
|
}
|
||||||
|
$setup = new \setup();
|
||||||
|
}
|
||||||
|
return $setup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check HTTP status in response
|
||||||
|
*
|
||||||
|
* @param int|array $expected one or more valid status codes
|
||||||
|
* @param ResponseInterface $response
|
||||||
|
* @param string $message ='' additional message to prefix result message
|
||||||
|
*/
|
||||||
|
protected function assertHttpStatus($expected, ResponseInterface $response, $message='')
|
||||||
|
{
|
||||||
|
$status = $response->getStatusCode();
|
||||||
|
$this->assertEquals(in_array($status, (array)$expected) ? $status : ((array)$expected)[0], $status,
|
||||||
|
(!empty($message) ? $message.': ' : ''). 'Expected HTTP status: '.json_encode($expected).
|
||||||
|
", Server returned: $status ".$response->getReasonPhrase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts an iCal file matches an expected one taking into account $_overwrites
|
||||||
|
*
|
||||||
|
* @param string $_expected
|
||||||
|
* @param string $_acctual
|
||||||
|
* @param string $_message
|
||||||
|
* @param array $_overwrites =[] eg. ['vEvent' => [['ATTENDEE' => ['mailto:boss@example.org' => ['PARTSTAT' => 'DECLINED']]]]]
|
||||||
|
* (first vEvent attendee with value 'mailto:boss@...' has param 'PARTSTAT=DECLINED')
|
||||||
|
* @throws Horde_Icalendar_Exception
|
||||||
|
*/
|
||||||
|
protected function assertIcal($_expected, $_acctual, $_message=null, $_overwrites=[])
|
||||||
|
{
|
||||||
|
// enable to see full iCals
|
||||||
|
//$this->assertEquals($_expected, (string)$_acctual, $_message.": iCal not byte-by-byte identical");
|
||||||
|
|
||||||
|
$expected = new Horde_Icalendar();
|
||||||
|
$expected->parsevCalendar($_expected);
|
||||||
|
$acctual = new Horde_Icalendar();
|
||||||
|
$acctual->parsevCalendar($_acctual);
|
||||||
|
|
||||||
|
if (($msgs = $this->checkComponentEqual($expected, $acctual, $_overwrites)))
|
||||||
|
{
|
||||||
|
$this->assertEquals($_expected, (string)$_acctual, ($_message ? $_message.":\n" : '').implode("\n", $msgs));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->assertTrue(true); // due to $_overwrite probable $_expected !== $_acctual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check two iCal components are equal modulo overwrites / expected difference
|
||||||
|
*
|
||||||
|
* Only a whitelist of attributes per component are checked, see $component_attrs2check variable.
|
||||||
|
*
|
||||||
|
* @param Horde_Icalendar $_expected
|
||||||
|
* @param Horde_Icalendar $_acctual
|
||||||
|
* @param string $_message
|
||||||
|
* @param array $_overwrites =[] eg. ['ATTENDEE' => ['boss@example.org' => ['PARTSTAT' => 'DECLINED']]]
|
||||||
|
* @throws Horde_Icalendar_Exception
|
||||||
|
* @return array message(s) what's not equal
|
||||||
|
*/
|
||||||
|
protected function checkComponentEqual(Horde_Icalendar $_expected, Horde_Icalendar $_acctual, $_overwrites=[])
|
||||||
|
{
|
||||||
|
// only following attributes in these components are checked:
|
||||||
|
static $component_attrs2check = [
|
||||||
|
'vcalendar' => ['VERSION'],
|
||||||
|
'vTimeZone' => ['TZID'],
|
||||||
|
'vEvent' => ['UID', 'SUMMARY', 'LOCATION', 'DESCRIPTION', 'DTSTART', 'DTEND', 'ORGANIZER', 'ATTENDEE'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($_expected->getType() !== $_acctual->getType())
|
||||||
|
{
|
||||||
|
return ["component type not equal"];
|
||||||
|
}
|
||||||
|
$msgs = [];
|
||||||
|
foreach ($component_attrs2check[$_expected->getType()] ?? [] as $attr)
|
||||||
|
{
|
||||||
|
$acctualAttrs = $_acctual->getAllAttributes($attr);
|
||||||
|
foreach($_expected->getAllAttributes($attr) as $expectedAttr)
|
||||||
|
{
|
||||||
|
$found = false;
|
||||||
|
foreach($acctualAttrs as $acctualAttr)
|
||||||
|
{
|
||||||
|
if (count($acctualAttrs) === 1 || $expectedAttr['value'] === $acctualAttr['value'])
|
||||||
|
{
|
||||||
|
$found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found)
|
||||||
|
{
|
||||||
|
$msgs[] = "No $attr {$expectedAttr['value']} found";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// remove / ignore X-parameters, eg. X-EGROUPWARE-UID in ATTENDEE or ORGANIZER
|
||||||
|
$acctualAttr['params'] = array_filter($acctualAttr['params'], function ($key) {
|
||||||
|
return substr($key, 0, 2) !== 'X-';
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
|
if (isset($_overwrites[$attr]) && is_scalar($_overwrites[$attr]))
|
||||||
|
{
|
||||||
|
$expectedAttr = [
|
||||||
|
'name' => $attr,
|
||||||
|
'value' => $_overwrites[$attr],
|
||||||
|
'values' => [$_overwrites[$attr]],
|
||||||
|
'params' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
elseif (isset($_overwrites[$attr]) && is_array($_overwrites[$attr]))
|
||||||
|
{
|
||||||
|
foreach ($_overwrites[$attr] as $value => $params)
|
||||||
|
{
|
||||||
|
if ($value === $expectedAttr['value'])
|
||||||
|
{
|
||||||
|
$expectedAttr['params'] = array_merge($expectedAttr['params'], $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($expectedAttr != $acctualAttr)
|
||||||
|
{
|
||||||
|
$this->assertEquals($expectedAttr, $acctualAttr, "$attr not equal");
|
||||||
|
$msgs[] = "$attr not equal";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check sub-components, overrites use an index by type eg. 1. vEvent: ['vEvent'=>[[<overwrites for 1. vEvent]]]
|
||||||
|
$idx_by_type = [];
|
||||||
|
foreach($_expected->getComponents() as $idx => $component)
|
||||||
|
{
|
||||||
|
if (!isset($idx_by_type[$type = $component->getType()])) $idx_by_type[$type] = 0;
|
||||||
|
$msgs = array_merge($msgs, $this->checkComponentEqual($component, $_acctual->getComponent($idx),
|
||||||
|
$_overwrites[$type][$idx_by_type[$type]] ?? []));
|
||||||
|
$idx_by_type[$type]++;
|
||||||
|
}
|
||||||
|
return $msgs;
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,7 @@ class DateTimeTest extends TestCase {
|
|||||||
/**
|
/**
|
||||||
* Work in server time, so tests match expectations
|
* Work in server time, so tests match expectations
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class DateTimeTest extends TestCase {
|
|||||||
// Set user time to server time for consistency
|
// Set user time to server time for consistency
|
||||||
DateTime::setUserPrefs(date_default_timezone_get());
|
DateTime::setUserPrefs(date_default_timezone_get());
|
||||||
}
|
}
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
// Reset
|
// Reset
|
||||||
DateTime::setUserPrefs(static::$usertime->getName());
|
DateTime::setUserPrefs(static::$usertime->getName());
|
||||||
|
@ -59,7 +59,7 @@ class SchemaTest extends LoggedInTest {
|
|||||||
/**
|
/**
|
||||||
* Get a database connection
|
* Get a database connection
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
|
@ -26,11 +26,11 @@ class EntryTest extends \EGroupware\Api\Etemplate\WidgetBaseTest {
|
|||||||
|
|
||||||
const TEST_TEMPLATE = 'api.entry_test_contact';
|
const TEST_TEMPLATE = 'api.entry_test_contact';
|
||||||
|
|
||||||
public static function setUpBeforeClass() {
|
public static function setUpBeforeClass() : void {
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
// Delete all elements
|
// Delete all elements
|
||||||
foreach($this->elements as $id)
|
foreach($this->elements as $id)
|
||||||
|
@ -29,7 +29,7 @@ class DateTest extends \EGroupware\Api\Etemplate\WidgetBaseTest
|
|||||||
/**
|
/**
|
||||||
* Work in server time, so tests match expectations
|
* Work in server time, so tests match expectations
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class DateTest extends \EGroupware\Api\Etemplate\WidgetBaseTest
|
|||||||
date_default_timezone_set('UTC');
|
date_default_timezone_set('UTC');
|
||||||
DateTime::$server_timezone = new \DateTimeZone('UTC');
|
DateTime::$server_timezone = new \DateTimeZone('UTC');
|
||||||
}
|
}
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
// Reset
|
// Reset
|
||||||
DateTime::setUserPrefs(static::$usertime->getName());
|
DateTime::setUserPrefs(static::$usertime->getName());
|
||||||
|
@ -39,7 +39,7 @@ abstract class WidgetBaseTest extends \EGroupware\Api\LoggedInTest {
|
|||||||
|
|
||||||
protected $ajax_response = null;
|
protected $ajax_response = null;
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
@ -49,12 +49,12 @@ abstract class WidgetBaseTest extends \EGroupware\Api\LoggedInTest {
|
|||||||
new \EGroupware\Api\Etemplate();
|
new \EGroupware\Api\Etemplate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
// Mock AJAX response
|
// Mock AJAX response
|
||||||
$this->ajax_response = $this->mock_ajax_response();
|
$this->ajax_response = $this->mock_ajax_response();
|
||||||
}
|
}
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
// Clean up AJAX response
|
// Clean up AJAX response
|
||||||
$this->ajax_response->initResponseArray();
|
$this->ajax_response->initResponseArray();
|
||||||
|
@ -35,7 +35,7 @@ abstract class LoggedInTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* Start session once before each test case
|
* Start session once before each test case
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -63,7 +63,7 @@ abstract class LoggedInTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function assertPreConditions()
|
protected function assertPreConditions() : void
|
||||||
{
|
{
|
||||||
// Do some checks to make sure things we expect are there
|
// Do some checks to make sure things we expect are there
|
||||||
$this->assertTrue(static::sanity_check(), 'Unable to connect to Egroupware - failed sanity check');
|
$this->assertTrue(static::sanity_check(), 'Unable to connect to Egroupware - failed sanity check');
|
||||||
@ -72,7 +72,7 @@ abstract class LoggedInTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* End session when done - every test class gets its own session
|
* End session when done - every test class gets its own session
|
||||||
*/
|
*/
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
if($GLOBALS['egw'])
|
if($GLOBALS['egw'])
|
||||||
{
|
{
|
||||||
@ -125,11 +125,6 @@ abstract class LoggedInTest extends TestCase
|
|||||||
'passwd_type' => 'text',
|
'passwd_type' => 'text',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (ini_get('session.save_handler') == 'files' && !is_writable(ini_get('session.save_path')) && is_dir('/tmp') && is_writable('/tmp'))
|
|
||||||
{
|
|
||||||
ini_set('session.save_path','/tmp'); // regular users may have no rights to apache's session dir
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!$info)
|
if(!$info)
|
||||||
{
|
{
|
||||||
$info = array(
|
$info = array(
|
||||||
|
@ -27,7 +27,7 @@ class BaseTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
private $storage;
|
private $storage;
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
if (ini_get('session.save_handler') == 'files' && !is_writable(ini_get('session.save_path')) && is_dir('/tmp') && is_writable('/tmp'))
|
if (ini_get('session.save_handler') == 'files' && !is_writable(ini_get('session.save_path')) && is_dir('/tmp') && is_writable('/tmp'))
|
||||||
{
|
{
|
||||||
@ -49,12 +49,12 @@ class BaseTest extends TestCase
|
|||||||
self::$db->connect();
|
self::$db->connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
$this->storage = new Api\Storage\Base('test', 'egw_test', self::$db);
|
$this->storage = new Api\Storage\Base('test', 'egw_test', self::$db);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertPreConditions()
|
protected function assertPreConditions() : void
|
||||||
{
|
{
|
||||||
$tables = self::$db->table_names(true);
|
$tables = self::$db->table_names(true);
|
||||||
$this->assertContains('egw_test', $tables, 'Could not find DB table "egw_test", make sure test app is installed');
|
$this->assertContains('egw_test', $tables, 'Could not find DB table "egw_test", make sure test app is installed');
|
||||||
|
@ -35,7 +35,7 @@ class CustomfieldsTest extends LoggedInTest
|
|||||||
'private' => array()
|
'private' => array()
|
||||||
);
|
);
|
||||||
|
|
||||||
public function assertPreConditions()
|
protected function assertPreConditions() : void
|
||||||
{
|
{
|
||||||
parent::assertPreConditions();
|
parent::assertPreConditions();
|
||||||
$tables = $GLOBALS['egw']->db->table_names(true);
|
$tables = $GLOBALS['egw']->db->table_names(true);
|
||||||
|
@ -61,7 +61,7 @@ class SharingBase extends LoggedInTest
|
|||||||
'maxdepth' => 5
|
'maxdepth' => 5
|
||||||
);
|
);
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
// Check we have basic access
|
// Check we have basic access
|
||||||
if(!is_readable($GLOBALS['egw_info']['server']['files_dir']))
|
if(!is_readable($GLOBALS['egw_info']['server']['files_dir']))
|
||||||
@ -75,7 +75,7 @@ class SharingBase extends LoggedInTest
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
LoggedInTest::tearDownAfterClass();
|
LoggedInTest::tearDownAfterClass();
|
||||||
LoggedInTest::setupBeforeClass();
|
LoggedInTest::setupBeforeClass();
|
||||||
@ -239,7 +239,7 @@ class SharingBase extends LoggedInTest
|
|||||||
break;
|
break;
|
||||||
case Sharing::WRITABLE:
|
case Sharing::WRITABLE:
|
||||||
// Root is not writable
|
// Root is not writable
|
||||||
if($file == '/') continue;
|
if($file == '/') break;
|
||||||
|
|
||||||
$this->assertTrue(Vfs::is_writable($file), $file . ' was not writable');
|
$this->assertTrue(Vfs::is_writable($file), $file . ' was not writable');
|
||||||
if(!Vfs::is_dir($file))
|
if(!Vfs::is_dir($file))
|
||||||
|
@ -21,7 +21,7 @@ use PHPUnit\Framework\TestCase as TestCase;
|
|||||||
|
|
||||||
class SecurityTest extends TestCase {
|
class SecurityTest extends TestCase {
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
// _check_script_tag uses HtmLawed, which calls GLOBALS['egw']->link()
|
// _check_script_tag uses HtmLawed, which calls GLOBALS['egw']->link()
|
||||||
$GLOBALS['egw'] = $this->getMockBuilder('Egw')
|
$GLOBALS['egw'] = $this->getMockBuilder('Egw')
|
||||||
@ -30,7 +30,7 @@ class SecurityTest extends TestCase {
|
|||||||
->getMock();
|
->getMock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
unset($GLOBALS['egw_inset_vars']);
|
unset($GLOBALS['egw_inset_vars']);
|
||||||
|
|
||||||
|
@ -1338,7 +1338,7 @@ class calendar_bo
|
|||||||
elseif ($grants[$uid] & Acl::READ)
|
elseif ($grants[$uid] & Acl::READ)
|
||||||
{
|
{
|
||||||
// if we have a READ grant from a participant, we dont give an implicit privat grant too
|
// if we have a READ grant from a participant, we dont give an implicit privat grant too
|
||||||
$grant |= Acl::READ;
|
$grant |= self::ACL_FREEBUSY | Acl::READ;
|
||||||
// we cant break here, as we might be a participant too, and would miss the privat grant
|
// we cant break here, as we might be a participant too, and would miss the privat grant
|
||||||
}
|
}
|
||||||
elseif (!is_numeric($uid))
|
elseif (!is_numeric($uid))
|
||||||
|
@ -1375,35 +1375,56 @@ class calendar_groupdav extends Api\CalDAV\Handler
|
|||||||
return true; // simply ignore DELETE in inbox for now
|
return true; // simply ignore DELETE in inbox for now
|
||||||
}
|
}
|
||||||
$return_no_access = true; // to allow to check if current use is a participant and reject the event for him
|
$return_no_access = true; // to allow to check if current use is a participant and reject the event for him
|
||||||
if (!is_array($event = $this->_common_get_put_delete('DELETE',$options,$id,$return_no_access)) || !$return_no_access ||
|
$event = $this->_common_get_put_delete('DELETE',$options,$id,$return_no_access);
|
||||||
// Work around problems with Outlook CalDAV Synchroniser (https://caldavsynchronizer.org/)
|
|
||||||
// - sends a DELETE to reject a meeting request --> deletes event for all participants, if user has delete rights on the calendar
|
// no event found --> 404 Not Found
|
||||||
// --> only set status for everyone else but the organizer
|
if (!is_array($event))
|
||||||
self::get_agent() == 'caldavsynchronizer' && is_array($event) && $event['owner'] != $user)
|
|
||||||
{
|
{
|
||||||
if (is_array($event) && (!$return_no_access || $event['owner'] != $user))
|
$ret = $event;
|
||||||
|
error_log("_common_get_put_delete('DELETE', ..., $id) user=$user, return_no_access=".array2string($return_no_access)." returned ".array2string($event));
|
||||||
|
}
|
||||||
|
// Work around problems with Outlook CalDAV Synchronizer (https://caldavsynchronizer.org/)
|
||||||
|
// - sends a DELETE to reject a meeting request --> deletes event for all participants, if user has delete rights from the organizer
|
||||||
|
// --> only set status for everyone else but the organizer
|
||||||
|
// OR no delete rights and deleting an event in someone else calendar --> check if calendar owner is a participant --> reject him
|
||||||
|
elseif ((!$return_no_access || (self::get_agent() === 'caldavsynchronizer' && $event['owner'] != $user)) &&
|
||||||
|
// check if current user has edit rights for calendar of $user, can change status / reject invitation for him
|
||||||
|
$this->bo->check_perms(Acl::EDIT, 0, $user))
|
||||||
|
{
|
||||||
|
// check if user is a participant or one of the groups he is a member of --> reject the meeting request
|
||||||
|
$ret = '403 Forbidden';
|
||||||
|
$memberships = $GLOBALS['egw']->accounts->memberships($user, true);
|
||||||
|
foreach(array_keys($event['participants']) as $uid)
|
||||||
{
|
{
|
||||||
// check if user is a participant or one of the groups he is a member of --> reject the meeting request
|
if ($user == $uid || in_array($uid, $memberships))
|
||||||
$ret = '403 Forbidden';
|
|
||||||
$memberships = $GLOBALS['egw']->accounts->memberships($this->bo->user, true);
|
|
||||||
foreach(array_keys($event['participants']) as $uid)
|
|
||||||
{
|
{
|
||||||
if ($this->bo->user == $uid || in_array($uid, $memberships))
|
$this->bo->set_status($event, $user, 'R');
|
||||||
{
|
$ret = true;
|
||||||
$this->bo->set_status($event,$this->bo->user, 'R');
|
break;
|
||||||
$ret = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
// current user has no delete rights for event --> reject invitation, if he is a participant
|
||||||
|
elseif (!$return_no_access)
|
||||||
|
{
|
||||||
|
// check if current user is a participant or one of the groups he is a member of --> reject the meeting request
|
||||||
|
$ret = '403 Forbidden';
|
||||||
|
$memberships = $GLOBALS['egw']->accounts->memberships($this->bo->user, true);
|
||||||
|
foreach(array_keys($event['participants']) as $uid)
|
||||||
{
|
{
|
||||||
$ret = $event;
|
if ($this->bo->user == $uid || in_array($uid, $memberships))
|
||||||
|
{
|
||||||
|
$this->bo->set_status($event, $this->bo->user, 'R');
|
||||||
|
$ret = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// we have delete rights on the event and (try to) delete it
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$ret = $this->bo->delete($event['id']);
|
$ret = $this->bo->delete($event['id']);
|
||||||
|
if (!$ret) { error_log("delete($event[id]) returned FALSE"); $ret = '400 Failed to delete event';}
|
||||||
}
|
}
|
||||||
if ($this->debug) error_log(__METHOD__."(,$id) return_no_access=$return_no_access, event[participants]=".array2string(is_array($event)?$event['participants']:null).", user={$this->bo->user} --> return ".array2string($ret));
|
if ($this->debug) error_log(__METHOD__."(,$id) return_no_access=$return_no_access, event[participants]=".array2string(is_array($event)?$event['participants']:null).", user={$this->bo->user} --> return ".array2string($ret));
|
||||||
return $ret;
|
return $ret;
|
||||||
|
@ -448,7 +448,7 @@ class calendar_ical extends calendar_boupdate
|
|||||||
$quantity = $role = null;
|
$quantity = $role = null;
|
||||||
calendar_so::split_status($status, $quantity, $role);
|
calendar_so::split_status($status, $quantity, $role);
|
||||||
// do not include event owner/ORGANIZER as participant in his own calendar, if he is only participant
|
// do not include event owner/ORGANIZER as participant in his own calendar, if he is only participant
|
||||||
if (count($event['participants']) == 1 && $event['owner'] == $uid) continue;
|
if (count($event['participants']) == 1 && $event['owner'] == $uid && $uid == $this->user) continue;
|
||||||
|
|
||||||
if (!($info = $this->resource_info($uid))) continue;
|
if (!($info = $this->resource_info($uid))) continue;
|
||||||
|
|
||||||
@ -497,15 +497,15 @@ class calendar_ical extends calendar_boupdate
|
|||||||
{
|
{
|
||||||
$user = $this->resource_info($this->user);
|
$user = $this->resource_info($this->user);
|
||||||
$attributes['ATTENDEE'][] = 'mailto:' . $user['email'];
|
$attributes['ATTENDEE'][] = 'mailto:' . $user['email'];
|
||||||
$parameters['ATTENDEE'][] = array(
|
$parameters['ATTENDEE'][] = array(
|
||||||
'CN' => $user['name'],
|
'CN' => $user['name'],
|
||||||
'ROLE' => 'REQ-PARTICIPANT',
|
'ROLE' => 'REQ-PARTICIPANT',
|
||||||
'PARTSTAT' => 'NEEDS-ACTION',
|
'PARTSTAT' => 'NEEDS-ACTION',
|
||||||
'CUTYPE' => 'INDIVIDUAL',
|
'CUTYPE' => 'INDIVIDUAL',
|
||||||
'RSVP' => 'TRUE',
|
'RSVP' => 'TRUE',
|
||||||
'X-EGROUPWARE-UID' => (string)$this->user,
|
'X-EGROUPWARE-UID' => (string)$this->user,
|
||||||
);
|
);
|
||||||
$event['participants'][$this->user] = true;
|
$event['participants'][$this->user] = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
@ -561,33 +561,33 @@ class calendar_ical extends calendar_boupdate
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ORGANIZER':
|
case 'ORGANIZER':
|
||||||
if (!$organizerURL)
|
if (!$organizerURL)
|
||||||
{
|
{
|
||||||
$organizerCN = '"' . trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname')
|
$organizerCN = '"' . trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname')
|
||||||
. ' ' . $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')) . '"';
|
. ' ' . $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')) . '"';
|
||||||
$organizerEMail = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
|
$organizerEMail = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email');
|
||||||
if ($version == '1.0')
|
if ($version == '1.0')
|
||||||
{
|
{
|
||||||
$organizerURL = trim($organizerCN . (empty($organizerURL) ? '' : ' <' . $organizerURL .'>'));
|
$organizerURL = trim($organizerCN . (empty($organizerURL) ? '' : ' <' . $organizerURL .'>'));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$organizerURL = empty($organizerEMail) ? '' : 'mailto:' . $organizerEMail;
|
$organizerURL = empty($organizerEMail) ? '' : 'mailto:' . $organizerEMail;
|
||||||
}
|
}
|
||||||
$organizerUID = $event['owner'];
|
$organizerUID = $event['owner'];
|
||||||
}
|
}
|
||||||
// do NOT use ORGANIZER for events without further participants or a different organizer
|
// do NOT use ORGANIZER for events without further participants or a different organizer
|
||||||
if (count($event['participants']) > 1 || !isset($event['participants'][$event['owner']]))
|
if (count($event['participants']) > 1 || !isset($event['participants'][$event['owner']]) || $event['owner'] != $this->user)
|
||||||
{
|
{
|
||||||
$attributes['ORGANIZER'] = $organizerURL;
|
$attributes['ORGANIZER'] = $organizerURL;
|
||||||
$parameters['ORGANIZER']['CN'] = $organizerCN;
|
$parameters['ORGANIZER']['CN'] = $organizerCN;
|
||||||
if (!empty($organizerUID))
|
if (!empty($organizerUID))
|
||||||
{
|
{
|
||||||
$parameters['ORGANIZER']['X-EGROUPWARE-UID'] = $organizerUID;
|
$parameters['ORGANIZER']['X-EGROUPWARE-UID'] = $organizerUID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'DTSTART':
|
case 'DTSTART':
|
||||||
if (empty($event['whole_day']))
|
if (empty($event['whole_day']))
|
||||||
@ -1014,12 +1014,12 @@ class calendar_ical extends calendar_boupdate
|
|||||||
foreach (is_array($value) && $parameters[$key]['VALUE']!='DATE' ? $value : array($value) as $valueID => $valueData)
|
foreach (is_array($value) && $parameters[$key]['VALUE']!='DATE' ? $value : array($value) as $valueID => $valueData)
|
||||||
{
|
{
|
||||||
$valueData = Api\Translation::convert($valueData,Api\Translation::charset(),$charset);
|
$valueData = Api\Translation::convert($valueData,Api\Translation::charset(),$charset);
|
||||||
$paramData = (array) Api\Translation::convert(is_array($value) ?
|
$paramData = (array) Api\Translation::convert(is_array($value) ?
|
||||||
$parameters[$key][$valueID] : $parameters[$key],
|
$parameters[$key][$valueID] : $parameters[$key],
|
||||||
Api\Translation::charset(),$charset);
|
Api\Translation::charset(),$charset);
|
||||||
$valuesData = (array) Api\Translation::convert($values[$key],
|
$valuesData = (array) Api\Translation::convert($values[$key],
|
||||||
Api\Translation::charset(),$charset);
|
Api\Translation::charset(),$charset);
|
||||||
$content = $valueData . implode(';', $valuesData);
|
$content = $valueData . implode(';', $valuesData);
|
||||||
|
|
||||||
if ($version == '1.0' && (preg_match('/[^\x20-\x7F]/', $content) ||
|
if ($version == '1.0' && (preg_match('/[^\x20-\x7F]/', $content) ||
|
||||||
($paramData['CN'] && preg_match('/[^\x20-\x7F]/', $paramData['CN']))))
|
($paramData['CN'] && preg_match('/[^\x20-\x7F]/', $paramData['CN']))))
|
||||||
@ -2325,7 +2325,7 @@ class calendar_ical extends calendar_boupdate
|
|||||||
* @param string|resource $_vcalData
|
* @param string|resource $_vcalData
|
||||||
* @param string $principalURL ='' Used for CalDAV imports
|
* @param string $principalURL ='' Used for CalDAV imports
|
||||||
* @param string $charset The encoding charset for $text. Defaults to
|
* @param string $charset The encoding charset for $text. Defaults to
|
||||||
* utf-8 for new format, iso-8859-1 for old format.
|
* utf-8 for new format, iso-8859-1 for old format.
|
||||||
* @return Iterator|array|boolean Iterator if resource given or array of events on success, false on failure
|
* @return Iterator|array|boolean Iterator if resource given or array of events on success, false on failure
|
||||||
*/
|
*/
|
||||||
function icaltoegw($_vcalData, $principalURL='', $charset=null)
|
function icaltoegw($_vcalData, $principalURL='', $charset=null)
|
||||||
@ -2744,10 +2744,10 @@ class calendar_ical extends calendar_boupdate
|
|||||||
// work around Ligthning sending @ as %40
|
// work around Ligthning sending @ as %40
|
||||||
$attributes['value'] = str_replace('%40', '@', $attributes['value']);
|
$attributes['value'] = str_replace('%40', '@', $attributes['value']);
|
||||||
if (isset($attributes['params']['PARTSTAT']))
|
if (isset($attributes['params']['PARTSTAT']))
|
||||||
{
|
{
|
||||||
$attributes['params']['STATUS'] = $attributes['params']['PARTSTAT'];
|
$attributes['params']['STATUS'] = $attributes['params']['PARTSTAT'];
|
||||||
}
|
}
|
||||||
if (isset($attributes['params']['STATUS']))
|
if (isset($attributes['params']['STATUS']))
|
||||||
{
|
{
|
||||||
$status = $this->status_ical2egw[strtoupper($attributes['params']['STATUS'])];
|
$status = $this->status_ical2egw[strtoupper($attributes['params']['STATUS'])];
|
||||||
if (empty($status)) $status = 'X';
|
if (empty($status)) $status = 'X';
|
||||||
@ -3165,7 +3165,7 @@ class calendar_ical extends calendar_boupdate
|
|||||||
array2string($event)."\n",3,$this->logfile);
|
array2string($event)."\n",3,$this->logfile);
|
||||||
}
|
}
|
||||||
//Horde::logMessage("vevent2egw:\n" . print_r($event, true),
|
//Horde::logMessage("vevent2egw:\n" . print_r($event, true),
|
||||||
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
|
// __FILE__, __LINE__, PEAR_LOG_DEBUG);
|
||||||
return $event;
|
return $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use EGroupware\Api;
|
use EGroupware\Api;
|
||||||
use EGroupware\Api\Link;
|
|
||||||
use EGroupware\Api\Framework;
|
|
||||||
use EGroupware\Api\Acl;
|
use EGroupware\Api\Acl;
|
||||||
use EGroupware\Api\Etemplate;
|
use EGroupware\Api\Etemplate;
|
||||||
|
use EGroupware\Api\Framework;
|
||||||
|
use EGroupware\Api\Link;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to generate the calendar listview and the search
|
* Class to generate the calendar listview and the search
|
||||||
@ -137,7 +137,7 @@ class calendar_uilist extends calendar_ui
|
|||||||
'filter_no_lang' => True, // I set no_lang for filter (=dont translate the options)
|
'filter_no_lang' => True, // I set no_lang for filter (=dont translate the options)
|
||||||
'no_filter2' => True, // I disable the 2. filter (params are the same as for filter)
|
'no_filter2' => True, // I disable the 2. filter (params are the same as for filter)
|
||||||
'no_cat' => True, // I disable the cat-selectbox
|
'no_cat' => True, // I disable the cat-selectbox
|
||||||
'filter' => 'after',
|
'filter' => 'month',
|
||||||
'order' => 'cal_start',// IO name of the column to sort after (optional for the sortheaders)
|
'order' => 'cal_start',// IO name of the column to sort after (optional for the sortheaders)
|
||||||
'sort' => 'ASC',// IO direction of the sort: 'ASC' or 'DESC'
|
'sort' => 'ASC',// IO direction of the sort: 'ASC' or 'DESC'
|
||||||
'default_cols' => '!week,weekday,cal_title,cal_description,recure,cal_location,cal_owner,cat_id,pm_id',
|
'default_cols' => '!week,weekday,cal_title,cal_description,recure,cal_location,cal_owner,cat_id,pm_id',
|
||||||
@ -150,6 +150,12 @@ class calendar_uilist extends calendar_ui
|
|||||||
}
|
}
|
||||||
$content['nm']['actions'] = $this->get_actions();
|
$content['nm']['actions'] = $this->get_actions();
|
||||||
|
|
||||||
|
// Skip first load if view is not listview
|
||||||
|
if($this->view && $this->view !== 'listview')
|
||||||
|
{
|
||||||
|
$content['nm']['num_rows'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($_GET['filter']) && in_array($_GET['filter'],array_keys($this->date_filters)))
|
if (isset($_GET['filter']) && in_array($_GET['filter'],array_keys($this->date_filters)))
|
||||||
{
|
{
|
||||||
$content['nm']['filter'] = $_GET['filter'];
|
$content['nm']['filter'] = $_GET['filter'];
|
||||||
@ -353,6 +359,7 @@ class calendar_uilist extends calendar_ui
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'month':
|
case 'month':
|
||||||
|
default:
|
||||||
$this->first = $this->bo->date2array($params['date'] ? $params['date'] : $this->date);
|
$this->first = $this->bo->date2array($params['date'] ? $params['date'] : $this->date);
|
||||||
$this->first['day'] = 1;
|
$this->first['day'] = 1;
|
||||||
unset($this->first['raw']);
|
unset($this->first['raw']);
|
||||||
@ -368,9 +375,7 @@ class calendar_uilist extends calendar_ui
|
|||||||
$params['enddate'] = Api\DateTime::to($this->last, Api\DateTime::ET2);
|
$params['enddate'] = Api\DateTime::to($this->last, Api\DateTime::ET2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// fall through to after given date
|
|
||||||
case 'after':
|
case 'after':
|
||||||
default:
|
|
||||||
$this->date = $params['startdate'] ? Api\DateTime::to($params['startdate'],'ts') : $this->date;
|
$this->date = $params['startdate'] ? Api\DateTime::to($params['startdate'],'ts') : $this->date;
|
||||||
$label = lang('After %1',$this->bo->long_date($this->date));
|
$label = lang('After %1',$this->bo->long_date($this->date));
|
||||||
$search_params['start'] = $this->date;
|
$search_params['start'] = $this->date;
|
||||||
|
@ -391,7 +391,7 @@ var CalendarApp = /** @class */ (function (_super) {
|
|||||||
//set onbeforeunload with json request to send request when the window gets close by X button
|
//set onbeforeunload with json request to send request when the window gets close by X button
|
||||||
if (content.data.lock_token) {
|
if (content.data.lock_token) {
|
||||||
window.onbeforeunload = function () {
|
window.onbeforeunload = function () {
|
||||||
this.egw.json('calendar.calendar_uiforms.ajax_unlock', [content.data.id, content.data.lock_token], null, true, null, null).sendRequest(true);
|
this.egw.json('calendar.calendar_uiforms.ajax_unlock', [content.data.id, content.data.lock_token], null, true, "keepalive", null).sendRequest();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,7 +292,7 @@ class CalendarApp extends EgwApp
|
|||||||
{
|
{
|
||||||
window.onbeforeunload = function () {
|
window.onbeforeunload = function () {
|
||||||
this.egw.json('calendar.calendar_uiforms.ajax_unlock',
|
this.egw.json('calendar.calendar_uiforms.ajax_unlock',
|
||||||
[content.data.id, content.data.lock_token],null,true,null,null).sendRequest(true);
|
[content.data.id, content.data.lock_token],null,true,"keepalive",null).sendRequest();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
121
calendar/tests/CalDAV/CalDAVcreateReadDelete.php
Normal file
121
calendar/tests/CalDAV/CalDAVcreateReadDelete.php
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CalDAV tests: create, read and delete an event
|
||||||
|
*
|
||||||
|
* @link https://www.egroupware.org
|
||||||
|
* @author Ralf Becker <rb@egroupware.org>
|
||||||
|
* @package calendar
|
||||||
|
* @subpackage tests
|
||||||
|
* @copyright (c) 2020 by Ralf Becker <rb@egroupware.org>
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace EGroupware\calendar;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../../../api/tests/CalDAVTest.php';
|
||||||
|
|
||||||
|
use EGroupware\Api\CalDAVTest;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
|
||||||
|
class CalDAVcreateReadDelete extends CalDAVTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test accessing CalDAV without authentication
|
||||||
|
*/
|
||||||
|
public function testNoAuth()
|
||||||
|
{
|
||||||
|
$response = $this->getClient([])->get($this->url('/'));
|
||||||
|
|
||||||
|
$this->assertHttpStatus(401, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test accessing CalDAV with authentication
|
||||||
|
*/
|
||||||
|
public function testAuth()
|
||||||
|
{
|
||||||
|
$response = $this->getClient()->get($this->url('/'));
|
||||||
|
|
||||||
|
$this->assertHttpStatus(200, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVENT_URL = '/demo/calendar/new-event-1233456789-new.ics';
|
||||||
|
const EVENT_ICAL = <<<EOICAL
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Europe/Berlin:20110406T210000
|
||||||
|
DTEND;TZID=Europe/Berlin:20110406T220000
|
||||||
|
DTSTAMP:20110406T183747Z
|
||||||
|
LAST-MODIFIED:20110406T183747Z
|
||||||
|
LOCATION:Somewhere
|
||||||
|
SUMMARY:Tonight
|
||||||
|
UID:new-event-1233456789-new
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
EOICAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an event
|
||||||
|
*/
|
||||||
|
public function testCreate()
|
||||||
|
{
|
||||||
|
$response = $this->getClient()->put($this->url(self::EVENT_URL), [
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Content-Type' => 'text/calendar',
|
||||||
|
'If-None-Match' => '*',
|
||||||
|
],
|
||||||
|
RequestOptions::BODY => self::EVENT_ICAL,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertHttpStatus(201, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read created event
|
||||||
|
*/
|
||||||
|
public function testRead()
|
||||||
|
{
|
||||||
|
$response = $this->getClient()->get($this->url(self::EVENT_URL));
|
||||||
|
|
||||||
|
$this->assertHttpStatus(200, $response);
|
||||||
|
$this->assertIcal(self::EVENT_ICAL, $response->getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete created event
|
||||||
|
*/
|
||||||
|
public function testDelete()
|
||||||
|
{
|
||||||
|
$response = $this->getClient()->delete($this->url(self::EVENT_URL));
|
||||||
|
|
||||||
|
$this->assertHttpStatus(204, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read created event
|
||||||
|
*/
|
||||||
|
public function testReadDeleted()
|
||||||
|
{
|
||||||
|
$response = $this->getClient()->get($this->url(self::EVENT_URL));
|
||||||
|
|
||||||
|
$this->assertHttpStatus(404, $response);
|
||||||
|
}
|
||||||
|
}
|
368
calendar/tests/CalDAV/CalDAVsingleDELETE.php
Normal file
368
calendar/tests/CalDAV/CalDAVsingleDELETE.php
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CalDAV tests: DELETE requests for non-series by Outlook CalDAV Synchronizer and other clients
|
||||||
|
*
|
||||||
|
* @link https://www.egroupware.org
|
||||||
|
* @author Ralf Becker <rb@egroupware.org>
|
||||||
|
* @package calendar
|
||||||
|
* @subpackage tests
|
||||||
|
* @copyright (c) 2020 by Ralf Becker <rb@egroupware.org>
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace EGroupware\calendar;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../../../api/tests/CalDAVTest.php';
|
||||||
|
|
||||||
|
use EGroupware\Api\CalDAVTest;
|
||||||
|
use GuzzleHttp\RequestOptions;
|
||||||
|
use EGroupware\Api\Acl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CalDAVsingleDELETE
|
||||||
|
*
|
||||||
|
* This tests check all sorts of DELETE requests by organizer and attendees, with and without (delete) rights on the organizer.
|
||||||
|
*
|
||||||
|
* For CalDAV Synchronizer, which does not distingues between deleting and rejecting events, we only allow the
|
||||||
|
* organizer to delete an event.
|
||||||
|
*
|
||||||
|
* @package EGroupware\calendar
|
||||||
|
* @covers \calendar_groupdav::delete()
|
||||||
|
* @uses \calendar_groupdav::put()
|
||||||
|
* @uses \calendar_groupdav::get()
|
||||||
|
*/
|
||||||
|
class CalDAVsingleDELETE extends CalDAVTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Users and their ACL for the test
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $users = [
|
||||||
|
'boss' => [],
|
||||||
|
'secretary' => [
|
||||||
|
'rights' => [
|
||||||
|
'boss' => Acl::READ|Acl::ADD|Acl::EDIT|Acl::DELETE,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'other' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create some users incl. ACL
|
||||||
|
*/
|
||||||
|
public static function setUpBeforeClass() : void
|
||||||
|
{
|
||||||
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
|
self::createUsersACL(self::$users, 'calendar');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check created users
|
||||||
|
*/
|
||||||
|
public function testPrincipals()
|
||||||
|
{
|
||||||
|
foreach(self::$users as $user => &$data)
|
||||||
|
{
|
||||||
|
$response = $this->getClient()->propfind($this->url('/principals/users/'.$user.'/'), [
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Depth' => 0,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$this->assertHttpStatus(207, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVENT_BOSS_ATTENDEE_ORGANIZER_URL = '/other/calendar/new-event-boss-attendee-123456789-new.ics';
|
||||||
|
const EVENT_BOSS_ATTENDEE_URL = '/boss/calendar/new-event-boss-attendee-123456789-new.ics';
|
||||||
|
const EVENT_BOSS_ATTENDEE_ICAL = <<<EOICAL
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Europe/Berlin:20110406T210000
|
||||||
|
DTEND;TZID=Europe/Berlin:20110406T220000
|
||||||
|
DTSTAMP:20110406T183747Z
|
||||||
|
LAST-MODIFIED:20110406T183747Z
|
||||||
|
LOCATION:Somewhere
|
||||||
|
SUMMARY:Tonight
|
||||||
|
ORGANIZER;CN="Other User":mailto:other@example.org
|
||||||
|
ATTENDEE;CN="Other User";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:other@example.org
|
||||||
|
ATTENDEE;CN="Boss User";CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:boss@example.org
|
||||||
|
UID:new-event-boss-attendee-123456789-new
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
EOICAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check secretary deletes in boss's calendar event he is an attendee / invited
|
||||||
|
*
|
||||||
|
* @throws \Horde_Icalendar_Exception
|
||||||
|
*/
|
||||||
|
public function testSecretaryDeletesBossAttendee()
|
||||||
|
{
|
||||||
|
// create invitation by organizer
|
||||||
|
$response = $this->getClient('other')->put($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL), [
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Content-Type' => 'text/calendar',
|
||||||
|
'Prefer' => 'return=representation'
|
||||||
|
],
|
||||||
|
RequestOptions::BODY => self::EVENT_BOSS_ATTENDEE_ICAL,
|
||||||
|
]);
|
||||||
|
$this->assertHttpStatus([200,201], $response);
|
||||||
|
$this->assertIcal(self::EVENT_BOSS_ATTENDEE_ICAL, $response->getBody());
|
||||||
|
|
||||||
|
// secretrary deletes event in boss's calendar
|
||||||
|
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_BOSS_ATTENDEE_URL));
|
||||||
|
$this->assertHttpStatus(204, $response, 'Secretary delete/rejects for boss');
|
||||||
|
|
||||||
|
// use organizer to check event still exists and boss rejected
|
||||||
|
$response = $this->getClient('other')->get($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(200, $response, 'Check event still exists after DELETE in attendee calendar');
|
||||||
|
$this->assertIcal(self::EVENT_BOSS_ATTENDEE_ICAL, $response->getBody(),
|
||||||
|
'Boss should have declined the invitation',
|
||||||
|
['vEvent' => [['ATTENDEE' => ['mailto:boss@example.org' => ['PARTSTAT' => 'DECLINED', 'RSVP' => 'FALSE']]]]]
|
||||||
|
);
|
||||||
|
|
||||||
|
// secretary tries to delete event in organizers calendar
|
||||||
|
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(403, $response, 'Secretary not allowed to delete for organizer');
|
||||||
|
|
||||||
|
// boss deletes/rejects event in his calendar
|
||||||
|
$response = $this->getClient('boss')->delete($this->url(self::EVENT_BOSS_ATTENDEE_URL));
|
||||||
|
$this->assertHttpStatus(204, $response, 'Boss deletes/rejects in his calendar');
|
||||||
|
|
||||||
|
// boss deletes/rejects event in organizers calendar
|
||||||
|
$response = $this->getClient('boss')->delete($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(204, $response, 'Boss deletes/rejects in organizers calendar');
|
||||||
|
|
||||||
|
// use organizer to delete event
|
||||||
|
$response = $this->getClient('other')->delete($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(204, $response);
|
||||||
|
|
||||||
|
// use organizer to check event deleted
|
||||||
|
$response = $this->getClient('other')->get($this->url(self::EVENT_BOSS_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVENT_BOSS_ORGANIZER_URL = '/boss/calendar/new-event-boss-organizer-123456789-new.ics';
|
||||||
|
const EVENT_BOSS_ORGANIZER_OTHER_URL = '/other/calendar/new-event-boss-organizer-123456789-new.ics';
|
||||||
|
const EVENT_BOSS_ORGANIZER_ICAL = <<<EOICAL
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Europe/Berlin:20110406T210000
|
||||||
|
DTEND;TZID=Europe/Berlin:20110406T220000
|
||||||
|
DTSTAMP:20110406T183747Z
|
||||||
|
LAST-MODIFIED:20110406T183747Z
|
||||||
|
LOCATION:Somewhere
|
||||||
|
SUMMARY:Tonight
|
||||||
|
ORGANIZER;CN="Boss User":mailto:boss@example.org
|
||||||
|
ATTENDEE;CN="Boss User";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:boss@example.org
|
||||||
|
ATTENDEE;CN="Other User";CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:other@example.org
|
||||||
|
UID:new-event-boss-organizer-123456789-new
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
EOICAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check secretary deletes for boss, which is organizer of event
|
||||||
|
*
|
||||||
|
* @throws \Horde_Icalendar_Exception
|
||||||
|
*/
|
||||||
|
public function testSecretaryDeletesBossOrganizer()
|
||||||
|
{
|
||||||
|
// create invitation by boss as organizer
|
||||||
|
$response = $this->getClient('boss')->put($this->url(self::EVENT_BOSS_ORGANIZER_URL), [
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Content-Type' => 'text/calendar',
|
||||||
|
'Prefer' => 'return=representation'
|
||||||
|
],
|
||||||
|
RequestOptions::BODY => self::EVENT_BOSS_ORGANIZER_ICAL,
|
||||||
|
]);
|
||||||
|
$this->assertHttpStatus([200,201], $response);
|
||||||
|
$this->assertIcal(self::EVENT_BOSS_ORGANIZER_ICAL, $response->getBody());
|
||||||
|
|
||||||
|
// attendee deletes/rejects event in his calendar
|
||||||
|
$response = $this->getClient('other')->delete($this->url(self::EVENT_BOSS_ORGANIZER_OTHER_URL));
|
||||||
|
$this->assertHttpStatus(204, $response);
|
||||||
|
|
||||||
|
// secretrary deletes event in boss's calendar
|
||||||
|
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(204, $response, 'Secretary deletes for boss');
|
||||||
|
|
||||||
|
// use organizer/boss to check event deleted
|
||||||
|
$response = $this->getClient('boss')->get($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(404, $response, "Check event deleted by secretary");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check organizer (boss) can delete event in his calendar
|
||||||
|
*
|
||||||
|
* @throws \Horde_Icalendar_Exception
|
||||||
|
*/
|
||||||
|
public function testOrganizerDeletes()
|
||||||
|
{
|
||||||
|
// create invitation by boss as organizer
|
||||||
|
$response = $this->getClient('boss')->put($this->url(self::EVENT_BOSS_ORGANIZER_URL), [
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Content-Type' => 'text/calendar',
|
||||||
|
'Prefer' => 'return=representation'
|
||||||
|
],
|
||||||
|
RequestOptions::BODY => self::EVENT_BOSS_ORGANIZER_ICAL,
|
||||||
|
]);
|
||||||
|
$this->assertHttpStatus([200,201], $response);
|
||||||
|
$this->assertIcal(self::EVENT_BOSS_ORGANIZER_ICAL, $response->getBody());
|
||||||
|
|
||||||
|
// organizer deletes event in his calendar
|
||||||
|
$response = $this->getClient('boss')->delete($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(204, $response, 'Organizer deletes');
|
||||||
|
|
||||||
|
// use organizer/boss to check event deleted
|
||||||
|
$response = $this->getClient('boss')->get($this->url(self::EVENT_BOSS_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
||||||
|
|
||||||
|
// use attendee to check event deleted
|
||||||
|
$response = $this->getClient('other')->get($this->url(self::EVENT_BOSS_ORGANIZER_OTHER_URL));
|
||||||
|
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
||||||
|
}
|
||||||
|
|
||||||
|
const EVENT_SECRETARY_ATTENDEE_URL = '/secretary/calendar/new-event-secreatary-attendee-123456789-new.ics';
|
||||||
|
const EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL = '/boss/calendar/new-event-secreatary-attendee-123456789-new.ics';
|
||||||
|
const EVENT_SECRETARY_ATTENDEE_ICAL = <<<EOICAL
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Berlin
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Europe/Berlin:20110406T210000
|
||||||
|
DTEND;TZID=Europe/Berlin:20110406T220000
|
||||||
|
DTSTAMP:20110406T183747Z
|
||||||
|
LAST-MODIFIED:20110406T183747Z
|
||||||
|
LOCATION:Somewhere
|
||||||
|
SUMMARY:Tonight
|
||||||
|
ORGANIZER;CN="Boss User":mailto:boss@example.org
|
||||||
|
ATTENDEE;CN="Boss User";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:boss@example.org
|
||||||
|
ATTENDEE;CN="Secretary User";CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:mailto:secretary@example.org
|
||||||
|
UID:new-event-secreatary-attendee-123456789-new
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
EOICAL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check secretary as attendee deletes event
|
||||||
|
*
|
||||||
|
* @throws \Horde_Icalendar_Exception
|
||||||
|
*/
|
||||||
|
public function testSecretaryAttendeeDeletes()
|
||||||
|
{
|
||||||
|
// create invitation by boss as organizer
|
||||||
|
$response = $this->getClient('boss')->put($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL), [
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Content-Type' => 'text/calendar',
|
||||||
|
'Prefer' => 'return=representation'
|
||||||
|
],
|
||||||
|
RequestOptions::BODY => self::EVENT_SECRETARY_ATTENDEE_ICAL,
|
||||||
|
]);
|
||||||
|
$this->assertHttpStatus([200,201], $response);
|
||||||
|
$this->assertIcal(self::EVENT_SECRETARY_ATTENDEE_ICAL, $response->getBody());
|
||||||
|
|
||||||
|
// secretary deletes in her calendar
|
||||||
|
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_SECRETARY_ATTENDEE_URL));
|
||||||
|
$this->assertHttpStatus(204, $response, 'Secretary (attendee) deletes');
|
||||||
|
|
||||||
|
// use organizer to check it's really deleted
|
||||||
|
$response = $this->getClient('boss')->get($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(404, $response, "Check event deleted by secretary");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check secretary as attendee deletes event with CalDAVSynchronizer
|
||||||
|
*
|
||||||
|
* @throws \Horde_Icalendar_Exception
|
||||||
|
*/
|
||||||
|
public function testSecretaryAttendeeDeletesCalDAVSynchronizer()
|
||||||
|
{
|
||||||
|
// create invitation by boss as organizer
|
||||||
|
$response = $this->getClient('boss')->put($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL), [
|
||||||
|
RequestOptions::HEADERS => [
|
||||||
|
'Content-Type' => 'text/calendar',
|
||||||
|
'Prefer' => 'return=representation'
|
||||||
|
],
|
||||||
|
RequestOptions::BODY => self::EVENT_SECRETARY_ATTENDEE_ICAL,
|
||||||
|
]);
|
||||||
|
$this->assertHttpStatus([200,201], $response);
|
||||||
|
$this->assertIcal(self::EVENT_SECRETARY_ATTENDEE_ICAL, $response->getBody());
|
||||||
|
|
||||||
|
// secretary deletes in her calendar with CalDAVSynchronizer
|
||||||
|
$response = $this->getClient('secretary')->delete($this->url(self::EVENT_SECRETARY_ATTENDEE_URL),
|
||||||
|
[RequestOptions::HEADERS => ['User-Agent' => 'CalDAVSynchronizer']]);
|
||||||
|
$this->assertHttpStatus(204, $response, 'Secretary (attendee) deletes/rejects');
|
||||||
|
|
||||||
|
// use organizer to check it's NOT deleted, as CalDAVSynchronizer / Outlook does not distinguish between reject and delete
|
||||||
|
$response = $this->getClient('boss')->get($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(200, $response, "Check event NOT deleted by secretary");
|
||||||
|
$this->assertIcal(self::EVENT_SECRETARY_ATTENDEE_ICAL, $response->getBody(),
|
||||||
|
'Secretary should have declined the invitation',
|
||||||
|
['vEvent' => [['ATTENDEE' => ['mailto:secretary@example.org' => ['PARTSTAT' => 'DECLINED', 'RSVP' => 'FALSE']]]]]
|
||||||
|
);
|
||||||
|
|
||||||
|
// organizer deletes in his calendar with CalDAVSynchronizer
|
||||||
|
$response = $this->getClient('boss')->delete($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL),
|
||||||
|
[RequestOptions::HEADERS => ['User-Agent' => 'CalDAVSynchronizer']]);
|
||||||
|
$this->assertHttpStatus(204, $response, 'Organizer deletes');
|
||||||
|
|
||||||
|
// use organizer to check it's deleted, as CalDAVSynchronizer / Outlook should still delete for organizer
|
||||||
|
$response = $this->getClient('boss')->get($this->url(self::EVENT_SECRETARY_ATTENDEE_ORGANIZER_URL));
|
||||||
|
$this->assertHttpStatus(404, $response, "Check event deleted by organizer");
|
||||||
|
}
|
||||||
|
}
|
@ -25,17 +25,17 @@ class ImportParticipantsTest extends \EGroupware\Api\AppTest
|
|||||||
// Method under test with modified access
|
// Method under test with modified access
|
||||||
private $parse_method = null;
|
private $parse_method = null;
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
}
|
}
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
$this->bo = new \calendar_bo();
|
$this->bo = new \calendar_bo();
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class ImportParticipantsTest extends \EGroupware\Api\AppTest
|
|||||||
$this->parse_method->setAccessible(true);
|
$this->parse_method->setAccessible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,17 +24,17 @@ class ResetParticipantStatusTest extends \EGroupware\Api\AppTest
|
|||||||
// Method under test with modified access
|
// Method under test with modified access
|
||||||
private $check_method = null;
|
private $check_method = null;
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
}
|
}
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
$this->bo = new \calendar_boupdate();
|
$this->bo = new \calendar_boupdate();
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ class ResetParticipantStatusTest extends \EGroupware\Api\AppTest
|
|||||||
$this->check_method->setAccessible(true);
|
$this->check_method->setAccessible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
|
|
||||||
// Clean up user
|
// Clean up user
|
||||||
|
@ -28,20 +28,20 @@ class TimezoneTest extends \EGroupware\Api\AppTest {
|
|||||||
protected $recur_end;
|
protected $recur_end;
|
||||||
protected $cal_id;
|
protected $cal_id;
|
||||||
|
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
static::$server_tz = date_default_timezone_get();
|
static::$server_tz = date_default_timezone_get();
|
||||||
}
|
}
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
date_default_timezone_set(static::$server_tz);
|
date_default_timezone_set(static::$server_tz);
|
||||||
|
|
||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
$this->bo = new \calendar_boupdate();
|
$this->bo = new \calendar_boupdate();
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ class TimezoneTest extends \EGroupware\Api\AppTest {
|
|||||||
$this->recur_end = new Api\DateTime(mktime(0,0,0,date('m'), date('d') + static::RECUR_DAYS, date('Y')));
|
$this->recur_end = new Api\DateTime(mktime(0,0,0,date('m'), date('d') + static::RECUR_DAYS, date('Y')));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
$this->bo->delete($this->cal_id);
|
$this->bo->delete($this->cal_id);
|
||||||
// Delete again to remove from delete history
|
// Delete again to remove from delete history
|
||||||
|
@ -45,12 +45,12 @@
|
|||||||
],
|
],
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.0"
|
"php": "7.2"
|
||||||
},
|
},
|
||||||
"sort-packages": true
|
"sort-packages": true
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.0,<=8.0.0alpha1",
|
"php": ">=7.2,<=8.0.0alpha1",
|
||||||
"ext-gd": "*",
|
"ext-gd": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mysqli": "*",
|
"ext-mysqli": "*",
|
||||||
@ -101,8 +101,10 @@
|
|||||||
"tinymce/tinymce": "^5.0"
|
"tinymce/tinymce": "^5.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"guzzlehttp/guzzle": "^6.5",
|
||||||
|
"phpunit/phpunit": "~8"
|
||||||
},
|
},
|
||||||
"suggests": {
|
"suggest": {
|
||||||
"ext-opcache": "Opcode cache to speed up PHP",
|
"ext-opcache": "Opcode cache to speed up PHP",
|
||||||
"ext-apcu": "Used for in-memory caching",
|
"ext-apcu": "Used for in-memory caching",
|
||||||
"ext-tidy": "Used for tidying up docx templates",
|
"ext-tidy": "Used for tidying up docx templates",
|
||||||
|
1854
composer.lock
generated
1854
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -14,6 +14,9 @@ The provided docker-compose.yml will run the following container:
|
|||||||
* **egroupware-nginx** running Nginx as webserver (by default http only on port 8080)
|
* **egroupware-nginx** running Nginx as webserver (by default http only on port 8080)
|
||||||
* **egroupware-db** latest MariaDB 10.4
|
* **egroupware-db** latest MariaDB 10.4
|
||||||
* **egroupware-watchtower** updating all above container automatically daily at 4am
|
* **egroupware-watchtower** updating all above container automatically daily at 4am
|
||||||
|
* **collabora-key** Collabora Online Office
|
||||||
|
* **rocketchat** Rocket.Chat server
|
||||||
|
* **rocketchat-mongodb** MongoDB for Rocket.Chat
|
||||||
```
|
```
|
||||||
version: '3'
|
version: '3'
|
||||||
volumes:
|
volumes:
|
||||||
@ -36,6 +39,29 @@ volumes:
|
|||||||
# # location of deprecated EGroupware packages like Wiki, SiteMgr, KnowledgeBase
|
# # location of deprecated EGroupware packages like Wiki, SiteMgr, KnowledgeBase
|
||||||
# device: /usr/share/egroupware
|
# device: /usr/share/egroupware
|
||||||
# #device: $PWD/extra
|
# #device: $PWD/extra
|
||||||
|
# collabora-config
|
||||||
|
collabora-config:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
# to upgrade an existing non-docker installation most easy is to use the existing
|
||||||
|
# data directory /var/lib/egroupware AND the host database see below
|
||||||
|
#device: /var/lib/egroupware/default/loolwsd
|
||||||
|
# otherwise data is stored in data subdirectory of the current directory
|
||||||
|
device: $PWD/data/default/loolwsd
|
||||||
|
# store Rocket.Chat MongoDB on an (internal) Volume
|
||||||
|
mongo:
|
||||||
|
# directory to store MongoDB dumps
|
||||||
|
rocketchat-dumps:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: $PWD/data/default/rocketchat/dump
|
||||||
|
rocketchat-uploads:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: $PWD/data/default/rocketchat/uploads
|
||||||
services:
|
services:
|
||||||
egroupware:
|
egroupware:
|
||||||
image: egroupware/egroupware:latest
|
image: egroupware/egroupware:latest
|
||||||
@ -52,13 +78,18 @@ services:
|
|||||||
# 1. comment out the whole db service below AND
|
# 1. comment out the whole db service below AND
|
||||||
# 2. set EGW_DB_HOST=localhost AND
|
# 2. set EGW_DB_HOST=localhost AND
|
||||||
# 3. uncomment the next line and modify the host path (first one), it depends on your distro:
|
# 3. uncomment the next line and modify the host path (first one), it depends on your distro:
|
||||||
# - RHEL/CentOS /var/lib/mysql/mysql.sock
|
# - RHEL/CentOS /var/lib/mysql/mysql.sock:/var/run/mysqld/mysqld.sock
|
||||||
# - openSUSE/SLE /var/run/mysql/mysql.sock
|
# - openSUSE/SLE /var/run/mysql/mysql.sock:/var/run/mysqld/mysqld.sock
|
||||||
# - Debian/Ubuntu /var/run/mysqld/mysqld.sock
|
# - Debian/Ubuntu /var/run/mysqld:/var/run/mysqld
|
||||||
#- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
|
#- /var/run/mysqld:/var/run/mysqld
|
||||||
|
# private CA so egroupware can validate your certificate to talk to Collabora or Rocket.Chat
|
||||||
|
# multiple certificates (eg. a chain) have to be single files in a directory, with one named private-ca.crt!
|
||||||
|
#- /etc/egroupware-docker/private-ca.crt:/usr/local/share/ca-certificates/private-ca.crt:ro
|
||||||
environment:
|
environment:
|
||||||
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
|
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
|
||||||
- EGW_DB_HOST=db
|
- EGW_DB_HOST=db
|
||||||
|
# grant host is needed for NOT using localhost / unix domain socket for MySQL/MariaDB
|
||||||
|
- EGW_DB_GRANT_HOST=172.%
|
||||||
# for internal db service you should to specify a root password here AND in db service
|
# for internal db service you should to specify a root password here AND in db service
|
||||||
# a database "egroupware" with a random password is created for you on installation (password is stored in header.inc.php in data directory)
|
# a database "egroupware" with a random password is created for you on installation (password is stored in header.inc.php in data directory)
|
||||||
#- EGW_DB_ROOT=root
|
#- EGW_DB_ROOT=root
|
||||||
@ -133,4 +164,66 @@ services:
|
|||||||
command: --schedule "0 0 4 * * *"
|
command: --schedule "0 0 4 * * *"
|
||||||
container_name: egroupware-watchtower
|
container_name: egroupware-watchtower
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
# Collabora Online Office
|
||||||
|
collabora-key:
|
||||||
|
image: "quay.io/egroupware/collabora-key:stable"
|
||||||
|
#image: collabora/code:latest
|
||||||
|
# needs to be initialised via: docker run --rm -v dev_collabora-config:/mnt --entrypoint '/bin/cp -r /etc/loolwsd /mnt' quay.io/egroupware/collabora-key:stable
|
||||||
|
volumes:
|
||||||
|
- collabora-config:/etc/loolwsd
|
||||||
|
# dont try to regenerate the (not used certificate) as volumn is readonly
|
||||||
|
environment:
|
||||||
|
- DONT_GEN_SSL_CERT=1
|
||||||
|
restart: always
|
||||||
|
container_name: collabora-key
|
||||||
|
# set the ip-address of your docker host AND your official DNS name so Collabora
|
||||||
|
# can access EGroupware without the need to go over your firewall
|
||||||
|
#extra_hosts:
|
||||||
|
#- "my.host.name:ip-address"
|
||||||
|
|
||||||
|
# Rocket.Chat server
|
||||||
|
rocketchat:
|
||||||
|
image: rocketchat/rocket.chat:latest
|
||||||
|
command: bash -c 'for i in `seq 1 30`; do node main.js && s=$$? && break || s=$$?; echo "Tried $$i times. Waiting 5 secs..."; sleep 5; done; (exit $$s)'
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- rocketchat-uploads:/app/uploads
|
||||||
|
# if EGroupware uses a certificate from a private CA, OAuth authentication will fail, you need to:
|
||||||
|
# - have the CA certificate stored at /etc/egroupware-docker/private-ca.crt
|
||||||
|
# - uncomment the next 2 lines about the private CA:
|
||||||
|
# - /etc/egroupware-docker/private-ca.crt:/usr/local/share/ca-certificates/private-ca.crt:ro
|
||||||
|
environment:
|
||||||
|
# - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/private-ca.crt
|
||||||
|
# IMPORTANT: change ROOT_URL to your actual url eg. https://domain.com/rocketchat
|
||||||
|
- ROOT_URL=http://localhost/rocketchat
|
||||||
|
- PORT=3000
|
||||||
|
- MONGO_URL=mongodb://mongo:27017/rocketchat
|
||||||
|
- MONGO_OPLOG_URL=mongodb://mongo:27017/local
|
||||||
|
# - HTTP_PROXY=http://proxy.domain.com
|
||||||
|
# - HTTPS_PROXY=http://proxy.domain.com
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
container_name: rocketchat
|
||||||
|
# set the ip-address of your docker host AND your official DNS name so Rocket.Chat
|
||||||
|
# can access EGroupware without the need to go over your firewall
|
||||||
|
#extra_hosts:
|
||||||
|
#- "my.host.name:ip-address"
|
||||||
|
|
||||||
|
# MongoDB for Rocket.Chat
|
||||||
|
mongo:
|
||||||
|
image: mongo:4.0
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- mongo:/data/db
|
||||||
|
- rocketchat-dumps:/dump
|
||||||
|
command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1
|
||||||
|
container_name: rocketchat-mongo
|
||||||
|
# this container's job is just run the command to initialize the replica set.
|
||||||
|
# it will run the command and remove himself (it will not stay running)
|
||||||
|
mongo-init-replica:
|
||||||
|
image: mongo:4.0
|
||||||
|
command: 'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})\" && s=$$? && break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
```
|
```
|
||||||
|
@ -7,6 +7,10 @@ The container and docker-compose.yml file in this directory are the most easy wa
|
|||||||
* data: EGroupware stores it's files here, by default $PWD/data subdirectory, can also be your existing /var/lib/egroupware
|
* data: EGroupware stores it's files here, by default $PWD/data subdirectory, can also be your existing /var/lib/egroupware
|
||||||
* db: volume for MariaDB (should be NOT a directory under Mac OS and Windows for performance reasons!)
|
* db: volume for MariaDB (should be NOT a directory under Mac OS and Windows for performance reasons!)
|
||||||
* sessions: volume for sessions, internal no need to change
|
* sessions: volume for sessions, internal no need to change
|
||||||
|
* sources-push: swoolpush sub-directory of sources
|
||||||
|
* collabora-config: /etc/loolwsd for Collabora container, by default $PWD/data/default/loolwsd
|
||||||
|
* rocketchat-uploads: Upload directory for Rocket.Chat, by default $PWD/data/default/rocketchat/uploads
|
||||||
|
* rocketchat-dumps: Dump directory for MongoDB, by default $PWD/data/default/rocketchat/dump
|
||||||
|
|
||||||
### It runs the following containers:
|
### It runs the following containers:
|
||||||
* egroupware: php-fpm
|
* egroupware: php-fpm
|
||||||
@ -14,11 +18,9 @@ The container and docker-compose.yml file in this directory are the most easy wa
|
|||||||
* egroupware-db: MariaDB
|
* egroupware-db: MariaDB
|
||||||
* egroupware-push: PHP Swoole based push server
|
* egroupware-push: PHP Swoole based push server
|
||||||
* egroupware-watchtower: to automatic keeps the containers up to date
|
* egroupware-watchtower: to automatic keeps the containers up to date
|
||||||
|
* collabora: Collabora Online Office
|
||||||
Planned, but not yet there:
|
* rocketchat: Rocket.Chat
|
||||||
* egroupware-collabora: Collabora Online Office
|
* rocketchat-mongo: MongoDB for Rocket.Chat
|
||||||
* egroupware-rocketchat: Rocket.Chat
|
|
||||||
* egroupware-mongo: MongoDB for Rocket.Chat
|
|
||||||
|
|
||||||
### Usage:
|
### Usage:
|
||||||
```
|
```
|
||||||
|
@ -40,6 +40,20 @@ volumes:
|
|||||||
sessions:
|
sessions:
|
||||||
# cache files from compose, npm and yarn (actually /root inside the container)
|
# cache files from compose, npm and yarn (actually /root inside the container)
|
||||||
cache:
|
cache:
|
||||||
|
# store Rocket.Chat MongoDB on an (internal) Volume
|
||||||
|
mongo:
|
||||||
|
# directory to store MongoDB dumps
|
||||||
|
rocketchat-dumps:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: $PWD/data/default/rocketchat/dump
|
||||||
|
rocketchat-uploads:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: $PWD/data/default/rocketchat/uploads
|
||||||
|
|
||||||
services:
|
services:
|
||||||
egroupware:
|
egroupware:
|
||||||
# you can also use tags like: 7.3, 7.3.12 or 7.4
|
# you can also use tags like: 7.3, 7.3.12 or 7.4
|
||||||
@ -56,10 +70,13 @@ services:
|
|||||||
# 1. comment out the whole db service below AND
|
# 1. comment out the whole db service below AND
|
||||||
# 2. set EGW_DB_HOST=localhost AND
|
# 2. set EGW_DB_HOST=localhost AND
|
||||||
# 3. uncomment the next line and modify the host path (first one), it depends on your distro:
|
# 3. uncomment the next line and modify the host path (first one), it depends on your distro:
|
||||||
# - RHEL/CentOS /var/lib/mysql/mysql.sock
|
# - RHEL/CentOS /var/lib/mysql/mysql.sock:/var/run/mysqld/mysqld.sock
|
||||||
# - openSUSE/SLE /var/run/mysql/mysql.sock
|
# - openSUSE/SLE /var/run/mysql/mysql.sock:/var/run/mysqld/mysqld.sock
|
||||||
# - Debian/Ubuntu /var/run/mysqld/mysqld.sock
|
# - Debian/Ubuntu /var/run/mysqld:/var/run/mysqld
|
||||||
#- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
|
#- /var/run/mysqld:/var/run/mysqld
|
||||||
|
# private CA so egroupware can validate your certificate to talk to Collabora or Rocket.Chat
|
||||||
|
# multiple certificates (eg. a chain) have to be single files in a directory, with one named private-ca.crt!
|
||||||
|
#- /etc/egroupware-docker/private-ca.crt:/usr/local/share/ca-certificates/private-ca.crt:ro
|
||||||
environment:
|
environment:
|
||||||
#
|
#
|
||||||
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
|
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
|
||||||
@ -122,6 +139,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- egroupware
|
- egroupware
|
||||||
- collabora-key
|
- collabora-key
|
||||||
|
- rocketchat
|
||||||
container_name: egroupware-nginx
|
container_name: egroupware-nginx
|
||||||
|
|
||||||
# run an own MariaDB:10.4 (you can use EGroupware's database backup and restore to add your existing database)
|
# run an own MariaDB:10.4 (you can use EGroupware's database backup and restore to add your existing database)
|
||||||
@ -185,3 +203,48 @@ services:
|
|||||||
# can access EGroupware without the need to go over your firewall
|
# can access EGroupware without the need to go over your firewall
|
||||||
#extra_hosts:
|
#extra_hosts:
|
||||||
#- "my.host.name:ip-address"
|
#- "my.host.name:ip-address"
|
||||||
|
|
||||||
|
# Rocket.Chat server
|
||||||
|
rocketchat:
|
||||||
|
image: rocketchat/rocket.chat:latest
|
||||||
|
command: bash -c 'for i in `seq 1 30`; do node main.js && s=$$? && break || s=$$?; echo "Tried $$i times. Waiting 5 secs..."; sleep 5; done; (exit $$s)'
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- rocketchat-uploads:/app/uploads
|
||||||
|
# if EGroupware uses a certificate from a private CA, OAuth authentication will fail, you need to:
|
||||||
|
# - have the CA certificate stored at /etc/egroupware-docker/private-ca.crt
|
||||||
|
# - uncomment the next 2 lines about the private CA:
|
||||||
|
# - /etc/egroupware-docker/private-ca.crt:/usr/local/share/ca-certificates/private-ca.crt:ro
|
||||||
|
environment:
|
||||||
|
# - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/private-ca.crt
|
||||||
|
# IMPORTANT: change ROOT_URL to your actual url eg. https://domain.com/rocketchat
|
||||||
|
- ROOT_URL=http://localhost/rocketchat
|
||||||
|
- PORT=3000
|
||||||
|
- MONGO_URL=mongodb://mongo:27017/rocketchat
|
||||||
|
- MONGO_OPLOG_URL=mongodb://mongo:27017/local
|
||||||
|
# - HTTP_PROXY=http://proxy.domain.com
|
||||||
|
# - HTTPS_PROXY=http://proxy.domain.com
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
container_name: rocketchat
|
||||||
|
# set the ip-address of your docker host AND your official DNS name so Rocket.Chat
|
||||||
|
# can access EGroupware without the need to go over your firewall
|
||||||
|
#extra_hosts:
|
||||||
|
#- "my.host.name:ip-address"
|
||||||
|
|
||||||
|
# MongoDB for Rocket.Chat
|
||||||
|
mongo:
|
||||||
|
image: mongo:4.0
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- mongo:/data/db
|
||||||
|
- rocketchat-dumps:/dump
|
||||||
|
command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1
|
||||||
|
container_name: rocketchat-mongo
|
||||||
|
# this container's job is just run the command to initialize the replica set.
|
||||||
|
# it will run the command and remove himself (it will not stay running)
|
||||||
|
mongo-init-replica:
|
||||||
|
image: mongo:4.0
|
||||||
|
command: 'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})\" && s=$$? && break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
@ -42,8 +42,8 @@ server {
|
|||||||
fastcgi_read_timeout 60m;
|
fastcgi_read_timeout 60m;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
fastcgi_split_path_info ^((?U).+\.php)(.*)$;
|
fastcgi_split_path_info ^((?U).+\.php)(.*)$;
|
||||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
|
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
|
||||||
# standard Nginx
|
# standard Nginx
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME /var/www/egroupware$1;
|
fastcgi_param SCRIPT_FILENAME /var/www/egroupware$1;
|
||||||
@ -80,9 +80,9 @@ server {
|
|||||||
fastcgi_read_timeout 60m;
|
fastcgi_read_timeout 60m;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
# ActiveSync support
|
# ActiveSync support
|
||||||
@ -92,8 +92,8 @@ server {
|
|||||||
fastcgi_read_timeout 60m;
|
fastcgi_read_timeout 60m;
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
fastcgi_split_path_info ^((?U).+\.php)(.*)$;
|
fastcgi_split_path_info ^((?U).+\.php)(.*)$;
|
||||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
|
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_param SCRIPT_FILENAME /var/www/egroupware/activesync/index.php;
|
fastcgi_param SCRIPT_FILENAME /var/www/egroupware/activesync/index.php;
|
||||||
}
|
}
|
||||||
@ -110,25 +110,38 @@ server {
|
|||||||
location = / {
|
location = / {
|
||||||
return 301 $scheme://$http_host/egroupware/index.php;
|
return 301 $scheme://$http_host/egroupware/index.php;
|
||||||
}
|
}
|
||||||
|
# redirect /egroupware to /egroupware/
|
||||||
|
location = /egroupware {
|
||||||
|
return 301 $scheme://$host/egroupware/index.php;
|
||||||
|
}
|
||||||
|
|
||||||
# Collabora sniplet meant to be included in server block of EGroupware vhost
|
# Collabora sniplet meant to be included in server block of EGroupware vhost
|
||||||
# static files
|
# static files
|
||||||
location ^~ /loleaflet {
|
location ^~ /loleaflet {
|
||||||
proxy_pass http://collabora-key:9980;
|
proxy_pass http://collabora-key:9980;
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
# WOPI discovery URL
|
# WOPI discovery URL
|
||||||
location ^~ /hosting/discovery {
|
location ^~ /hosting/discovery {
|
||||||
proxy_pass http://collabora-key:9980;
|
proxy_pass http://collabora-key:9980;
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
# websockets, download, presentation and image upload
|
# websockets, download, presentation and image upload
|
||||||
location ^~ /lool {
|
location ^~ /lool {
|
||||||
proxy_pass http://collabora-key:9980;
|
proxy_pass http://collabora-key:9980;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Rocket.Chat sniplet meant to be included in server block of EGroupware vhost
|
||||||
|
# proxy into rocketchat container
|
||||||
|
location /rocketchat {
|
||||||
|
proxy_pass http://rocketchat:3000;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,29 @@ volumes:
|
|||||||
# # location of deprecated EGroupware packages like Wiki, SiteMgr, KnowledgeBase
|
# # location of deprecated EGroupware packages like Wiki, SiteMgr, KnowledgeBase
|
||||||
# device: /usr/share/egroupware
|
# device: /usr/share/egroupware
|
||||||
# #device: $PWD/extra
|
# #device: $PWD/extra
|
||||||
|
# collabora-config
|
||||||
|
collabora-config:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
# to upgrade an existing non-docker installation most easy is to use the existing
|
||||||
|
# data directory /var/lib/egroupware AND the host database see below
|
||||||
|
#device: /var/lib/egroupware/default/loolwsd
|
||||||
|
# otherwise data is stored in data subdirectory of the current directory
|
||||||
|
device: $PWD/data/default/loolwsd
|
||||||
|
# store Rocket.Chat MongoDB on an (internal) Volume
|
||||||
|
mongo:
|
||||||
|
# directory to store MongoDB dumps
|
||||||
|
rocketchat-dumps:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: $PWD/data/default/rocketchat/dump
|
||||||
|
rocketchat-uploads:
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: $PWD/data/default/rocketchat/uploads
|
||||||
services:
|
services:
|
||||||
egroupware:
|
egroupware:
|
||||||
image: egroupware/egroupware:latest
|
image: egroupware/egroupware:latest
|
||||||
@ -35,10 +58,13 @@ services:
|
|||||||
# 1. comment out the whole db service below AND
|
# 1. comment out the whole db service below AND
|
||||||
# 2. set EGW_DB_HOST=localhost AND
|
# 2. set EGW_DB_HOST=localhost AND
|
||||||
# 3. uncomment the next line and modify the host path (first one), it depends on your distro:
|
# 3. uncomment the next line and modify the host path (first one), it depends on your distro:
|
||||||
# - RHEL/CentOS /var/lib/mysql/mysql.sock
|
# - RHEL/CentOS /var/lib/mysql/mysql.sock:/var/run/mysqld/mysqld.sock
|
||||||
# - openSUSE/SLE /var/run/mysql/mysql.sock
|
# - openSUSE/SLE /var/run/mysql/mysql.sock:/var/run/mysqld/mysqld.sock
|
||||||
# - Debian/Ubuntu /var/run/mysqld/mysqld.sock
|
# - Debian/Ubuntu /var/run/mysqld:/var/run/mysqld
|
||||||
#- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
|
#- /var/run/mysqld:/var/run/mysqld
|
||||||
|
# private CA so egroupware can validate your certificate to talk to Collabora or Rocket.Chat
|
||||||
|
# multiple certificates (eg. a chain) have to be single files in a directory, with one named private-ca.crt!
|
||||||
|
#- /etc/egroupware-docker/private-ca.crt:/usr/local/share/ca-certificates/private-ca.crt:ro
|
||||||
environment:
|
environment:
|
||||||
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
|
# MariaDB/MySQL host to use: for internal service use "db", for host database (socket bind-mounted into container) use "localhost"
|
||||||
- EGW_DB_HOST=db
|
- EGW_DB_HOST=db
|
||||||
@ -83,6 +109,8 @@ services:
|
|||||||
- "4443:443"
|
- "4443:443"
|
||||||
depends_on:
|
depends_on:
|
||||||
- egroupware
|
- egroupware
|
||||||
|
- collabora-key
|
||||||
|
- rocketchat
|
||||||
container_name: egroupware-nginx
|
container_name: egroupware-nginx
|
||||||
|
|
||||||
# run an own MariaDB:10.4 (you can use EGroupware's database backup and restore to add your existing database)
|
# run an own MariaDB:10.4 (you can use EGroupware's database backup and restore to add your existing database)
|
||||||
@ -118,3 +146,65 @@ services:
|
|||||||
command: --schedule "0 0 4 * * *"
|
command: --schedule "0 0 4 * * *"
|
||||||
container_name: egroupware-watchtower
|
container_name: egroupware-watchtower
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
|
# Collabora Online Office
|
||||||
|
collabora-key:
|
||||||
|
image: "quay.io/egroupware/collabora-key:stable"
|
||||||
|
#image: collabora/code:latest
|
||||||
|
# needs to be initialised via: docker run --rm -v dev_collabora-config:/mnt --entrypoint '/bin/cp -r /etc/loolwsd /mnt' quay.io/egroupware/collabora-key:stable
|
||||||
|
volumes:
|
||||||
|
- collabora-config:/etc/loolwsd
|
||||||
|
# dont try to regenerate the (not used certificate) as volumn is readonly
|
||||||
|
environment:
|
||||||
|
- DONT_GEN_SSL_CERT=1
|
||||||
|
restart: always
|
||||||
|
container_name: collabora-key
|
||||||
|
# set the ip-address of your docker host AND your official DNS name so Collabora
|
||||||
|
# can access EGroupware without the need to go over your firewall
|
||||||
|
#extra_hosts:
|
||||||
|
#- "my.host.name:ip-address"
|
||||||
|
|
||||||
|
# Rocket.Chat server
|
||||||
|
rocketchat:
|
||||||
|
image: rocketchat/rocket.chat:latest
|
||||||
|
command: bash -c 'for i in `seq 1 30`; do node main.js && s=$$? && break || s=$$?; echo "Tried $$i times. Waiting 5 secs..."; sleep 5; done; (exit $$s)'
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- rocketchat-uploads:/app/uploads
|
||||||
|
# if EGroupware uses a certificate from a private CA, OAuth authentication will fail, you need to:
|
||||||
|
# - have the CA certificate stored at /etc/egroupware-docker/private-ca.crt
|
||||||
|
# - uncomment the next 2 lines about the private CA:
|
||||||
|
# - /etc/egroupware-docker/private-ca.crt:/usr/local/share/ca-certificates/private-ca.crt:ro
|
||||||
|
environment:
|
||||||
|
# - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/private-ca.crt
|
||||||
|
# IMPORTANT: change ROOT_URL to your actual url eg. https://domain.com/rocketchat
|
||||||
|
- ROOT_URL=http://localhost/rocketchat
|
||||||
|
- PORT=3000
|
||||||
|
- MONGO_URL=mongodb://mongo:27017/rocketchat
|
||||||
|
- MONGO_OPLOG_URL=mongodb://mongo:27017/local
|
||||||
|
# - HTTP_PROXY=http://proxy.domain.com
|
||||||
|
# - HTTPS_PROXY=http://proxy.domain.com
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
container_name: rocketchat
|
||||||
|
# set the ip-address of your docker host AND your official DNS name so Rocket.Chat
|
||||||
|
# can access EGroupware without the need to go over your firewall
|
||||||
|
#extra_hosts:
|
||||||
|
#- "my.host.name:ip-address"
|
||||||
|
|
||||||
|
# MongoDB for Rocket.Chat
|
||||||
|
mongo:
|
||||||
|
image: mongo:4.0
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- mongo:/data/db
|
||||||
|
- rocketchat-dumps:/dump
|
||||||
|
command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1
|
||||||
|
container_name: rocketchat-mongo
|
||||||
|
# this container's job is just run the command to initialize the replica set.
|
||||||
|
# it will run the command and remove himself (it will not stay running)
|
||||||
|
mongo-init-replica:
|
||||||
|
image: mongo:4.0
|
||||||
|
command: 'bash -c "for i in `seq 1 30`; do mongo mongo/rocketchat --eval \"rs.initiate({ _id: ''rs0'', members: [ { _id: 0, host: ''localhost:27017'' } ]})\" && s=$$? && break || s=$$?; echo \"Tried $$i times. Waiting 5 secs...\"; sleep 5; done; (exit $$s)"'
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# if EGW_APC_SHM_SIZE is set in environment, propagate value to apcu.ini
|
||||||
|
test -n "$EGW_APC_SHM_SIZE" && {
|
||||||
|
grep "apc.shm_size" /etc/php/7.3/fpm/conf.d/20-apcu.ini >/dev/null && \
|
||||||
|
sed -e "s/^;\?apc.shm_size.*/apc.shm_size=$EGW_APC_SHM_SIZE/g" \
|
||||||
|
-i /etc/php/7.3/fpm/conf.d/20-apcu.ini || \
|
||||||
|
echo "apc.shm_size=$EGW_APC_SHM_SIZE" >> /etc/php/7.3/fpm/conf.d/20-apcu.ini \
|
||||||
|
}
|
||||||
|
|
||||||
# if EGW_SESSION_TIMEOUT is set in environment, propagate value to php.ini
|
# if EGW_SESSION_TIMEOUT is set in environment, propagate value to php.ini
|
||||||
test -n "$EGW_SESSION_TIMEOUT" && test "$EGW_SESSION_TIMEOUT" -ge 1440 &&
|
test -n "$EGW_SESSION_TIMEOUT" && test "$EGW_SESSION_TIMEOUT" -ge 1440 && \
|
||||||
sed -e "s/^;\?session.gc_maxlifetime.*/session.gc_maxlifetime=$EGW_SESSION_TIMEOUT/g" \
|
sed -e "s/^;\?session.gc_maxlifetime.*/session.gc_maxlifetime=$EGW_SESSION_TIMEOUT/g" \
|
||||||
-i /etc/php/7.3/fpm/php.ini
|
-i /etc/php/7.3/fpm/php.ini
|
||||||
|
|
||||||
|
@ -121,4 +121,38 @@ server {
|
|||||||
location = / {
|
location = / {
|
||||||
return 301 $redirectscheme://$host/egroupware/index.php;
|
return 301 $redirectscheme://$host/egroupware/index.php;
|
||||||
}
|
}
|
||||||
|
# redirect /egroupware to /egroupware/
|
||||||
|
location = /egroupware {
|
||||||
|
return 301 $redirectscheme://$host/egroupware/index.php;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collabora sniplet meant to be included in server block of EGroupware vhost
|
||||||
|
# static files
|
||||||
|
location ^~ /loleaflet {
|
||||||
|
proxy_pass http://collabora-key:9980;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# WOPI discovery URL
|
||||||
|
location ^~ /hosting/discovery {
|
||||||
|
proxy_pass http://collabora-key:9980;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# websockets, download, presentation and image upload
|
||||||
|
location ^~ /lool {
|
||||||
|
proxy_pass http://collabora-key:9980;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Rocket.Chat sniplet meant to be included in server block of EGroupware vhost
|
||||||
|
# proxy into rocketchat container
|
||||||
|
location /rocketchat {
|
||||||
|
proxy_pass http://rocketchat:3000;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,17 @@ if (!class_exists('\PHPUnit\Framework\TestCase') && class_exists('\PHPUnit_Frame
|
|||||||
// Needed to let Cache work
|
// Needed to let Cache work
|
||||||
$GLOBALS['egw_info']['server']['temp_dir'] = '/tmp';
|
$GLOBALS['egw_info']['server']['temp_dir'] = '/tmp';
|
||||||
$GLOBALS['egw_info']['server']['install_id'] = 'PHPUnit test';
|
$GLOBALS['egw_info']['server']['install_id'] = 'PHPUnit test';
|
||||||
|
// setting a working session.save_path
|
||||||
|
if (ini_get('session.save_handler') === 'files' && !is_writable(ini_get('session.save_path')) &&
|
||||||
|
is_dir('/tmp') && is_writable('/tmp'))
|
||||||
|
{
|
||||||
|
ini_set('session.save_path','/tmp'); // regular users may have no rights to apache's session dir
|
||||||
|
}
|
||||||
|
// set domain from doc/phpunit.xml
|
||||||
|
if (!isset($_SERVER['HTTP_HOST']) && $GLOBALS['EGW_DOMAIN'] !== 'default')
|
||||||
|
{
|
||||||
|
$_SERVER['HTTP_HOST'] = $GLOBALS['EGW_DOMAIN'];
|
||||||
|
}
|
||||||
|
|
||||||
// Symlink api/src/fixtures/apps/* to root
|
// Symlink api/src/fixtures/apps/* to root
|
||||||
foreach(scandir($path=__DIR__.'/../api/tests/fixtures/apps') as $app)
|
foreach(scandir($path=__DIR__.'/../api/tests/fixtures/apps') as $app)
|
||||||
|
27
doc/travis-ci-apache.conf
Normal file
27
doc/travis-ci-apache.conf
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<VirtualHost *:80>
|
||||||
|
# https://docs.travis-ci.com/user/languages/php/#apache--php
|
||||||
|
|
||||||
|
DocumentRoot %TRAVIS_BUILD_DIR%
|
||||||
|
|
||||||
|
# tests assume EGroupware to be under /egroupware not docroot
|
||||||
|
Alias /egroupware %TRAVIS_BUILD_DIR%
|
||||||
|
|
||||||
|
<Directory "%TRAVIS_BUILD_DIR%/">
|
||||||
|
Options FollowSymLinks MultiViews ExecCGI
|
||||||
|
AllowOverride All
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
# Wire up Apache to use Travis CI's php-fpm.
|
||||||
|
<IfModule mod_fastcgi.c>
|
||||||
|
AddHandler php7-fcgi .php
|
||||||
|
Action php7-fcgi /php7-fcgi
|
||||||
|
Alias /php7-fcgi /usr/lib/cgi-bin/php7-fcgi
|
||||||
|
FastCgiExternalServer /usr/lib/cgi-bin/php7-fcgi -host 127.0.0.1:9000 -pass-header Authorization
|
||||||
|
|
||||||
|
<Directory /usr/lib/cgi-bin>
|
||||||
|
Require all granted
|
||||||
|
</Directory>
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
</VirtualHost>
|
@ -11,11 +11,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use EGroupware\Api;
|
use EGroupware\Api;
|
||||||
use EGroupware\Api\Link;
|
|
||||||
use EGroupware\Api\Framework;
|
|
||||||
use EGroupware\Api\Egw;
|
use EGroupware\Api\Egw;
|
||||||
use EGroupware\Api\Etemplate;
|
use EGroupware\Api\Etemplate;
|
||||||
|
use EGroupware\Api\Framework;
|
||||||
|
use EGroupware\Api\Link;
|
||||||
use EGroupware\Api\Vfs;
|
use EGroupware\Api\Vfs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1023,6 +1022,10 @@ class filemanager_ui
|
|||||||
{
|
{
|
||||||
$vfs_options['mime'] = $query['col_filter']['mime'];
|
$vfs_options['mime'] = $query['col_filter']['mime'];
|
||||||
}
|
}
|
||||||
|
if($namefilter)
|
||||||
|
{
|
||||||
|
$vfs_options['name'] = $query['search'];
|
||||||
|
}
|
||||||
|
|
||||||
return $vfs_options;
|
return $vfs_options;
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class ContactTest extends \EGroupware\Api\AppTest
|
|||||||
// Infolog under test
|
// Infolog under test
|
||||||
protected $info_id = null;
|
protected $info_id = null;
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
$this->ui = new \infolog_ui();
|
$this->ui = new \infolog_ui();
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ class ContactTest extends \EGroupware\Api\AppTest
|
|||||||
$this->mockTracking($this->bo, 'infolog_tracking');
|
$this->mockTracking($this->bo, 'infolog_tracking');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
// Double delete to make sure it's gone, not preserved due to history setting
|
// Double delete to make sure it's gone, not preserved due to history setting
|
||||||
if($this->info_id)
|
if($this->info_id)
|
||||||
|
@ -34,7 +34,7 @@ class SetProjectManagerTest extends \EGroupware\Api\AppTest
|
|||||||
protected $pm_id = null;
|
protected $pm_id = null;
|
||||||
|
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
$this->ui = new \infolog_ui();
|
$this->ui = new \infolog_ui();
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ class SetProjectManagerTest extends \EGroupware\Api\AppTest
|
|||||||
$this->makeProject();
|
$this->makeProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
// Remove infolog under test
|
// Remove infolog under test
|
||||||
if($this->info_id)
|
if($this->info_id)
|
||||||
|
@ -37,7 +37,7 @@ class StatusTest extends \EGroupware\Api\AppTest
|
|||||||
/**
|
/**
|
||||||
* Create a custom status we can use to test
|
* Create a custom status we can use to test
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ class StatusTest extends \EGroupware\Api\AppTest
|
|||||||
|
|
||||||
Api\Config::save_value('status',$bo->status,'infolog');
|
Api\Config::save_value('status',$bo->status,'infolog');
|
||||||
}
|
}
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
// Remove custom status
|
// Remove custom status
|
||||||
$bo = new \infolog_bo();
|
$bo = new \infolog_bo();
|
||||||
@ -58,14 +58,14 @@ class StatusTest extends \EGroupware\Api\AppTest
|
|||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
$this->bo = new \infolog_bo();
|
$this->bo = new \infolog_bo();
|
||||||
|
|
||||||
$this->mockTracking($this->bo, 'infolog_tracking');
|
$this->mockTracking($this->bo, 'infolog_tracking');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
$this->bo = null;
|
$this->bo = null;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +142,7 @@ else
|
|||||||
$passwd_type = $_POST['passwd_type'];
|
$passwd_type = $_POST['passwd_type'];
|
||||||
|
|
||||||
// forced password change
|
// forced password change
|
||||||
if($GLOBALS['egw']->session->cd_reason != Api\Session::CD_FORCE_PASSWORD_CHANGE)
|
if($GLOBALS['egw']->session->cd_reason == Api\Session::CD_FORCE_PASSWORD_CHANGE)
|
||||||
{
|
{
|
||||||
// no automatic login
|
// no automatic login
|
||||||
}
|
}
|
||||||
|
@ -446,6 +446,9 @@ div.mail-compose_fileselector {
|
|||||||
position: relative;
|
position: relative;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
div#mail-index_mailPreview > div {
|
||||||
|
padding-left: 8px !important;
|
||||||
|
}
|
||||||
#mail-index_mailPreview .et2_email>span{
|
#mail-index_mailPreview .et2_email>span{
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
@ -72,10 +72,10 @@ tr.mail.labelone td:first-child {
|
|||||||
border-left: 6px solid #ff0080 !important;
|
border-left: 6px solid #ff0080 !important;
|
||||||
}
|
}
|
||||||
tr.mail.labeltwo td:first-child {
|
tr.mail.labeltwo td:first-child {
|
||||||
border-left: 6px solid #ff8000 !important;
|
border-left: 6px solid #ff8000 !important;
|
||||||
}
|
}
|
||||||
tr.mail.labelthree td:first-child {
|
tr.mail.labelthree td:first-child {
|
||||||
border-left: 6px solid #008000 !important;
|
border-left: 6px solid #008000 !important;
|
||||||
}
|
}
|
||||||
tr.mail.labelfour td:first-child {
|
tr.mail.labelfour td:first-child {
|
||||||
border-left: 6px solid #0000ff !important;
|
border-left: 6px solid #0000ff !important;
|
||||||
@ -456,6 +456,9 @@ div.mail-compose_fileselector {
|
|||||||
position: relative;
|
position: relative;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
div#mail-index_mailPreview > div {
|
||||||
|
padding-left: 8px !important;
|
||||||
|
}
|
||||||
#mail-index_mailPreview .et2_email > span {
|
#mail-index_mailPreview .et2_email > span {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
@ -469,7 +472,7 @@ div.mail-compose_fileselector {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
#mail-compose_composeToolbar > button {
|
#mail-compose_composeToolbar > button {
|
||||||
padding: .2em .4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
#mail-compose_composeToolbar > img {
|
#mail-compose_composeToolbar > img {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@ -482,7 +485,7 @@ div.mail-compose_fileselector {
|
|||||||
height: 16px !important;
|
height: 16px !important;
|
||||||
}
|
}
|
||||||
#mail-compose_composeToolbar > button {
|
#mail-compose_composeToolbar > button {
|
||||||
padding: .2em .4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
#mail-compose_to div.ms-sel-ctn,
|
#mail-compose_to div.ms-sel-ctn,
|
||||||
#mail-compose_cc .ms-sel-ctn,
|
#mail-compose_cc .ms-sel-ctn,
|
||||||
@ -490,7 +493,7 @@ div.mail-compose_fileselector {
|
|||||||
max-height: 75px;
|
max-height: 75px;
|
||||||
}
|
}
|
||||||
#mail-display_toolbar > button > span {
|
#mail-display_toolbar > button > span {
|
||||||
padding: .2em .4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
#mail-display_toolbar > button > span > img {
|
#mail-display_toolbar > button > span > img {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@ -513,7 +516,7 @@ div.mail-compose_fileselector {
|
|||||||
height: 35px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
#mail-index_toolbar > button > span {
|
#mail-index_toolbar > button > span {
|
||||||
padding: .2em .4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
#mail-index_toolbar > button > span > img {
|
#mail-index_toolbar > button > span > img {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@ -1649,11 +1652,11 @@ tr.mail.deleted td:first-child {
|
|||||||
}
|
}
|
||||||
span.status_img {
|
span.status_img {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 12px;
|
width: 16px;
|
||||||
height: 12px;
|
height: 16px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-image: url(../pixelegg/images/kmmsgread.svg);
|
background-image: url(../pixelegg/images/kmmsgread.svg);
|
||||||
background-size: 12px 12px;
|
background-size: 16px 16px;
|
||||||
}
|
}
|
||||||
tr.deleted span.status_img {
|
tr.deleted span.status_img {
|
||||||
background-image: url(../pixelegg/images/kmmsgdel.svg);
|
background-image: url(../pixelegg/images/kmmsgdel.svg);
|
||||||
@ -1668,7 +1671,7 @@ tr.flagged_unseen span.status_img {
|
|||||||
background-image: url(../pixelegg/images/unread_flagged_small.svg) !important;
|
background-image: url(../pixelegg/images/unread_flagged_small.svg) !important;
|
||||||
}
|
}
|
||||||
tr.recent span.status_img {
|
tr.recent span.status_img {
|
||||||
background-image: url(../pixelegg/images/kmmsgnew.png) !important;
|
background-image: url(../pixelegg/images/kmmsgnew.svg) !important;
|
||||||
}
|
}
|
||||||
tr.replied span.status_img {
|
tr.replied span.status_img {
|
||||||
background-image: url(../pixelegg/images/mail_reply.svg) !important;
|
background-image: url(../pixelegg/images/mail_reply.svg) !important;
|
||||||
@ -1890,7 +1893,7 @@ input[type=button] {
|
|||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
#mail-display_toolbar > button > span {
|
#mail-display_toolbar > button > span {
|
||||||
padding: .2em .4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
#mail-display_toolbar > button > span > img {
|
#mail-display_toolbar > button > span > img {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
@ -444,6 +444,9 @@ div.mail-compose_fileselector {
|
|||||||
position: relative;
|
position: relative;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
div#mail-index_mailPreview > div {
|
||||||
|
padding-left: 8px !important;
|
||||||
|
}
|
||||||
#mail-index_mailPreview .et2_email > span {
|
#mail-index_mailPreview .et2_email > span {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
@ -18,23 +18,23 @@ class SaveToVfsTest extends \PHPUnit\Framework\TestCase
|
|||||||
/**
|
/**
|
||||||
* Create a custom status we can use to test
|
* Create a custom status we can use to test
|
||||||
*/
|
*/
|
||||||
public static function setUpBeforeClass()
|
public static function setUpBeforeClass() : void
|
||||||
{
|
{
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
|
|
||||||
}
|
}
|
||||||
public static function tearDownAfterClass()
|
public static function tearDownAfterClass() : void
|
||||||
{
|
{
|
||||||
|
|
||||||
// Have to remove custom status first, before the DB is gone
|
// Have to remove custom status first, before the DB is gone
|
||||||
parent::tearDownAfterClass();
|
parent::tearDownAfterClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUp()
|
protected function setUp() : void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function tearDown()
|
protected function tearDown() : void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1809,7 +1809,6 @@ div#loginMainDiv.stockLoginBackground div#centerBox form {
|
|||||||
background: white;
|
background: white;
|
||||||
opacity: 0.94;
|
opacity: 0.94;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
border-radius: 5px;
|
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@ -1888,7 +1887,6 @@ div#loginMainDiv.stockLoginBackground div#centerBox form {
|
|||||||
width: 280px;
|
width: 280px;
|
||||||
}
|
}
|
||||||
#loginMainDiv div#centerBox form {
|
#loginMainDiv div#centerBox form {
|
||||||
border-radius: 5px;
|
|
||||||
opacity: 0.94;
|
opacity: 0.94;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
@ -96,7 +96,6 @@ div#loginMainDiv.stockLoginBackground {
|
|||||||
background: white;
|
background: white;
|
||||||
opacity: 0.94;
|
opacity: 0.94;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
border-radius: 5px;
|
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@ -184,7 +183,6 @@ div#loginMainDiv.stockLoginBackground {
|
|||||||
|
|
||||||
// Formular
|
// Formular
|
||||||
form {
|
form {
|
||||||
border-radius:5px;
|
|
||||||
opacity:0.94;
|
opacity:0.94;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding:1em;
|
padding:1em;
|
||||||
|
@ -253,6 +253,7 @@ class setup
|
|||||||
case PHP_SESSION_DISABLED:
|
case PHP_SESSION_DISABLED:
|
||||||
throw new \ErrorException('EGroupware requires PHP session extension!');
|
throw new \ErrorException('EGroupware requires PHP session extension!');
|
||||||
case PHP_SESSION_NONE:
|
case PHP_SESSION_NONE:
|
||||||
|
if (headers_sent()) return true;
|
||||||
ini_set('session.use_cookie', true);
|
ini_set('session.use_cookie', true);
|
||||||
session_name(self::SESSIONID);
|
session_name(self::SESSIONID);
|
||||||
session_set_cookie_params(0, '/', self::cookiedomain(),
|
session_set_cookie_params(0, '/', self::cookiedomain(),
|
||||||
|
@ -25,9 +25,9 @@ $GLOBALS['egw_info'] = array(
|
|||||||
'currentapp' => 'setup',
|
'currentapp' => 'setup',
|
||||||
'noapi' => True
|
'noapi' => True
|
||||||
));
|
));
|
||||||
if(file_exists('../header.inc.php'))
|
if(file_exists(__DIR__.'/../../header.inc.php'))
|
||||||
{
|
{
|
||||||
include('../header.inc.php');
|
include_once(__DIR__.'/../../header.inc.php');
|
||||||
}
|
}
|
||||||
// for an old header we need to setup a reference for the domains
|
// for an old header we need to setup a reference for the domains
|
||||||
if (!is_array($GLOBALS['egw_domain'])) $GLOBALS['egw_domain'] =& $GLOBALS['phpgw_domain'];
|
if (!is_array($GLOBALS['egw_domain'])) $GLOBALS['egw_domain'] =& $GLOBALS['phpgw_domain'];
|
||||||
|
Loading…
Reference in New Issue
Block a user