mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-24 23:58:54 +01:00
Merge branch 'master' into web-components
This commit is contained in:
commit
76d7447dab
10
SECURITY.md
Normal file
10
SECURITY.md
Normal 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.
|
@ -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;
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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]',
|
||||
|
@ -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 ?? [];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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]))
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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'];
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -70,17 +70,17 @@ class Html
|
||||
{
|
||||
$additionalQuote="";//at the end, ...
|
||||
// only one " 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],'"',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'"')===false)
|
||||
if (!empty($match[5]) && $match[5]===';' && (strlen($match[4])-6) >=0 && strpos($match[4],'"',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'"')===false)
|
||||
{
|
||||
$match[4] = substr($match[4],0,strpos($match[4],'"',strlen($match[4])-6));
|
||||
$additionalQuote = """;
|
||||
}
|
||||
// if there is quoted stuff within the URL then we have at least one more " in match[4], so chance is the last " is matched by the one within
|
||||
if ($match[5]==';' && (strlen($match[4])-6) >=0 && strpos($match[4],'"',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'"')!==false)
|
||||
if (!empty($match[5]) && $match[5]===';' && (strlen($match[4])-6) >=0 && strpos($match[4],'"',strlen($match[4])-6)!==false && strpos(substr($match[4],0,strlen($match[4])-6),'"')!==false)
|
||||
{
|
||||
$match[4] .= $match[5];
|
||||
}
|
||||
if ($match[5]==';'&&$match[4]==""")
|
||||
if (!empty($match[5]) && $match[5]===';' && $match[4]===""")
|
||||
{
|
||||
$match[4] ='';
|
||||
$additionalQuote = """;
|
||||
@ -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],'>',strlen($match[3])-4)!==false)
|
||||
if (!empty($match[4]) && $match[4]===';' && (strlen($match[3])-4) >=0 && strpos($match[3],'>',strlen($match[3])-4)!==false)
|
||||
{
|
||||
$match[3] = substr($match[3],0,strpos($match[3],'>',strlen($match[3])-4));
|
||||
$match[4] = ">";
|
||||
}
|
||||
if ($match[4]==';'&&$match[3]==">")
|
||||
if (!empty($match[4]) && $match[4]===';' && $match[3]==">")
|
||||
{
|
||||
$match[3] ='';
|
||||
$match[4] = ">";
|
||||
}
|
||||
//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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)))));
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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) == '[]')
|
||||
{
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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>
|
||||
|
@ -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']);
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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']))
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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' && (
|
||||
|
@ -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
20
composer.lock
generated
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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))
|
||||
|
@ -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]);
|
||||
|
@ -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'];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
53
pixelegg/images/unlock.svg
Normal file
53
pixelegg/images/unlock.svg
Normal 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 |
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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&res_id=$row_cont[accessory_of]" id="${row}[accessory_of_label]" no_lang="1"/>
|
||||
<grid width="100%" id="${row}[accessories]">
|
||||
|
Loading…
Reference in New Issue
Block a user