Merge branch 'master' into web-components

This commit is contained in:
nathan 2021-12-01 15:31:45 -07:00
commit 2545c8fde1
17 changed files with 160 additions and 50 deletions

View File

@ -176,7 +176,7 @@ class admin_mail
* Step 1: IMAP account
*
* @param array $content
* @param type $msg
* @param string $msg
*/
public function add(array $content=array(), $msg='', $msg_type='success')
{

View File

@ -338,7 +338,7 @@
<row class="emailadmin_no_user dialogHeader2">
<description for="account_id" value="Valid for"/>
<hbox>
<select type="select-account" id="account_id" onchange="app.admin.account_hide_not_applying" options="Everyone,both" multiple="dynamic"/>
<select-account account_type="both" id="account_id" onchange="app.admin.account_hide_not_applying" empty_label="Everyone" multiple="dynamic"/>
<buttononly label="Select multiple" id="button[multiple]" onclick="app.admin.edit_multiple" options="users"/>
<checkbox label="account editable by user" id="acc_user_editable"/>
</hbox>

View File

@ -14,7 +14,7 @@ $setup_info['api']['title'] = 'EGroupware API';
$setup_info['api']['version'] = '21.1.001';
$setup_info['api']['versions']['current_header'] = '1.29';
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
$setup_info['api']['versions']['maintenance_release'] = '21.1.20210923';
$setup_info['api']['versions']['maintenance_release'] = '21.1.20211130';
$setup_info['api']['enable'] = 3;
$setup_info['api']['app_order'] = 1;
$setup_info['api']['license'] = 'GPL';

View File

@ -764,7 +764,7 @@ class Contacts extends Contacts\Storage
$data[$name] = DateTime::server2user($data[$name], $date_format);
}
}
$data['photo'] = $this->photo_src($data['id'],!empty($data['jpegphoto']) || (($data['files']??0) & self::FILES_BIT_PHOTO), '', $data['etag'] ?? null);
$data['photo'] = $this->photo_src($data['id'] ?? null,!empty($data['jpegphoto']) || (($data['files']??0) & self::FILES_BIT_PHOTO), '', $data['etag'] ?? null);
// set freebusy_uri for accounts
if (empty($data['freebusy_uri']) && empty($data['owner']) && !empty($data['account_id']) && empty($GLOBALS['egw_setup']))

View File

@ -482,13 +482,13 @@ class Sql extends Api\Storage
$owner = isset($filter['owner']) ? $filter['owner'] : (isset($criteria['owner']) ? $criteria['owner'] : null);
// fix cat_id criteria to search in comma-separated multiple cats and return subcats
if (is_array($criteria) && ($cats = $criteria['cat_id']))
if (is_array($criteria) && !empty($criteria['cat_id']))
{
$criteria = array_merge($criteria, $this->_cat_search($criteria['cat_id']));
unset($criteria['cat_id']);
}
// fix cat_id filter to search in comma-separated multiple cats and return subcats
if (($cats = $filter['cat_id']))
if (!empty($filter['cat_id']))
{
if ($filter['cat_id'][0] == '!')
{
@ -680,7 +680,7 @@ class Sql extends Api\Storage
{
$extra_cols[$key] = "$shared_with AS shared_with";
}
switch ((string)$filter['shared_with'])
switch ($filter['shared_with'] ?? '')
{
case '': // filter not set
break;

View File

@ -655,7 +655,7 @@ class Storage
//error_log(__METHOD__.'('.array2string($criteria,true).','.array2string($only_keys).",'$order_by','$extra_cols','$wildcard','$empty','$op',".array2string($start).','.array2string($filter,true).",'$join')");
// Handle 'None' country option
if(is_array($filter) && $filter['adr_one_countrycode'] == '-custom-')
if(is_array($filter) && isset($filter['adr_one_countrycode']) && $filter['adr_one_countrycode'] === '-custom-')
{
$filter[] = 'adr_one_countrycode IS NULL';
unset($filter['adr_one_countrycode']);

View File

@ -1630,9 +1630,10 @@ class Db
$not_null = is_array($column_definitions) && isset($column_definitions[$col]['nullable']) ? !$column_definitions[$col]['nullable'] : false;
$maxlength = null;
if ($truncate_varchar)
if ($truncate_varchar && !is_int($col) && isset($column_definitions[$col]) &&
in_array($column_definitions[$col]['type'], ['varchar','ascii']))
{
$maxlength = in_array($column_definitions[$col]['type'], array('varchar','ascii')) ? $column_definitions[$col]['precision'] : null;
$maxlength = $column_definitions[$col]['precision'];
}
// dont use IN ( ), if there's only one value, it's slower for MySQL
if (is_array($data) && count($data) <= 1)

View File

@ -67,6 +67,10 @@ class Backup
* @var string|boolean
*/
var $egw_tables = false;
/**
* Regular expression to identify a Guacamole table OR view
*/
const GUACAMOLE_REGEXP = '/^guacamole_/';
/**
* Backup directory.
*
@ -360,6 +364,8 @@ class Backup
if (substr($this->db->Type,0,5) == 'mysql')
{
$this->db->query("SET SESSION sql_mode=(SELECT REPLACE(REPLACE(@@sql_mode,'STRICT_ALL_TABLES',''),'STRICT_TRANS_TABLES',''))", __LINE__, __FILE__);
// disable foreign key checks, in case Guacamole is installed
$this->db->query('SET FOREIGN_KEY_CHECKS = 0');
}
else
{
@ -371,7 +377,9 @@ class Backup
foreach($this->adodb->MetaTables('TABLES') as $table)
{
if ($this->system_tables && preg_match($this->system_tables,$table) ||
$this->egw_tables && !preg_match($this->egw_tables,$table))
$this->egw_tables && !preg_match($this->egw_tables,$table) ||
// do NOT drop Guacamole tables and views
preg_match(self::GUACAMOLE_REGEXP, $table))
{
continue;
}
@ -513,7 +521,7 @@ class Backup
if (substr($line,0,9) == 'version: ')
{
// currenty not used: $api_version = trim(substr($line,9));
// currently not used: $api_version = trim(substr($line,9));
continue;
}
if (substr($line,0,9) == 'charset: ')
@ -536,6 +544,12 @@ class Backup
$this->schemas = json_php_unserialize(trim(substr($line,8)));
foreach($this->schemas as $table_name => $schema)
{
// do NOT create GUACAMOLE tables, just truncate them (as we have no abstraction to create the foreign keys)
if (preg_match(self::GUACAMOLE_REGEXP, $table_name))
{
$this->db->query('TRUNCATE TABLE '.$this->db->name_quote($table_name));
continue;
}
// if column is longtext in current schema, convert text to longtext, in case user already updated column
foreach($schema['fd'] as $col => &$def)
{
@ -566,7 +580,7 @@ class Backup
{
if ($data['type'] == 'blob') $blobs[] = $col;
}
// check if we have an old PostgreSQL backup useing 't'/'f' for bool values
// check if we have an old PostgreSQL backup using 't'/'f' for bool values
// --> convert them to MySQL and our new PostgreSQL format of 1/0
$bools = array();
foreach($this->schemas[$table]['fd'] as $col => $def)

View File

@ -119,22 +119,22 @@ class Etemplate extends Etemplate\Widget\Template
{
if (!empty($extra['data']) && is_array($extra['data']))
{
$content = array_merge($content, $extra['data']);
$content = array_merge_recursive($content, $extra['data']);
}
if (!empty($extra['preserve']) && is_array($extra['preserve']))
{
$preserv = array_merge($preserv, $extra['preserve']);
$preserv = array_merge_recursive($preserv, $extra['preserve']);
}
if (!empty($extra['readonlys']) && is_array($extra['readonlys']))
{
$readonlys = array_merge($readonlys, $extra['readonlys']);
$readonlys = array_merge_recursive($readonlys, $extra['readonlys']);
}
if (!empty($extra['sel_options']) && is_array($extra['sel_options']))
{
$sel_options = array_merge($sel_options, $extra['sel_options']);
$sel_options = array_merge_recursive($sel_options, $extra['sel_options']);
}
}
}

View File

@ -569,7 +569,10 @@ class Widget
$types = $paramType instanceof \ReflectionUnionType
? $paramType->getTypes()
: [$paramType];
if(in_array('array', array_map(fn(\ReflectionNamedType $t) => $t->getName(), $types)) && !is_array($params[$index]))
if(in_array('array', array_map(static function(\ReflectionNamedType $t)
{
return $t->getName();
}, $types)) && !is_array($params[$index]))
{
error_log("$method_name expects an array for {$param->getPosition()}: {$param->getName()}");
$params[$index] = (array)$params[$index];

View File

@ -75,11 +75,32 @@ class File extends Etemplate\Widget
return;
}
if (!($template = Template::instance(self::$request->template['name'], self::$request->template['template_set'],
self::$request->template['version'], self::$request->template['load_via'])))
{
// Can't use callback
error_log("Could not get template for file upload, callback skipped");
try {
if (!($template = Template::instance(self::$request->template['name'], self::$request->template['template_set'],
self::$request->template['version'], self::$request->template['load_via'])))
{
// Can't use callback
error_log("Could not get template for file upload, callback skipped");
}
}
catch (\Error $e) {
// retry 3 times, in case the problem (Call to undefined method EGroupware\Api\Etemplate\Widget\Vfs::set_attrs()) is caused by something internal in PHP 8.0
if (!isset($_REQUEST['retry']) || $_REQUEST['retry'] < 3)
{
$url = Api\Header\Http::schema().'://'.Api\Header\Http::host().$_SERVER['REQUEST_URI'];
if (strpos($url, '&retry=') === false)
{
$url .= '&retry=1';
}
else
{
$url = preg_replace('/&retry=\d+/', '&retry='.($_REQUEST['retry']+1), $url);
}
header('Location: '.$url);
http_response_code(307);
exit;
}
throw new \Error('Error instantiating template '.json_encode(self::$request->template).', $_REQUEST='.json_encode($_REQUEST).': '.$e->getMessage(), $e->getCode(), $e);
}
$file_data = array();

View File

@ -578,9 +578,9 @@ class calendar_ui
// ignore failed discovery
unset($e);
}
if ($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'])
if($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'])
{
$sel_options['merge'] = calendar_merge::get_documents($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'], '', null,'calendar');
$sel_options['merge'] = calendar_merge::get_documents($GLOBALS['egw_info']['user']['preferences']['calendar']['document_dir'], '', null, 'calendar');
}
else
@ -588,6 +588,22 @@ class calendar_ui
$readonlys['merge'] = true;
}
// Add integration UI into sidemenu
$integration_data = Api\Hooks::process(array('location' => 'calendar_search_union'));
foreach($integration_data as $app => $app_hooks)
{
foreach($app_hooks as $data)
{
// App might have multiple hooks, let it specify something else
$app = $data['selects']['app'] ?: $app;
if(array_key_exists('sidebox_template', $data))
{
$cont['integration'][] = ['template' => $data['sidebox_template'], 'app' => $app];
}
}
}
// Sidebox?
$sidebox->exec('calendar.calendar_ui.sidebox_etemplate', $cont, $sel_options, $readonlys);
}

View File

@ -861,10 +861,14 @@ export class CalendarApp extends EgwApp
if(action.checked)
{
integration_preference.push(app);
if(integration_preference.indexOf(app) === -1)
{
integration_preference.push(app);
}
// After the preference change is done, get new info which should now include the app
callback = callback ? callback : function() {
callback = callback ? callback : function()
{
this._fetch_data(this.state);
}.bind(this);
}

View File

@ -14,27 +14,52 @@ Egroupware
<overlay>
<template id="calendar.sidebox">
<vbox parent_node="calendar-et2_target">
<buttononly id="header_today" label="•" icon="nope" onclick="
<buttononly id="header_today" label="•" icon="nope" onclick="
var tempDate = new Date();
var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(),0,-tempDate.getTimezoneOffset(),0);
var change = {date: today.toJSON()};
app.calendar.update_state(change);
widget.getRoot().getWidgetById('date').set_value(today);
return false;"/>
<buttononly id="header_go" label="&#8629;" icon="nope" class="ui-corner-all" onclick="var change = {date: widget.btn.attr('data-date')}; if ( app.calendar.state.view == 'listview') {change.filter='month';} else if (app.calendar.state.view == 'planner') {} else {change.view = 'month';}app.calendar.update_state(change);" />
<date id="date" class="et2_fullWidth" inline="true" onchange="var view_change = app.calendar.sidebox_changes_views.indexOf(app.calendar.state.view);
<buttononly id="header_go" label="&#8629;" icon="nope" class="ui-corner-all"
onclick="var change = {date: widget.btn.attr('data-date')}; if ( app.calendar.state.view == 'listview') {change.filter='month';} else if (app.calendar.state.view == 'planner') {} else {change.view = 'month';}app.calendar.update_state(change);"/>
<date id="date" class="et2_fullWidth" inline="true" onchange="var view_change = app.calendar.sidebox_changes_views.indexOf(app.calendar.state.view);
var update = {date:widget.getValue()};
if(view_change >= 0) {update.view = app.calendar.sidebox_changes_views[view_change ? view_change - 1 : view_change];} else if (app.calendar.state.view == 'listview') {update.filter = 'after';} else if (app.calendar.state.view =='planner') { update.planner_view = 'day'; } app.calendar.update_state(update);"/>
<textbox type="hidden" id="first"/>
<textbox type="hidden" id="last"/>
<hrule/>
<select-cat id="cat_id" empty_label="All categories" width="86%" onchange="app.calendar.update_state({cat_id: widget.getValue()});" expand_multiple_rows="4"/>
<select id="status_filter" no_lang="true" class="et2_fullWidth" onchange="app.calendar.update_state({status_filter: widget.getValue()});"/>
<hrule/>
<calendar-owner id="owner" class="et2_fullWidth" onchange="app.calendar.update_state({owner: widget.getValue()}); return false;" multiple="true" allowFreeEntries="false" autocomplete_params="{&quot;checkgrants&quot;: true}"/>
<hrule/>
<select id="merge" empty_label="Insert in document" onchange="app.calendar.sidebox_merge" class="et2_fullWidth"/>
</vbox>
<iframe id="iframe" width="100%" height="100%"/>
</template>
<textbox type="hidden" id="first"/>
<textbox type="hidden" id="last"/>
<hrule/>
<select-cat id="cat_id" empty_label="All categories" width="86%"
onchange="app.calendar.update_state({cat_id: widget.getValue()});" expand_multiple_rows="4"/>
<select id="status_filter" no_lang="true" class="et2_fullWidth"
onchange="app.calendar.update_state({status_filter: widget.getValue()});"/>
<hrule/>
<calendar-owner id="owner" class="et2_fullWidth"
onchange="app.calendar.update_state({owner: widget.getValue()}); return false;"
multiple="true" allowFreeEntries="false"
autocomplete_params="{&quot;checkgrants&quot;: true}"/>
<hrule/>
<select id="merge" empty_label="Insert in document" onchange="app.calendar.sidebox_merge"
class="et2_fullWidth"/>
<box>
<grid id="integration" disabled="!@integration" width="100%">
<columns>
<column/>
</columns>
<rows>
<row>
<template id="$row_cont[template]" width="100%" content="$row_cont[app]"/>
</row>
</rows>
</grid>
</box>
</vbox>
<iframe id="iframe" width="100%" height="100%"/>
<styles>
#calendar-sidebox_integration: {
display: table;
width: 100%
}
</styles>
</template>
</overlay>

View File

@ -118,7 +118,7 @@
"egroupware/tracker": "self.version",
"egroupware/util": "^2.6.2",
"egroupware/webdav": "^v0.3.2",
"egroupware/z-push-dev": "^2.5",
"egroupware/z-push-dev": "2.5.0.1",
"giggsey/libphonenumber-for-php": "^8.12",
"npm-asset/as-jqplot": "1.0.*",
"npm-asset/gridster": "0.5.*",

14
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "beeabf26cf7c537dea75e236217d44c6",
"content-hash": "393ec43e69c00afa115414c62789e29a",
"packages": [
{
"name": "adldap2/adldap2",
@ -207,7 +207,7 @@
"version": "v2.3.4",
"source": {
"type": "git",
"url": "https://github.com/fengyuanchen/cropper.git",
"url": "git@github.com:fengyuanchen/cropper.git",
"reference": "30c58b29ee21010e17e58ebab165fbd84285c685"
},
"dist": {
@ -305,7 +305,7 @@
"version": "1.12.4",
"source": {
"type": "git",
"url": "https://github.com/jquery/jquery-dist.git",
"url": "git@github.com:jquery/jquery-dist.git",
"reference": "5e89585e0121e72ff47de177c5ef604f3089a53d"
},
"dist": {
@ -1924,16 +1924,16 @@
},
{
"name": "egroupware/z-push-dev",
"version": "2.5.0",
"version": "2.5.0.1",
"source": {
"type": "git",
"url": "https://github.com/EGroupware/z-push.git",
"reference": "32da00e1024038a8f57c8a185c671179c3922ebe"
"reference": "7774018b19b5b55e24dbd1107693496597ab70ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/EGroupware/z-push/zipball/32da00e1024038a8f57c8a185c671179c3922ebe",
"reference": "32da00e1024038a8f57c8a185c671179c3922ebe",
"url": "https://api.github.com/repos/EGroupware/z-push/zipball/7774018b19b5b55e24dbd1107693496597ab70ee",
"reference": "7774018b19b5b55e24dbd1107693496597ab70ee",
"shasum": ""
},
"require": {

View File

@ -1,3 +1,29 @@
egroupware-docker (21.1.20211130) hardy; urgency=low
* PHP 8.0: tons of fixes to support 8.0, this is probably the last container using PHP 7.4 by default
* Addressbook: new REST API for contacts https://github.com/EGroupware/egroupware/tree/master/doc/REST-CalDAV-CardDAV
* LDAP/Addressbook: fix region contains for given country invalid value gives an LDAP error on update
* Admin/Filemanager: correctly encode user "WORKGROUP\$user" for SMB mounts and do NOT require mountpoints to exist
* Filemanager: fix not working variables eg. $user in GUI mount (Admin > Filemanager)
* Filemanager: fix video controller not working in filemanager gallery
* Filemanager: add action to unlock files
* Filemanager: fix super user could not remove other users' subscriptions
* Filemanager: add actions to convert editable files to PDF or PNG and a checkbox to merge file as PDF
* Collabora: merge placeholder dialogs
* All apps: add preference to set directory and filename of merged documents using placeholders
* Calendar: fix changing the recurrence end date did not add/remove the events in the UI
* InfoLog: fix not working overwrite check (optimistic locking) plus incrementing etag
* Mail: make sure pressing [del] key twice in a row does not delete the first row on the second press
* Mail: add set flags action into mail filters
* Mail: implements date extension for vacation rule. None imap admin user can also set vacation rule by date.
* Kanban: fix deleting card did not delete link to the board (includes a DB update to remove orphans from links)
* Resources: add inventory number to resource list columns
* smallPART/PostgreSQL: fix SQL error when opening a course
* smallPART/PostgreSQL: fix SQL error during update (you need to restore egw_smallpart* tables AND set egw_applications.app_version='21.1')
* API: update jQuery-ui to 1.13.0 and TinyMCE to 5.10.1
-- Ralf Becker <rb@egroupware.org> Tue, 30 Nov 2021 09:11:56 +0100
egroupware-docker (21.1.20210923) hardy; urgency=low
* smallPART: many new features and improvements for the new semester: