Merge branch 'master' into web-components

This commit is contained in:
nathan 2021-11-01 09:37:26 -06:00
commit 76d7447dab
63 changed files with 542 additions and 270 deletions

10
SECURITY.md Normal file
View File

@ -0,0 +1,10 @@
# Security Policy
## Supported Versions
* next release / master
* latest released version: **21.1**
* old released version: 20.1 (major security fixes only!)
Please report security issues to <security@egroupware.org>
If you need to send information in a secure way, please contakt us first, so we can send you our PGP keys to do so.

View File

@ -971,11 +971,12 @@ class addressbook_vcal extends addressbook_bo
{
if (!empty($fieldName))
{
$value = trim($vcardValues[$vcardKey]['values'][$fieldKey]);
$value = $vcardValues[$vcardKey]['values'][$fieldKey];
if (is_string($value)) $value = trim($value);
if ($pref_tel && (($vcardKey == $pref_tel) ||
($vcardValues[$vcardKey]['name'] == 'TEL') &&
($vcardValues[$vcardKey]['value'] == $vcardValues[$pref_tel]['value'])))
($vcardValues[$vcardKey]['name'] == 'TEL') &&
($vcardValues[$vcardKey]['value'] == $vcardValues[$pref_tel]['value'])))
{
$contact['tel_prefer'] = $fieldName;
}

View File

@ -163,9 +163,9 @@
</vbox>
<description id="${row}[id]" class="contactid"/>
<vbox>
<link id="${row}[last_link]"/>
<link id="${row}[next_link]"/>
</vbox>
<link id="${row}[last_link]" readonly="true"/>
<link id="${row}[next_link]" readonly="true"/>
</vbox>
<vbox>
<date-time id="${row}[created]" readonly="true" class="noWrap"/>
<select-account id="${row}[creator]" readonly="true"/>

View File

@ -869,7 +869,14 @@ class admin_mail
// clear current account-data, as account has changed and we going to read selected one
$content = array_intersect_key($content, array_flip(array('called_for', 'accounts', 'acc_id', 'tabs')));
if ($content['acc_id'] > 0)
if ($content['acc_id'] === 'new')
{
$content['account_id'] = $content['called_for'];
$content['old_acc_id'] = $content['acc_id']; // to not call add/wizard, if we return from to
unset($content['tabs']);
return $this->add($content);
}
elseif ($content['acc_id'] > 0)
{
try {
$account = Mail\Account::read($content['acc_id'], $this->is_admin && $content['called_for'] ?
@ -899,13 +906,6 @@ class admin_mail
Framework::window_close($e->getMessage().' ('.get_class($e).': '.$e->getCode().')');
}
}
elseif ($content['acc_id'] === 'new')
{
$content['account_id'] = $content['called_for'];
$content['old_acc_id'] = $content['acc_id']; // to not call add/wizard, if we return from to
unset($content['tabs']);
return $this->add($content);
}
}
// some defaults for new accounts
if (!isset($content['account_id']) || empty($content['acc_id']) || $content['acc_id'] === 'new')
@ -1005,7 +1005,7 @@ class admin_mail
{
$account->imapServer()->retrieveRules();
}
$new_account = !($content['acc_id'] > 0);
$new_account = !((int)$content['acc_id'] > 0);
// check for deliveryMode="forwardOnly", if a forwarding-address is given
if ($content['acc_smtp_type'] != 'EGroupware\\Api\\Mail\\Smtp' &&
$content['deliveryMode'] == Mail\Smtp::FORWARD_ONLY &&
@ -1340,7 +1340,7 @@ class admin_mail
$readonlys['button[multiple]'] = true;
}
// when called by admin for existing accounts, display further administrative actions
if ($content['called_for'] && $content['acc_id'] > 0)
if ($content['called_for'] && (int)$content['acc_id'] > 0)
{
$admin_actions = array();
foreach(Api\Hooks::process(array(
@ -1471,8 +1471,8 @@ class admin_mail
$url = 'https://autoconfig.thunderbird.net/v1.1/'.$domain;
try {
$xml = @simplexml_load_file($url);
if (!$xml->emailProvider) throw new Api\Exception\NotFound();
$xml = simplexml_load_string(file_get_contents($url) ?: '');
if (!$xml || !$xml->emailProvider) throw new Api\Exception\NotFound();
$provider = array(
'displayName' => (string)$xml->emailProvider->displayName,
);
@ -1604,11 +1604,11 @@ class admin_mail
if (strpos($_data['domain'], '.') !== false)
{
$userData['mailLocalAddress'] = preg_replace('/@'.preg_quote($ea_account->acc_domain).'$/', '@'.$_data['domain'], $userData['mailLocalAddress']);
$userData['mailLocalAddress'] = preg_replace('/@'.preg_quote($ea_account->acc_domain, '/').'$/', '@'.$_data['domain'], $userData['mailLocalAddress']);
foreach($userData['mailAlternateAddress'] as &$alias)
{
$alias = preg_replace('/@'.preg_quote($ea_account->acc_domain).'$/', '@'.$_data['domain'], $alias);
$alias = preg_replace('/@'.preg_quote($ea_account->acc_domain, '/').'$/', '@'.$_data['domain'], $alias);
}
}
// fullfill the saveUserData requirements
@ -1618,7 +1618,7 @@ class admin_mail
}
else
{
$msg .= lang('No profile defined for user %1', '#'.$_data['id'].' '.$account['account_fullname']."\n");
$msg = lang('No profile defined for user %1', '#'.$_data['id'].' '.$account['account_fullname']."\n");
}
}

View File

@ -397,9 +397,15 @@ String: A string in the user\'s date format, or a relative date. Relative dates
*/
set_min(_value)
{
// minDate option checks the type, so make sure we're passing numbers as numbers (0, not "0")
// @ts-ignore
if(typeof _value === "string" && !isNaN(_value) && "" + parseInt(_value) == _value)
{
_value = parseInt(_value);
}
if(this.input_date)
{
if (this.is_mobile)
if(this.is_mobile)
{
this.input_date.attr('min', this._relativeDate(_value));
}
@ -452,9 +458,15 @@ String: A string in the user\'s date format, or a relative date. Relative dates
*/
set_max(_value)
{
// maxDate option checks the type, so make sure we're passing numbers as numbers (0, not "0")
// @ts-ignore
if(typeof _value === "string" && !isNaN(_value) && "" + parseInt(_value) == _value)
{
_value = parseInt(_value);
}
if(this.input_date)
{
if (this.is_mobile)
if(this.is_mobile)
{
this.input_date.attr('max', this._relativeDate(_value));
}
@ -1516,12 +1528,18 @@ export class et2_date_range extends et2_inputWidget
this.from = <et2_date>et2_createWidget('date',{
id: this.id+'[from]',
blur: egw.lang('From'),
onchange() { widget.to.set_min(widget.from.getValue()); }
onchange(_node,_widget) {
widget.to.set_min(widget.from.getValue());
if (_node instanceof jQuery) widget.onchange.call(widget, _widget, widget);
}
},this);
this.to = <et2_date>et2_createWidget('date',{
id: this.id+'[to]',
blur: egw.lang('To'),
onchange() {widget.from.set_max(widget.to.getValue()); }
onchange(_node,_widget) {
widget.from.set_max(widget.to.getValue());
if (_node instanceof jQuery) widget.onchange.call(widget, _widget,widget);
}
},this);
this.select = <et2_selectbox><unknown>et2_createWidget('select',{
id: this.id+'[relative]',

View File

@ -986,7 +986,7 @@ class Accounts
}
if ($account_id && ($data = self::cache_read($account_id)))
{
$ret = $just_id && $data['memberships'] ? array_keys($data['memberships']) : $data['memberships'];
$ret = $just_id && $data['memberships'] ? array_keys($data['memberships']) : ($data['memberships'] ?? []);
}
//error_log(__METHOD__."($account_id, $just_id) data=".array2string($data)." returning ".array2string($ret));
return $ret ?? [];

View File

@ -166,8 +166,8 @@ class Sql
$data['account_id'] = -$data['account_id'];
$data['mailAllowed'] = true;
}
if (!$data['account_firstname']) $data['account_firstname'] = $data['account_lid'];
if (!$data['account_lastname'])
if (empty($data['account_firstname'])) $data['account_firstname'] = $data['account_lid'];
if (empty($data['account_lastname']))
{
$data['account_lastname'] = $data['account_type'] == 'g' ? 'Group' : 'User';
// if we call lang() before the translation-class is correctly setup,
@ -177,7 +177,7 @@ class Sql
$data['account_lastname'] = lang($data['account_lastname']);
}
}
if (!$data['account_fullname']) $data['account_fullname'] = $data['account_firstname'].' '.$data['account_lastname'];
if (empty($data['account_fullname'])) $data['account_fullname'] = $data['account_firstname'].' '.$data['account_lastname'];
return $data;
}

View File

@ -74,7 +74,7 @@ class Acl
*/
function __construct($account_id = null)
{
if (is_object($GLOBALS['egw_setup']->db))
if (isset($GLOBALS['egw_setup']) && is_object($GLOBALS['egw_setup']->db))
{
$this->db = $GLOBALS['egw_setup']->db;
}
@ -481,6 +481,10 @@ class Acl
'acl_appname' => $appname,
),__LINE__,__FILE__) as $row)
{
if (!isset($rights[$row['acl_location']]))
{
$rights[$row['acl_location']] = 0;
}
$rights[$row['acl_location']] |= $row['acl_rights'];
}
return $rights;

View File

@ -232,7 +232,7 @@ class Asyncservice
{
if ((string)$t == '*') $t = '*/1';
list($one,$inc) = $arr = explode('/',$t);
list($one,$inc) = ($arr = explode('/', $t))+[null,null];
if (!(is_numeric($one) && count($arr) == 1 ||
count($arr) == 2 && is_numeric($inc)))
@ -247,7 +247,7 @@ class Asyncservice
}
else
{
list($min,$max) = $arr = explode('-',$one);
list($min,$max) = ($arr = explode('-', $one))+[null,null];
if (empty($one) || $one == '*')
{
$min = $min_unit[$u];

View File

@ -207,8 +207,8 @@ class Contacts extends Contacts\Storage
$this->default_private = substr($GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'] ?? '',-1) == 'p';
if ($this->default_addressbook > 0 && $this->default_addressbook != $this->user &&
($this->default_private ||
$this->default_addressbook == (int)$GLOBALS['egw']->preferences->forced['addressbook']['add_default'] ||
$this->default_addressbook == (int)$GLOBALS['egw']->preferences->default['addressbook']['add_default']))
$this->default_addressbook == (int)($GLOBALS['egw']->preferences->forced['addressbook']['add_default'] ?? 0) ||
$this->default_addressbook == (int)($GLOBALS['egw']->preferences->default['addressbook']['add_default'] ?? 0)))
{
$this->default_addressbook = $this->user; // admin set a default or forced pref for personal addressbook
}

View File

@ -1601,7 +1601,7 @@ class Db
}
if (empty($column_definitions))
{
$column_definitions = $this->column_definitions;
$column_definitions = $this->column_definitions ?? null;
}
if ($this->Debug) echo "<p>db::column_data_implode('$glue',".print_r($array,True).",'$use_key',".print_r($only,True).",<pre>".print_r($column_definitions,True)."</pre>\n";
@ -2164,7 +2164,7 @@ class Db
}
if (is_array($where))
{
$where = $this->column_data_implode(' AND ',$where,True,False,$table_def['fd']);
$where = $this->column_data_implode(' AND ',$where,True,False, $table_def ? $table_def['fd'] : null);
}
if (self::$tablealiases && isset(self::$tablealiases[$table]))
{

View File

@ -39,7 +39,7 @@ class Applications
*/
function __construct($account_id = '')
{
if (is_object($GLOBALS['egw_setup']) && is_object($GLOBALS['egw_setup']->db))
if (isset($GLOBALS['egw_setup']) && is_object($GLOBALS['egw_setup']->db))
{
$this->db = $GLOBALS['egw_setup']->db;
}

View File

@ -634,17 +634,22 @@ class Widget
*/
protected static function check_disabled($disabled, array $expand)
{
if (($not = $disabled[0] == '!'))
if(($not = $disabled[0] == '!'))
{
$disabled = substr($disabled,1);
$disabled = substr($disabled, 1);
}
list($value,$check) = $vals = explode('=',$disabled);
list($value, $check) = $vals = explode('=', $disabled);
// use expand_name to be able to use @ or $
$val = self::expand_name($value, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
$check_val = self::expand_name($check, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
$result = count($vals) == 1 ? $val != '' : ($check_val[0] == '/' ? preg_match($check_val,$val) : $val == $check_val);
if ($not) $result = !$result;
$result = count($vals) == 1 ?
boolval($val) :
($check_val[0] == '/' ? preg_match($check_val, $val) : $val == $check_val);
if($not)
{
$result = !$result;
}
//error_log(__METHOD__."('".($not?'!':'')."$disabled' = '$val' ".(count($vals) == 1 ? '' : ($not?'!':'=')."= '$check_val'")." = ".($result?'True':'False'));
return $result;

View File

@ -92,7 +92,7 @@ class Grid extends Box
{
$cname = self::form_name($cname, $this->id, $expand);
}
if($cname && (!empty($expand['cname']) && $expand['cname'] !== $cname || !$expand['cname']))
if ($cname && (empty($expand['cname']) || $expand['cname'] !== $cname))
{
$expand['cont'] =& self::get_array(self::$request->content, $cname);
$expand['cname'] = $cname;

View File

@ -87,7 +87,7 @@ class Link extends Etemplate\Widget
// ToDo: implement on client-side
if (!$attrs['help']) self::setElementAttribute($form_name, 'help', 'view this linked entry in its application');
if($attrs['type'] == 'link-list')
if (!empty($attrs['type']) && $attrs['type'] === 'link-list')
{
$app = $value['to_app'];
$id = $value['to_id'];

View File

@ -317,10 +317,10 @@ class Select extends Etemplate\Widget
{
// Check selection preference, we may be able to skip reading some data
$select_pref = $GLOBALS['egw_info']['user']['preferences']['common']['account_selection'];
if($this->attrs['type'] == 'select-account' && !$GLOBALS['egw_info']['user']['apps']['admin'] && $select_pref == 'none')
if($this->attrs['type'] == 'select-account' && empty($GLOBALS['egw_info']['user']['apps']['admin']) && $select_pref == 'none')
{
// Preserve but do not send the value if preference is 'none'
self::$request->preserv[$this->id] = self::$request->content[$this->id];
self::$request->preserv[$this->id] = self::$request->content[$this->id] ?? null;
unset(self::$request->content[$this->id]);
$this->attrs['readonly'] = true;
}
@ -335,7 +335,7 @@ class Select extends Etemplate\Widget
if (!isset($form_names_done[$form_name]) &&
($type_options = self::typeOptions($this,
// typeOptions thinks # of rows is the first thing in options
(!empty($this->attrs['rows']) && !empty($this->attrs['options']) && strpos($this->attrs['options'], $this->attrs['rows']) !== 0 ? $this->attrs['rows'].','.$this->attrs['options'] : $this->attrs['options']),
(!empty($this->attrs['rows']) && !empty($this->attrs['options']) && strpos($this->attrs['options'], $this->attrs['rows']) !== 0 ? $this->attrs['rows'].','.$this->attrs['options'] : ($this->attrs['options']??null)),
$no_lang, $this->attrs['readonly'] ?? false, self::get_array(self::$request->content, $form_name), $form_name)))
{
self::fix_encoded_options($type_options);
@ -966,6 +966,9 @@ class Select extends Etemplate\Widget
*/
public static function ajax_get_options($type, $attributes, $value = null)
{
// close session now, to not block other user actions
$GLOBALS['egw']->session->commit_session();
$no_lang = false;
if(is_array($attributes))
{

View File

@ -85,7 +85,7 @@ class Tabbox extends Etemplate\Widget
{
foreach($this->children[1]->children as $tab)
{
if($readonlys[$tab->id])
if (!empty($readonlys[$tab->id]))
{
$tab->attrs['disabled'] = $readonlys[$tab->id];
}

View File

@ -42,7 +42,7 @@ class Vfs extends File
$form_name = self::form_name($cname, $this->id, $expand ? $expand : array('cont'=>self::$request->content));
if (!empty($this->attrs['path']))
{
$path = self::expand_name($this->attrs['path'],$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
$path = self::expand_name($this->attrs['path'], $expand['c']??null, $expand['row'], $expand['c_']??null, $expand['row_']??null, $expand['cont']);
}
else
{

View File

@ -1662,6 +1662,9 @@ abstract class Framework extends Framework\Extra
*/
public static function ajax_user_list()
{
// close session now, to not block other user actions
$GLOBALS['egw']->session->commit_session();
$list = array('accounts' => array(),'groups' => array(), 'owngroups' => array());
if($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'primary_group')
{
@ -1688,6 +1691,9 @@ abstract class Framework extends Framework\Extra
*/
public static function ajax_account_data($_account_ids, $_field, $_resolve_groups=false)
{
// close session now, to not block other user actions
$GLOBALS['egw']->session->commit_session();
$list = array();
foreach((array)$_account_ids as $account_id)
{

View File

@ -70,17 +70,17 @@ class Html
{
$additionalQuote="";//at the end, ...
// only one &quot at the end is found. chance is, it is not belonging to the URL
if ($match[5]==';' && (strlen($match[4])-6) >=0 && strpos($match[4],'&quot',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'&quot')===false)
if (!empty($match[5]) && $match[5]===';' && (strlen($match[4])-6) >=0 && strpos($match[4],'&quot',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'&quot')===false)
{
$match[4] = substr($match[4],0,strpos($match[4],'&quot',strlen($match[4])-6));
$additionalQuote = "&quot;";
}
// if there is quoted stuff within the URL then we have at least one more &quot; in match[4], so chance is the last &quot is matched by the one within
if ($match[5]==';' && (strlen($match[4])-6) >=0 && strpos($match[4],'&quot',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'&quot')!==false)
if (!empty($match[5]) && $match[5]===';' && (strlen($match[4])-6) >=0 && strpos($match[4],'&quot',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'&quot')!==false)
{
$match[4] .= $match[5];
}
if ($match[5]==';'&&$match[4]=="&quot")
if (!empty($match[5]) && $match[5]===';' && $match[4]==="&quot")
{
$match[4] ='';
$additionalQuote = "&quot;";
@ -101,18 +101,18 @@ class Html
$result4 = preg_replace_callback( $Expr, function ($match) {
//error_log(__METHOD__.__LINE__.array2string($match));
$match += [null,null,null,null];
if ($match[4]==';' && (strlen($match[3])-4) >=0 && strpos($match[3],'&gt',strlen($match[3])-4)!==false)
if (!empty($match[4]) && $match[4]===';' && (strlen($match[3])-4) >=0 && strpos($match[3],'&gt',strlen($match[3])-4)!==false)
{
$match[3] = substr($match[3],0,strpos($match[3],'&gt',strlen($match[3])-4));
$match[4] = "&gt;";
}
if ($match[4]==';'&&$match[3]=="&gt")
if (!empty($match[4]) && $match[4]===';' && $match[3]=="&gt")
{
$match[3] ='';
$match[4] = "&gt;";
}
//error_log(__METHOD__.__LINE__.array2string($match));
return $match[1]."<a href=\"https://www".$match[2].$match[3]."\" target=\"_blank\">"."www".$match[2].$match[3]."</a>".$match[4];
return $match[1]."<a href=\"https://www".$match[2].$match[3]."\" target=\"_blank\">"."www".$match[2].$match[3]."</a>".($match[4]??'');
}, $result3 );
}
return $result4;

View File

@ -182,7 +182,7 @@ class Image
}
$app_map =& $map['vfs'];
if (true) $app_map = array();
if (($dir = $GLOBALS['egw_info']['server']['vfs_image_dir']) && Vfs::file_exists($dir) && Vfs::is_readable($dir))
if (!empty($dir = $GLOBALS['egw_info']['server']['vfs_image_dir']) && Vfs::file_exists($dir) && Vfs::is_readable($dir))
{
foreach(Vfs::find($dir) as $img)
{

View File

@ -1527,7 +1527,7 @@ class Link extends Link\Storage
{
self::notify('update',$link['app'],$link['id'],$app,$id,$link_id,$data);
}
if($data[Link::OLD_LINK_TITLE] && Json\Response::isJSONResponse())
if (!empty($data[Link::OLD_LINK_TITLE]) && Json\Response::isJSONResponse())
{
// Update client side with new title
Json\Response::get()->apply('egw.link_title_callback',array(array($app => array($id => self::title($app, $id)))));

View File

@ -213,7 +213,7 @@ class Mail
}
if ($_oldImapServerObject instanceof Mail\Imap)
{
if (!is_object(self::$instances[$_profileID]))
if (!isset(self::$instances[$_profileID]))
{
self::$instances[$_profileID] = new Mail('utf-8',false,$_profileID,false,$_reuseCache);
}
@ -1113,7 +1113,7 @@ class Mail
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_sent));
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_draft));
//error_log(__METHOD__.' ('.__LINE__.') '.array2string($this->icServer->acc_folder_template));
self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId];
self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId] ?? [];
if (isset($_specialUseFolders[$this->icServer->ImapServerId]) && !empty($_specialUseFolders[$this->icServer->ImapServerId]))
return $_specialUseFolders[$this->icServer->ImapServerId];
$_specialUseFolders[$this->icServer->ImapServerId]=array();

View File

@ -154,10 +154,10 @@ class Imap extends Horde_Imap_Client_Socket implements Imap\PushIface
$this->params = $params;
$this->isAdminConnection = $_adminConnection;
$this->enableSieve = (boolean)$this->params['acc_sieve_enabled'];
$this->loginType = $this->params['acc_imap_logintype'];
$this->domainName = $this->params['acc_domain'];
$this->loginType = $this->params['acc_imap_logintype'] ?? null;
$this->domainName = $this->params['acc_domain'] ?? null;
if (is_null($_timeout)) $_timeout = $this->params['acc_imap_timeout']?$this->params['acc_imap_timeout']:self::getTimeOut ();
if (is_null($_timeout)) $_timeout = $this->params['acc_imap_timeout']??self::getTimeOut ();
// Horde use locale for translation of error messages
// need to set LC_CTYPE for charachter classification (eg. Umlauts)

View File

@ -1554,7 +1554,7 @@ class Session
{
foreach(explode('&', $extravars) as $expr)
{
list($var,$val) = explode('=', $expr,2);
list($var,$val) = explode('=', $expr,2)+[null,null];
if (strpos($val,'%26') != false) $val = str_replace('%26','&',$val); // make sure to not double encode &
if (substr($var,-2) == '[]')
{

View File

@ -965,7 +965,7 @@ class Base
}
}
$num_rows = 0; // as spec. in max_matches in the user-prefs
if (is_array($start)) list($start,$num_rows) = $start;
if (is_array($start)) list($start,$num_rows) = $start+[null,null];
// fix GROUP BY clause to contain all non-aggregate selected columns
if ($order_by && stripos($order_by,'GROUP BY') !== false)
@ -1106,8 +1106,8 @@ class Base
$query[$db_col] = '';
}
}
elseif ($wildcard || $criteria[$col][0] == '!' ||
is_string($criteria[$col]) && (strpos($criteria[$col],'*')!==false || strpos($criteria[$col],'?')!==false))
elseif ($wildcard || is_string($criteria[$col]) && ($criteria[$col][0] == '!' ||
(strpos($criteria[$col],'*') !== false || strpos($criteria[$col],'?') !== false)))
{
// if search pattern alread contains a wildcard, do NOT add further ones automatic
if (is_string($criteria[$col]) && (strpos($criteria[$col],'*')!==false || strpos($criteria[$col],'?')!==false))

View File

@ -62,7 +62,7 @@ class History
$this->appname = $appname ?: $GLOBALS['egw_info']['flags']['currentapp'];
$this->user = $user ?: $GLOBALS['egw_info']['user']['account_id'];
if(is_object($GLOBALS['egw_setup']->db))
if (isset($GLOBALS['egw_setup']) && is_object($GLOBALS['egw_setup']->db))
{
$this->db = $GLOBALS['egw_setup']->db;
}

View File

@ -1723,7 +1723,7 @@ abstract class Merge
{
// If we send the real content it can result in infinite loop of lookups
// so we send only the used fields
$content = $expand_sub_cfs[$field] ? $expand_sub_cfs[$field] : '';
$content = $expand_sub_cfs[$field] ?? $matches[0][$index];
$app_replacements[$field] = $this->get_app_replacements($field_app, $values['#' . $field], $content);
}
$replacements[$placeholders[$index]] = $app_replacements[$field]['$$' . $sub[$index] . '$$'];
@ -1793,7 +1793,7 @@ abstract class Merge
public function get_app_replacements($app, $id, $content, $prefix = '')
{
$replacements = array();
if(!$app || $id || !$content)
if(!$app || !$id || !$content)
{
return $replacements;
}
@ -1820,7 +1820,7 @@ abstract class Merge
// Don't break merge, just log it
error_log($e->getMessage());
}
return $replacements;
return $replacements ?: [];
}
/**
@ -2607,7 +2607,7 @@ abstract class Merge
$action_base,
array(
'icon' => Api\Vfs::mime_icon($file['mime']),
'caption' => Api\Vfs::decodePath($file['name']),
'caption' => Api\Vfs::decodePath(Api\Vfs::basename($file['name'])),
'onExecute' => 'javaScript:app.' . $GLOBALS['egw_info']['flags']['currentapp'] . '.merge',
'merge_data' => $edit_attributes
),
@ -2832,7 +2832,7 @@ abstract class Merge
);
// Check for a configured preferred directory
if(($pref = $GLOBALS['egw_info']['user']['preferences'][$this->get_app()][Merge::PREF_STORE_LOCATION]) && Vfs::is_writable($pref))
if(!empty($pref = $GLOBALS['egw_info']['user']['preferences'][$this->get_app()][Merge::PREF_STORE_LOCATION]) && Vfs::is_writable($pref))
{
$target = $pref;
}

View File

@ -435,7 +435,7 @@ abstract class Tracking
$this->historylog = new History($this->app, $this->user);
}
// log user-agent and session-action
if ($GLOBALS['egw_info']['server']['log_user_agent_action'] && ($changed_fields || !$old))
if (!empty($GLOBALS['egw_info']['server']['log_user_agent_action']) && ($changed_fields || !$old))
{
$this->historylog->add('user_agent_action', $data[$this->id_field],
$_SERVER['HTTP_USER_AGENT'], $_SESSION[Api\Session::EGW_SESSION_VAR]['session_action']);
@ -509,7 +509,7 @@ abstract class Tracking
$changed_fields = array();
foreach($this->field2history as $name => $status)
{
if (!$old[$name] && !$data[$name]) continue; // treat all sorts of empty equally
if (empty($old[$name]) && empty($data[$name])) continue; // treat all sorts of empty equally
if ($name[0] == '#' && !isset($data[$name])) continue; // no set customfields are not stored, therefore not changed
@ -544,7 +544,7 @@ abstract class Tracking
}
foreach($data as $name => $value)
{
if ($name[0] == '#' && $name[1] == '#' && $value !== $old[$name])
if (isset($name[0]) && $name[0] == '#' && $name[1] == '#' && $value !== $old[$name])
{
$changed_fields[] = $name;
}
@ -843,7 +843,7 @@ abstract class Tracking
$notification->set_reply_to($reply_to);
$notification->set_subject($subject);
$notification->set_links(array($link));
$notification->set_popupdata($link['app'], $link);
$notification->set_popupdata($link?$link['app']:null, $link);
if ($attachments && is_array($attachments))
{
$notification->set_attachments($attachments);
@ -938,9 +938,9 @@ abstract class Tracking
$sender = $name ? $name.' <'.$email.'>' : $email;
}
}
elseif(!$sender)
elseif (!$sender)
{
$sender = 'EGroupware '.lang($this->app).' <noreply@'.$GLOBALS['egw_info']['server']['mail_suffix'].'>';
$sender = 'EGroupware '.lang($this->app).' <noreply@'.($GLOBALS['egw_info']['server']['mail_suffix']??'nodomain.org').'>';
}
//echo "<p>".__METHOD__."()='".htmlspecialchars($sender)."'</p>\n";
return $sender;

View File

@ -283,7 +283,7 @@ class Translation
self::add_app($apps);
}
$phrase = static::translate($message, $vars);
if($old_lang)
if (!empty($old_lang))
{
$GLOBALS['egw_info']['user']['preferences']['common']['lang'] = $old_lang;
self::init(true);

View File

@ -1,4 +1,5 @@
<?php
<?php /** @noinspection ALL */
/**
* EGroupware API: VFS - static methods to use the new eGW virtual file system
*
@ -427,7 +428,7 @@ class Vfs extends Vfs\Base
self::_check_add($options,$path,$result);
}
if ($is_dir && (!isset($options['maxdepth']) || ($options['maxdepth'] > 0 &&
$options['depth'] < $options['maxdepth'])) &&
($options['depth'] ?? 0) < $options['maxdepth'])) &&
($dir = @opendir($path, $context)))
{
while(($fname = readdir($dir)) !== false)
@ -1296,7 +1297,7 @@ class Vfs extends Vfs\Base
$b = explode('/',$b_str);
$ret = implode('/',array_merge($a,$b));
}
return $ret.($query ? (strpos($url,'?')===false ? '?' : '&').$query : '');
return $ret.(isset($query) ? (strpos($url,'?')===false ? '?' : '&').$query : '');
}
/**
@ -1662,39 +1663,57 @@ class Vfs extends Vfs\Base
* checkLock() helper
*
* @param string $url url or path, lock is granted for the path only, but url is used for access checks
* @return array|boolean false if there's no lock, else array with lock info
* @param int $depth=0 currently only 0 or >0 = infinit/whole tree is evaluated
* @return array[]|array|boolean $depth > 0: array of path => lock info arrays for $depth > 0
* $depth=0: false if there's no lock, else array with lock info
*/
static function checkLock($url)
static function checkLock($url, int $depth=0)
{
$path = self::parse_url($url, PHP_URL_PATH);
if (isset(self::$lock_cache[$path]))
if (!$depth && isset(self::$lock_cache[$path]))
{
if (self::LOCK_DEBUG) error_log(__METHOD__."($url) returns from CACHE ".str_replace(array("\n",' '),'',print_r(self::$lock_cache[$url],true)));
return self::$lock_cache[$path];
}
$where = 'lock_path='.self::$db->quote($path);
if ($depth > 0)
{
$where = ['lock_path LIKE '.self::$db->quote($path.'%')];
}
else
{
$where = 'lock_path='.self::$db->quote($path);
}
// ToDo: additional check parent dirs for locks and children of the requested directory
//$where .= ' OR '.self::$db->quote($path).' LIKE '.self::$db->concat('lock_path',"'%'").' OR lock_path LIKE '.self::$db->quote($path.'%');
// ToDo: shared locks can return multiple rows
if (($result = self::$db->select(self::LOCK_TABLE,'*',$where,__LINE__,__FILE__)->fetch()))
$results = [];
foreach(self::$db->select(self::LOCK_TABLE,'*',$where,__LINE__,__FILE__) as $result)
{
$result = Db::strip_array_keys($result,'lock_');
$result['type'] = Db::from_bool($result['write']) ? 'write' : 'read';
$result = Db::strip_array_keys($result, 'lock_');
$result['type'] = Db::from_bool($result['write']) ? 'write' : 'read';
$result['scope'] = Db::from_bool($result['exclusive']) ? 'exclusive' : 'shared';
$result['depth'] = Db::from_bool($result['recursive']) ? 'infinite' : 0;
}
if ($result && $result['expires'] < time()) // lock is expired --> remove it
{
self::$db->delete(self::LOCK_TABLE,array(
'lock_path' => $result['path'],
'lock_token' => $result['token'],
),__LINE__,__FILE__);
if ($result['expires'] < time()) // lock is expired --> remove it
{
self::$db->delete(self::LOCK_TABLE, array(
'lock_path' => $result['path'],
'lock_token' => $result['token'],
), __LINE__, __FILE__);
if (self::LOCK_DEBUG) error_log(__METHOD__."($url) lock is expired at ".date('Y-m-d H:i:s',$result['expires'])." --> removed");
$result = false;
if (self::LOCK_DEBUG) error_log(__METHOD__ . "($url) lock is expired at " . date('Y-m-d H:i:s', $result['expires']) . " --> removed");
$result = false;
}
else
{
if ($result['path'] === $path || str_starts_with($result['path'], $path))
{
$results[$result['path']] = $result;
}
self::$lock_cache[$result['path']] = $result;
}
}
if (self::LOCK_DEBUG) error_log(__METHOD__."($url) returns ".($result?array2string($result):'false'));
return self::$lock_cache[$path] = $result;
if (self::LOCK_DEBUG) error_log(__METHOD__."($url, $depth) returns ".array2string($depth ? $result : ($result ?? false)));
return $depth ? $results : ($result ?? false);
}
/**
@ -2043,7 +2062,7 @@ class Vfs extends Vfs\Base
}
else
{
$ret = ($context ? copy($tmp_name, self::PREFIX.$target, $context) :
$ret = (isset($context) ? copy($tmp_name, self::PREFIX.$target, $context) :
copy($tmp_name, self::PREFIX.$target)) ?
self::stat($target) : false;
}

View File

@ -573,7 +573,7 @@ class Base
{
return __CLASS__;
}
list($app, $app_scheme) = explode('.', $scheme);
list($app, $app_scheme) = explode('.', $scheme)+[null,null];
foreach(array(
empty($app_scheme) ? 'EGroupware\\Api\\Vfs\\' . ucfirst($scheme) . '\\StreamWrapper' : // streamwrapper in Api\Vfs
'EGroupware\\' . ucfirst($app) . '\\Vfs\\' . ucfirst($app_scheme) . '\\StreamWrapper',

View File

@ -262,7 +262,7 @@ class StreamWrapper extends LinksParent
if($path[0] != '/')
{
if (strpos($path,'?') !== false) $query = Vfs::parse_url($path,PHP_URL_QUERY);
$path = Vfs::parse_url($path,PHP_URL_PATH).($query ? '?'.$query : '');
$path = Vfs::parse_url($path,PHP_URL_PATH).(!empty($query) ? '?'.$query : '');
}
list(,$apps,$app,$id) = explode('/',$path);

View File

@ -310,7 +310,7 @@ class StreamWrapper extends Api\Db\Pdo implements Vfs\StreamWrapperIface
{
if (self::LOG_LEVEL > 1) error_log(__METHOD__." fopen (may create a directory? mkdir) ($this->opened_fs_id,$mode,$options)");
// if creating a new file as root eg. via (docker exec) filemanager/cli.php do NOT create files unreadable by webserver
if ($new_file && function_exists('posix_getuid') && !posix_getuid())
if (!empty($new_file) && function_exists('posix_getuid') && !posix_getuid())
{
umask(0666);
}
@ -1628,7 +1628,7 @@ GROUP BY A.fs_id';
// first check our stat-cache for the ids
foreach(self::$stat_cache as $path => $stat)
{
if (($key = array_search($stat['fs_id'],$ids)) !== false)
if ($stat && ($key = array_search($stat['fs_id'],$ids)) !== false)
{
$pathes[$stat['fs_id']] = $path;
unset($ids[$key]);
@ -1948,7 +1948,7 @@ GROUP BY A.fs_id';
}
if (!is_array($path_ids))
{
$props = $props[$row['fs_id']] ? $props[$row['fs_id']] : array(); // return empty array for no props
$props = isset($row) && !empty($props[$row['fs_id']]) ? $props[$row['fs_id']] : []; // return empty array for no props
}
elseif ($props && isset($stat) && is_array($id2path = self::id2path(array_keys($props)))) // need to map fs_id's to pathes
{

View File

@ -209,7 +209,7 @@ class StreamWrapper extends Base implements StreamWrapperIface
// are we requested to treat the opened file as new file (only for files opened NOT for reading)
if ($mode[0] != 'r' && !$this->opened_stream_is_new && $this->context &&
($opts = stream_context_get_options($this->context)) &&
$opts['options'][self::SCHEME]['treat_as_new'])
!empty($opts['options'][self::SCHEME]['treat_as_new']))
{
$this->opened_stream_is_new = true;
//error_log(__METHOD__."($path,$mode,...) stat=$stat, context=".array2string($opts)." --> ".array2string($this->opened_stream_is_new));
@ -994,7 +994,7 @@ class StreamWrapper extends Base implements StreamWrapperIface
if (Vfs::$user != $GLOBALS['egw_info']['user']['account_id'])
{
$prefs = new Api\Preferences(Vfs::$user);
$vfs_fstab = $prefs->data['common']['vfs_fstab'];
$vfs_fstab = $prefs->data['common']['vfs_fstab'] ?? [];
}
else
{

View File

@ -117,7 +117,7 @@ trait UserContextTrait
}
// if we check writable and have a readonly mount --> return false, as backends dont know about r/o url parameter
if ($check == Vfs::WRITABLE && Vfs\StreamWrapper::url_is_readonly($stat['url']))
if ($check == Vfs::WRITABLE && Vfs\StreamWrapper::url_is_readonly($stat['url'] ?? null))
{
//error_log(__METHOD__."(path=$path, check=writable, ...) failed because mount is readonly");
return false;
@ -125,7 +125,7 @@ trait UserContextTrait
// check if we use an EGroupwre stream wrapper, or a stock php one
// if it's not an EGroupware one, we can NOT use uid, gid and mode!
if (($scheme = Vfs::parse_url($stat['url'], PHP_URL_SCHEME)) && !(class_exists(Vfs::scheme2class($scheme))))
if (($scheme = Vfs::parse_url($stat['url'] ?? null, PHP_URL_SCHEME)) && !(class_exists(Vfs::scheme2class($scheme))))
{
switch($check)
{

View File

@ -26,22 +26,18 @@
</box>
</box>
<template template="@extra_template"/>
<details title="Common">
<description value="Common" class="group title"/>
<box id="common">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
</details>
<details title="Current user">
<description value="Current user" class="group title"/>
<box id="user">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
</details>
</vbox>
<styles>
.et2_details_title, .title {
@ -60,6 +56,17 @@
#api-show_replacements_title:first-letter, .title {
text-transform: capitalize;
}
div#api-show_replacements_placeholders, #api-show_replacements_common, #api-show_replacements_user {
display: flex;
flex-wrap: wrap;
gap: 10px 20px;
}
div[id^="api-show_replacements"] {
min-width: 350px;
}
table#api-show_replacements_placeholders > tbody > tr > td:first-child {
padding-right: 5em;
}
</styles>
</template>
</overlay>

View File

@ -1356,7 +1356,7 @@ class calendar_groupdav extends Api\CalDAV\Handler
}
// from now on we deal with exceptions
$org_recurrence = $org_recurrences[$recurrence['recurrence']];
$org_recurrence = isset($recurrence['recurrence']) ? $org_recurrences[$recurrence['recurrence']] : null;
if (isset($org_recurrence)) // already existing recurrence
{
//error_log(__METHOD__.'() setting id #'.$org_recurrence['id']).' for '.$recurrence['recurrence'].' = '.date('Y-m-d H:i:s',$recurrence['recurrence']);

View File

@ -33,7 +33,6 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
public function beforeSendToClient($cname, array $expand=null)
{
Framework::includeJS('.','et2_widget_owner','calendar');
Framework::includeCSS('calendar','calendar');
$bo = new calendar_bo();
@ -139,6 +138,9 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
*/
public static function ajax_owner($id = null)
{
// close session now, to not block other user actions
$GLOBALS['egw']->session->commit_session();
// Handle a request for a single ID
if($id && !is_array($id))
{

View File

@ -785,7 +785,7 @@ class calendar_so
}
if (!empty($params['sql_filter']))
{
if (is_string($params['sql_filter']))
if(is_string($params['sql_filter']))
{
$where[] = $params['sql_filter'];
}
@ -794,15 +794,45 @@ class calendar_so
$where = array_merge($where, $params['sql_filter']);
}
}
if(array_filter($where, fn($key) => str_contains($key, '#'), ARRAY_FILTER_USE_KEY))
{
$custom_fields = Api\Storage\Customfields::get('calendar');
foreach($where as $col => $data)
{
if($col[0] == '#' && $data)
{
unset($where[$col]);
$filtermethod = " $this->cal_table.cal_id IN (SELECT DISTINCT cal_id FROM $this->extra_table WHERE ";
if($custom_fields[substr($col, 1)]['type'] == 'select' && $custom_fields[substr($col, 1)]['rows'] > 1)
{
// Multi-select - any entry with the filter value selected matches
$filtermethod .= $this->db->expression($this->extra_table, array(
'cal_extra_name' => substr($col, 1),
$this->db->concat("','", 'cal_extra_value', "','") . ' ' . $this->db->capabilities[Api\Db::CAPABILITY_CASE_INSENSITIV_LIKE] . ' ' . $this->db->quote('%,' . $data . ',%'),
)) . ')';
}
else
{
$filtermethod .= $this->db->expression($this->extra_table, array(
'cal_extra_name' => substr($col, 1),
'cal_extra_value' => $data,
)) . ')';
}
$where[] = $filtermethod;
}
}
}
$useUnionQuery = $this->db->capabilities['distinct_on_text'] && $this->db->capabilities['union'];
if ($users)
if($users)
{
$users_by_type = array();
foreach((array)$users as $user)
{
if (is_numeric($user))
if(is_numeric($user))
{
$users_by_type['u'][] = (int) $user;
$users_by_type['u'][] = (int)$user;
}
else
{

View File

@ -408,14 +408,18 @@ class calendar_uilist extends calendar_ui
$col_filter = array();
foreach($params['col_filter'] as $name => $val)
{
if (!in_array($name, array('participant','row_id')) && (string)$val !== '')
if(!in_array($name, array('participant', 'row_id')) && (string)$val !== '')
{
$col_filter[$name] = $val;
}
else if ( $name == 'row_id' && (string)$val !== '')
elseif($name == 'row_id' && (string)$val !== '')
{
$col_filter['cal_id'] = $val;
}
if($name[0] == '#')
{
$search_params['cfs'][] = $name;
}
}
// Videocalls
if(array_key_exists('include_videocalls',$params['col_filter']))

View File

@ -31,7 +31,6 @@ class calendar_wizard_import_ical
*/
function __construct()
{
Api\Framework::includeJS('.','et2_widget_owner','calendar');
Api\Framework::includeCSS('calendar','calendar');
$this->steps = array(
'wizard_step55' => lang('Edit conditions'),
@ -73,7 +72,7 @@ class calendar_wizard_import_ical
$content['step'] = 'wizard_step55';
foreach(array('skip_conflicts','empty_before_import','remove_past','remove_future','override_values') as $field)
{
if(!$content[$field] && array_key_exists($field, $content['plugin_options']))
if(!$content[$field] && is_array($content['plugin_options']) && array_key_exists($field, $content['plugin_options']))
{
$content[$field] = $content['plugin_options'][$field];
}

View File

@ -22,7 +22,7 @@ export abstract class View
* @param {Object} state
* @returns {string}
*/
static header(state)
public static header(state)
{
let formatDate = new Date(state.date);
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
@ -34,7 +34,7 @@ export abstract class View
*
* @param {object} state
*/
static _owner(state)
public static _owner(state)
{
let owner = '';
if(state.owner.length && state.owner.length == 1 && app.calendar.sidebox_et2)
@ -53,7 +53,7 @@ export abstract class View
* @param {Object} state
* @returns {Date}
*/
static start_date(state)
public static start_date(state)
{
const d = state.date ? new Date(state.date) : new Date();
d.setUTCHours(0);
@ -68,7 +68,7 @@ export abstract class View
* @param {Object} state
* @returns {Date}
*/
static end_date(state)
public static end_date(state)
{
const d = state.date ? new Date(state.date) : new Date();
d.setUTCHours(23);
@ -87,7 +87,7 @@ export abstract class View
* @param {number[]|String} state state.owner List of owner IDs, or a comma seperated list
* @returns {number[]|String}
*/
static owner(state)
public static owner(state)
{
return state.owner || 0;
}
@ -98,7 +98,7 @@ export abstract class View
* @param {object} state
* @returns {boolean} Current preference to show 5 or 7 days in weekview
*/
static show_weekend(state)
public static show_weekend(state)
{
return state.weekend;
}
@ -108,27 +108,30 @@ export abstract class View
*
* @param {object} state
*/
static granularity(state)
public static granularity(state)
{
var list = egw.preference('use_time_grid', 'calendar');
if(list == '0' || typeof list === 'undefined')
{
return parseInt('' + egw.preference('interval', 'calendar')) || 30;
}
if(typeof list == 'string') list = list.split(',');
if(typeof list == 'string')
{
list = list.split(',');
}
if(!(<string><unknown>list).indexOf && jQuery.isPlainObject(list))
{
list = jQuery.map(list, function (el)
list = jQuery.map(list, function(el)
{
return el;
});
}
return (<string[]> list).indexOf(state.view) >= 0 ?
0 :
parseInt(<string> egw.preference('interval', 'calendar')) || 30;
return (<string[]>list).indexOf(state.view) >= 0 ?
0 :
parseInt(<string>egw.preference('interval', 'calendar')) || 30;
}
static extend(sub)
public static extend(sub)
{
return jQuery.extend({}, this, {_super: this}, sub);
}
@ -140,7 +143,7 @@ export abstract class View
* forward, negative for backward
* @returns {Date}
*/
static scroll(delta)
public static scroll(delta)
{
var d = new Date(app.calendar.state.date);
d.setUTCDate(d.getUTCDate() + (7 * delta));
@ -158,27 +161,27 @@ export class day extends View
{
public static etemplates : (string | etemplate2)[] = ['calendar.view', 'calendar.todo'];
static header(state)
public static header(state)
{
var formatDate = new Date(state.date);
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
return date('l, ', formatDate) + super.header(state);
}
static start_date(state)
public static start_date(state)
{
var d = super.start_date(state);
state.date = app.calendar.date.toString(d);
return d;
}
static show_weekend(state)
public static show_weekend(state)
{
state.days = '1';
return true;
}
static scroll(delta)
public static scroll(delta)
{
var d = new Date(app.calendar.state.date);
d.setUTCDate(d.getUTCDate() + (delta));
@ -188,7 +191,7 @@ export class day extends View
export class day4 extends View
{
static end_date(state)
public static end_date(state)
{
var d = super.end_date(state);
state.days = '4';
@ -199,13 +202,13 @@ export class day4 extends View
return d;
}
static show_weekend(state)
public static show_weekend(state)
{
state.weekend = 'true';
return true;
}
static scroll(delta)
public static scroll(delta)
{
var d = new Date(app.calendar.state.date);
d.setUTCDate(d.getUTCDate() + (4 * delta));
@ -215,7 +218,7 @@ export class day4 extends View
export class week extends View
{
static header(state)
public static header(state)
{
var end_date = state.last;
if(!week.show_weekend(state))
@ -228,12 +231,12 @@ export class week extends View
app.calendar.date.long_date(state.first, end_date);
}
static start_date(state)
public static start_date(state)
{
return app.calendar.date.start_of_week(super.start_date(state));
}
static end_date(state)
public static end_date(state)
{
var d = app.calendar.date.start_of_week(state.date || new Date());
// Always 7 days, we just turn weekends on or off
@ -247,7 +250,7 @@ export class week extends View
export class weekN extends View
{
static header(state)
public static header(state)
{
return super._owner(state) + app.calendar.egw.lang('Week') + ' ' +
app.calendar.date.week_number(state.first) + ' - ' +
@ -255,12 +258,12 @@ export class weekN extends View
app.calendar.date.long_date(state.first, state.last);
}
static start_date(state)
public static start_date(state)
{
return app.calendar.date.start_of_week(super.start_date(state));
}
static end_date(state)
public static end_date(state)
{
state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview', 'calendar') || 7);
@ -273,21 +276,21 @@ export class weekN extends View
export class month extends View
{
static header(state)
public static header(state)
{
var formatDate = new Date(state.date);
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
return super._owner(state) + app.calendar.egw.lang(date('F', formatDate)) + ' ' + date('Y', formatDate);
}
static start_date(state)
public static start_date(state)
{
var d = super.start_date(state);
d.setUTCDate(1);
return app.calendar.date.start_of_week(d);
}
static end_date(state)
public static end_date(state)
{
var d = super.end_date(state);
d = new Date(d.getFullYear(), d.getUTCMonth() + 1, 1, 0, -d.getTimezoneOffset(), 0);
@ -295,7 +298,7 @@ export class month extends View
return app.calendar.date.end_of_week(d);
}
static scroll(delta)
public static scroll(delta)
{
var d = new Date(app.calendar.state.date);
// Set day to 15 so we don't get overflow on short months
@ -310,7 +313,7 @@ export class planner extends View
{
public static etemplates : (string | etemplate2)[] = ['calendar.planner'];
static header(state)
public static header(state)
{
var startDate = new Date(state.first);
startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
@ -321,17 +324,17 @@ export class planner extends View
(startDate == endDate ? '' : ' - ' + date(<string>egw.preference('dateformat'), endDate));
}
static group_by(state)
public static group_by(state)
{
return state.sortby ? state.sortby : 0;
}
// Note: Planner uses the additional value of planner_view to determine
// the start & end dates using other view's functions
static start_date(state)
public static start_date(state)
{
// Start here, in case we can't find anything better
var d = super.start_date( state);
var d = super.start_date(state);
if(state.sortby && state.sortby === 'month')
{
@ -353,10 +356,10 @@ export class planner extends View
return d;
}
static end_date(state)
public static end_date(state)
{
var d = super.end_date( state);
var d = super.end_date(state);
if(state.sortby && state.sortby === 'month')
{
d.setUTCDate(0);
@ -379,13 +382,13 @@ export class planner extends View
return d;
}
static hide_empty(state)
public static hide_empty(state)
{
var check = state.sortby == 'user' ? ['user', 'both'] : ['cat', 'both'];
return (check.indexOf(egw.preference('planner_show_empty_rows', 'calendar') + '') === -1);
}
static scroll(delta)
public static scroll(delta)
{
if(app.calendar.state.planner_view && !isNaN(delta) && app.calendar.state.sortby !== "month")
{
@ -425,7 +428,7 @@ export class listview extends View
{
public static etemplates : (string | etemplate2)[] = ['calendar.list'];
static header(state)
public static header(state)
{
var startDate = new Date(state.first || state.date);
startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);

View File

@ -29,6 +29,7 @@ import {day, day4, listview, month, planner, week, weekN} from "./View";
import {et2_calendar_view} from "./et2_widget_view";
import {et2_calendar_timegrid} from "./et2_widget_timegrid";
import {et2_calendar_daycol} from "./et2_widget_daycol";
import {et2_calendar_planner} from "./et2_widget_planner";
import {et2_calendar_planner_row} from "./et2_widget_planner_row";
import {et2_calendar_event} from "./et2_widget_event";
import {et2_dialog} from "../../api/js/etemplate/et2_widget_dialog";
@ -256,10 +257,12 @@ export class CalendarApp extends EgwApp
if(sidebox.length == 0 && egw_getFramework() != null)
{
// Force rollup to load owner widget, it leaves it out otherwise
new et2_calendar_owner(_et2.widgetContainer,{});
new et2_calendar_owner(_et2.widgetContainer, {});
// Force rollup to load planner widget, it leaves it out otherwise
new et2_calendar_planner(_et2.widgetContainer, {});
var egw_fw = egw_getFramework();
sidebox= jQuery('#favorite_sidebox_'+this.appname,egw_fw.sidemenuDiv);
sidebox = jQuery('#favorite_sidebox_' + this.appname, egw_fw.sidemenuDiv);
}
var content = this.et2.getArrayMgr('content');
@ -3051,21 +3054,28 @@ export class CalendarApp extends EgwApp
{
// Simple, easy case - just one widget for the selected time span. (planner)
// Update existing view's special attribute filters, defined in the view list
for(var updater in view)
for(let updater of Object.getOwnPropertyNames(view))
{
if(typeof view[updater] === 'function')
{
let value = view[updater].call(this,state.state);
if(updater === 'start_date') state.state.first = this.date.toString(value);
if(updater === 'end_date') state.state.last = this.date.toString(value);
let value = view[updater].call(this, state.state);
if(updater === 'start_date')
{
state.state.first = this.date.toString(value);
}
if(updater === 'end_date')
{
state.state.last = this.date.toString(value);
}
// Set value
for(var i = 0; i < view.etemplates.length; i++)
{
view.etemplates[i].widgetContainer.iterateOver(function(widget) {
if(typeof widget['set_'+updater] === 'function')
view.etemplates[i].widgetContainer.iterateOver(function(widget)
{
if(typeof widget['set_' + updater] === 'function')
{
widget['set_'+updater](value);
widget['set_' + updater](value);
}
}, this, et2_calendar_view);
}

View File

@ -927,7 +927,7 @@ export class et2_calendar_daycol extends et2_valueWidget implements et2_IDetache
(new Date(b.options.value.end) - new Date(b.options.value.start)) -
(new Date(a.options.value.end) - new Date(a.options.value.start));
return duration ? duration : (a.options.value.app_id - b.options.value.app_id);
return (Math.abs(duration) > 360000) ? duration : (a.options.value.title.localeCompare(b.options.value.title));
}
else if (a.options.value.whole_day || b.options.value.whole_day)
{

View File

@ -8,8 +8,7 @@
* @author Nathan Gray
*/
import {et2_register_widget} from "../../api/js/etemplate/et2_core_widget";
import {et2_selectbox} from "../../api/js/etemplate/et2_widget_selectbox";
import {et2_register_widget} from "../../api/js/etemplate/et2_core_widget.ts";
import {et2_taglist_email} from "../../api/js/etemplate/et2_widget_taglist";
/**
@ -20,7 +19,7 @@ import {et2_taglist_email} from "../../api/js/etemplate/et2_widget_taglist";
*
* Uses MagicSuggest library
* @see http://nicolasbize.github.io/magicsuggest/
* @augments et2_selectbox
* @augments et2_taglist_email
*/
export class et2_calendar_owner extends et2_taglist_email
{

View File

@ -29,6 +29,8 @@ import {et2_compileLegacyJS} from "../../api/js/etemplate/et2_core_legacyJSFunct
import {et2_no_init} from "../../api/js/etemplate/et2_core_common";
import {CalendarApp} from "./app";
import {sprintf} from "../../api/js/egw_action/egw_action_common.js";
import {et2_dataview_grid} from "../../api/js/etemplate/et2_dataview_view_grid";
import {et2_selectbox} from "../../api/js/etemplate/et2_widget_selectbox";
/**
* Class which implements the "calendar-planner" XET-Tag for displaying a longer
@ -1906,7 +1908,6 @@ export class et2_calendar_planner extends et2_calendar_view implements et2_IDeta
}
else
{
fetch = true;
// Assume it's empty, if there is data it will be filled later
egw.dataStoreUID(cache_id, []);
}
@ -2375,22 +2376,26 @@ export class et2_calendar_planner extends et2_calendar_view implements et2_IDeta
*/
_get_time_from_position( x,y)
{
if(!this.options.start_date || !this.options.end_date)
{
return false;
}
x = Math.round(x);
y = Math.round(y);
// Round to user's preferred event interval
var interval = egw.preference('interval','calendar') || 30;
var interval = egw.preference('interval', 'calendar') || 30;
// Relative horizontal position, as a percentage
var width = 0;
jQuery('.calendar_eventRows',this.div).each(function() {width = Math.max(width,jQuery(this).width());});
var rel_x = Math.min(x / width,1);
jQuery('.calendar_eventRows', this.div).each(function() {width = Math.max(width, jQuery(this).width());});
var rel_x = Math.min(x / width, 1);
// Relative time, in minutes from start
var rel_time = 0;
var day_header = jQuery('.calendar_plannerScaleDay',this.headers);
var day_header = jQuery('.calendar_plannerScaleDay', this.headers);
// Simple math, the x is offset from start date
if(this.options.group_by !== 'month' && (

View File

@ -125,7 +125,7 @@
"robrichards/xmlseclibs": "^3.1.1",
"simplesamlphp/simplesamlphp": "^1.19.0",
"simplesamlphp/twig-configurable-i18n": "~2.3.3",
"tinymce/tinymce": "^5"
"tinymce/tinymce": "5.9.*"
},
"require-dev": {
"guzzlehttp/guzzle": "^6.5",

20
composer.lock generated
View File

@ -8493,16 +8493,16 @@
},
{
"name": "tinymce/tinymce",
"version": "5.5.1",
"version": "5.9.2",
"source": {
"type": "git",
"url": "https://github.com/tinymce/tinymce-dist.git",
"reference": "a436d254bc8d62e50be1fdb3d3e98981ab0e4c40"
"reference": "48c665ad12ba0e4d8068ba0784026c7488aa4746"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tinymce/tinymce-dist/zipball/a436d254bc8d62e50be1fdb3d3e98981ab0e4c40",
"reference": "a436d254bc8d62e50be1fdb3d3e98981ab0e4c40",
"url": "https://api.github.com/repos/tinymce/tinymce-dist/zipball/48c665ad12ba0e4d8068ba0784026c7488aa4746",
"reference": "48c665ad12ba0e4d8068ba0784026c7488aa4746",
"shasum": ""
},
"type": "component",
@ -8528,16 +8528,22 @@
"LGPL-2.1-only"
],
"description": "Web based JavaScript HTML WYSIWYG editor control.",
"homepage": "http://www.tinymce.com",
"homepage": "https://www.tiny.cloud/",
"keywords": [
"editor",
"contenteditable",
"editing",
"html",
"javascript",
"rich editor",
"rich text",
"rich text editor",
"richtext",
"rte",
"text",
"tinymce",
"wysiwyg"
],
"time": "2020-10-01T05:31:22+00:00"
"time": "2021-09-08T03:45:09+00:00"
},
{
"name": "twig/extensions",

View File

@ -193,7 +193,7 @@ class filemanager_merge extends Api\Storage\Merge
if(is_array($link))
{
// Directories have their internal protocol in path here
if($link['path'] && strpos($link['path'], '://') !== false) $link['path'] = $file['path'];
if (!empty($link['path']) && strpos($link['path'], '://') !== false) $link['path'] = $file['path'];
$link = Api\Session::link('/index.php', $link);
}
else
@ -215,7 +215,7 @@ class filemanager_merge extends Api\Storage\Merge
if(!$value) $value = '';
$info['$$' . ($prefix ? $prefix . '/' : '') . $key . '$$'] = $value;
}
if($app_placeholders)
if (!empty($app_placeholders) && is_array($app_placeholders))
{
$info = array_merge($app_placeholders, $info);
}

View File

@ -142,58 +142,68 @@ class filemanager_ui
public static function get_actions()
{
$actions = array(
'open' => array(
'caption' => lang('Open'),
'icon' => '',
'group' => $group=1,
'open' => array(
'caption' => lang('Open'),
'icon' => '',
'group' => $group = 1,
'allowOnMultiple' => false,
'onExecute' => 'javaScript:app.filemanager.open',
'default' => true
'onExecute' => 'javaScript:app.filemanager.open',
'default' => true
),
'new' => array(
'caption' => 'New',
'group' => $group,
'new' => array(
'caption' => 'New',
'group' => $group,
'disableClass' => 'noEdit',
'children' => array (
'document' => array (
'caption' => 'Document',
'icon' => 'new',
'children' => array(
'document' => array(
'caption' => 'Document',
'icon' => 'new',
'onExecute' => 'javaScript:app.filemanager.create_new',
)
)
),
'mkdir' => array(
'caption' => lang('Create directory'),
'icon' => 'filemanager/button_createdir',
'group' => $group,
'mkdir' => array(
'caption' => lang('Create directory'),
'icon' => 'filemanager/button_createdir',
'group' => $group,
'allowOnMultiple' => false,
'disableClass' => 'noEdit',
'onExecute' => 'javaScript:app.filemanager.createdir'
'disableClass' => 'noEdit',
'onExecute' => 'javaScript:app.filemanager.createdir'
),
'edit' => array(
'caption' => lang('Edit settings'),
'group' => $group,
'allowOnMultiple' => false,
'onExecute' => Api\Header\UserAgent::mobile()?'javaScript:app.filemanager.viewEntry':'javaScript:app.filemanager.editprefs',
'mobileViewTemplate' => 'file?'.filemtime(Api\Etemplate\Widget\Template::rel2path('/filemanager/templates/mobile/file.xet'))
'edit' => array(
'caption' => lang('Edit settings'),
'group' => $group,
'allowOnMultiple' => false,
'onExecute' => Api\Header\UserAgent::mobile() ? 'javaScript:app.filemanager.viewEntry' : 'javaScript:app.filemanager.editprefs',
'mobileViewTemplate' => 'file?' . filemtime(Api\Etemplate\Widget\Template::rel2path('/filemanager/templates/mobile/file.xet'))
),
'saveas' => array(
'caption' => lang('Save as'),
'group' => $group,
'unlock' => array(
'caption' => lang('Unlock'),
'icon' => 'unlock',
'enableClass' => 'locked',
'group' => $group,
'allowOnMultiple' => true,
'icon' => 'filesave',
'onExecute' => 'javaScript:app.filemanager.force_download',
'disableClass' => 'isDir',
'enabled' => 'javaScript:app.filemanager.is_multiple_allowed',
'shortcut' => array('ctrl' => true, 'shift' => true, 'keyCode' => 83, 'caption' => 'Ctrl + Shift + S'),
'hideOnDisabled' => true
),
'saveas' => array(
'caption' => lang('Save as'),
'group' => $group,
'allowOnMultiple' => true,
'icon' => 'filesave',
'onExecute' => 'javaScript:app.filemanager.force_download',
'disableClass' => 'isDir',
'enabled' => 'javaScript:app.filemanager.is_multiple_allowed',
'shortcut' => array('ctrl' => true, 'shift' => true, 'keyCode' => 83,
'caption' => 'Ctrl + Shift + S'),
),
'saveaszip' => array(
'caption' => lang('Save as ZIP'),
'group' => $group,
'caption' => lang('Save as ZIP'),
'group' => $group,
'allowOnMultiple' => true,
'icon' => 'save_zip',
'postSubmit' => true,
'shortcut' => array('ctrl' => true, 'shift' => true, 'keyCode' => 90, 'caption' => 'Ctrl + Shift + Z'),
'icon' => 'save_zip',
'postSubmit' => true,
'shortcut' => array('ctrl' => true, 'shift' => true, 'keyCode' => 90,
'caption' => 'Ctrl + Shift + Z'),
),
'egw_paste' => array(
'enabled' => false,
@ -815,7 +825,7 @@ class filemanager_ui
case 'createdir':
$dst = Vfs::concat($dir, is_array($selected) ? $selected[0] : $selected);
if (Vfs::mkdir($dst, null, STREAM_MKDIR_RECURSIVE))
if(Vfs::mkdir($dst, null, STREAM_MKDIR_RECURSIVE))
{
return lang("Directory successfully created.");
}
@ -824,16 +834,36 @@ class filemanager_ui
case 'saveaszip':
Vfs::download_zip($selected);
exit;
case 'unlock':
foreach((array)$selected as $target)
{
$link = Vfs::concat($dir, Vfs::basename($target));
$lock = Vfs::checkLock($link);
if($lock && Vfs::unlock($link, $lock['token']))
{
$files++;
}
else
{
$errs++;
}
}
return lang('%1 files unlocked.', $files);
default:
list($action, $settings) = explode('_', $action, 2);
switch($action)
{
case 'document':
if (!$settings) $settings = $GLOBALS['egw_info']['user']['preferences']['filemanager']['default_document'];
if(!$settings)
{
$settings = $GLOBALS['egw_info']['user']['preferences']['filemanager']['default_document'];
}
$document_merge = new filemanager_merge(Vfs::decodePath($dir));
$msg = $document_merge->download($settings, $selected, '', $GLOBALS['egw_info']['user']['preferences']['filemanager']['document_dir']);
if($msg) return $msg;
if($msg)
{
return $msg;
}
$errs = count($selected);
return false;
}
@ -982,6 +1012,13 @@ class filemanager_ui
$GLOBALS['egw']->session->commit_session();
$rows = $dir_is_writable = array();
$vfs_options = $this->get_vfs_options($query);
// query and cache locks for whole directory
$locks = [];
foreach(!empty($query['col_filter']['dir']) ? (array)$query['col_filter']['dir'] : (array)$query['path'] as $path)
{
$locks += Vfs::checkLock($path, 999);
}
$n = 0;
foreach(Vfs::find(!empty($query['col_filter']['dir']) ? $query['col_filter']['dir'] : $query['path'],$vfs_options,true) as $path => $row)
{
//echo $path; _debug_array($row);
@ -1009,16 +1046,33 @@ class filemanager_ui
$row['class'] .= 'noExecute';
}
}
elseif (!$dir_is_writable[Vfs::dirname($path)])
elseif(!$dir_is_writable[Vfs::dirname($path)])
{
$row['class'] .= 'noEdit ';
}
if (!empty($lock = $locks[$path]))
{
$row['locked'] = 'lock';
$row['locked_status'] = lang(
"LOCK from %1, created %2",
// Not sure why sometimes the lock is owned by a user ID, sometimes mailto:user@email
is_numeric($lock['owner']) ? $GLOBALS['egw']->accounts->username($lock['owner']) : str_replace('mailto:', '', $lock['owner']),
Api\DateTime::to(APi\DateTime::server2user($lock['created']), '')
);
if($GLOBALS['egw_info']['user']['apps']['admin'] || Vfs::$is_admin || Vfs::$is_root ||
$lock['owner'] == $GLOBALS['egw_info']['user']['account_id'] ||
$lock['owner'] == 'mailto:' . $GLOBALS['egw_info']['user']['account_email']
)
{
$row['class'] .= ' locked ';
}
}
$row['class'] .= !$dir_is_writable[$dir] ? 'noDelete' : '';
$row['download_url'] = Vfs::download_url($path);
$row['gid'] = -abs($row['gid']); // gid are positive, but we use negagive account_id for groups internal
$row['gid'] = -abs($row['gid']); // gid are positive, but we use negagive account_id for groups internal
foreach(['mtime','ctime'] as $date_field)
foreach(['mtime', 'ctime'] as $date_field)
{
$row[$date_field] = Api\DateTime::server2user($row[$date_field]);
}

View File

@ -192,6 +192,7 @@ link target filemanager de Ziel der Verknüpfung
link target %1 not found! filemanager de Verknüpfungsziel %1 nicht gefunden!
list view filemanager de Listenansicht
location filemanager de Ort
lock filemanager de Gesperrt
log out as superuser filemanager de Superuser abmelden
mail files filemanager de Dateien per E-Mail versenden
mail paste filemanager de per E-Mail versenden
@ -302,6 +303,7 @@ to overwrite the existing file store again. filemanager de Zum Überschreiben de
total files filemanager de Gesamtanzahl Dateien
ui mode filemanager de Benutzeroberfläche
under directory filemanager de unter dem Verzeichnis
unlock filemanager de entsperren
unmount filemanager de Unmount
unused space filemanager de Nicht benutzter Platz
up filemanager de Nach oben

View File

@ -14,6 +14,7 @@
%1 files deleted. filemanager en %1 files deleted.
%1 files moved. filemanager en %1 files moved.
%1 files or directories deleted in %2 seconds. filemanager en %1 files or directories deleted in %2 seconds.
%1 files unlocked. filemanager en %1 files unlocked.
%1 shares deleted. filemanager en %1 shares deleted.
%1 starts with '%2' filemanager en %1 starts with '%2'
%1 successful unmounted. filemanager en %1 successful unmounted.
@ -192,6 +193,7 @@ link target filemanager en Link target
link target %1 not found! filemanager en Link target %1 not found!
list view filemanager en List view
location filemanager en Location
lock filemanager en Lock
log out as superuser filemanager en Log out as super user
mail files filemanager en Mail files
mail paste filemanager en Mail paste
@ -303,6 +305,7 @@ to overwrite the existing file store again. filemanager en To overwrite the exis
total files filemanager en Total files
ui mode filemanager en Standard Toolbar
under directory filemanager en under directory
unlock filemanager en Unlock
unmount filemanager en Unmount
unused space filemanager en Unused space
up filemanager en Up

View File

@ -6,6 +6,7 @@
<grid width="100%">
<columns>
<column width="150"/>
<column width="80"/>
<column width="50%"/>
<column width="80"/>
<column width="120"/>
@ -19,6 +20,7 @@
<rows>
<row class="th">
<nextmatch-sortheader align="center" label="Type" id="mime"/>
<nextmatch-header align="center" label="Lock" id="lock"/>
<nextmatch-sortheader label="Name" id="name"/>
<nextmatch-sortheader label="Size" id="size"/>
<nextmatch-sortheader label="Modified" id="mtime"/>
@ -31,6 +33,7 @@
</row>
<row class="row $row_cont[class]">
<vfs-mime align="center" id="$row"/>
<image src="${row}[locked]" statustext="$row_cont[locked_status]"/>
<vfs id="$row" no_lang="1"/>
<vfs-size align="right" id="${row}[size]"/>
<date-time id="${row}[mtime]" readonly="true"/>

View File

@ -320,7 +320,7 @@ class importexport_export_csv implements importexport_iface_export_record
list($c_fields, $c_selects, $links, $methods) = self::$cf_parse_cache[$appname];
// Add in any fields that are keys to another app
foreach((array)$fields['links'] as $link_field => $app)
foreach($fields['links'] ?? [] as $link_field => $app)
{
if(is_numeric($link_field)) continue;
$links[$link_field] = $app;
@ -332,7 +332,7 @@ class importexport_export_csv implements importexport_iface_export_record
// Not quite a recursive merge, since only one level
foreach($fields as $type => &$list)
{
if($c_fields[$type])
if (!empty($c_fields[$type]))
{
$list = array_merge($c_fields[$type], $list);
unset($c_fields[$type]);
@ -341,7 +341,7 @@ class importexport_export_csv implements importexport_iface_export_record
$fields += $c_fields;
$selects += $c_selects;
}
foreach((array)$fields['select'] as $name)
foreach($fields['select'] ?? [] as $name)
{
if($record->$name != null && is_array($selects) && $selects[$name])
{
@ -366,7 +366,7 @@ class importexport_export_csv implements importexport_iface_export_record
$record->$name = '';
}
}
foreach((array)$fields['links'] as $name) {
foreach($fields['links'] ?? [] as $name) {
if($record->$name) {
if(is_numeric($record->$name) && !$links[$name]) {
$link = Link::get_link($record->$name);
@ -387,7 +387,7 @@ class importexport_export_csv implements importexport_iface_export_record
$record->$name = '';
}
}
foreach((array)$fields['select-account'] as $name) {
foreach($fields['select-account'] ?? [] as $name) {
// Compare against null to deal with empty arrays
if ($record->$name !== null) {
if(is_array($record->$name)) {
@ -405,25 +405,25 @@ class importexport_export_csv implements importexport_iface_export_record
$record->$name = '';
}
}
foreach((array)$fields['select-bool'] as $name) {
foreach($fields['select-bool'] ?? [] as $name) {
if($record->$name !== null) {
$record->$name = $record->$name ? lang('Yes') : lang('No');
}
}
foreach((array)$fields['date-time'] as $name) {
foreach($fields['date-time'] ?? [] as $name) {
//if ($record->$name) $record->$name = date('Y-m-d H:i:s',$record->$name); // Standard date format
if ($record->$name && !is_numeric($record->$name)) $record->$name = strtotime($record->$name); // Custom fields stored as string
if ($record->$name && is_numeric($record->$name)) $record->$name = date($GLOBALS['egw_info']['user']['preferences']['common']['dateformat'] . ' '.
($GLOBALS['egw_info']['user']['preferences']['common']['timeformat'] == '24' ? 'H:i:s' : 'h:i:s a'),$record->$name); // User date format
if (!$record->$name) $record->$name = '';
}
foreach((array)$fields['date'] as $name) {
foreach($fields['date'] ?? [] as $name) {
//if ($record->$name) $record->$name = date('Y-m-d',$record->$name); // Standard date format
if ($record->$name && !is_numeric($record->$name)) $record->$name = strtotime($record->$name); // Custom fields stored as string
if ($record->$name && is_numeric($record->$name)) $record->$name = date($GLOBALS['egw_info']['user']['preferences']['common']['dateformat'], $record->$name); // User date format
if (!$record->$name) $record->$name = '';
}
foreach((array)$fields['float'] as $name)
foreach($fields['float'] ?? [] as $name)
{
static $dec_separator,$thousands_separator;
if (is_null($dec_separator))
@ -445,7 +445,7 @@ class importexport_export_csv implements importexport_iface_export_record
}
// Some custom methods for conversion
foreach((array)$methods as $name => $method) {
foreach($methods ?? [] as $name => $method) {
if ($record->$name)
{
if(is_string($method))

View File

@ -358,7 +358,7 @@ class importexport_import_csv implements importexport_iface_import_record { //,
if ($record[$name]) {
// Automatically handle text owner without explicit translation
$new_owner = importexport_helper_functions::account_name2id($record[$name]);
if(count($new_owner) != count(explode(',',$record[$name])))
if(!is_array($new_owner) || count($new_owner) != count(explode(',', $record[$name])))
{
// Unable to parse value into account
$warnings[] = $name . ': ' .lang('%1 is not a known user or group', $record[$name]);

View File

@ -784,9 +784,9 @@ class infolog_so
'event' => 'calendar'
);
// query children independent of action
if ((string)$query['col_filter']['info_id_parent'] === '')
if (empty($query['col_filter']['info_id_parent']))
{
$action = isset($action2app[$query['action']]) ? $action2app[$query['action']] : $query['action'];
$action = isset($action2app[$query['action']]) ? $action2app[$query['action']] : ($query['action'] ?? null);
if ($action)
{
$links = Link\Storage::get_links($action=='sp'?'infolog':$action,
@ -833,10 +833,10 @@ class infolog_so
$ordermethod = 'ORDER BY info_datemodified DESC'; // newest first
}
$filtermethod = $no_acl ? '1=1' : $this->aclFilter($query['filter']);
if (!$query['col_filter']['info_status']) $filtermethod .= $this->statusFilter($query['filter']);
if (empty($query['col_filter']['info_status'])) $filtermethod .= $this->statusFilter($query['filter']);
$filtermethod .= $this->dateFilter($query['filter']);
$cfcolfilter=0;
if (is_array($query['col_filter']))
if (isset($query['col_filter']) && is_array($query['col_filter']))
{
foreach($query['col_filter'] as $col => $data)
{
@ -894,15 +894,15 @@ class infolog_so
}
//echo "<p>filtermethod='$filtermethod'</p>";
if ((int)$query['cat_id'])
if (!empty($query['cat_id']) && (int)$query['cat_id'])
{
$categories = new Api\Categories('','infolog');
$cats = $categories->return_all_children((int)$query['cat_id']);
$filtermethod .= ' AND info_cat'.(count($cats)>1? ' IN ('.implode(',',$cats).') ' : '='.(int)$query['cat_id']);
}
$join = $distinct = '';
if ($query['query']) $query['search'] = $query['query']; // allow both names
if ($query['search']) // we search in _from, _subject, _des and _extra_value for $query
if (!empty($query['query'])) $query['search'] = $query['query']; // allow both names
if (!empty($query['search'])) // we search in _from, _subject, _des and _extra_value for $query
{
$columns = array('info_from','info_location','info_subject');
// at the moment MaxDB 7.5 cant cast nor search text columns, it's suppost to change in 7.6
@ -924,7 +924,7 @@ class infolog_so
$join .= " LEFT JOIN $this->users_table attendees ON main.info_id=attendees.info_id AND attendees.info_res_deleted IS NULL";
$group_by = ' GROUP BY main.info_id ';
// check if $query['append'] already contains a GROUP BY clause
if (stripos($query['append'], 'group by') !== false)
if (!empty($query['append']) && stripos($query['append'], 'group by') !== false)
{
$query['append'] .= ',main.info_id ';
}
@ -943,7 +943,7 @@ class infolog_so
$ids = array( );
if ($action == '' || $action == 'sp' || count($links))
{
$sql_query = "FROM $this->info_table main $join WHERE ($filtermethod $pid $sql_query) $link_extra";
$sql_query = "FROM $this->info_table main $join WHERE ($filtermethod $pid ".($sql_query ?? '').') '.($link_extra??'');
#error_log("infolog.so.search:\n" . print_r($sql_query, true));
if ($this->db->Type == 'mysql' && (float)$this->db->ServerInfo['version'] >= 4.0)
@ -984,7 +984,7 @@ class infolog_so
$cols .= ','.$this->db->group_concat('attendees.info_res_attendee').' AS info_cc';
$rs = $this->db->query($sql='SELECT '.$mysql_calc_rows.' '.$distinct.' '.$cols.' '.$info_customfield.' '.$sql_query.
$query['append'].$ordermethod,__LINE__,__FILE__,
(int) $query['start'],isset($query['start']) ? (int) $query['num_rows'] : -1,false,Api\Db::FETCH_ASSOC);
(int)($query['start']??0),isset($query['start']) ? (int) $query['num_rows'] : -1,false,Api\Db::FETCH_ASSOC);
//echo "<p>db::query('$sql',,,".(int)$query['start'].','.(isset($query['start']) ? (int) $query['num_rows'] : -1).")</p>\n";
if ($mysql_calc_rows)
@ -1011,7 +1011,7 @@ class infolog_so
$ids[$info['info_id']] = $info;
}
static $index_load_cfs = null;
if (is_null($index_load_cfs) && $query['col_filter']['info_type'])
if (is_null($index_load_cfs) && !empty($query['col_filter']['info_type']))
{
$config_data = Api\Config::read('infolog');
$index_load_cfs = $config_data['index_load_cfs'];

View File

@ -608,7 +608,10 @@ class infolog_ui
}
if(count($links))
{
$query['col_filter']['info_id'] = count($links) > 1 ? call_user_func_array('array_intersect', array_values($links)) : $links[$key ?? 'info_id'];
$query['col_filter']['info_id'] = count($links) > 1 ? array_intersect(...array_map(static function($ids)
{
return (array)$ids;
}, array_values($links))) : $links[$key ?? 'info_id'];
}
return $linked;
}

View File

@ -4998,6 +4998,10 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
{
if(Mail::$debug) error_log(__METHOD__."->".array2string($_messageList));
$uidA = self::splitRowID($_messageList['msg'][0]);
if ($uidA['profileID'] && $uidA['profileID'] != $this->mail_bo->profileID)
{
$this->changeProfile($uidA['profileID']);
}
$folder = $uidA['folder']; // all messages in one set are supposed to be within the same folder
$this->mail_bo->sendMDN($uidA['msgUID'],$folder);
}
@ -5024,6 +5028,10 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
{
// we have both messageIds AND allFlag folder information
$uidA = self::splitRowID($_messageList['msg'][0]);
if ($uidA['profileID'] && $uidA['profileID'] != $this->mail_bo->profileID)
{
$this->changeProfile($uidA['profileID']);
}
$folder = $uidA['folder']; // all messages in one set are supposed to be within the same folder
if(!$folder && !$uidA['msg'] && $uidA['accountID'])
{
@ -5161,6 +5169,10 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
else
{
$uidA = self::splitRowID($_messageList['msg'][0]);
if ($uidA['profileID'] && $uidA['profileID'] != $this->mail_bo->profileID)
{
$this->changeProfile($uidA['profileID']);
}
$folder = $uidA['folder']; // all messages in one set are supposed to be within the same folder
}
if (!$alreadyFlagged)
@ -5222,6 +5234,10 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
{
// we have both messageIds AND allFlag folder information
$uidA = self::splitRowID($_messageList['msg'][0]);
if ($uidA['profileID'] && $uidA['profileID'] != $this->mail_bo->profileID)
{
$this->changeProfile($uidA['profileID']);
}
$folder = $uidA['folder']; // all messages in one set are supposed to be within the same folder
if (isset($_messageList['activeFilters']) && $_messageList['activeFilters'])
{
@ -5292,6 +5308,10 @@ $filter['before']= date("d-M-Y", $cutoffdate2);
else
{
$uidA = self::splitRowID($_messageList['msg'][0]);
if ($uidA['profileID'] && $uidA['profileID'] != $this->mail_bo->profileID)
{
$this->changeProfile($uidA['profileID']);
}
$folder = $uidA['folder']; // all messages in one set are supposed to be within the same folder
foreach($_messageList['msg'] as $rowID)
{

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<?xml-stylesheet type="text/css" href="../less/svg.css" ?><svg
version="1.1"
id="pixelegg_lock"
x="0px"
y="0px"
width="32px"
height="32px"
viewBox="0 0 32 32"
enable-background="new 0 0 32 32"
xml:space="preserve"
sodipodi:docname="unlock.svg"
inkscape:version="1.1.1 (eb90963e84, 2021-10-02)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs11">
</defs><sodipodi:namedview
id="namedview9"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="22.46875"
inkscape:cx="11.527121"
inkscape:cy="14.50904"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="pixelegg_lock" />
<path
id="path3096"
style="fill:#636363;fill-opacity:1;fill-rule:nonzero"
d="m 8.1533873,0.59754662 c -4.246,0 -7.68945304,2.99159378 -7.68945304,6.68359378 V 14.912 l 3.84006004,0 c 0.00369,-0.945313 0.00369,-0.612867 0.00369,-0.945313 V 7.2811404 c 0,-1.583 1.722703,-2.8652344 3.845703,-2.8652344 2.1219997,0 3.8437497,1.2822344 3.8437497,2.8652344 v 6.6855466 c 0,0.332446 -0.09295,0.648089 -0.232422,0.945313 h 4.076172 V 7.2811404 c 0,-3.692 -3.4415,-6.68359377 -7.6874997,-6.68359378 z"
sodipodi:nodetypes="ssccsssssccss" /><path
id="path3098"
style="fill:#636363;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-opacity:1"
d="m 11.568704,14.902344 v 0.0078 h -0.960937 c -1.5920001,0 -2.8847657,1.283235 -2.8847657,2.865235 V 28.28125 c 0,1.581 1.2937656,2.865234 2.8847657,2.865234 h 17.300781 c 1.591,0 2.882812,-1.284234 2.882812,-2.865234 V 17.775391 c -0.001,-1.582 -1.292812,-2.863282 -2.882812,-2.863282 h -0.962891 v -0.0098 z m 7.6875,2.875 c 2.122,0 3.845703,1.711312 3.845703,3.820312 0,1.41 -0.778828,2.630016 -1.923828,3.291016 v 1.482422 c 0,1.055 -0.859875,1.910156 -1.921875,1.910156 -1.062,0 -1.921875,-0.855156 -1.921875,-1.910156 v -1.484375 c -1.144,-0.661 -1.921875,-1.879063 -1.921875,-3.289063 0,-2.109 1.72075,-3.820312 3.84375,-3.820312 z" /><path
id="path3102"
d="M 20.27049 14.902473 C 20.268999 14.905651 20.267502 14.908827 20.266 14.912 L 13.048 14.912 C 13.046487 14.908827 13.04498 14.905651 13.043479 14.902473 "
style="fill:#000000;fill-rule:evenodd;fill-opacity:0.49119297" />
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -89,7 +89,7 @@ class preferences_password
break;
case 'two_factor_auth':
switch(key($content['2fa']['action']))
switch(key($content['2fa']['action'] ?? []))
{
case 'show':
$content['2fa'] = $this->generateQRCode($google2fa, false);

View File

@ -412,7 +412,7 @@ class preferences_settings
continue 2;
case 'notify':
$vars = $GLOBALS['egw']->preferences->vars;
$vars = $GLOBALS['egw']->preferences->vars ?? [];
if (is_array($setting['values'])) $vars += $setting['values'];
$GLOBALS['egw']->preferences->{$attribute}[$appname][$setting['name']] =
$GLOBALS['egw']->preferences->lang_notify($GLOBALS['egw']->preferences->{$attribute}[$appname][$setting['name']], $vars);

View File

@ -5,14 +5,15 @@
<template id="resources.show.rows" template="" lang="" group="0" version="1.9.003">
<grid width="100%">
<columns>
<column width="70"/>
<column width="50%"/>
<column width="50"/>
<column width="15%"/>
<column width="15%"/>
<column width="15%"/>
<column width="15%" disabled="@no_customfields"/>
</columns>
<column width="70"/>
<column width="50%"/>
<column width="50"/>
<column width="15%"/>
<column width="15%"/>
<column width="15%"/>
<column width="15%"/>
<column width="15%" disabled="@no_customfields"/>
</columns>
<rows>
<row class="th">
<nextmatch-header label="Image" id="image"/>
@ -32,6 +33,7 @@
<nextmatch-sortheader label="Location" id="location"/>
<description value="Storage information"/>
</vbox>
<nextmatch-sortheader label="Inventory Number" id="inventory_number"/>
<nextmatch-header label="Resource / Accessories"/>
<nextmatch-customfields id="customfields"/>
</row>
@ -57,6 +59,7 @@
<description id="${row}[location]" no_lang="1"/>
<description id="${row}[storage_info]" no_lang="1"/>
</vbox>
<description id="${row}[inventory_number]" no_lang="1"/>
<vbox no_lang="1">
<description extra_link_popup="850x600" href="resources.resources_ui.edit&amp;res_id=$row_cont[accessory_of]" id="${row}[accessory_of_label]" no_lang="1"/>
<grid width="100%" id="${row}[accessories]">