forked from extern/egroupware
Merge branch 'master' into web-components
This commit is contained in:
commit
02dce82010
@ -629,13 +629,15 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
* @param int $id
|
||||
* @param int $user =null account_id of owner, default null
|
||||
* @param string $prefix =null user prefix from path (eg. /ralf from /ralf/addressbook)
|
||||
* @param string $method='PUT' also called for POST and PATCH
|
||||
* @param string $content_type=null
|
||||
* @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
|
||||
*/
|
||||
function put(&$options,$id,$user=null,$prefix=null)
|
||||
function put(&$options, $id, $user=null, $prefix=null, string $method='PUT', string $content_type=null)
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__.'('.array2string($options).",$id,$user)");
|
||||
|
||||
$oldContact = $this->_common_get_put_delete('PUT',$options,$id);
|
||||
$oldContact = $this->_common_get_put_delete($method,$options,$id);
|
||||
if (!is_null($oldContact) && !is_array($oldContact))
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__."(,'$id', $user, '$prefix') returning ".array2string($oldContact));
|
||||
@ -658,7 +660,8 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
}
|
||||
}
|
||||
$contact = $type === JsContact::MIME_TYPE_JSCARD ?
|
||||
JsContact::parseJsCard($options['content']) : JsContact::parseJsCardGroup($options['content']);
|
||||
JsContact::parseJsCard($options['content'], $oldContact ?: [], $content_type, $method) :
|
||||
JsContact::parseJsCardGroup($options['content']);
|
||||
|
||||
if (!empty($id) && strpos($id, self::JS_CARDGROUP_ID_PREFIX) === 0)
|
||||
{
|
||||
@ -757,10 +760,10 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
}
|
||||
else
|
||||
{
|
||||
$contact['carddav_name'] = $id;
|
||||
$contact['carddav_name'] = (!empty($id) ? basename($id, '.vcf') : $contact['uid']).'.vcf';
|
||||
|
||||
// only set owner, if user is explicitly specified in URL (check via prefix, NOT for /addressbook/) or sync-all-in-one!)
|
||||
if ($prefix && !in_array('O',$this->home_set_pref) && $user)
|
||||
if ($prefix && ($is_json || !in_array('O',$this->home_set_pref)) && $user)
|
||||
{
|
||||
$contact['owner'] = $user;
|
||||
}
|
||||
@ -1105,6 +1108,11 @@ class addressbook_groupdav extends Api\CalDAV\Handler
|
||||
$keys['id'] = $id;
|
||||
}
|
||||
}
|
||||
// json with uid
|
||||
elseif (empty(self::$path_extension) && (string)$id !== (string)(int)$id)
|
||||
{
|
||||
$keys['uid'] = $id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$keys[self::$path_attr] = $id;
|
||||
|
@ -304,17 +304,27 @@ class addressbook_hooks
|
||||
'admin' => False,
|
||||
);
|
||||
$settings['document_dir'] = array(
|
||||
'type' => 'vfs_dirs',
|
||||
'size' => 60,
|
||||
'label' => 'Directory with documents to insert contacts',
|
||||
'name' => 'document_dir',
|
||||
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.',lang('addressbook')).' '.
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','n_fn').' '.
|
||||
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
|
||||
'type' => 'vfs_dirs',
|
||||
'size' => 60,
|
||||
'label' => 'Directory with documents to insert contacts',
|
||||
'name' => 'document_dir',
|
||||
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.', lang('addressbook')) . ' ' .
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'n_fn') . ' ' .
|
||||
lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()),
|
||||
'run_lang' => false,
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => '/templates/addressbook',
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => '/templates/addressbook',
|
||||
);
|
||||
$settings[Api\Storage\Merge::PREF_DOCUMENT_FILENAME] = array(
|
||||
'type' => 'taglist',
|
||||
'label' => 'Document download filename',
|
||||
'name' => 'document_download_name',
|
||||
'values' => Api\Storage\Merge::DOCUMENT_FILENAME_OPTIONS,
|
||||
'help' => 'Choose the default filename for downloaded documents.',
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => 'document',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -183,12 +183,12 @@ class addressbook_ui extends addressbook_bo
|
||||
$msg = '';
|
||||
}
|
||||
}
|
||||
if ($_content['nm']['rows']['infolog'])
|
||||
if (!empty($_content['nm']['rows']['infolog']))
|
||||
{
|
||||
$org = key($_content['nm']['rows']['infolog']);
|
||||
return $this->infolog_org_view($org);
|
||||
}
|
||||
if ($_content['nm']['rows']['view']) // show all contacts of an organisation
|
||||
if (!empty($_content['nm']['rows']['view'])) // show all contacts of an organisation
|
||||
{
|
||||
$grouped_view = key($_content['nm']['rows']['view']);
|
||||
}
|
||||
@ -1736,9 +1736,13 @@ class addressbook_ui extends addressbook_bo
|
||||
if (isset($this->grouped_views[(string) $query['grouped_view']]))
|
||||
{
|
||||
// we have a grouped view, reset the advanced search
|
||||
if(!$query['search'] && $old_state['advanced_search']) $query['advanced_search'] = $old_state['advanced_search'];
|
||||
if (empty($query['search']) && !empty($old_state['advanced_search']))
|
||||
{
|
||||
$query['advanced_search'] = $old_state['advanced_search'];
|
||||
}
|
||||
}
|
||||
elseif(!$query['search'] && array_key_exists('advanced_search',$old_state)) // eg. paging in an advanced search
|
||||
// eg. paging in an advanced search
|
||||
elseif(empty($query['search']) && is_array($old_state) && array_key_exists('advanced_search', $old_state))
|
||||
{
|
||||
$query['advanced_search'] = $old_state['advanced_search'];
|
||||
}
|
||||
@ -2256,7 +2260,7 @@ class addressbook_ui extends addressbook_bo
|
||||
// remove invalid shared-with entries (should not happen, as we validate already on client-side)
|
||||
$this->check_shared_with($content['shared']);
|
||||
|
||||
$button = @key($content['button']);
|
||||
$button = @key($content['button'] ?? []);
|
||||
unset($content['button']);
|
||||
$content['private'] = (int) ($content['owner'] && substr($content['owner'],-1) == 'p');
|
||||
$content['owner'] = (string) (int) $content['owner'];
|
||||
@ -2984,7 +2988,7 @@ class addressbook_ui extends addressbook_bo
|
||||
|
||||
if(is_array($content))
|
||||
{
|
||||
$button = is_array($content['button']) ? key($content['button']) : "";
|
||||
$button = key($content['button'] ?? []);
|
||||
switch ($button)
|
||||
{
|
||||
case 'vcard':
|
||||
@ -3067,7 +3071,7 @@ class addressbook_ui extends addressbook_bo
|
||||
$_GET['contact_id'] = array_shift($rows);
|
||||
$_GET['index'] = 0;
|
||||
}
|
||||
$contact_id = $_GET['contact_id'] ? $_GET['contact_id'] : ((int)$_GET['account_id'] ? 'account:'.(int)$_GET['account_id'] : 0);
|
||||
$contact_id = $_GET['contact_id'] ?? ((int)$_GET['account_id'] ? 'account:'.(int)$_GET['account_id'] : 0);
|
||||
if(!$contact_id || !is_array($content = $this->read($contact_id)))
|
||||
{
|
||||
Egw::redirect_link('/index.php',array(
|
||||
|
@ -139,7 +139,7 @@ class admin_categories
|
||||
$button = 'delete';
|
||||
$delete_subs = $content['delete']['subs']?true:false;
|
||||
}
|
||||
else
|
||||
elseif (!empty($content['button']))
|
||||
{
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
@ -564,7 +564,7 @@ class admin_categories
|
||||
{
|
||||
$content = array_merge($content,$content[$action.'_popup']);
|
||||
}
|
||||
$content['nm']['action'] .= '_' . key($content[$action . '_action']);
|
||||
$content['nm']['action'] .= '_' . key($content[$action . '_action'] ?? []);
|
||||
|
||||
if(is_array($content[$action]))
|
||||
{
|
||||
@ -680,7 +680,7 @@ class admin_categories
|
||||
{
|
||||
$cmd = new admin_cmd_delete_category(
|
||||
$cat_id,
|
||||
key($content['button']) == 'delete_sub',
|
||||
key($content['button'] ?? []) == 'delete_sub',
|
||||
$content['admin_cmd']
|
||||
);
|
||||
$cmd->run();
|
||||
|
@ -70,7 +70,7 @@ class admin_cmds
|
||||
}
|
||||
$content['nm']['actions'] = self::cmd_actions();
|
||||
}
|
||||
elseif ($content['nm']['rows']['delete'])
|
||||
elseif (!empty($content['nm']['rows']['delete']))
|
||||
{
|
||||
$id = key($content['nm']['rows']['delete']);
|
||||
unset($content['nm']['rows']);
|
||||
|
@ -212,7 +212,7 @@ class admin_mail
|
||||
public function autoconfig(array $content)
|
||||
{
|
||||
// user pressed [Skip IMAP] --> jump to SMTP config
|
||||
if ($content['button'] && key($content['button']) == 'skip_imap')
|
||||
if (!empty($content['button']) && key($content['button']) === 'skip_imap')
|
||||
{
|
||||
unset($content['button']);
|
||||
if (!isset($content['acc_smtp_host'])) $content['acc_smtp_host'] = ''; // do manual mode right away
|
||||
@ -361,7 +361,7 @@ class admin_mail
|
||||
*/
|
||||
public function folder(array $content, $msg='', Horde_Imap_Client_Socket $imap=null)
|
||||
{
|
||||
if (isset($content['button']))
|
||||
if (!empty($content['button']))
|
||||
{
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
@ -482,7 +482,7 @@ class admin_mail
|
||||
);
|
||||
$content['msg'] = $msg;
|
||||
|
||||
if (isset($content['button']))
|
||||
if (!empty($content['button']))
|
||||
{
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
@ -619,7 +619,7 @@ class admin_mail
|
||||
);
|
||||
$content['msg'] = $msg;
|
||||
|
||||
if (isset($content['button']))
|
||||
if (!empty($content['button']))
|
||||
{
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
@ -835,7 +835,7 @@ class admin_mail
|
||||
{
|
||||
$content['called_for'] = (int)$_GET['account_id'];
|
||||
$content['accounts'] = iterator_to_array(Mail\Account::search($content['called_for']));
|
||||
if ($content['accounts'])
|
||||
if (!empty($content['accounts']))
|
||||
{
|
||||
$content['acc_id'] = key($content['accounts']);
|
||||
//error_log(__METHOD__.__LINE__.'.'.array2string($content['acc_id']));
|
||||
@ -949,7 +949,7 @@ class admin_mail
|
||||
$tpl->disableElement('notify_save_default', !$is_multiple || !$edit_access);
|
||||
$tpl->disableElement('notify_use_default', !$is_multiple);
|
||||
|
||||
if (isset($content['button']))
|
||||
if (!empty($content['button']))
|
||||
{
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
@ -1031,7 +1031,7 @@ class admin_mail
|
||||
unset($content['smimeKeyUpload']);
|
||||
}
|
||||
self::fix_account_id_0($content['account_id'], true);
|
||||
$content = Mail\Account::write($content, $content['called_for'] || !$this->is_admin ?
|
||||
$content = Mail\Account::write($content, !empty($content['called_for']) && $this->is_admin ?
|
||||
$content['called_for'] : $GLOBALS['egw_info']['user']['account_id']);
|
||||
self::fix_account_id_0($content['account_id']);
|
||||
$msg = lang('Account saved.');
|
||||
|
@ -204,7 +204,7 @@ class admin_ui
|
||||
$item['id'] = substr($item['extradata'], 11);
|
||||
unset($item['extradata']);
|
||||
$matches = null;
|
||||
if ($item['options'] && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
|
||||
if (!empty($item['options']) && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
|
||||
{
|
||||
$item['popup'] = $matches[2].'x'.$matches[3];
|
||||
if (isset($matches[5])) $item['tooltip'] = $matches[5];
|
||||
@ -213,7 +213,7 @@ class admin_ui
|
||||
}
|
||||
if (empty($item['icon'])) $item['icon'] = $app.'/navbar';
|
||||
if (empty($item['group'])) $item['group'] = $group;
|
||||
if (empty($item['onExecute'])) $item['onExecute'] = $item['popup'] ?
|
||||
if (empty($item['onExecute'])) $item['onExecute'] = !empty($item['popup']) ?
|
||||
'javaScript:nm_action' : 'javaScript:app.admin.iframe_location';
|
||||
if (!isset($item['allowOnMultiple'])) $item['allowOnMultiple'] = false;
|
||||
|
||||
@ -297,7 +297,7 @@ class admin_ui
|
||||
$item['id'] = substr($item['extradata'], 11);
|
||||
unset($item['extradata']);
|
||||
$matches = null;
|
||||
if ($item['options'] && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
|
||||
if (!empty($item['options']) && preg_match('/(egw_openWindowCentered2?|window.open)\([^)]+,(\d+),(\d+).*(title="([^"]+)")?/', $item['options'], $matches))
|
||||
{
|
||||
$item['popup'] = $matches[2].'x'.$matches[3];
|
||||
$item['onExecute'] = 'javaScript:nm_action';
|
||||
@ -326,7 +326,7 @@ class admin_ui
|
||||
public static function get_users(array $query, array &$rows=null)
|
||||
{
|
||||
$params = array(
|
||||
'type' => (int)$query['filter'] ? (int)$query['filter'] : 'accounts',
|
||||
'type' => (int)($query['filter'] ?? 0) ?: 'accounts',
|
||||
'start' => $query['start'],
|
||||
'offset' => $query['num_rows'],
|
||||
'order' => $query['order'],
|
||||
@ -334,7 +334,7 @@ class admin_ui
|
||||
'active' => !empty($query['active']) ? $query['active'] : false,
|
||||
);
|
||||
// Make sure active filter give status what it needs
|
||||
switch($query['filter2'])
|
||||
switch($query['filter2'] ?? '')
|
||||
{
|
||||
case 'disabled':
|
||||
case 'expired':
|
||||
@ -356,12 +356,12 @@ class admin_ui
|
||||
break;
|
||||
}
|
||||
|
||||
if ($query['searchletter'])
|
||||
if (!empty($query['searchletter']))
|
||||
{
|
||||
$params['query'] = $query['searchletter'];
|
||||
$params['query_type'] = 'start';
|
||||
}
|
||||
elseif($query['search'])
|
||||
elseif(!empty($query['search']))
|
||||
{
|
||||
$params['query'] = $query['search'];
|
||||
$params['query_type'] = 'all';
|
||||
@ -377,7 +377,7 @@ class admin_ui
|
||||
foreach($rows as $key => &$row)
|
||||
{
|
||||
// Filter by status
|
||||
if ($need_status_filter && !static::filter_status($need_status_filter, $row))
|
||||
if (!empty($need_status_filter) && !static::filter_status($need_status_filter, $row))
|
||||
{
|
||||
unset($rows[$key]);
|
||||
$total--;
|
||||
@ -391,8 +391,8 @@ class admin_ui
|
||||
|
||||
if (!self::$accounts->is_active($row)) $row['status_class'] = 'adminAccountInactive';
|
||||
}
|
||||
// finally limit query, if status filter was used
|
||||
if ($need_status_filter)
|
||||
// finally, limit query, if status filter was used
|
||||
if (!empty($need_status_filter))
|
||||
{
|
||||
$rows = array_values(array_slice($rows, (int)$query['start'], $query['num_rows'] ?: count($rows)));
|
||||
}
|
||||
@ -436,9 +436,9 @@ class admin_ui
|
||||
{
|
||||
$groups = $GLOBALS['egw']->accounts->search(array(
|
||||
'type' => 'groups',
|
||||
'query' => $query['search'],
|
||||
'order' => $query['order'],
|
||||
'sort' => $query['sort'],
|
||||
'query' => $query['search'] ?? null,
|
||||
'order' => $query['order'] ?? null,
|
||||
'sort' => $query['sort'] ?? null,
|
||||
'start' => (int)$query['start'],
|
||||
'offset' => (int)$query['num_rows']
|
||||
));
|
||||
@ -463,7 +463,7 @@ class admin_ui
|
||||
$run_rights = $GLOBALS['egw']->acl->get_user_applications($group['account_id'], false, false);
|
||||
foreach($apps as $app)
|
||||
{
|
||||
if((boolean)$run_rights[$app])
|
||||
if(!empty($run_rights[$app]))
|
||||
{
|
||||
$group['apps'][] = $app;
|
||||
}
|
||||
@ -537,7 +537,7 @@ class admin_ui
|
||||
if (!empty($data['icon']))
|
||||
{
|
||||
$icon = Etemplate\Widget\Tree::imagePath($data['icon']);
|
||||
if ($data['child'] || $data[Tree::CHILDREN])
|
||||
if (!empty($data['child']) || !empty($data[Tree::CHILDREN]))
|
||||
{
|
||||
$data[Tree::IMAGE_FOLDER_OPEN] = $data[Tree::IMAGE_FOLDER_CLOSED] = $icon;
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ export class et2_details extends et2_box
|
||||
.click(function () {
|
||||
self._toggle();
|
||||
})
|
||||
.text(this.options.title);
|
||||
.text(this.egw().lang(this.options.title));
|
||||
}
|
||||
|
||||
// Align toggle button left/right
|
||||
|
@ -87,6 +87,12 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
[],
|
||||
function(_content)
|
||||
{
|
||||
if(typeof _content === 'object' && _content.message)
|
||||
{
|
||||
// Something went wrong
|
||||
this.egw().message(_content.message, 'error');
|
||||
return;
|
||||
}
|
||||
this.egw().loading_prompt('placeholder_select', false);
|
||||
et2_placeholder_select.placeholders = _content;
|
||||
callback.apply(self, arguments);
|
||||
@ -132,7 +138,13 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
let data = {
|
||||
content: {app: '', group: '', entry: {}},
|
||||
sel_options: {app: [], group: []},
|
||||
modifications: {outer_box: {entry: {}}}
|
||||
modifications: {
|
||||
outer_box: {
|
||||
entry: {
|
||||
application_list: []
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(_data).map((key) =>
|
||||
@ -145,9 +157,16 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
});
|
||||
data.sel_options.group = this._get_group_options(Object.keys(_data)[0]);
|
||||
data.content.app = data.sel_options.app[0].value;
|
||||
data.content.group = data.sel_options.group[0].value;
|
||||
data.content.entry = data.modifications.outer_box.entry.only_app = data.content.app;
|
||||
data.content.group = data.sel_options.group[0]?.value;
|
||||
data.content.entry = {app: data.content.app};
|
||||
data.modifications.outer_box.entry.application_list = Object.keys(_data);
|
||||
// Remove non-app placeholders (user & general)
|
||||
let non_apps = ['user', 'general'];
|
||||
for(let i = 0; i < non_apps.length; i++)
|
||||
{
|
||||
let index = data.modifications.outer_box.entry.application_list.indexOf(non_apps[i]);
|
||||
data.modifications.outer_box.entry.application_list.splice(index, 1);
|
||||
}
|
||||
|
||||
// callback for dialog
|
||||
this.submit_callback = function(submit_button_id, submit_value)
|
||||
@ -162,7 +181,7 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
this.dialog = <et2_dialog>et2_createWidget("dialog",
|
||||
{
|
||||
callback: this.submit_callback,
|
||||
title: this.options.dialog_title || this.egw().lang("Insert Placeholder"),
|
||||
title: this.egw().lang(this.options.dialog_title) || this.egw().lang("Insert Placeholder"),
|
||||
buttons: buttons,
|
||||
minWidth: 500,
|
||||
minHeight: 400,
|
||||
@ -207,14 +226,35 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
// Bind some handlers
|
||||
app.onchange = (node, widget) =>
|
||||
{
|
||||
group.set_select_options(this._get_group_options(widget.get_value()));
|
||||
entry.set_value({app: widget.get_value()});
|
||||
preview.set_value("");
|
||||
if(['user'].indexOf(widget.get_value()) >= 0)
|
||||
{
|
||||
entry.set_disabled(true);
|
||||
entry.app_select.val('user');
|
||||
entry.set_value({app: 'user', id: '', query: ''});
|
||||
}
|
||||
else if(widget.get_value() == 'general')
|
||||
{
|
||||
// Don't change entry app, leave it
|
||||
entry.set_disabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.set_disabled(false);
|
||||
entry.app_select.val(widget.get_value());
|
||||
entry.set_value({app: widget.get_value(), id: '', query: ''});
|
||||
}
|
||||
let groups = this._get_group_options(widget.get_value());
|
||||
group.set_select_options(groups);
|
||||
group.set_value(groups[0].value);
|
||||
group.onchange();
|
||||
}
|
||||
group.onchange = (select_node, select_widget) =>
|
||||
{
|
||||
console.log(this, arguments);
|
||||
placeholder_list.set_select_options(this._get_placeholders(app.get_value(), group.get_value()));
|
||||
let options = this._get_placeholders(app.get_value(), group.get_value())
|
||||
placeholder_list.set_select_options(options);
|
||||
preview.set_value("");
|
||||
placeholder_list.set_value(options[0].value);
|
||||
}
|
||||
placeholder_list.onchange = this._on_placeholder_select.bind(this);
|
||||
entry.onchange = this._on_placeholder_select.bind(this);
|
||||
@ -227,7 +267,7 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
this.options.insert_callback(this.dialog.template.widgetContainer.getDOMWidgetById("preview_content").getDOMNode().textContent);
|
||||
};
|
||||
|
||||
this._on_placeholder_select();
|
||||
app.set_value(app.get_value());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,9 +292,13 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
// Show the selected placeholder replaced with value from the selected entry
|
||||
this.egw().json(
|
||||
'EGroupware\\Api\\Etemplate\\Widget\\Placeholder::ajax_fill_placeholders',
|
||||
[app.get_value(), placeholder_list.get_value(), entry.get_value()],
|
||||
[placeholder_list.get_value(), entry.get_value()],
|
||||
function(_content)
|
||||
{
|
||||
if(!_content)
|
||||
{
|
||||
_content = '';
|
||||
}
|
||||
preview_content.set_value(_content);
|
||||
preview_content.getDOMNode().parentNode.style.visibility = _content.trim() ? null : 'hidden';
|
||||
}.bind(this)
|
||||
@ -277,11 +321,37 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
let options = [];
|
||||
Object.keys(et2_placeholder_select.placeholders[appname]).map((key) =>
|
||||
{
|
||||
options.push(
|
||||
// @ts-ignore
|
||||
if(Object.keys(et2_placeholder_select.placeholders[appname][key]).filter((key) => isNaN(key)).length > 0)
|
||||
{
|
||||
// Handle groups of groups
|
||||
if(typeof et2_placeholder_select.placeholders[appname][key].label !== "undefined")
|
||||
{
|
||||
options[key] = et2_placeholder_select.placeholders[appname][key];
|
||||
}
|
||||
else
|
||||
{
|
||||
options[this.egw().lang(key)] = [];
|
||||
for(let sub of Object.keys(et2_placeholder_select.placeholders[appname][key]))
|
||||
{
|
||||
if(!et2_placeholder_select.placeholders[appname][key][sub])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
options[key].push({
|
||||
value: key + '-' + sub,
|
||||
label: this.egw().lang(sub)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
options.push({
|
||||
value: key,
|
||||
label: this.egw().lang(key)
|
||||
});
|
||||
}
|
||||
});
|
||||
return options;
|
||||
}
|
||||
@ -295,16 +365,13 @@ export class et2_placeholder_select extends et2_inputWidget
|
||||
*/
|
||||
_get_placeholders(appname : string, group : string)
|
||||
{
|
||||
let options = [];
|
||||
Object.keys(et2_placeholder_select.placeholders[appname][group]).map((key) =>
|
||||
let _group = group.split('-', 2);
|
||||
let ph = et2_placeholder_select.placeholders[appname];
|
||||
for(let i = 0; typeof ph !== "undefined" && i < _group.length; i++)
|
||||
{
|
||||
options.push(
|
||||
{
|
||||
value: key,
|
||||
label: et2_placeholder_select.placeholders[appname][group][key]
|
||||
});
|
||||
});
|
||||
return options;
|
||||
ph = ph[_group[i]];
|
||||
}
|
||||
return ph || [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -342,8 +409,9 @@ export class et2_placeholder_snippet_select extends et2_placeholder_select
|
||||
static placeholders = {
|
||||
"addressbook": {
|
||||
"addresses": {
|
||||
"{{n_fn}}\n{{adr_one_street}}{{NELF adr_one_street2}}\n{{adr_one_formatted}}": "Work address",
|
||||
"{{org_name}}\n{{n_fn}}\n{{adr_one_street}}{{NELF adr_one_street2}}\n{{adr_one_formatted}}": "Business address",
|
||||
"{{n_fn}}\n{{adr_two_street}}{{NELF adr_two_street2}}\n{{adr_two_formatted}}": "Home address",
|
||||
"{{n_fn}}\n{{email}}\n{{tel_work}}": "Name, email, phone"
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -385,6 +453,7 @@ export class et2_placeholder_snippet_select extends et2_placeholder_select
|
||||
placeholder_list.onchange = this._on_placeholder_select.bind(this);
|
||||
entry.onchange = this._on_placeholder_select.bind(this);
|
||||
|
||||
app.set_value(app.get_value());
|
||||
this._on_placeholder_select();
|
||||
}
|
||||
|
||||
@ -405,9 +474,13 @@ export class et2_placeholder_snippet_select extends et2_placeholder_select
|
||||
// Show the selected placeholder replaced with value from the selected entry
|
||||
this.egw().json(
|
||||
'EGroupware\\Api\\Etemplate\\Widget\\Placeholder::ajax_fill_placeholders',
|
||||
[app.get_value(), placeholder_list.get_value(), entry.get_value()],
|
||||
[placeholder_list.get_value(), {app: "addressbook", id: entry.get_value()}],
|
||||
function(_content)
|
||||
{
|
||||
if(!_content)
|
||||
{
|
||||
_content = '';
|
||||
}
|
||||
this.set_value(_content);
|
||||
preview_content.set_value(_content);
|
||||
preview_content.getDOMNode().parentNode.style.visibility = _content.trim() ? null : 'hidden';
|
||||
@ -459,7 +532,7 @@ export class et2_placeholder_snippet_select extends et2_placeholder_select
|
||||
options.push(
|
||||
{
|
||||
value: key,
|
||||
label: et2_placeholder_snippet_select.placeholders[appname][group][key]
|
||||
label: this.egw().lang(et2_placeholder_snippet_select.placeholders[appname][group][key])
|
||||
});
|
||||
});
|
||||
return options;
|
||||
|
@ -991,7 +991,8 @@ export class et2_selectbox extends et2_inputWidget
|
||||
if(sub == 'value') continue;
|
||||
if (typeof _options[key][sub] === 'object' && _options[key][sub] !== null)
|
||||
{
|
||||
this._appendOptionElement(sub,
|
||||
this._appendOptionElement(
|
||||
typeof _options[key][sub]["value"] !== "undefined" ? _options[key][sub]["value"] : sub,
|
||||
_options[key][sub]["label"] ? _options[key][sub]["label"] : "",
|
||||
_options[key][sub]["title"] ? _options[key][sub]["title"] : "",
|
||||
group
|
||||
|
@ -853,6 +853,38 @@ export class etemplate2
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is an invalid widget / all widgets are valid
|
||||
*
|
||||
* @param container
|
||||
* @param values
|
||||
* @return et2_widget|null first invalid widget or null, if all are valid
|
||||
*/
|
||||
isInvalid(container : et2_container|undefined, values : object|undefined) : et2_widget|null
|
||||
{
|
||||
if (typeof container === 'undefined')
|
||||
{
|
||||
container = this._widgetContainer;
|
||||
}
|
||||
if (typeof values === 'undefined')
|
||||
{
|
||||
values = this.getValues(container);
|
||||
}
|
||||
let invalid = null;
|
||||
container.iterateOver(function (_widget)
|
||||
{
|
||||
if (_widget.submit(values) === false)
|
||||
{
|
||||
if(!invalid && !_widget.isValid([]))
|
||||
{
|
||||
invalid = _widget;
|
||||
}
|
||||
}
|
||||
}, this, et2_ISubmitListener);
|
||||
|
||||
return invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit form via ajax
|
||||
*
|
||||
@ -881,17 +913,7 @@ export class etemplate2
|
||||
let invalid = null;
|
||||
if (!no_validation)
|
||||
{
|
||||
container.iterateOver(function (_widget)
|
||||
{
|
||||
if (_widget.submit(values) === false)
|
||||
{
|
||||
if (!invalid && !_widget.isValid())
|
||||
{
|
||||
invalid = _widget;
|
||||
}
|
||||
canSubmit = false;
|
||||
}
|
||||
}, this, et2_ISubmitListener);
|
||||
canSubmit = !(invalid = this.isInvalid(container, values));
|
||||
}
|
||||
|
||||
if (canSubmit)
|
||||
@ -1098,7 +1120,6 @@ export class etemplate2
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* "Intelligently" refresh the template based on the given ID
|
||||
*
|
||||
|
@ -725,7 +725,7 @@ export abstract class EgwApp
|
||||
framework.pushState('view');
|
||||
if(templateName)
|
||||
{
|
||||
this.et2_view.load(this.appname+'.'+templateName,templateURL, data, typeof et2_callback == 'function'?et2_callback:function(){}, app);
|
||||
this.et2_view.load(this.appname + '.' + templateName, templateURL, data, typeof et2_callback == 'function' ? et2_callback : function() {}, app);
|
||||
}
|
||||
|
||||
// define a global close function for view template
|
||||
@ -733,6 +733,49 @@ export abstract class EgwApp
|
||||
this.et2_view.close = destroy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge selected entries into template document
|
||||
*
|
||||
* @param {egwAction} _action
|
||||
* @param {egwActionObject[]} _selected
|
||||
*/
|
||||
merge(_action : egwAction, _selected : egwActionObject[])
|
||||
{
|
||||
// Find what we need
|
||||
let nm = null;
|
||||
let action = _action;
|
||||
let as_pdf = false;
|
||||
|
||||
// Find Select all
|
||||
while(nm == null && action != null)
|
||||
{
|
||||
if(action.data != null && action.data.nextmatch)
|
||||
{
|
||||
nm = action.data.nextmatch;
|
||||
}
|
||||
action = action.parent;
|
||||
}
|
||||
let all = nm?.getSelection().all || false;
|
||||
|
||||
as_pdf = action.getActionById('as_pdf')?.checked || false;
|
||||
|
||||
// Get list of entry IDs
|
||||
let ids = [];
|
||||
for(let i = 0; !all && i < _selected.length; i++)
|
||||
{
|
||||
let split = _selected[i].id.split("::");
|
||||
ids.push(split[1]);
|
||||
}
|
||||
|
||||
let vars = {
|
||||
..._action.data.merge_data,
|
||||
pdf: as_pdf,
|
||||
select_all: all,
|
||||
id: JSON.stringify(ids)
|
||||
};
|
||||
egw.open_link(egw.link('/index.php', vars), '_blank');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes actions and handlers on sidebox (delete)
|
||||
*
|
||||
@ -741,12 +784,12 @@ export abstract class EgwApp
|
||||
_init_sidebox(sidebox)
|
||||
{
|
||||
// Initialize egw tutorial sidebox, but only for non-popups, as calendar edit app.js has this.et2 set to tutorial et2 object
|
||||
if (!this.egw.is_popup())
|
||||
if(!this.egw.is_popup())
|
||||
{
|
||||
var egw_fw = egw_getFramework();
|
||||
var tutorial = jQuery('#egw_tutorial_'+this.appname+'_sidebox', egw_fw ? egw_fw.sidemenuDiv : document);
|
||||
var tutorial = jQuery('#egw_tutorial_' + this.appname + '_sidebox', egw_fw ? egw_fw.sidemenuDiv : document);
|
||||
// _init_sidebox gets currently called multiple times, which needs to be fixed
|
||||
if (tutorial.length && !this.tutorial_initialised)
|
||||
if(tutorial.length && !this.tutorial_initialised)
|
||||
{
|
||||
this.egwTutorial_init(tutorial[0]);
|
||||
this.tutorial_initialised = true;
|
||||
|
@ -724,6 +724,7 @@ insert new column behind this one common de Neue Spalte hinter dieser einfügen
|
||||
insert new column in front of all common de Neue Spalte vor dieser einfügen
|
||||
insert new row after this one common de Neue Zeile nach dieser einfügen
|
||||
insert new row in front of first line common de Neue Zeile vor dieser einfügen
|
||||
insert placeholder common de Platzhalter einfügen
|
||||
insert row after common de Zeile danach einfügen
|
||||
insert row before common de Zeile davor einfügen
|
||||
insert timestamp into description field common de Zeitstempel in das Beschreibungs-Feld einfügen
|
||||
@ -1072,6 +1073,7 @@ preference common de Einstellung
|
||||
preferences common de Einstellungen
|
||||
preferences for the %1 template set preferences de Einstellungen für das %1 Template
|
||||
prev common de Vorheriger
|
||||
preview with entry common de Vorschau aus Eintrag
|
||||
previous common de Vorherige
|
||||
previous page common de Vorherige Seite
|
||||
primary group common de Hauptgruppe
|
||||
|
@ -724,6 +724,7 @@ insert new column behind this one common en Insert new column after
|
||||
insert new column in front of all common en Insert new column before all
|
||||
insert new row after this one common en Insert new row after
|
||||
insert new row in front of first line common en Insert new row before first line
|
||||
insert placeholder common en Insert placeholder
|
||||
insert row after common en Insert row after
|
||||
insert row before common en Insert row before
|
||||
insert timestamp into description field common en Insert timestamp into description field
|
||||
@ -1073,6 +1074,7 @@ preference common en Preference
|
||||
preferences common en Preferences
|
||||
preferences for the %1 template set preferences en Preferences for the %1 template set
|
||||
prev common en Prev
|
||||
preview with entry common en Preview with entry
|
||||
previous common en Previous
|
||||
previous page common en Previous page
|
||||
primary group common en Primary group
|
||||
|
@ -223,7 +223,7 @@ class Accounts
|
||||
if (!empty($param['offset']) && !isset($param['start'])) $param['start'] = 0;
|
||||
|
||||
// Check for lang(Group) in search - if there, we search all groups
|
||||
$group_index = array_search(strtolower(lang('Group')), array_map('strtolower', $query = explode(' ',$param['query'])));
|
||||
$group_index = array_search(strtolower(lang('Group')), array_map('strtolower', $query = explode(' ',$param['query'] ?? '')));
|
||||
if($group_index !== FALSE && !(
|
||||
in_array($param['type'], array('accounts', 'groupmembers')) || is_int($param['type'])
|
||||
))
|
||||
@ -595,12 +595,12 @@ class Accounts
|
||||
/**
|
||||
* Return formatted username for a given account_id
|
||||
*
|
||||
* @param string $account_id =null account id
|
||||
* @return string full name of user or "#$accountid" if user not found
|
||||
* @param int $account_id account id
|
||||
* @return string full name of user or "#$account_id" if user not found
|
||||
*/
|
||||
static function username($account_id=null)
|
||||
static function username(int $account_id)
|
||||
{
|
||||
if ($account_id && !($account = self::cache_read((int)$account_id)))
|
||||
if (!($account = self::cache_read($account_id)))
|
||||
{
|
||||
return '#'.$account_id;
|
||||
}
|
||||
|
@ -993,6 +993,34 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
parent::http_PROPFIND('REPORT');
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API PATCH handler
|
||||
*
|
||||
* Currently, only implemented for REST not CalDAV/CardDAV
|
||||
*
|
||||
* @param $options
|
||||
* @param $files
|
||||
* @return string|void
|
||||
*/
|
||||
function PATCH(array &$options)
|
||||
{
|
||||
if (!preg_match('#^application/([^; +]+\+)?json#', $_SERVER['HTTP_CONTENT_TYPE']))
|
||||
{
|
||||
return '501 Not implemented';
|
||||
}
|
||||
return $this->PUT($options, 'PATCH');
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API PATCH handler
|
||||
*
|
||||
* Just calls http_PUT()
|
||||
*/
|
||||
function http_PATCH()
|
||||
{
|
||||
return parent::http_PUT('PATCH');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if client want or sends JSON
|
||||
*
|
||||
@ -1003,7 +1031,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
if (!isset($type))
|
||||
{
|
||||
$type = in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'POST', 'PROPPATCH']) ?
|
||||
$type = in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'POST', 'PATCH', 'PROPPATCH']) ?
|
||||
$_SERVER['HTTP_CONTENT_TYPE'] : $_SERVER['HTTP_ACCEPT'];
|
||||
}
|
||||
return preg_match('#application/(([^+ ;]+)\+)?json#', $type, $matches) ?
|
||||
@ -1427,7 +1455,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
substr($options['path'], -1) === '/' && self::isJSON())
|
||||
{
|
||||
$_GET['add-member'] = ''; // otherwise we give no Location header
|
||||
return $this->PUT($options);
|
||||
return $this->PUT($options, 'POST');
|
||||
}
|
||||
if ($this->debug) error_log(__METHOD__.'('.array2string($options).')');
|
||||
|
||||
@ -1915,7 +1943,7 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
* @param array parameter passing array
|
||||
* @return bool true on success
|
||||
*/
|
||||
function PUT(&$options)
|
||||
function PUT(&$options, $method='PUT')
|
||||
{
|
||||
// read the content in a string, if a stream is given
|
||||
if (isset($options['stream']))
|
||||
@ -1934,9 +1962,14 @@ class CalDAV extends HTTP_WebDAV_Server
|
||||
{
|
||||
return '404 Not Found';
|
||||
}
|
||||
// REST API & PATCH only implemented for addressbook currently
|
||||
if ($app !== 'addressbook' && $method === 'PATCH')
|
||||
{
|
||||
return '501 Not implemented';
|
||||
}
|
||||
if (($handler = self::app_handler($app)))
|
||||
{
|
||||
$status = $handler->put($options,$id,$user,$prefix);
|
||||
$status = $handler->put($options, $id, $user, $prefix, $method, $_SERVER['HTTP_CONTENT_TYPE']);
|
||||
|
||||
// set default stati: true --> 204 No Content, false --> should be already handled
|
||||
if (is_bool($status)) $status = $status ? '204 No Content' : '400 Something went wrong';
|
||||
|
@ -60,6 +60,7 @@ abstract class Handler
|
||||
var $method2acl = array(
|
||||
'GET' => Api\Acl::READ,
|
||||
'PUT' => Api\Acl::EDIT,
|
||||
'PATCH' => Api\Acl::EDIT,
|
||||
'DELETE' => Api\Acl::DELETE,
|
||||
);
|
||||
/**
|
||||
|
@ -483,7 +483,7 @@ class Contacts extends Contacts\Storage
|
||||
'bday' => (int)$contact['bday'] ? DateTime::to($contact['bday'], true) : $contact['bday'],
|
||||
)));
|
||||
|
||||
while ($fileas[0] == ':' || $fileas[0] == ',')
|
||||
while (!empty($fileas) && ($fileas[0] == ':' || $fileas[0] == ','))
|
||||
{
|
||||
$fileas = substr($fileas,2);
|
||||
}
|
||||
@ -764,10 +764,10 @@ class Contacts extends Contacts\Storage
|
||||
$data[$name] = DateTime::server2user($data[$name], $date_format);
|
||||
}
|
||||
}
|
||||
$data['photo'] = $this->photo_src($data['id'],$data['jpegphoto'] || ($data['files'] & self::FILES_BIT_PHOTO), '', $data['etag']);
|
||||
$data['photo'] = $this->photo_src($data['id'],!empty($data['jpegphoto']) || (($data['files']??0) & self::FILES_BIT_PHOTO), '', $data['etag'] ?? null);
|
||||
|
||||
// set freebusy_uri for accounts
|
||||
if (!$data['freebusy_uri'] && !$data['owner'] && $data['account_id'] && !is_object($GLOBALS['egw_setup']))
|
||||
if (empty($data['freebusy_uri']) && empty($data['owner']) && !empty($data['account_id']) && empty($GLOBALS['egw_setup']))
|
||||
{
|
||||
if ($fb_url || @is_dir(EGW_SERVER_ROOT.'/calendar/inc'))
|
||||
{
|
||||
@ -1686,7 +1686,7 @@ class Contacts extends Contacts\Storage
|
||||
{
|
||||
$result[$contact['id']] = $this->link_title($contact+(array)$cfs[$contact['id']]);
|
||||
// make sure to return a correctly quoted rfc822 address, if requested
|
||||
if ($options['type'] === 'email')
|
||||
if (isset($options['type']) && $options['type'] === 'email')
|
||||
{
|
||||
$args = explode('@', $contact['email']);
|
||||
$args[] = $result[$contact['id']];
|
||||
|
@ -78,16 +78,34 @@ class JsContact
|
||||
/**
|
||||
* Parse JsCard
|
||||
*
|
||||
* We use strict parsing for "application/jscontact+json" content-type, not for "application/json".
|
||||
* Strict parsing checks objects for proper @type attributes and value attributes, non-strict allows scalar values.
|
||||
*
|
||||
* Non-strict parsing also automatic detects patch for POST requests.
|
||||
*
|
||||
* @param string $json
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param array $old=[] existing contact for patch
|
||||
* @param ?string $content_type=null application/json no strict parsing and automatic patch detection, if method not 'PATCH' or 'PUT'
|
||||
* @param string $method='PUT' 'PUT', 'POST' or 'PATCH'
|
||||
* @return array
|
||||
*/
|
||||
public static function parseJsCard(string $json, bool $check_at_type=true)
|
||||
public static function parseJsCard(string $json, array $old=[], string $content_type=null, $method='PUT')
|
||||
{
|
||||
try
|
||||
{
|
||||
$strict = !isset($content_type) || !preg_match('#^application/json#', $content_type);
|
||||
$data = json_decode($json, true, 10, JSON_THROW_ON_ERROR);
|
||||
|
||||
// check if we use patch: method is PATCH or method is POST AND keys contain slashes
|
||||
if ($method === 'PATCH' || !$strict && $method === 'POST' && array_filter(array_keys($data), static function ($key)
|
||||
{
|
||||
return strpos($key, '/') !== false;
|
||||
}))
|
||||
{
|
||||
// apply patch on JsCard of contact
|
||||
$data = self::patch($data, $old ? self::getJsCard($old, false) : [], !$old);
|
||||
}
|
||||
|
||||
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
||||
|
||||
$contact = [];
|
||||
@ -96,53 +114,72 @@ class JsContact
|
||||
switch ($name)
|
||||
{
|
||||
case 'uid':
|
||||
$contact['uid'] = self::parseUid($value);
|
||||
$contact['uid'] = self::parseUid($value, $old['uid'], !$strict);
|
||||
break;
|
||||
|
||||
case 'name':
|
||||
$contact += self::parseNameComponents($value, $check_at_type);
|
||||
$contact += self::parseNameComponents($value, $strict);
|
||||
break;
|
||||
|
||||
case 'fullName':
|
||||
$contact['n_fn'] = self::parseString($value);
|
||||
// if no separate name-components given, simply split first word off as n_given and rest as n_family
|
||||
if (!isset($data['name']) && !empty($contact['n_fn']))
|
||||
{
|
||||
if (preg_match('/^([^ ,]+)(,?) (.*)$/', $contact['n_fn'], $matches))
|
||||
{
|
||||
if (!empty($matches[2]))
|
||||
{
|
||||
list(, $contact['n_family'], , $contact['n_given']) = $matches;
|
||||
}
|
||||
else
|
||||
{
|
||||
list(, $contact['n_given'], , $contact['n_family']) = $matches;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$contact['n_family'] = $contact['n_fn'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'organizations':
|
||||
$contact += self::parseOrganizations($value, $check_at_type);
|
||||
$contact += self::parseOrganizations($value, $strict);
|
||||
break;
|
||||
|
||||
case 'titles':
|
||||
$contact += self::parseTitles($value, $check_at_type);
|
||||
$contact += self::parseTitles($value, $strict);
|
||||
break;
|
||||
|
||||
case 'emails':
|
||||
$contact += self::parseEmails($value, $check_at_type);
|
||||
$contact += self::parseEmails($value, $strict);
|
||||
break;
|
||||
|
||||
case 'phones':
|
||||
$contact += self::parsePhones($value, $check_at_type);
|
||||
$contact += self::parsePhones($value, $strict);
|
||||
break;
|
||||
|
||||
case 'online':
|
||||
$contact += self::parseOnline($value, $check_at_type);
|
||||
$contact += self::parseOnline($value, $strict);
|
||||
break;
|
||||
|
||||
case 'addresses':
|
||||
$contact += self::parseAddresses($value, $check_at_type);
|
||||
$contact += self::parseAddresses($value, $strict);
|
||||
break;
|
||||
|
||||
case 'photos':
|
||||
$contact += self::parsePhotos($value, $check_at_type);
|
||||
$contact += self::parsePhotos($value, $strict);
|
||||
break;
|
||||
|
||||
case 'anniversaries':
|
||||
$contact += self::parseAnniversaries($value);
|
||||
$contact += self::parseAnniversaries($value, $strict);
|
||||
break;
|
||||
|
||||
case 'notes':
|
||||
$contact['note'] = implode("\n", array_map(static function ($note) {
|
||||
return self::parseString($note);
|
||||
}, $value));
|
||||
}, (array)$value));
|
||||
break;
|
||||
|
||||
case 'categories':
|
||||
@ -150,7 +187,7 @@ class JsContact
|
||||
break;
|
||||
|
||||
case 'egroupware.org:customfields':
|
||||
$contact += self::parseCustomfields($value);
|
||||
$contact += self::parseCustomfields($value, $strict);
|
||||
break;
|
||||
|
||||
case 'egroupware.org:assistant':
|
||||
@ -197,11 +234,12 @@ class JsContact
|
||||
* Parse and optionally generate UID
|
||||
*
|
||||
* @param string|null $uid
|
||||
* @param string|null $old old value, if given it must NOT change
|
||||
* @param bool $generate_when_empty true: generate UID if empty, false: throw error
|
||||
* @return string without urn:uuid: prefix
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected static function parseUid(string $uid=null, $generate_when_empty=false)
|
||||
protected static function parseUid(string $uid=null, string $old=null, bool $generate_when_empty=false)
|
||||
{
|
||||
if (empty($uid) || strlen($uid) < 12)
|
||||
{
|
||||
@ -211,7 +249,15 @@ class JsContact
|
||||
}
|
||||
$uid = \HTTP_WebDAV_Server::_new_uuid();
|
||||
}
|
||||
return strpos($uid, self::URN_UUID_PREFIX) === 0 ? substr($uid, 9) : $uid;
|
||||
if (strpos($uid, self::URN_UUID_PREFIX) === 0)
|
||||
{
|
||||
$uid = substr($uid, strlen(self::URN_UUID_PREFIX));
|
||||
}
|
||||
if (isset($old) && $old !== $uid)
|
||||
{
|
||||
throw new \InvalidArgumentException("You must NOT change the UID ('$old'): ".json_encode($uid));
|
||||
}
|
||||
return $uid;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -248,15 +294,15 @@ class JsContact
|
||||
* As we store only one organization, the rest get lost, multiple units get concatenated by space.
|
||||
*
|
||||
* @param array $orgas
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseOrganizations(array $orgas, bool $check_at_type=true)
|
||||
protected static function parseOrganizations(array $orgas, bool $stict=true)
|
||||
{
|
||||
$contact = [];
|
||||
foreach($orgas as $orga)
|
||||
{
|
||||
if ($check_at_type && $orga[self::AT_TYPE] !== self::TYPE_ORGANIZATION)
|
||||
if ($stict && $orga[self::AT_TYPE] !== self::TYPE_ORGANIZATION)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($orga, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -306,15 +352,19 @@ class JsContact
|
||||
* Parse titles, thought we only have "title" and "role" available for storage.
|
||||
*
|
||||
* @param array $titles
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseTitles(array $titles, bool $check_at_type=true)
|
||||
protected static function parseTitles(array $titles, bool $stict=true)
|
||||
{
|
||||
$contact = [];
|
||||
foreach($titles as $id => $title)
|
||||
{
|
||||
if ($check_at_type && $title[self::AT_TYPE] !== self::TYPE_TITLE)
|
||||
if (!$stict && is_string($title))
|
||||
{
|
||||
$title = ['title' => $title];
|
||||
}
|
||||
if ($stict && $title[self::AT_TYPE] !== self::TYPE_TITLE)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: " . json_encode($title[self::AT_TYPE]));
|
||||
}
|
||||
@ -397,8 +447,12 @@ class JsContact
|
||||
foreach($definitions as $name => $definition)
|
||||
{
|
||||
$data = $cfs[$name];
|
||||
if (isset($data[$name]))
|
||||
if (isset($data))
|
||||
{
|
||||
if (is_scalar($data))
|
||||
{
|
||||
$data = ['value' => $data];
|
||||
}
|
||||
if (!is_array($data) || !array_key_exists('value', $data))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid customfield object $name: ".json_encode($data, self::JSON_OPTIONS_ERROR));
|
||||
@ -526,21 +580,21 @@ class JsContact
|
||||
* Parse addresses object containing multiple addresses
|
||||
*
|
||||
* @param array $addresses
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseAddresses(array $addresses, bool $check_at_type=true)
|
||||
protected static function parseAddresses(array $addresses, bool $stict=true)
|
||||
{
|
||||
$n = 0;
|
||||
$last_type = null;
|
||||
$contact = [];
|
||||
foreach($addresses as $id => $address)
|
||||
{
|
||||
if ($check_at_type && $address[self::AT_TYPE] !== self::TYPE_ADDRESS)
|
||||
if ($stict && $address[self::AT_TYPE] !== self::TYPE_ADDRESS)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($address));
|
||||
}
|
||||
$contact += ($values=self::parseAddress($address, $id, $last_type));
|
||||
$contact += ($values=self::parseAddress($address, $id, $last_type, $stict));
|
||||
|
||||
if (++$n > 2)
|
||||
{
|
||||
@ -567,9 +621,10 @@ class JsContact
|
||||
* @param array $address address-object
|
||||
* @param string $id index
|
||||
* @param ?string $last_type "work" or "home"
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseAddress(array $address, string $id, string &$last_type=null)
|
||||
protected static function parseAddress(array $address, string $id, string &$last_type=null, bool $stict=true)
|
||||
{
|
||||
$type = !isset($last_type) && (empty($address['contexts']['private']) || $id === 'work') ||
|
||||
$last_type === 'home' ? 'work' : 'home';
|
||||
@ -577,7 +632,10 @@ class JsContact
|
||||
$prefix = $type === 'work' ? 'adr_one_' : 'adr_two_';
|
||||
|
||||
$contact = [$prefix.'street' => null, $prefix.'street2' => null];
|
||||
list($contact[$prefix.'street'], $contact[$prefix.'street2']) = self::parseStreetComponents($address['street']);
|
||||
if (!empty($address['street']))
|
||||
{
|
||||
list($contact[$prefix.'street'], $contact[$prefix.'street2']) = self::parseStreetComponents($address['street'], $stict);
|
||||
}
|
||||
foreach(self::$jsAddress2attr+self::$jsAddress2workAttr as $js => $attr)
|
||||
{
|
||||
if (isset($address[$js]) && !is_string($address[$js]))
|
||||
@ -586,6 +644,17 @@ class JsContact
|
||||
}
|
||||
$contact[$prefix.$attr] = $address[$js];
|
||||
}
|
||||
// no country-code but a name translating to a code --> use it
|
||||
if (empty($contact[$prefix.'countrycode']) && !empty($contact[$prefix.'countryname']) &&
|
||||
strlen($code = Api\Country::country_code($contact[$prefix.'countryname'])) === 2)
|
||||
{
|
||||
$contact[$prefix.'countrycode'] = $code;
|
||||
}
|
||||
// if we have a valid code, the untranslated name as our UI does
|
||||
if (!empty($contact[$prefix.'countrycode']) && !empty($name = Api\Country::get_full_name($contact[$prefix.'countrycode'], false)))
|
||||
{
|
||||
$contact[$prefix.'countryname'] = $name;
|
||||
}
|
||||
return $contact;
|
||||
}
|
||||
|
||||
@ -637,12 +706,20 @@ class JsContact
|
||||
* As we have only 2 address-lines, we combine all components, with one space as separator, if none given.
|
||||
* Then we split it into 2 lines.
|
||||
*
|
||||
* @param array $components
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param array|string $components string only for relaxed parsing
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return string[] street and street2 values
|
||||
*/
|
||||
protected static function parseStreetComponents(array $components, bool $check_at_type=true)
|
||||
protected static function parseStreetComponents($components, bool $stict=true)
|
||||
{
|
||||
if (!$stict && is_string($components))
|
||||
{
|
||||
$components = [['type' => 'name', 'value' => $components]];
|
||||
}
|
||||
if (!is_array($components))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid street-components: ".json_encode($components, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
$street = [];
|
||||
$last_type = null;
|
||||
foreach($components as $component)
|
||||
@ -651,7 +728,7 @@ class JsContact
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid street-component: ".json_encode($component, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if ($check_at_type && $component[self::AT_TYPE] !== self::TYPE_STREET_COMPONENT)
|
||||
if ($stict && $component[self::AT_TYPE] !== self::TYPE_STREET_COMPONENT)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($component, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -712,21 +789,25 @@ class JsContact
|
||||
* Parse phone objects
|
||||
*
|
||||
* @param array $phones $id => object with attribute "phone" and optional "features" and "context"
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parsePhones(array $phones, bool $check_at_type=true)
|
||||
protected static function parsePhones(array $phones, bool $stict=true)
|
||||
{
|
||||
$contact = [];
|
||||
|
||||
// check for good matches
|
||||
foreach($phones as $id => $phone)
|
||||
{
|
||||
if (!$stict && is_string($phone))
|
||||
{
|
||||
$phone = ['phone' => $phone];
|
||||
}
|
||||
if (!is_array($phone) || !is_string($phone['phone']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid phone: " . json_encode($phone, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if ($check_at_type && $phone[self::AT_TYPE] !== self::TYPE_PHONE)
|
||||
if ($stict && $phone[self::AT_TYPE] !== self::TYPE_PHONE)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($phone, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -818,19 +899,23 @@ class JsContact
|
||||
* We currently only support 2 URLs, rest get's ignored!
|
||||
*
|
||||
* @param array $values
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseOnline(array $values, bool $check_at_type)
|
||||
protected static function parseOnline(array $values, bool $stict)
|
||||
{
|
||||
$contact = [];
|
||||
foreach($values as $id => $value)
|
||||
{
|
||||
if (!$stict && is_string($value))
|
||||
{
|
||||
$value = ['resource' => $value];
|
||||
}
|
||||
if (!is_array($value) || !is_string($value['resource']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid online resource with id '$id': ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if ($check_at_type && $value[self::AT_TYPE] !== self::TYPE_RESOURCE)
|
||||
if ($stict && $value[self::AT_TYPE] !== self::TYPE_RESOURCE)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -889,15 +974,19 @@ class JsContact
|
||||
*
|
||||
* @link https://datatracker.ietf.org/doc/html/draft-ietf-jmap-jscontact-07#section-2.3.1
|
||||
* @param array $emails id => object with attribute "email" and optional "context"
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseEmails(array $emails, bool $check_at_type=true)
|
||||
protected static function parseEmails(array $emails, bool $stict=true)
|
||||
{
|
||||
$contact = [];
|
||||
foreach($emails as $id => $value)
|
||||
{
|
||||
if ($check_at_type && $value[self::AT_TYPE] !== self::TYPE_EMAIL)
|
||||
if (!$stict && is_string($value))
|
||||
{
|
||||
$value = ['email' => $value];
|
||||
}
|
||||
if ($stict && $value[self::AT_TYPE] !== self::TYPE_EMAIL)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -905,7 +994,7 @@ class JsContact
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid email object (requires email attribute): ".json_encode($value, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if (!isset($contact['email']) && $id === 'work' && empty($value['context']['private']))
|
||||
if (!isset($contact['email']) && ($id === 'work' || empty($value['contexts']['private']) || isset($contact['email_home'])))
|
||||
{
|
||||
$contact['email'] = $value['email'];
|
||||
}
|
||||
@ -953,11 +1042,11 @@ class JsContact
|
||||
* @return array
|
||||
* @ToDo
|
||||
*/
|
||||
protected static function parsePhotos(array $photos, bool $check_at_type)
|
||||
protected static function parsePhotos(array $photos, bool $stict)
|
||||
{
|
||||
foreach($photos as $id => $photo)
|
||||
{
|
||||
if ($check_at_type && $photo[self::AT_TYPE] !== self::TYPE_FILE)
|
||||
if ($stict && $photo[self::AT_TYPE] !== self::TYPE_FILE)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($photo, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -1008,18 +1097,23 @@ class JsContact
|
||||
* @param array $components
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseNameComponents(array $components, bool $check_at_type=true)
|
||||
protected static function parseNameComponents(array $components, bool $stict=true)
|
||||
{
|
||||
$contact = array_combine(array_values(self::$nameType2attribute),
|
||||
array_fill(0, count(self::$nameType2attribute), null));
|
||||
|
||||
foreach($components as $component)
|
||||
foreach($components as $type => $component)
|
||||
{
|
||||
// for relaxed checks, allow $type => $value pairs
|
||||
if (!$stict && is_string($type) && is_scalar($component))
|
||||
{
|
||||
$component = ['type' => $type, 'value' => $component];
|
||||
}
|
||||
if (empty($component['type']) || isset($component) && !is_string($component['value']))
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid name-component (must have type and value attributes): ".json_encode($component, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if ($check_at_type && $component[self::AT_TYPE] !== self::TYPE_NAME_COMPONENT)
|
||||
if ($stict && $component[self::AT_TYPE] !== self::TYPE_NAME_COMPONENT)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($component, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -1052,14 +1146,28 @@ class JsContact
|
||||
*
|
||||
* @link https://datatracker.ietf.org/doc/html/draft-ietf-jmap-jscontact-07#section-2.5.1
|
||||
* @param array $anniversaries id => object with attribute date and optional type
|
||||
* @param bool $check_at_type true: check if objects have their proper @type attribute
|
||||
* @param bool $stict true: check if objects have their proper @type attribute
|
||||
* @return array
|
||||
*/
|
||||
protected static function parseAnniversaries(array $anniversaries, bool $check_at_type=true)
|
||||
protected static function parseAnniversaries(array $anniversaries, bool $stict=true)
|
||||
{
|
||||
$contact = [];
|
||||
foreach($anniversaries as $id => $anniversary)
|
||||
{
|
||||
if (!$stict && is_string($anniversary))
|
||||
{
|
||||
// allow German date format "dd.mm.yyyy"
|
||||
if (preg_match('/^(\d+)\.(\d+).(\d+)$/', $anniversary, $matches))
|
||||
{
|
||||
$matches = sprintf('%04d-%02d-%02d', (int)$matches[3], (int)$matches[2], (int)$matches[1]);
|
||||
}
|
||||
// allow US date format "mm/dd/yyyy"
|
||||
elseif (preg_match('#^(\d+)/(\d+)/(\d+)$#', $anniversary, $matches))
|
||||
{
|
||||
$matches = sprintf('%04d-%02d-%02d', (int)$matches[3], (int)$matches[1], (int)$matches[2]);
|
||||
}
|
||||
$anniversary = ['type' => $id, 'date' => $anniversary];
|
||||
}
|
||||
if (!is_array($anniversary) || !is_string($anniversary['date']) ||
|
||||
!preg_match('/^\d{4}-\d{2}-\d{2}$/', $anniversary['date']) ||
|
||||
(!list($year, $month, $day) = explode('-', $anniversary['date'])) ||
|
||||
@ -1067,7 +1175,7 @@ class JsContact
|
||||
{
|
||||
throw new \InvalidArgumentException("Invalid anniversary object with id '$id': ".json_encode($anniversary, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
if ($check_at_type && $anniversary[self::AT_TYPE] !== self::TYPE_ANNIVERSARY)
|
||||
if ($stict && $anniversary[self::AT_TYPE] !== self::TYPE_ANNIVERSARY)
|
||||
{
|
||||
throw new \InvalidArgumentException("Missing or invalid @type: ".json_encode($anniversary, self::JSON_OPTIONS_ERROR));
|
||||
}
|
||||
@ -1251,16 +1359,51 @@ class JsContact
|
||||
return $members;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch JsCard
|
||||
*
|
||||
* @param array $patches JSON path
|
||||
* @param array $jscard to patch
|
||||
* @param bool $create =false true: create missing components
|
||||
* @return array patched $jscard
|
||||
*/
|
||||
public static function patch(array $patches, array $jscard, bool $create=false)
|
||||
{
|
||||
foreach($patches as $path => $value)
|
||||
{
|
||||
$parts = explode('/', $path);
|
||||
$target = &$jscard;
|
||||
foreach($parts as $n => $part)
|
||||
{
|
||||
if (!isset($target[$part]) && $n < count($parts)-1 && !$create)
|
||||
{
|
||||
throw new \InvalidArgumentException("Trying to patch not existing attribute with path $path!");
|
||||
}
|
||||
$parent = $target;
|
||||
$target = &$target[$part];
|
||||
}
|
||||
if (isset($value))
|
||||
{
|
||||
$target = $value;
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($parent[$part]);
|
||||
}
|
||||
}
|
||||
return $jscard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map all kind of exceptions while parsing to a JsContactParseException
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @param string $type
|
||||
* @param string $name
|
||||
* @param ?string $name
|
||||
* @param mixed $value
|
||||
* @throws JsContactParseException
|
||||
*/
|
||||
protected static function handleExceptions(\Throwable $e, $type='JsContact', string $name, $value)
|
||||
protected static function handleExceptions(\Throwable $e, $type='JsContact', ?string $name, $value)
|
||||
{
|
||||
try {
|
||||
throw $e;
|
||||
|
@ -275,7 +275,30 @@ class Merge extends Api\Storage\Merge
|
||||
*/
|
||||
public function get_placeholder_list($prefix = '')
|
||||
{
|
||||
$placeholders = [];
|
||||
// Specific order for these ones
|
||||
$placeholders = [
|
||||
'contact' => [],
|
||||
'details' => [
|
||||
[
|
||||
'value' => $this->prefix($prefix, 'categories', '{'),
|
||||
'label' => lang('Category path')
|
||||
],
|
||||
['value' => $this->prefix($prefix, 'note', '{'),
|
||||
'label' => $this->contacts->contact_fields['note']],
|
||||
['value' => $this->prefix($prefix, 'id', '{'),
|
||||
'label' => $this->contacts->contact_fields['id']],
|
||||
['value' => $this->prefix($prefix, 'owner', '{'),
|
||||
'label' => $this->contacts->contact_fields['owner']],
|
||||
['value' => $this->prefix($prefix, 'private', '{'),
|
||||
'label' => $this->contacts->contact_fields['private']],
|
||||
['value' => $this->prefix($prefix, 'cat_id', '{'),
|
||||
'label' => $this->contacts->contact_fields['cat_id']],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
// Iterate through the list & switch groups as we go
|
||||
// Hopefully a little better than assigning each field to a group
|
||||
$group = 'contact';
|
||||
foreach($this->contacts->contact_fields as $name => $label)
|
||||
{
|
||||
@ -299,25 +322,37 @@ class Merge extends Api\Storage\Merge
|
||||
case 'email_home':
|
||||
$group = 'email';
|
||||
break;
|
||||
case 'url':
|
||||
case 'freebusy_uri':
|
||||
$group = 'details';
|
||||
}
|
||||
$placeholders[$group]["{{" . ($prefix ? $prefix . '/' : '') . $name . "}}"] = $label;
|
||||
if($name == 'cat_id')
|
||||
$marker = $this->prefix($prefix, $name, '{');
|
||||
if(!array_filter($placeholders, function ($a) use ($marker)
|
||||
{
|
||||
$placeholders[$group]["{{" . ($prefix ? $prefix . '/' : '') . $name . "}}"] = lang('Category path');
|
||||
count(array_filter($a, function ($b) use ($marker)
|
||||
{
|
||||
return $b['value'] == $marker;
|
||||
})
|
||||
) > 0;
|
||||
}))
|
||||
{
|
||||
$placeholders[$group][] = [
|
||||
'value' => $marker,
|
||||
'label' => $label
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Correctly formatted address by country / preference
|
||||
$placeholders['business']['{{' . ($prefix ? $prefix . '/' : '') . 'adr_one_formatted}}'] = "Formatted business address";
|
||||
$placeholders['private']['{{' . ($prefix ? $prefix . '/' : '') . 'adr_two_formatted}}'] = "Formatted private address";
|
||||
$placeholders['business'][] = [
|
||||
'value' => $this->prefix($prefix, 'adr_one_formatted', '{'),
|
||||
'label' => "Formatted business address"
|
||||
];
|
||||
$placeholders['private'][] = [
|
||||
'value' => $this->prefix($prefix, 'adr_two_formatted', '{'),
|
||||
'label' => "Formatted private address"
|
||||
];
|
||||
|
||||
$group = 'customfields';
|
||||
foreach($this->contacts->customfields as $name => $field)
|
||||
{
|
||||
$placeholders[$group]["{{" . ($prefix ? $prefix . '/' : '') . $name . "}}"] = $field['label'];
|
||||
}
|
||||
$this->add_customfield_placeholders($placeholders, $prefix);
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
|
@ -766,6 +766,19 @@ class Country
|
||||
{
|
||||
if (!$name) return ''; // nothing to do
|
||||
|
||||
// handle names like "Germany (Deutschland)"
|
||||
if (preg_match('/^([^(]+) \(([^)]+)\)$/', $name, $matches))
|
||||
{
|
||||
if (($code = self::country_code($matches[1])) && strlen($code) === 2)
|
||||
{
|
||||
return $code;
|
||||
}
|
||||
if (($code = self::country_code($matches[2])) && strlen($code) === 2)
|
||||
{
|
||||
return $code;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen($name) == 2 && isset(self::$country_array[$name]))
|
||||
{
|
||||
return $name; // $name is already a country-code
|
||||
|
@ -114,7 +114,7 @@ class Widget
|
||||
// Update content?
|
||||
if(self::$cont == null)
|
||||
self::$cont = is_array(self::$request->content) ? self::$request->content : array();
|
||||
if($this->id && is_array(self::$cont[$this->id]))
|
||||
if($this->id && is_array(self::$cont[$this->id] ?? null))
|
||||
{
|
||||
$old_cont = self::$cont;
|
||||
self::$cont = self::$cont[$this->id];
|
||||
@ -147,7 +147,8 @@ class Widget
|
||||
}
|
||||
|
||||
// Reset content as we leave
|
||||
if($old_cont) {
|
||||
if (isset($old_cont))
|
||||
{
|
||||
self::$cont = $old_cont;
|
||||
}
|
||||
}
|
||||
@ -206,7 +207,7 @@ class Widget
|
||||
$template = $this;
|
||||
while($reader->moveToNextAttribute())
|
||||
{
|
||||
if ($reader->name != 'id' && $template->attr[$reader->name] !== $reader->value)
|
||||
if ($reader->name != 'id' && (!isset($template->attr[$reader->name]) || $template->attr[$reader->name] !== $reader->value))
|
||||
{
|
||||
if (!$cloned)
|
||||
{
|
||||
@ -218,7 +219,7 @@ class Widget
|
||||
$template->attrs[$reader->name] = $value = $reader->value;
|
||||
|
||||
// expand attributes values, otherwise eg. validation can not use attrs referencing to content
|
||||
if ($value[0] == '@' || strpos($value, '$cont') !== false)
|
||||
if (!empty($value) && ($value[0] === '@' || strpos($value, '$cont') !== false))
|
||||
{
|
||||
$value = self::expand_name($value, null, null, null, null,
|
||||
isset(self::$cont) ? self::$cont : self::$request->content);
|
||||
@ -237,7 +238,7 @@ class Widget
|
||||
}
|
||||
|
||||
// Add in anything in the modification array
|
||||
if(is_array(self::$request->modifications[$this->id]))
|
||||
if (is_array(self::$request->modifications[$this->id] ?? null))
|
||||
{
|
||||
$this->attrs = array_merge($this->attrs,self::$request->modifications[$this->id]);
|
||||
}
|
||||
@ -426,7 +427,7 @@ class Widget
|
||||
class_exists($class_name = $basetype.'_etemplate_widget'))))
|
||||
{
|
||||
// Try for base type, it's probably better than the root
|
||||
if(self::$widget_registry[$basetype] && self::$widget_registry[$basetype] != $class_name)
|
||||
if(isset(self::$widget_registry[$basetype]) && self::$widget_registry[$basetype] !== $class_name)
|
||||
{
|
||||
$class_name = self::$widget_registry[$basetype];
|
||||
}
|
||||
@ -535,12 +536,12 @@ class Widget
|
||||
// maintain $expand array name-expansion
|
||||
$cname = $params[0];
|
||||
$expand =& $params[1];
|
||||
if ($expand['cname'] && $expand['cname'] !== $cname)
|
||||
if (isset($expand['cname']) && $expand['cname'] !== $cname)
|
||||
{
|
||||
$expand['cont'] =& self::get_array(self::$request->content, $cname);
|
||||
$expand['cname'] = $cname;
|
||||
}
|
||||
if ($respect_disabled && ($disabled = $this->attrs['disabled'] && self::check_disabled($this->attrs['disabled'], $expand)))
|
||||
if ($respect_disabled && ($disabled = isset($this->attrs['disabled']) && self::check_disabled($this->attrs['disabled'], $expand)))
|
||||
{
|
||||
//error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'=".array2string($disabled).": NOT running");
|
||||
return;
|
||||
@ -593,13 +594,13 @@ class Widget
|
||||
foreach($attrs as $name => &$value)
|
||||
{
|
||||
if(!is_string($value)) continue;
|
||||
$value = self::expand_name($value,$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
|
||||
$value = self::expand_name($value, $expand['c'] ?? null, $expand['row'] ?? null, $expand['c_'] ?? null, $expand['row_'] ?? null, $expand['cont'] ?? []);
|
||||
}
|
||||
if($attrs['attributes'])
|
||||
if (!empty($attrs['attributes']))
|
||||
{
|
||||
$attrs = array_merge($attrs, $attrs['attributes']);
|
||||
}
|
||||
if(strpos($child->attrs['type'], '@') !== false || strpos($child->attrs['type'], '$') !== false)
|
||||
if (!empty($child->attrs['type']) && (strpos($child->attrs['type'], '@') !== false || strpos($child->attrs['type'], '$') !== false))
|
||||
{
|
||||
$type = self::expand_name($child->attrs['type'],$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
|
||||
$id = self::expand_name($child->id,$expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
|
||||
@ -677,7 +678,7 @@ class Widget
|
||||
*/
|
||||
protected static function expand_name($name,$c,$row,$c_=0,$row_=0,$cont=array())
|
||||
{
|
||||
$is_index_in_content = $name[0] == '@';
|
||||
$is_index_in_content = !empty($name) && $name[0] == '@';
|
||||
if (($pos_var=strpos($name,'$')) !== false)
|
||||
{
|
||||
if (!$cont)
|
||||
@ -687,8 +688,8 @@ class Widget
|
||||
if (!is_numeric($c)) $c = self::chrs2num($c);
|
||||
$col = self::num2chrs($c-1); // $c-1 to get: 0:'@', 1:'A', ...
|
||||
if (is_numeric($c_)) $col_ = self::num2chrs($c_-1);
|
||||
$row_cont = $cont[$row];
|
||||
$col_row_cont = $cont[$col.$row];
|
||||
$row_cont = $cont[$row] ?? null;
|
||||
$col_row_cont = $cont[$col.$row] ?? null;
|
||||
|
||||
try {
|
||||
eval('$name = "' . str_replace('"', '\\"', $name) . '";');
|
||||
@ -726,9 +727,9 @@ class Widget
|
||||
*/
|
||||
static function chrs2num($chrs)
|
||||
{
|
||||
if (empty($chrs)) return 0;
|
||||
$min = ord('A');
|
||||
$max = ord('Z') - $min + 1;
|
||||
|
||||
$num = 1+ord($chrs[0])-$min;
|
||||
if (strlen($chrs) > 1)
|
||||
{
|
||||
@ -751,7 +752,7 @@ class Widget
|
||||
if ($num >= $max)
|
||||
{
|
||||
$chrs = chr(($num / $max) + $min - 1);
|
||||
}
|
||||
} else $chrs = '';
|
||||
$chrs .= chr(($num % $max) + $min);
|
||||
|
||||
return $chrs;
|
||||
@ -829,7 +830,7 @@ class Widget
|
||||
{
|
||||
if ($expand && !empty($name))
|
||||
{
|
||||
$name = self::expand_name($name, $expand['c'], $expand['row'], $expand['c_'], $expand['row_'], $expand['cont']);
|
||||
$name = self::expand_name($name, $expand['c'] ?? null, $expand['row'] ?? null, $expand['c_'] ?? null, $expand['row_'] ?? null, $expand['cont'] ?? []);
|
||||
}
|
||||
if (count($name_parts = explode('[', $name, 2)) > 1)
|
||||
{
|
||||
|
@ -50,12 +50,12 @@ class Box extends Etemplate\Widget
|
||||
$old_expand = $params[1];
|
||||
|
||||
if ($this->id && $this->type != 'groupbox') $cname = self::form_name($cname, $this->id, $params[1]);
|
||||
if ($expand['cname'] !== $cname && trim($cname) != '')
|
||||
if (!empty($expand['cname']) && $expand['cname'] !== $cname && trim($cname))
|
||||
{
|
||||
$expand['cont'] =& self::get_array(self::$request->content, $cname);
|
||||
$expand['cname'] = $cname;
|
||||
}
|
||||
if ($respect_disabled && ($disabled = $this->attrs['disabled'] && self::check_disabled($this->attrs['disabled'], $expand)))
|
||||
if ($respect_disabled && isset($this->attrs['disabled']) && self::check_disabled($this->attrs['disabled'], $expand))
|
||||
{
|
||||
//error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'=".array2string($disabled).": NOT running");
|
||||
return;
|
||||
@ -73,7 +73,7 @@ class Box extends Etemplate\Widget
|
||||
|
||||
// Expand children
|
||||
$columns_disabled = null;
|
||||
if($this->id && $this->children[0] && strpos($this->children[0]->id, '$') !== false)
|
||||
if($this->id && isset($this->children[0]) && strpos($this->children[0]->id, '$') !== false)
|
||||
{
|
||||
// Need to set this so the first child can repeat
|
||||
$expand['row'] = 0;
|
||||
|
@ -29,7 +29,7 @@ use EGroupware\Api;
|
||||
* &8 = dont show time for readonly and type date-time if time is 0:00,
|
||||
* &16 = prefix r/o display with dow
|
||||
* &32 = prefix r/o display with week-number
|
||||
* &64 = prefix r/o display with weeknumber and dow
|
||||
* &64 = prefix r/o display with weeknumber and dow
|
||||
* &128 = no icon to trigger popup, click into input trigers it, also removing the separators to save space
|
||||
*
|
||||
* @todo validation of date-duration
|
||||
@ -120,7 +120,7 @@ class Date extends Transformer
|
||||
{
|
||||
$date = Api\DateTime::server2user($value);
|
||||
}
|
||||
elseif($this->attrs['data_format'] && $this->attrs['data_format'] !== 'object')
|
||||
elseif (!empty($this->attrs['data_format']) && $this->attrs['data_format'] !== 'object')
|
||||
{
|
||||
$date = Api\DateTime::createFromFormat($this->attrs['data_format'], $value, Api\DateTime::$user_timezone);
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class Grid extends Box
|
||||
$columns_disabled = array();
|
||||
}
|
||||
|
||||
if ($respect_disabled && ($disabled = $this->attrs['disabled'] && self::check_disabled($this->attrs['disabled'], $expand)))
|
||||
if ($respect_disabled && isset($this->attrs['disabled']) && self::check_disabled($this->attrs['disabled'], $expand))
|
||||
{
|
||||
//error_log(__METHOD__."('$method_name', ".array2string($params).', '.array2string($respect_disabled).") $this disabled='{$this->attrs['disabled']}'=".array2string($disabled).": NOT running");
|
||||
$params[0] = $old_cname;
|
||||
@ -89,7 +89,7 @@ class Grid extends Box
|
||||
}
|
||||
|
||||
if ($this->id && $this->type !== 'row') $cname = self::form_name($cname, $this->id, $expand);
|
||||
if ($expand['cname'] !== $cname && $cname)
|
||||
if (!empty($expand['cname']) && $expand['cname'] !== $cname && $cname)
|
||||
{
|
||||
$expand['cont'] =& self::get_array(self::$request->content, $cname);
|
||||
$expand['cname'] = $cname;
|
||||
|
@ -186,21 +186,21 @@ class Nextmatch extends Etemplate\Widget
|
||||
if (true) $value =& self::get_array(self::$request->content, $form_name, true);
|
||||
|
||||
// Add favorite here so app doesn't save it in the session
|
||||
if($_GET['favorite'])
|
||||
if (!empty($_GET['favorite']))
|
||||
{
|
||||
$send_value['favorite'] = $safe_name;
|
||||
}
|
||||
if (true) $value = $send_value;
|
||||
$value['total'] = $total;
|
||||
$value['total'] = $total ?? null;
|
||||
|
||||
// Send categories
|
||||
if(!$value['no_cat'] && !$value['cat_is_select'])
|
||||
if(empty($value['no_cat']) && empty($value['cat_is_select']))
|
||||
{
|
||||
$cat_app = $value['cat_app'] ? $value['cat_app'] : $GLOBALS['egw_info']['flags']['current_app'];
|
||||
$value['options-cat_id'] = self::$request->sel_options['cat_id'] ? self::$request->sel_options['cat_id'] : array();
|
||||
$cat_app = $value['cat_app'] ?? $GLOBALS['egw_info']['flags']['current_app'] ?? '';
|
||||
$value['options-cat_id'] = self::$request->sel_options['cat_id'] ?? [];
|
||||
|
||||
// Add 'All', if not already there
|
||||
if(!$value['options-cat_id'][''] && !$value['options-cat_id'][0])
|
||||
if(empty($value['options-cat_id']['']) && empty($value['options-cat_id'][0]))
|
||||
{
|
||||
$value['options-cat_id'][''] = lang('All categories');
|
||||
}
|
||||
@ -220,7 +220,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
if(strpos($name, 'options-') !== false && $_value)
|
||||
{
|
||||
$select = substr($name, 8);
|
||||
if(!self::$request->sel_options[$select])
|
||||
if (empty(self::$request->sel_options[$select]))
|
||||
{
|
||||
self::$request->sel_options[$select] = array();
|
||||
}
|
||||
@ -231,21 +231,21 @@ class Nextmatch extends Etemplate\Widget
|
||||
//unset($value[$name]);
|
||||
}
|
||||
}
|
||||
if($value['rows']['sel_options'])
|
||||
if (!empty($value['rows']['sel_options']))
|
||||
{
|
||||
self::$request->sel_options = array_merge(self::$request->sel_options,$value['rows']['sel_options']);
|
||||
unset($value['rows']['sel_options']);
|
||||
}
|
||||
|
||||
// If column selection preference is forced, set a flag to turn off UI
|
||||
$pref_name = 'nextmatch-' . (isset($value['columnselection_pref']) ? $value['columnselection_pref'] : $this->attrs['template']);
|
||||
$value['no_columnselection'] = $value['no_columnselection'] || (
|
||||
$GLOBALS['egw']->preferences->forced[$app][$pref_name] &&
|
||||
$pref_name = 'nextmatch-' . ($value['columnselection_pref'] ?? $this->attrs['template'] ?? '');
|
||||
$value['no_columnselection'] = !empty($value['no_columnselection']) || (
|
||||
!empty($GLOBALS['egw']->preferences->forced[$app][$pref_name]) &&
|
||||
// Need to check admin too, or it will be impossible to turn off
|
||||
!$GLOBALS['egw_info']['user']['apps']['admin']
|
||||
empty($GLOBALS['egw_info']['user']['apps']['admin'])
|
||||
);
|
||||
// Use this flag to indicate to the admin that columns are forced (and that's why they can't change)
|
||||
$value['columns_forced'] = (boolean)$GLOBALS['egw']->preferences->forced[$app][$pref_name];
|
||||
$value['columns_forced'] = !empty($GLOBALS['egw']->preferences->forced[$app][$pref_name]);
|
||||
|
||||
// todo: no need to store rows in request, it's enought to send them to client
|
||||
|
||||
@ -256,7 +256,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
if (isset($value['actions']) && !isset($value['actions'][0]))
|
||||
{
|
||||
$value['action_links'] = array();
|
||||
$template_name = isset($value['template']) ? $value['template'] : ($this->attrs['template'] ?: $this->attrs['options']);
|
||||
$template_name = isset($value['template']) ? $value['template'] : ($this->attrs['template'] ?? $this->attrs['options'] ?? null);
|
||||
if (!is_array($value['action_links'])) $value['action_links'] = array();
|
||||
$value['actions'] = self::egw_actions($value['actions'], $template_name, '', $value['action_links']);
|
||||
}
|
||||
@ -375,8 +375,8 @@ class Nextmatch extends Etemplate\Widget
|
||||
|
||||
$GLOBALS['egw']->session->commit_session();
|
||||
|
||||
$row_id = isset($value['row_id']) ? $value['row_id'] : 'id';
|
||||
$row_modified = $value['row_modified'];
|
||||
$row_id = $value['row_id'] ?? 'id';
|
||||
$row_modified = $value['row_modified'] ?? null;
|
||||
|
||||
foreach($rows as $n => $row)
|
||||
{
|
||||
@ -384,12 +384,12 @@ class Nextmatch extends Etemplate\Widget
|
||||
if (is_int($n) && $row)
|
||||
{
|
||||
if (!isset($row[$row_id])) unset($row_id); // unset default row_id of 'id', if not used
|
||||
if (!isset($row[$row_modified])) unset($row_modified);
|
||||
if (empty($row[$row_modified])) unset($row_modified);
|
||||
|
||||
$id = $row_id ? $row[$row_id] : $n;
|
||||
$result['order'][] = $id;
|
||||
|
||||
$modified = $row[$row_modified];
|
||||
$modified = $row[$row_modified] ?? null;
|
||||
if (isset($modified) && !(is_int($modified) || is_string($modified) && is_numeric($modified)))
|
||||
{
|
||||
$modified = Api\DateTime::to(str_replace('Z', '', $modified), 'ts');
|
||||
@ -497,10 +497,10 @@ class Nextmatch extends Etemplate\Widget
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if($value_in[$key] == $value[$key]) continue;
|
||||
if (($value_in[$key]??null) == ($value[$key]??null)) continue;
|
||||
|
||||
// These keys we don't send row data back, as they cause a partial reload
|
||||
if(in_array($key, array('template'))) $no_rows = true;
|
||||
if (in_array($key, array('template'))) $no_rows = true;
|
||||
|
||||
// Actions still need extra handling
|
||||
if($key == 'actions' && !isset($value['actions'][0]))
|
||||
@ -626,7 +626,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
), array(), true); // true = no permission check
|
||||
|
||||
// if we have a nextmatch widget, find the repeating row
|
||||
if ($widget && $widget->attrs['template'])
|
||||
if ($widget && !empty($widget->attrs['template']))
|
||||
{
|
||||
$row_template = $widget->getElementById($widget->attrs['template']);
|
||||
if(!$row_template)
|
||||
@ -642,12 +642,12 @@ class Nextmatch extends Etemplate\Widget
|
||||
if($child->type == 'row') $repeating_row = $child;
|
||||
}
|
||||
}
|
||||
// otherwise we might get stoped by max_excutiontime
|
||||
// otherwise, we might get stopped by max_excutiontime
|
||||
if ($total > 200) @set_time_limit(0);
|
||||
|
||||
$is_parent = $value['is_parent'];
|
||||
$is_parent_value = $value['is_parent_value'];
|
||||
$parent_id = $value['parent_id'];
|
||||
$is_parent = $value['is_parent'] ?? null;
|
||||
$is_parent_value = $value['is_parent_value'] ?? null;
|
||||
$parent_id = $value['parent_id'] ?? null;
|
||||
|
||||
// remove empty rows required by old etemplate to compensate for header rows
|
||||
$first = $total ? null : 0;
|
||||
@ -658,14 +658,14 @@ class Nextmatch extends Etemplate\Widget
|
||||
{
|
||||
if (is_null($first)) $first = $n;
|
||||
|
||||
if ($row[$is_parent]) // if app supports parent_id / hierarchy, set parent_id and is_parent
|
||||
if (!empty($row[$is_parent])) // if app supports parent_id / hierarchy, set parent_id and is_parent
|
||||
{
|
||||
$row['is_parent'] = isset($is_parent_value) ?
|
||||
$row[$is_parent] == $is_parent_value : (boolean)$row[$is_parent];
|
||||
$row['parent_id'] = $row[$parent_id]; // seems NOT used on client!
|
||||
$row['parent_id'] = $row[$parent_id] ?? null; // seems NOT used on client!
|
||||
}
|
||||
// run beforeSendToClient methods of widgets in row on row-data
|
||||
if($repeating_row)
|
||||
if (!empty($repeating_row))
|
||||
{
|
||||
// Change anything by widget for each row ($row set to 1)
|
||||
$_row = array(1 => &$row);
|
||||
@ -894,7 +894,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
if ($default_attrs) $action += $default_attrs;
|
||||
|
||||
// Add 'Select All' after first group
|
||||
if ($first_level && $group !== false && $action['group'] != $group && !$egw_actions[$prefix.'select_all'])
|
||||
if ($first_level && $group !== false && $action['group'] != $group && empty($egw_actions[$prefix.'select_all']))
|
||||
{
|
||||
|
||||
$egw_actions[$prefix.'select_all'] = array(
|
||||
@ -911,7 +911,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
);
|
||||
$action_links[] = $prefix.'select_all';
|
||||
}
|
||||
$group = $action['group'];
|
||||
$group = $action['group'] ?? 0;
|
||||
|
||||
if (!$first_level && $n == $max_length && count($actions) > $max_length)
|
||||
{
|
||||
@ -941,29 +941,29 @@ class Nextmatch extends Etemplate\Widget
|
||||
}
|
||||
|
||||
// add all first level popup actions plus ones with enabled = 'javaScript:...' to action_links
|
||||
if ((!isset($action['type']) || in_array($action['type'],array('popup','drag','drop'))) && // popup is the default
|
||||
($first_level || substr($action['enabled'],0,11) == 'javaScript:'))
|
||||
if ((!isset($action['type']) || in_array($action['type'], array('popup','drag','drop'))) && // popup is the default
|
||||
($first_level || isset($action['enabled']) && substr($action['enabled'],0,11) === 'javaScript:'))
|
||||
{
|
||||
$action_links[] = $prefix.$id;
|
||||
}
|
||||
|
||||
// add sub-menues
|
||||
if ($action['children'])
|
||||
// add sub-menus
|
||||
if (!empty($action['children']))
|
||||
{
|
||||
static $inherit_attrs = array('url','popup','nm_action','onExecute','type','egw_open','allowOnMultiple','confirm','confirm_multiple');
|
||||
$inherit_keys = array_flip($inherit_attrs);
|
||||
$action['children'] = self::egw_actions($action['children'], $template_name, $action['prefix'], $action_links, $max_length,
|
||||
$action['children'] = self::egw_actions($action['children'], $template_name, $action['prefix'] ?? '', $action_links, $max_length,
|
||||
array_intersect_key($action, $inherit_keys));
|
||||
|
||||
unset($action['prefix']);
|
||||
|
||||
// Allow default actions to keep their onExecute
|
||||
if($action['default']) unset($inherit_keys['onExecute']);
|
||||
if (!empty($action['default'])) unset($inherit_keys['onExecute']);
|
||||
$action = array_diff_key($action, $inherit_keys);
|
||||
}
|
||||
|
||||
// link or popup action
|
||||
if ($action['url'])
|
||||
if (!empty($action['url']))
|
||||
{
|
||||
$action['url'] = Api\Framework::link('/index.php',str_replace('$action',$id,$action['url']));
|
||||
if ($action['popup'])
|
||||
@ -984,7 +984,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($action['egw_open'])
|
||||
if (!empty($action['egw_open']))
|
||||
{
|
||||
$action['data']['nm_action'] = 'egw_open';
|
||||
}
|
||||
@ -997,10 +997,10 @@ class Nextmatch extends Etemplate\Widget
|
||||
// Make sure select all is in a group by itself
|
||||
foreach($egw_actions as $id => &$_action)
|
||||
{
|
||||
if($id == $prefix . 'select_all') continue;
|
||||
if($_action['group'] >= $egw_actions[$prefix.'select_all']['group'] )
|
||||
if ($id == $prefix . 'select_all') continue;
|
||||
if (($_action['group'] ?? 0) >= (($egw_actions[$prefix.'select_all'] ?? [])['group'] ?? 0))
|
||||
{
|
||||
$egw_actions[$id]['group']+=1;
|
||||
$egw_actions[$id]['group'] = ($egw_actions[$id]['group'] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
//echo "egw_actions="; _debug_array($egw_actions);
|
||||
@ -1044,7 +1044,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
'no_lang' => true,
|
||||
);
|
||||
// add category icon
|
||||
if (is_array($cat['data']) && $cat['data']['icon'] && file_exists(EGW_SERVER_ROOT.self::ICON_PATH.'/'.basename($cat['data']['icon'])))
|
||||
if (is_array($cat['data']) && !empty($cat['data']['icon']) && file_exists(EGW_SERVER_ROOT.self::ICON_PATH.'/'.basename($cat['data']['icon'])))
|
||||
{
|
||||
$cat_actions[$cat['id']]['iconUrl'] = $GLOBALS['egw_info']['server']['webserver_url'].self::ICON_PATH.'/'.$cat['data']['icon'];
|
||||
}
|
||||
@ -1083,7 +1083,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
'prefix' => $prefix,
|
||||
);
|
||||
// add category icon
|
||||
if ($cat['data']['icon'] && file_exists(EGW_SERVER_ROOT.self::ICON_PATH.'/'.basename($cat['data']['icon'])))
|
||||
if (!empty($cat['data']['icon']) && file_exists(EGW_SERVER_ROOT.self::ICON_PATH.'/'.basename($cat['data']['icon'])))
|
||||
{
|
||||
$cat_actions[$cat['id']]['iconUrl'] = $GLOBALS['egw_info']['server']['webserver_url'].self::ICON_PATH.'/'.$cat['data']['icon'];
|
||||
}
|
||||
@ -1222,7 +1222,7 @@ class Nextmatch extends Etemplate\Widget
|
||||
// Run on all the sub-templates
|
||||
foreach(array('template', 'header_left', 'header_right', 'header_row') as $sub_template)
|
||||
{
|
||||
if($this->attrs[$sub_template])
|
||||
if (!empty($this->attrs[$sub_template]))
|
||||
{
|
||||
$row_template = Template::instance($this->attrs[$sub_template]);
|
||||
$row_template->run($method_name, $params, $respect_disabled);
|
||||
@ -1230,14 +1230,6 @@ class Nextmatch extends Etemplate\Widget
|
||||
}
|
||||
}
|
||||
$params[0] = $old_param0;
|
||||
|
||||
// Prevent troublesome keys from breaking the nextmatch
|
||||
// TODO: Figure out where these come from
|
||||
foreach(array('$row','${row}', '$', '0','1','2') as $key)
|
||||
{
|
||||
if(is_array(self::$request->content[$cname])) unset(self::$request->content[$cname][$key]);
|
||||
if(is_array(self::$request->preserve[$cname])) unset(self::$request->preserve[$cname][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,7 +64,9 @@ class Placeholder extends Etemplate\Widget
|
||||
|
||||
if(is_null($apps))
|
||||
{
|
||||
$apps = ['addressbook', 'user'];
|
||||
$apps = ['addressbook', 'user', 'general'] +
|
||||
// We use linking for preview, so limit to apps that support links
|
||||
array_keys(Api\Link::app_list('query'));
|
||||
}
|
||||
|
||||
foreach($apps as $appname)
|
||||
@ -75,31 +77,53 @@ class Placeholder extends Etemplate\Widget
|
||||
case 'user':
|
||||
$list = $merge->get_user_placeholder_list();
|
||||
break;
|
||||
case 'general':
|
||||
$list = $merge->get_common_placeholder_list();
|
||||
break;
|
||||
default:
|
||||
$list = $merge->get_placeholder_list();
|
||||
if(get_class($merge) === 'EGroupware\Api\Contacts\Merge' && $appname !== 'addressbook' || $placeholders[$appname])
|
||||
{
|
||||
// Looks like app doesn't support merging
|
||||
continue 2;
|
||||
}
|
||||
$list = method_exists($merge, 'get_placeholder_list') ? $merge->get_placeholder_list() : [];
|
||||
break;
|
||||
}
|
||||
if(!is_null($group))
|
||||
if(!is_null($group) && is_array($list))
|
||||
{
|
||||
$list = array_intersect_key($list, $group);
|
||||
}
|
||||
$placeholders[$appname] = $list;
|
||||
// Remove if empty
|
||||
foreach($list as $p_group => $p_list)
|
||||
{
|
||||
if(count($p_list) == 0)
|
||||
{
|
||||
unset($list[$p_group]);
|
||||
}
|
||||
}
|
||||
|
||||
if($list)
|
||||
{
|
||||
$placeholders[$appname] = $list;
|
||||
}
|
||||
}
|
||||
|
||||
$response = Api\Json\Response::get();
|
||||
$response->data($placeholders);
|
||||
}
|
||||
|
||||
public function ajax_fill_placeholders($app, $content, $entry)
|
||||
public function ajax_fill_placeholders($content, $entry)
|
||||
{
|
||||
$merge = Api\Storage\Merge::get_app_class($app);
|
||||
$merge = Api\Storage\Merge::get_app_class($entry['app']);
|
||||
$err = "";
|
||||
|
||||
switch($app)
|
||||
switch($entry['app'])
|
||||
{
|
||||
case 'addressbook':
|
||||
case 'user':
|
||||
$entry = ['id' => $GLOBALS['egw_info']['user']['person_id']];
|
||||
// fall through
|
||||
default:
|
||||
$merged = $merge->merge_string($content, [$entry], $err, 'text/plain');
|
||||
$merged = $merge->merge_string($content, [$entry['id']], $err, 'text/plain');
|
||||
}
|
||||
$response = Api\Json\Response::get();
|
||||
$response->data($merged);
|
||||
|
@ -496,17 +496,10 @@ abstract class Framework extends Framework\Extra
|
||||
{
|
||||
$lang_code = $GLOBALS['egw_info']['user']['preferences']['common']['lang'];
|
||||
}
|
||||
// IE specific fixes
|
||||
if (Header\UserAgent::type() == 'msie')
|
||||
{
|
||||
// tell IE to use it's own mode, not old compatibility modes (set eg. via group policy for all intranet sites)
|
||||
// has to be before any other header tags, but meta and title!!!
|
||||
$pngfix = '<meta http-equiv="X-UA-Compatible" content="IE=edge" />'."\n";
|
||||
}
|
||||
|
||||
$app = $GLOBALS['egw_info']['flags']['currentapp'];
|
||||
$app_title = isset($GLOBALS['egw_info']['apps'][$app]) ? $GLOBALS['egw_info']['apps'][$app]['title'] : lang($app);
|
||||
$app_header = $GLOBALS['egw_info']['flags']['app_header'] ? $GLOBALS['egw_info']['flags']['app_header'] : $app_title;
|
||||
$app_header = $GLOBALS['egw_info']['flags']['app_header'] ?? $app_title;
|
||||
$site_title = strip_tags($GLOBALS['egw_info']['server']['site_title'].' ['.($app_header ? $app_header : $app_title).']');
|
||||
|
||||
// send appheader to clientside
|
||||
@ -516,7 +509,7 @@ abstract class Framework extends Framework\Extra
|
||||
|
||||
$var['favicon_file'] = self::get_login_logo_or_bg_url('favicon_file', 'favicon.ico');
|
||||
|
||||
if ($GLOBALS['egw_info']['flags']['include_wz_tooltip'] &&
|
||||
if (!empty($GLOBALS['egw_info']['flags']['include_wz_tooltip']) &&
|
||||
file_exists(EGW_SERVER_ROOT.($wz_tooltip = '/phpgwapi/js/wz_tooltip/wz_tooltip.js')))
|
||||
{
|
||||
$include_wz_tooltip = '<script src="'.$GLOBALS['egw_info']['server']['webserver_url'].
|
||||
@ -525,7 +518,6 @@ abstract class Framework extends Framework\Extra
|
||||
return $this->_get_css()+array(
|
||||
'img_icon' => $var['favicon_file'],
|
||||
'img_shortcut' => $var['favicon_file'],
|
||||
'pngfix' => $pngfix,
|
||||
'lang_code' => $lang_code,
|
||||
'charset' => Translation::charset(),
|
||||
'website_title' => $site_title,
|
||||
@ -533,7 +525,7 @@ abstract class Framework extends Framework\Extra
|
||||
'java_script' => self::_get_js($extra),
|
||||
'meta_robots' => $robots,
|
||||
'dir_code' => lang('language_direction_rtl') != 'rtl' ? '' : ' dir="rtl"',
|
||||
'include_wz_tooltip'=> $include_wz_tooltip,
|
||||
'include_wz_tooltip'=> $include_wz_tooltip ?? '',
|
||||
'webserver_url' => $GLOBALS['egw_info']['server']['webserver_url'],
|
||||
'darkmode' => !empty(Cache::getSession('api','darkmode')) ?? $GLOBALS['egw_info']['user']['preferences']['common']['darkmode']
|
||||
);
|
||||
@ -602,12 +594,12 @@ abstract class Framework extends Framework\Extra
|
||||
*/
|
||||
static function get_login_logo_or_bg_url ($type, $find_type)
|
||||
{
|
||||
$url = is_array($GLOBALS['egw_info']['server'][$type]) ?
|
||||
$url = !empty($GLOBALS['egw_info']['server'][$type]) && is_array($GLOBALS['egw_info']['server'][$type]) ?
|
||||
$GLOBALS['egw_info']['server'][$type][0] :
|
||||
$GLOBALS['egw_info']['server'][$type];
|
||||
$GLOBALS['egw_info']['server'][$type] ?? null;
|
||||
|
||||
if (substr($url, 0, 4) == 'http' ||
|
||||
$url[0] == '/')
|
||||
if (substr($url, 0, 4) === 'http' ||
|
||||
!empty($url) && $url[0] === '/')
|
||||
{
|
||||
return $url;
|
||||
}
|
||||
@ -801,7 +793,7 @@ abstract class Framework extends Framework\Extra
|
||||
$index = '/index.php?menuaction='.$data['index'];
|
||||
}
|
||||
}
|
||||
return self::link($index,$GLOBALS['egw_info']['flags']['params'][$app]);
|
||||
return self::link($index, $GLOBALS['egw_info']['flags']['params'][$app] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -982,7 +974,7 @@ abstract class Framework extends Framework\Extra
|
||||
{
|
||||
if (file_exists(EGW_SERVER_ROOT.$theme_css)) break;
|
||||
}
|
||||
$debug_minify = $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
|
||||
$debug_minify = !empty($GLOBALS['egw_info']['server']['debug_minify']) && $GLOBALS['egw_info']['server']['debug_minify'] === 'True';
|
||||
if (!$debug_minify && file_exists(EGW_SERVER_ROOT.($theme_min_css = str_replace('.css', '.min.css', $theme_css))))
|
||||
{
|
||||
//error_log(__METHOD__."() Framework\CssIncludes::get()=".array2string(Framework\CssIncludes::get()));
|
||||
@ -1110,8 +1102,7 @@ abstract class Framework extends Framework\Extra
|
||||
if(@isset($_GET['menuaction']))
|
||||
{
|
||||
list(, $class) = explode('.',$_GET['menuaction']);
|
||||
if(is_array($GLOBALS[$class]->public_functions) &&
|
||||
$GLOBALS[$class]->public_functions['java_script'])
|
||||
if (!empty($GLOBALS[$class]->public_functions['java_script']))
|
||||
{
|
||||
$java_script .= $GLOBALS[$class]->java_script();
|
||||
}
|
||||
@ -1578,8 +1569,8 @@ abstract class Framework extends Framework\Extra
|
||||
foreach(Framework\CssIncludes::get() as $path)
|
||||
{
|
||||
unset($query);
|
||||
list($path,$query) = explode('?',$path,2);
|
||||
$path .= '?'. ($query ? $query : filemtime(EGW_SERVER_ROOT.$path));
|
||||
list($path,$query) = explode('?', $path,2)+[null,null];
|
||||
$path .= '?'. ($query ?? filemtime(EGW_SERVER_ROOT.$path));
|
||||
$response->includeCSS($GLOBALS['egw_info']['server']['webserver_url'].$path);
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ class Bundle
|
||||
}
|
||||
elseif (in_array($file, ['/api/js/jsapi.min.js', '/vendor/bower-asset/jquery/dist/jquery.min.js','/vendor/bower-asset/jquery/dist/jquery.js']))
|
||||
{
|
||||
error_log(function_backtrace()); // no NOT include
|
||||
//error_log(function_backtrace()); // no NOT include
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -63,8 +63,8 @@ class Hooks
|
||||
$location = is_array($args) ? (isset($args['hook_location']) ? $args['hook_location'] : $args['location']) : $args;
|
||||
|
||||
if (!isset(self::$locations)) self::read();
|
||||
if (empty(self::$locations[$location])) return []; // not a single app implements that hook
|
||||
$hooks = self::$locations[$location];
|
||||
if (!isset($hooks) || empty($hooks)) return array(); // not a single app implements that hook
|
||||
|
||||
$apps = array_keys($hooks);
|
||||
if (!$no_permission_check)
|
||||
@ -115,7 +115,7 @@ class Hooks
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
foreach((array)self::$locations[$location][$appname] as $hook)
|
||||
foreach(self::$locations[$location][$appname] ?? [] as $hook)
|
||||
{
|
||||
try {
|
||||
// old style file hook
|
||||
@ -130,7 +130,7 @@ class Hooks
|
||||
return true;
|
||||
}
|
||||
|
||||
list($class, $method) = explode('::', $hook);
|
||||
list($class, $method) = explode('::', $hook)+[null,null];
|
||||
|
||||
// static method of an autoloadable class
|
||||
if (isset($method) && class_exists($class))
|
||||
|
@ -391,7 +391,7 @@ function hl_email_tag_transform($element, $attribute_array=0)
|
||||
// $GLOBALS['egw_info']['user']['preferences']['mail']['allowExternalIMGs'] ? '' : 'match' => '/^cid:.*/'),
|
||||
if (isset($attribute_array['src']))
|
||||
{
|
||||
if (!(strlen($attribute_array['src'])>4 && strlen($attribute_array['src']<400)))
|
||||
if (!(strlen($attribute_array['src'])>4 && strlen($attribute_array['src'])<400))
|
||||
{
|
||||
$attribute_array['alt']= $attribute_array['alt'].' [blocked (reason: url length):'.$attribute_array['src'].']';
|
||||
if (!isset($attribute_array['title'])) $attribute_array['title']=$attribute_array['alt'];
|
||||
|
@ -762,7 +762,7 @@ class Link extends Link\Storage
|
||||
if ($must_support && !isset($reg[$must_support])) continue;
|
||||
|
||||
list($app) = explode('-', $type);
|
||||
if ($GLOBALS['egw_info']['user']['apps'][$app])
|
||||
if (!empty($GLOBALS['egw_info']['user']['apps'][$app]))
|
||||
{
|
||||
$apps[$type] = lang(self::get_registry($type, 'name'));
|
||||
}
|
||||
@ -1132,7 +1132,7 @@ class Link extends Link\Storage
|
||||
*/
|
||||
static function get_registry($app, $name, $url_id=false)
|
||||
{
|
||||
$reg = self::$app_register[$app];
|
||||
$reg = self::$app_register[$app] ?? null;
|
||||
|
||||
if (!isset($reg)) return false;
|
||||
|
||||
@ -1181,7 +1181,7 @@ class Link extends Link\Storage
|
||||
return $str;
|
||||
}
|
||||
|
||||
return isset($reg) ? $reg[$name] : false;
|
||||
return $reg[$name] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2950,7 +2950,7 @@ class Mail
|
||||
// Trigger examination of namespace to retrieve
|
||||
// folders located in other and shared; needed only for some servers
|
||||
if (is_null(self::$mailConfig)) self::$mailConfig = Config::read('mail');
|
||||
if (self::$mailConfig['examineNamespace'])
|
||||
if (!empty(self::$mailConfig['examineNamespace']))
|
||||
{
|
||||
$prefixes=array();
|
||||
if (is_array($nameSpace))
|
||||
@ -3014,7 +3014,7 @@ class Mail
|
||||
$subFolders = $this->icServer->getMailboxes($node['MAILBOX'].$node['delimiter'], $_search, true);
|
||||
}
|
||||
|
||||
if (is_array($mainFolder['INBOX']))
|
||||
if (isset($mainFolder['INBOX']) && is_array($mainFolder['INBOX']))
|
||||
{
|
||||
// Array container of auto folders
|
||||
$aFolders = array();
|
||||
@ -3180,7 +3180,7 @@ class Mail
|
||||
{
|
||||
foreach(array('others','shared') as $type)
|
||||
{
|
||||
if ($nameSpace[$type]['prefix_present']&&$nameSpace[$type]['prefix'])
|
||||
if (!empty($nameSpace[$type]['prefix_present']) && !empty($nameSpace[$type]['prefix']))
|
||||
{
|
||||
if (substr($k,0,strlen($nameSpace[$type]['prefix']))==$nameSpace[$type]['prefix']||
|
||||
substr($k,0,strlen($nameSpace[$type]['prefix'])-strlen($nameSpace[$type]['delimiter']))==substr($nameSpace[$type]['prefix'],0,strlen($nameSpace[$type]['delimiter'])*-1)) {
|
||||
@ -3211,7 +3211,7 @@ class Mail
|
||||
}
|
||||
//error_log(__METHOD__.__LINE__.array2string($autoFolderObjects));
|
||||
if (!$isGoogleMail) {
|
||||
$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)$typeFolderObject['others'],(array)$typeFolderObject['shared']);
|
||||
$folders = array_merge($inboxFolderObject,$autoFolderObjects,(array)$inboxSubFolderObjects,(array)$folders,(array)$typeFolderObject['others'] ?? [],(array)$typeFolderObject['shared'] ?? []);
|
||||
} else {
|
||||
// avoid calling sortByAutoFolder as it is not regarding subfolders
|
||||
$gAutoFolderObjectsTmp = $googleAutoFolderObjects;
|
||||
@ -3910,7 +3910,7 @@ class Mail
|
||||
{
|
||||
//error_log(__METHOD__.' ('.__LINE__.') '.'->'.array2string($_messageUID).','.array2string($_folder).', '.$_forceDeleteMethod);
|
||||
$oldMailbox = '';
|
||||
if (is_null($_folder) || empty($_folder)) $_folder = $this->sessionData['mailbox'];
|
||||
if (empty($_folder) && !empty($this->sessionData['mailbox'])) $_folder = $this->sessionData['mailbox'];
|
||||
if (empty($_messageUID))
|
||||
{
|
||||
if (self::$debug) error_log(__METHOD__." no messages Message(s): ".implode(',',$_messageUID));
|
||||
@ -4210,7 +4210,10 @@ class Mail
|
||||
}
|
||||
if ($folder instanceof Horde_Imap_Client_Mailbox) $_folder = $folder->utf8;
|
||||
//error_log(__METHOD__.__LINE__.'#'.$this->icServer->ImapServerId.'#'.array2string($_folder).'#');
|
||||
self::$folderStatusCache[$this->icServer->ImapServerId][(!empty($_folder)?$_folder: $this->sessionData['mailbox'])]['uidValidity'] = 0;
|
||||
if (isset(self::$folderStatusCache[$this->icServer->ImapServerId][($_folder??$this->sessionData['mailbox'])]['uidValidity']))
|
||||
{
|
||||
self::$folderStatusCache[$this->icServer->ImapServerId][($_folder??$this->sessionData['mailbox'])]['uidValidity'] = 0;
|
||||
}
|
||||
|
||||
//error_log(__METHOD__.' ('.__LINE__.') '.'->' .$_flag." ".array2string($_messageUID).",".($_folder?$_folder:$this->sessionData['mailbox']));
|
||||
return true; // as we do not catch/examine setFlags returnValue
|
||||
|
@ -313,7 +313,7 @@ class Account implements \ArrayAccess
|
||||
try {
|
||||
if ($this->acc_imap_type != __NAMESPACE__.'\\Imap' &&
|
||||
// do NOT query IMAP server, if we are in forward-only delivery-mode, imap will NOT answer, as switched off for that account!
|
||||
$this->params['deliveryMode'] != Smtp::FORWARD_ONLY && $need_quota &&
|
||||
($this->params['deliveryMode'] ?? null) != Smtp::FORWARD_ONLY && $need_quota &&
|
||||
$this->imapServer($this->user) && is_a($this->imapServer, __NAMESPACE__.'\\Imap') &&
|
||||
($data = $this->imapServer->getUserData($GLOBALS['egw']->accounts->id2name($this->user))))
|
||||
{
|
||||
@ -335,7 +335,7 @@ class Account implements \ArrayAccess
|
||||
}
|
||||
$this->params += array_fill_keys(self::$user_data, null); // make sure all keys exist now
|
||||
|
||||
return (array)$data + (array)$smtp_data;
|
||||
return ($data ?? []) + ($smtp_data ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -454,10 +454,10 @@ class Account implements \ArrayAccess
|
||||
$this->smtpServer->host = 'tls://'.$this->smtpServer->host;
|
||||
}
|
||||
$this->smtpServer->smtpAuth = !empty($this->params['acc_smtp_username']);
|
||||
$this->smtpServer->username = $this->params['acc_smtp_username'];
|
||||
$this->smtpServer->password = $this->params['acc_smtp_password'];
|
||||
$this->smtpServer->username = $this->params['acc_smtp_username'] ?? null;
|
||||
$this->smtpServer->password = $this->params['acc_smtp_password'] ?? null;
|
||||
$this->smtpServer->defaultDomain = $this->params['acc_domain'];
|
||||
$this->smtpServer->loginType = $this->params['acc_imap_login_type'];
|
||||
$this->smtpServer->loginType = $this->params['acc_imap_login_type'] ?? null;
|
||||
}
|
||||
return $this->smtpServer;
|
||||
}
|
||||
@ -676,7 +676,7 @@ class Account implements \ArrayAccess
|
||||
$to_replace = array();
|
||||
foreach($fields as $name)
|
||||
{
|
||||
if (strpos($identity[$name], '{{') !== false || strpos($identity[$name], '$$') !== false)
|
||||
if (!empty($identity[$name]) && (strpos($identity[$name], '{{') !== false || strpos($identity[$name], '$$') !== false))
|
||||
{
|
||||
$to_replace[$name] = $identity[$name];
|
||||
}
|
||||
@ -781,7 +781,7 @@ class Account implements \ArrayAccess
|
||||
'account_id' => self::is_multiple($identity) ? 0 :
|
||||
(is_array($identity['account_id']) ? $identity['account_id'][0] : $identity['account_id']),
|
||||
);
|
||||
if ($identity['ident_id'] > 0)
|
||||
if ($identity['ident_id'] !== 'new' && (int)$identity['ident_id'] > 0)
|
||||
{
|
||||
self::$db->update(self::IDENTITIES_TABLE, $data, array(
|
||||
'ident_id' => $identity['ident_id'],
|
||||
@ -837,7 +837,7 @@ class Account implements \ArrayAccess
|
||||
// let getUserData "know" if we are interested in quota (requiring IMAP login) or not
|
||||
$this->getUserData(substr($name, 0, 5) === 'quota');
|
||||
}
|
||||
return $this->params[$name];
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1196,7 +1196,7 @@ class Account implements \ArrayAccess
|
||||
|
||||
// store identity
|
||||
$new_ident_id = self::save_identity($data);
|
||||
if (!($data['ident_id'] > 0))
|
||||
if ($data['ident_id'] === 'new' || empty($data['ident_id']))
|
||||
{
|
||||
$data['ident_id'] = $new_ident_id;
|
||||
self::$db->update(self::TABLE, array(
|
||||
@ -1578,7 +1578,7 @@ class Account implements \ArrayAccess
|
||||
'ident_realname' => $account['ident_realname'],
|
||||
'ident_org' => $account['ident_org'],
|
||||
'ident_email' => $account['ident_email'],
|
||||
'acc_name' => $account['acc_name'],
|
||||
'acc_name' => $account['acc_name'] ?? null,
|
||||
'acc_imap_username' => $account['acc_imap_username'],
|
||||
'acc_imap_logintype' => $account['acc_imap_logintype'],
|
||||
'acc_domain' => $account['acc_domain'],
|
||||
@ -1605,7 +1605,7 @@ class Account implements \ArrayAccess
|
||||
}
|
||||
}
|
||||
// fill an empty ident_realname or ident_email of current user with data from user account
|
||||
if ($replace_placeholders && (!isset($account_id) || $account_id == $GLOBALS['egw_info']['user']['acount_id']))
|
||||
if ($replace_placeholders && (!isset($account_id) || $account_id == $GLOBALS['egw_info']['user']['account_id']))
|
||||
{
|
||||
if (empty($account['ident_realname'])) $account['ident_realname'] = $GLOBALS['egw_info']['user']['account_fullname'];
|
||||
if (empty($account['ident_email'])) $account['ident_email'] = $GLOBALS['egw_info']['user']['account_email'];
|
||||
|
@ -87,7 +87,7 @@ class Notifications
|
||||
$account_specific = $account_id;
|
||||
}
|
||||
}
|
||||
$folders = (array)self::$cache[$acc_id][$account_specific];
|
||||
$folders = self::$cache[$acc_id][$account_specific] ?? [];
|
||||
if (!$return_empty_marker && $folders == array(null)) $folders = array();
|
||||
$result = array(
|
||||
'notify_folders' => $folders,
|
||||
|
@ -233,10 +233,23 @@ class Smtp
|
||||
* default use $this->loginType
|
||||
* @return string
|
||||
*/
|
||||
/*static*/ public function mailbox_addr($account,$domain=null,$mail_login_type=null)
|
||||
public function mailbox_addr($account, $domain=null, $mail_login_type=null)
|
||||
{
|
||||
return self::mailbox_address($account, $domain ?? $this->defaultDomain, $mail_login_type ?? $this->loginType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build mailbox address for given account and mail_addr_type
|
||||
*
|
||||
* If $account is an array (with values for keys account_(id|lid|email), it does NOT call accounts class
|
||||
*
|
||||
* @param int|array $account account_id or whole account array with values for keys
|
||||
* @param string $domain domain
|
||||
* @param string $mail_login_type=null standard(uid), vmailmgr(uid@domain), email or uidNumber
|
||||
* @return string
|
||||
*/
|
||||
static public function mailbox_address($account, string $domain, string $mail_login_type=null)
|
||||
{
|
||||
if (is_null($domain)) $domain = $this->defaultDomain;
|
||||
if (is_null($mail_login_type)) $mail_login_type = $this->loginType;
|
||||
|
||||
switch($mail_login_type)
|
||||
{
|
||||
|
@ -340,7 +340,7 @@ class Sql extends Mail\Smtp
|
||||
}
|
||||
}
|
||||
|
||||
// let interesed parties know account was update
|
||||
// let interested parties know account was update
|
||||
Api\Hooks::process(array(
|
||||
'location' => 'mailaccount_userdata_updated',
|
||||
'account_id' => $_uidnumber,
|
||||
|
@ -196,7 +196,7 @@ class Preferences
|
||||
foreach((array)$ids as $id)
|
||||
{
|
||||
// if prefs are not returned, null or not an array, read them from db
|
||||
if (!isset($prefs[$id]) && !is_array($prefs[$id])) $db_read[] = $id;
|
||||
if (!isset($prefs[$id]) || !is_array($prefs[$id])) $db_read[] = $id;
|
||||
}
|
||||
if ($db_read)
|
||||
{
|
||||
@ -237,7 +237,7 @@ class Preferences
|
||||
$replace = $with = array();
|
||||
foreach($vals as $key => $val)
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__." replacing \$\$$key\$\$ with $val ");
|
||||
if (!empty($this->debug)) error_log(__METHOD__." replacing \$\$$key\$\$ with $val ");
|
||||
$replace[] = '$$'.$key.'$$';
|
||||
$with[] = $val;
|
||||
}
|
||||
@ -275,7 +275,7 @@ class Preferences
|
||||
*/
|
||||
function standard_substitutes()
|
||||
{
|
||||
if ($this->debug) error_log(__METHOD__." is called ");
|
||||
if (!empty($this->debug)) error_log(__METHOD__." is called ");
|
||||
if (!is_array(@$GLOBALS['egw_info']['user']['preferences']))
|
||||
{
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $this->data; // else no lang()
|
||||
@ -301,7 +301,7 @@ class Preferences
|
||||
'email' => lang('email-address of the user, eg. "%1"',$this->values['email']),
|
||||
'date' => lang('todays date, eg. "%1"',$this->values['date']),
|
||||
);
|
||||
if ($this->debug) error_log(__METHOD__.print_r($this->vars,true));
|
||||
if (!empty($this->debug)) error_log(__METHOD__.print_r($this->vars,true));
|
||||
// do the substituetion in the effective prefs (data)
|
||||
//
|
||||
foreach($this->data as $app => $data)
|
||||
@ -421,7 +421,7 @@ class Preferences
|
||||
default:
|
||||
foreach($values as $app => $vals)
|
||||
{
|
||||
$this->group[$app] = (array)$vals + (array)$this->group[$app];
|
||||
$this->group[$app] = (array)$vals + ($this->group[$app] ?? []);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -474,7 +474,7 @@ class Preferences
|
||||
}
|
||||
// setup the standard substitutes and substitutes the data in $this->data
|
||||
//
|
||||
if ($GLOBALS['egw_info']['flags']['load_translations'] !== false)
|
||||
if (!empty($GLOBALS['egw_info']['flags']['load_translations']))
|
||||
{
|
||||
$this->standard_substitutes();
|
||||
}
|
||||
|
@ -1363,7 +1363,7 @@ class Session
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($GLOBALS['egw_info']['server']['sessions_checkip'])
|
||||
if (!empty($GLOBALS['egw_info']['server']['sessions_checkip']))
|
||||
{
|
||||
if (strtoupper(substr(PHP_OS,0,3)) != 'WIN' && (!$GLOBALS['egw_info']['user']['session_ip'] ||
|
||||
$GLOBALS['egw_info']['user']['session_ip'] != $this->getuser_ip()))
|
||||
@ -1538,21 +1538,21 @@ class Session
|
||||
}
|
||||
|
||||
// check if the url already contains a query and ensure that vars is an array and all strings are in extravars
|
||||
list($ret_url,$othervars) = explode('?', $url, 2);
|
||||
if (strpos($ret_url=$url, '?') !== false) list($ret_url,$othervars) = explode('?', $url, 2)+[null,null];
|
||||
if ($extravars && is_array($extravars))
|
||||
{
|
||||
$vars += $extravars;
|
||||
$extravars = $othervars;
|
||||
}
|
||||
else
|
||||
elseif (!empty($othervars))
|
||||
{
|
||||
if ($othervars) $extravars .= ($extravars?'&':'').$othervars;
|
||||
$extravars .= ($extravars ? '&' : '') . $othervars;
|
||||
}
|
||||
|
||||
// parse extravars string into the vars array
|
||||
if ($extravars)
|
||||
if (!empty($extravars))
|
||||
{
|
||||
foreach(explode('&',$extravars) as $expr)
|
||||
foreach(explode('&', $extravars) as $expr)
|
||||
{
|
||||
list($var,$val) = explode('=', $expr,2);
|
||||
if (strpos($val,'%26') != false) $val = str_replace('%26','&',$val); // make sure to not double encode &
|
||||
@ -1720,7 +1720,7 @@ class Session
|
||||
{
|
||||
if (PHP_SAPI === "cli") return; // gives warnings and has no benefit
|
||||
|
||||
if ($GLOBALS['egw_info']['server']['cookiedomain'])
|
||||
if (!empty($GLOBALS['egw_info']['server']['cookiedomain']))
|
||||
{
|
||||
// Admin set domain, eg. .domain.com to allow egw.domain.com and www.domain.com
|
||||
self::$cookie_domain = $GLOBALS['egw_info']['server']['cookiedomain'];
|
||||
@ -1741,7 +1741,7 @@ class Session
|
||||
// setcookie dont likes domains without dots, leaving it empty, gets setcookie to fill the domain in
|
||||
self::$cookie_domain = '';
|
||||
}
|
||||
if (!$GLOBALS['egw_info']['server']['cookiepath'] ||
|
||||
if (empty($GLOBALS['egw_info']['server']['cookiepath']) ||
|
||||
!(self::$cookie_path = parse_url($GLOBALS['egw_info']['server']['webserver_url'],PHP_URL_PATH)))
|
||||
{
|
||||
self::$cookie_path = '/';
|
||||
@ -1851,7 +1851,7 @@ class Session
|
||||
private function update_dla($update_access_log=false)
|
||||
{
|
||||
// This way XML-RPC users aren't always listed as xmlrpc.php
|
||||
if (isset($_GET['menuaction']))
|
||||
if (isset($_GET['menuaction']) && strpos($_GET['menuaction'], '.ajax_exec.template.') !== false)
|
||||
{
|
||||
list(, $action) = explode('.ajax_exec.template.', $_GET['menuaction']);
|
||||
|
||||
|
@ -16,6 +16,7 @@ namespace EGroupware\Api\Storage;
|
||||
use DOMDocument;
|
||||
use EGroupware\Api;
|
||||
use EGroupware\Api\Vfs;
|
||||
use EGroupware\Collabora\Conversion;
|
||||
use EGroupware\Stylite;
|
||||
use tidy;
|
||||
use uiaccountsel;
|
||||
@ -31,6 +32,26 @@ use ZipArchive;
|
||||
*/
|
||||
abstract class Merge
|
||||
{
|
||||
/**
|
||||
* Preference, path where we will put the generated document
|
||||
*/
|
||||
const PREF_STORE_LOCATION = "merge_store_path";
|
||||
|
||||
/**
|
||||
* Preference, placeholders for creating the filename of the generated document
|
||||
*/
|
||||
const PREF_DOCUMENT_FILENAME = "document_download_name";
|
||||
|
||||
/**
|
||||
* List of placeholders
|
||||
*/
|
||||
const DOCUMENT_FILENAME_OPTIONS = [
|
||||
'$$document$$' => 'Template name',
|
||||
'$$link_title$$' => 'Entry link-title',
|
||||
'$$contact_title$$' => 'Contact link-title',
|
||||
'$$current_date$$' => 'Current date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Instance of the addressbook_bo class
|
||||
*
|
||||
@ -48,7 +69,12 @@ abstract class Merge
|
||||
/**
|
||||
* Fields that are to be treated as datetimes, when merged into spreadsheets
|
||||
*/
|
||||
var $date_fields = array();
|
||||
var $date_fields = [];
|
||||
|
||||
/**
|
||||
* Fields that are numeric, for special numeric handling
|
||||
*/
|
||||
protected $numeric_fields = [];
|
||||
|
||||
/**
|
||||
* Mimetype of document processed by merge
|
||||
@ -77,10 +103,10 @@ abstract class Merge
|
||||
*/
|
||||
public $export_limit;
|
||||
|
||||
|
||||
public $public_functions = array(
|
||||
"merge_entries" => true
|
||||
);
|
||||
|
||||
/**
|
||||
* Configuration for HTML Tidy to clean up any HTML content that is kept
|
||||
*/
|
||||
@ -237,47 +263,59 @@ abstract class Merge
|
||||
$replacements = array();
|
||||
foreach(array_keys($this->contacts->contact_fields) as $name)
|
||||
{
|
||||
$value = $contact[$name];
|
||||
$value = $contact[$name] ?? '';
|
||||
if(!$value)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
switch($name)
|
||||
{
|
||||
case 'created': case 'modified':
|
||||
if($value) $value = Api\DateTime::to($value);
|
||||
case 'created':
|
||||
case 'modified':
|
||||
if($value)
|
||||
{
|
||||
$value = Api\DateTime::to($value);
|
||||
}
|
||||
break;
|
||||
case 'bday':
|
||||
if ($value)
|
||||
if($value)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
$value = Api\DateTime::to($value, true);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
unset($e); // ignore exception caused by wrongly formatted date
|
||||
catch (\Exception $e)
|
||||
{
|
||||
unset($e); // ignore exception caused by wrongly formatted date
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'owner': case 'creator': case 'modifier':
|
||||
case 'owner':
|
||||
case 'creator':
|
||||
case 'modifier':
|
||||
$value = Api\Accounts::username($value);
|
||||
break;
|
||||
case 'cat_id':
|
||||
if ($value)
|
||||
if($value)
|
||||
{
|
||||
// if cat-tree is displayed, we return a full category path not just the name of the cat
|
||||
$use = $GLOBALS['egw_info']['server']['cat_tab'] == 'Tree' ? 'path' : 'name';
|
||||
$cats = array();
|
||||
foreach(is_array($value) ? $value : explode(',',$value) as $cat_id)
|
||||
foreach(is_array($value) ? $value : explode(',', $value) as $cat_id)
|
||||
{
|
||||
$cats[] = $GLOBALS['egw']->categories->id2name($cat_id,$use);
|
||||
$cats[] = $GLOBALS['egw']->categories->id2name($cat_id, $use);
|
||||
}
|
||||
$value = implode(', ',$cats);
|
||||
$value = implode(', ', $cats);
|
||||
}
|
||||
break;
|
||||
case 'jpegphoto': // returning a link might make more sense then the binary photo
|
||||
if ($contact['photo'])
|
||||
case 'jpegphoto': // returning a link might make more sense then the binary photo
|
||||
if($contact['photo'])
|
||||
{
|
||||
$value = Api\Framework::getUrl(Api\Framework::link('/index.php',$contact['photo']));
|
||||
$value = Api\Framework::getUrl(Api\Framework::link('/index.php', $contact['photo']));
|
||||
}
|
||||
break;
|
||||
case 'tel_prefer':
|
||||
if ($value && $contact[$value])
|
||||
if($value && $contact[$value])
|
||||
{
|
||||
$value = $contact[$value];
|
||||
}
|
||||
@ -354,7 +392,9 @@ abstract class Merge
|
||||
$cats[$cat_id] = array();
|
||||
}
|
||||
}
|
||||
foreach($cats as $main => $cat) {
|
||||
$replacements['$$'.($prefix ? $prefix.'/':'').'categories$$'] = '';
|
||||
foreach($cats as $main => $cat)
|
||||
{
|
||||
$replacements['$$'.($prefix ? $prefix.'/':'').'categories$$'] .= $GLOBALS['egw']->categories->id2name($main,'name')
|
||||
. (count($cat) > 0 ? ': ' : '') . implode(', ', $cats[$main]) . "\n";
|
||||
}
|
||||
@ -807,6 +847,7 @@ abstract class Merge
|
||||
*/
|
||||
public function &merge_string($_content,$ids,&$err,$mimetype,array $fix=null,$charset=null)
|
||||
{
|
||||
$ids = empty($ids) ? [] : (array)$ids;
|
||||
$matches = null;
|
||||
if ($mimetype == 'application/xml' &&
|
||||
preg_match('/'.preg_quote('<?mso-application progid="', '/').'([^"]+)'.preg_quote('"?>', '/').'/',substr($_content,0,200),$matches))
|
||||
@ -838,7 +879,7 @@ abstract class Merge
|
||||
$content = preg_replace(array_keys($fix),array_values($fix),$content);
|
||||
//die("<pre>".htmlspecialchars($content)."</pre>\n");
|
||||
}
|
||||
list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY); //get differt parts of document, seperatet by Pagerepeat
|
||||
list($contentstart,$contentrepeat,$contentend) = preg_split('/\$\$pagerepeat\$\$/',$content,-1, PREG_SPLIT_NO_EMPTY)+[null,null,null]; //get differt parts of document, seperatet by Pagerepeat
|
||||
if ($mimetype == 'text/plain' && $ids && count($ids) > 1)
|
||||
{
|
||||
// textdocuments are simple, they do not hold start and end, but they may have content before and after the $$pagerepeat$$ tag
|
||||
@ -882,11 +923,11 @@ abstract class Merge
|
||||
$contentstart .= '<w:body>';
|
||||
$contentend = '</w:body></w:document>';
|
||||
}
|
||||
list($Labelstart,$Labelrepeat,$Labeltend) = preg_split('/\$\$label\$\$/',$contentrepeat,-1, PREG_SPLIT_NO_EMPTY); //get the Lable content
|
||||
list($Labelstart,$Labelrepeat,$Labeltend) = preg_split('/\$\$label\$\$/',$contentrepeat,-1, PREG_SPLIT_NO_EMPTY)+[null,null,null]; //get the label content
|
||||
preg_match_all('/\$\$labelplacement\$\$/',$contentrepeat,$countlables, PREG_SPLIT_NO_EMPTY);
|
||||
$countlables = count($countlables[0]);
|
||||
preg_replace('/\$\$labelplacement\$\$/','',$Labelrepeat,1);
|
||||
if ($countlables > 1) $lableprint = true;
|
||||
$lableprint = $countlables > 1;
|
||||
if (count($ids) > 1 && !$contentrepeat)
|
||||
{
|
||||
$err = lang('for more than one contact in a document use the tag pagerepeat!');
|
||||
@ -937,7 +978,7 @@ abstract class Merge
|
||||
if ($contentrepeat) $content = $contentrepeat; //content to repeat
|
||||
if ($lableprint) $content = $Labelrepeat;
|
||||
|
||||
// generate replacements; if exeption is thrown, catch it set error message and return false
|
||||
// generate replacements; if exception is thrown, catch it set error message and return false
|
||||
try
|
||||
{
|
||||
if(!($replacements = $this->get_replacements($id,$content)))
|
||||
@ -956,10 +997,10 @@ abstract class Merge
|
||||
}
|
||||
if ($this->report_memory_usage) error_log(__METHOD__."() $n: $id ".Api\Vfs::hsize(memory_get_usage(true)));
|
||||
// some general replacements: current user, date and time
|
||||
if (strpos($content,'$$user/') !== null && ($user = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'person_id')))
|
||||
if(strpos($content, '$$user/') !== false && ($user = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'], 'person_id')))
|
||||
{
|
||||
$replacements += $this->contact_replacements($user,'user', false, $content);
|
||||
$replacements['$$user/primary_group$$'] = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'],'account_primary_group'));
|
||||
$replacements += $this->contact_replacements($user, 'user', false, $content);
|
||||
$replacements['$$user/primary_group$$'] = $GLOBALS['egw']->accounts->id2name($GLOBALS['egw']->accounts->id2name($GLOBALS['egw_info']['user']['account_id'], 'account_primary_group'));
|
||||
}
|
||||
$replacements['$$date$$'] = Api\DateTime::to('now',true);
|
||||
$replacements['$$datetime$$'] = Api\DateTime::to('now');
|
||||
@ -1131,7 +1172,7 @@ abstract class Merge
|
||||
{
|
||||
foreach($this->date_fields as $field)
|
||||
{
|
||||
if(($value = $replacements['$$'.$field.'$$']))
|
||||
if(($value = $replacements['$$'.$field.'$$'] ?? null))
|
||||
{
|
||||
$time = Api\DateTime::createFromFormat('+'.Api\DateTime::$user_dateformat.' '.Api\DateTime::$user_timeformat.'*', $value);
|
||||
$replacements['$$'.$field.'/date$$'] = $time ? $time->format(Api\DateTime::$user_dateformat) : '';
|
||||
@ -1254,7 +1295,8 @@ abstract class Merge
|
||||
// Look for numbers, set their value if needed
|
||||
if(property_exists($this,'numeric_fields') || count($names))
|
||||
{
|
||||
foreach((array)$this->numeric_fields as $fieldname) {
|
||||
foreach($this->numeric_fields as $fieldname)
|
||||
{
|
||||
$names[] = preg_quote($fieldname,'/');
|
||||
}
|
||||
$this->format_spreadsheet_numbers($content, $names, $mimetype.$mso_application_progid);
|
||||
@ -1347,7 +1389,8 @@ abstract class Merge
|
||||
*/
|
||||
protected function format_spreadsheet_numbers(&$content, $names, $mimetype)
|
||||
{
|
||||
foreach((array)$this->numeric_fields as $fieldname) {
|
||||
foreach($this->numeric_fields as $fieldname)
|
||||
{
|
||||
$names[] = preg_quote($fieldname,'/');
|
||||
}
|
||||
switch($mimetype)
|
||||
@ -1371,7 +1414,7 @@ abstract class Merge
|
||||
|
||||
break;
|
||||
}
|
||||
if($format && $names)
|
||||
if (!empty($format) && $names)
|
||||
{
|
||||
// Dealing with backtrack limit per AmigoJack 10-Jul-2010 comment on php.net preg-replace docs
|
||||
do {
|
||||
@ -1600,9 +1643,9 @@ abstract class Merge
|
||||
*/
|
||||
public static function get_app_class($appname)
|
||||
{
|
||||
if(class_exists($appname) && is_subclass_of($appname, 'EGroupware\\Api\\Storage\\Merge'))
|
||||
$classname = "{$appname}_merge";
|
||||
if(class_exists($classname, false) && is_subclass_of($classname, 'EGroupware\\Api\\Storage\\Merge'))
|
||||
{
|
||||
$classname = "{$appname}_merge";
|
||||
$document_merge = new $classname();
|
||||
}
|
||||
else
|
||||
@ -1630,14 +1673,9 @@ abstract class Merge
|
||||
|
||||
try
|
||||
{
|
||||
$classname = "{$app}_merge";
|
||||
if(!class_exists($classname))
|
||||
{
|
||||
return $replacements;
|
||||
}
|
||||
$class = new $classname();
|
||||
$method = $app.'_replacements';
|
||||
if(method_exists($class,$method))
|
||||
$class = $this->get_app_class($app);
|
||||
$method = $app . '_replacements';
|
||||
if(method_exists($class, $method))
|
||||
{
|
||||
$replacements = $class->$method($id, $prefix, $content);
|
||||
}
|
||||
@ -1654,6 +1692,30 @@ abstract class Merge
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix a placeholder, taking care of $$ or {{}} markers
|
||||
*
|
||||
* @param string $prefix Placeholder prefix
|
||||
* @param string $placeholder Placeholder, with or without {{...}} or $$...$$ markers
|
||||
* @param null|string $wrap "{" or "$" to add markers, omit to exclude markers
|
||||
* @return string
|
||||
*/
|
||||
protected function prefix($prefix, $placeholder, $wrap = null)
|
||||
{
|
||||
$marker = ['', ''];
|
||||
if($placeholder[0] == '{' && is_null($wrap) || $wrap[0] == '{')
|
||||
{
|
||||
$marker = ['{{', '}}'];
|
||||
}
|
||||
elseif($placeholder[0] == '$' && is_null($wrap) || $wrap[0] == '$')
|
||||
{
|
||||
$marker = ['$$', '$$'];
|
||||
}
|
||||
|
||||
$placeholder = str_replace(['{{', '}}', '$$'], '', $placeholder);
|
||||
return $marker[0] . ($prefix ? $prefix . '/' : '') . $placeholder . $marker[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process special flags, such as IF or NELF
|
||||
*
|
||||
@ -1780,13 +1842,12 @@ abstract class Merge
|
||||
if (strpos($param[0],'$$LETTERPREFIXCUSTOM') === 0)
|
||||
{ //sets a Letterprefix
|
||||
$replaceprefixsort = array();
|
||||
// ToDo Stefan: $contentstart is NOT defined here!!!
|
||||
$replaceprefix = explode(' ',substr($param[0],21,-2));
|
||||
foreach ($replaceprefix as $nameprefix)
|
||||
{
|
||||
if ($this->replacements['$$'.$nameprefix.'$$'] !='') $replaceprefixsort[] = $this->replacements['$$'.$nameprefix.'$$'];
|
||||
}
|
||||
$replace = implode($replaceprefixsort,' ');
|
||||
$replace = implode(' ', $replaceprefixsort);
|
||||
}
|
||||
return $replace;
|
||||
}
|
||||
@ -2092,7 +2153,6 @@ abstract class Merge
|
||||
$export_limit=null)
|
||||
{
|
||||
$documents = array();
|
||||
$editable_mimes = array();
|
||||
if ($export_limit == null) $export_limit = self::getExportLimit(); // check if there is a globalsetting
|
||||
|
||||
try {
|
||||
@ -2118,11 +2178,11 @@ abstract class Merge
|
||||
$file['path'] = $default_doc;
|
||||
}
|
||||
$documents['document'] = array(
|
||||
'icon' => Api\Vfs::mime_icon($file['mime']),
|
||||
'icon' => Api\Vfs::mime_icon($file['mime']),
|
||||
'caption' => Api\Vfs::decodePath(Api\Vfs::basename($default_doc)),
|
||||
'group' => 1,
|
||||
'postSubmit' => true, // download needs post submit (not Ajax) to work
|
||||
'group' => 1
|
||||
);
|
||||
self::document_editable_action($documents['document'], $file);
|
||||
if ($file['mime'] == 'message/rfc822')
|
||||
{
|
||||
self::document_mail_action($documents['document'], $file);
|
||||
@ -2177,13 +2237,6 @@ abstract class Merge
|
||||
}
|
||||
foreach($files as $file)
|
||||
{
|
||||
$edit_attributes = array(
|
||||
'menuaction' => $GLOBALS['egw_info']['flags']['currentapp'].'.'.get_called_class().'.merge_entries',
|
||||
'document' => $file['path'],
|
||||
'merge' => get_called_class(),
|
||||
'id' => '$id',
|
||||
'select_all' => '$select_all'
|
||||
);
|
||||
if (count($dircount) > 1)
|
||||
{
|
||||
$name_arr = explode('/', $file['name']);
|
||||
@ -2200,30 +2253,24 @@ abstract class Merge
|
||||
}
|
||||
switch($count)
|
||||
{
|
||||
case (count($name_arr)-1):
|
||||
$current_level[$prefix.$file['name']] = array(
|
||||
'icon' => Api\Vfs::mime_icon($file['mime']),
|
||||
'caption' => Api\Vfs::decodePath($name_arr[$count]),
|
||||
'group' => 2,
|
||||
'postSubmit' => true, // download needs post submit (not Ajax) to work,
|
||||
'target' => '_blank',
|
||||
'url' => urldecode(http_build_query($edit_attributes))
|
||||
);
|
||||
if ($file['mime'] == 'message/rfc822')
|
||||
case (count($name_arr) - 1):
|
||||
$current_level[$prefix . $file['name']];
|
||||
self::document_editable_action($current_level[$prefix . $file['name']], $file);
|
||||
if($file['mime'] == 'message/rfc822')
|
||||
{
|
||||
self::document_mail_action($current_level[$prefix.$file['name']], $file);
|
||||
self::document_mail_action($current_level[$prefix . $file['name']], $file);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(!is_array($current_level[$prefix.$name_arr[$count]]))
|
||||
if(!is_array($current_level[$prefix . $name_arr[$count]]))
|
||||
{
|
||||
// create parent folder
|
||||
$current_level[$prefix.$name_arr[$count]] = array(
|
||||
'icon' => 'phpgwapi/foldertree_folder',
|
||||
'caption' => Api\Vfs::decodePath($name_arr[$count]),
|
||||
'group' => 2,
|
||||
'children' => array(),
|
||||
$current_level[$prefix . $name_arr[$count]] = array(
|
||||
'icon' => 'phpgwapi/foldertree_folder',
|
||||
'caption' => Api\Vfs::decodePath($name_arr[$count]),
|
||||
'group' => 2,
|
||||
'children' => array(),
|
||||
);
|
||||
}
|
||||
break;
|
||||
@ -2232,50 +2279,47 @@ abstract class Merge
|
||||
}
|
||||
else if (count($files) >= self::SHOW_DOCS_BY_MIME_LIMIT)
|
||||
{
|
||||
if (!isset($documents[$file['mime']]))
|
||||
if(!isset($documents[$file['mime']]))
|
||||
{
|
||||
$documents[$file['mime']] = array(
|
||||
'icon' => Api\Vfs::mime_icon($file['mime']),
|
||||
'caption' => Api\MimeMagic::mime2label($file['mime']),
|
||||
'group' => 2,
|
||||
'icon' => Api\Vfs::mime_icon($file['mime']),
|
||||
'caption' => Api\MimeMagic::mime2label($file['mime']),
|
||||
'group' => 2,
|
||||
'children' => array(),
|
||||
);
|
||||
}
|
||||
$documents[$file['mime']]['children'][$prefix.$file['name']] = array(
|
||||
'caption' => Api\Vfs::decodePath($file['name']),
|
||||
'target' => '_blank',
|
||||
'postSubmit' => true, // download needs post submit (not Ajax) to work
|
||||
);
|
||||
$documents[$file['mime']]['children'][$prefix.$file['name']]['url'] = urldecode(http_build_query($edit_attributes));
|
||||
if ($file['mime'] == 'message/rfc822')
|
||||
$documents[$file['mime']]['children'][$prefix . $file['name']] = array();
|
||||
self::document_editable_action($documents[$file['mime']]['children'][$prefix . $file['name']], $file);
|
||||
if($file['mime'] == 'message/rfc822')
|
||||
{
|
||||
self::document_mail_action($documents[$file['mime']]['children'][$prefix.$file['name']], $file);
|
||||
self::document_mail_action($documents[$file['mime']]['children'][$prefix . $file['name']], $file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$documents[$prefix.$file['name']] = array(
|
||||
'icon' => Api\Vfs::mime_icon($file['mime']),
|
||||
'caption' => Api\Vfs::decodePath($file['name']),
|
||||
'group' => 2,
|
||||
'target' => '_blank'
|
||||
);
|
||||
$documents[$prefix.$file['name']]['url'] = urldecode(http_build_query($edit_attributes));
|
||||
if ($file['mime'] == 'message/rfc822')
|
||||
$documents[$prefix . $file['name']] = array();
|
||||
self::document_editable_action($documents[$prefix . $file['name']], $file);
|
||||
if($file['mime'] == 'message/rfc822')
|
||||
{
|
||||
self::document_mail_action($documents[$prefix.$file['name']], $file);
|
||||
self::document_mail_action($documents[$prefix . $file['name']], $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add PDF checkbox
|
||||
$documents['as_pdf'] = array(
|
||||
'caption' => 'As PDF',
|
||||
'checkbox' => true,
|
||||
);
|
||||
return array(
|
||||
'icon' => 'etemplate/merge',
|
||||
'caption' => $caption,
|
||||
'children' => $documents,
|
||||
'icon' => 'etemplate/merge',
|
||||
'caption' => $caption,
|
||||
'children' => $documents,
|
||||
// disable action if no document or export completly forbidden for non-admins
|
||||
'enabled' => (boolean)$documents && (self::hasExportLimit($export_limit,'ISALLOWED') || self::is_export_limit_excepted()),
|
||||
'hideOnDisabled' => true, // do not show 'Insert in document', if no documents defined or no export allowed
|
||||
'group' => $group,
|
||||
'enabled' => (boolean)$documents && (self::hasExportLimit($export_limit, 'ISALLOWED') || self::is_export_limit_excepted()),
|
||||
'hideOnDisabled' => true,
|
||||
// do not show 'Insert in document', if no documents defined or no export allowed
|
||||
'group' => $group,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2293,6 +2337,7 @@ abstract class Merge
|
||||
private static function document_mail_action(Array &$action, $file)
|
||||
{
|
||||
unset($action['postSubmit']);
|
||||
unset($action['onExecute']);
|
||||
|
||||
// Lots takes a while, confirm
|
||||
$action['confirm_multiple'] = lang('Do you want to send the message to all selected entries, WITHOUT further editing?');
|
||||
@ -2325,15 +2370,30 @@ abstract class Merge
|
||||
*/
|
||||
private static function document_editable_action(Array &$action, $file)
|
||||
{
|
||||
unset($action['postSubmit']);
|
||||
$edit_attributes = array(
|
||||
'menuaction' => 'collabora.EGroupware\\collabora\\Ui.merge_edit',
|
||||
'document' => $file['path'],
|
||||
'merge' => get_called_class(),
|
||||
'id' => '$id',
|
||||
'select_all' => '$select_all'
|
||||
static $action_base = array(
|
||||
// The same for every file
|
||||
'group' => 2,
|
||||
// Overwritten for every file
|
||||
'icon' => '', //Api\Vfs::mime_icon($file['mime']),
|
||||
'caption' => '', //Api\Vfs::decodePath($name_arr[$count]),
|
||||
);
|
||||
$edit_attributes = array(
|
||||
'menuaction' => $GLOBALS['egw_info']['flags']['currentapp'] . '.' . get_called_class() . '.merge_entries',
|
||||
'document' => $file['path'],
|
||||
'merge' => get_called_class(),
|
||||
);
|
||||
|
||||
$action = array_merge(
|
||||
$action_base,
|
||||
array(
|
||||
'icon' => Api\Vfs::mime_icon($file['mime']),
|
||||
'caption' => Api\Vfs::decodePath($file['name']),
|
||||
'onExecute' => 'javaScript:app.' . $GLOBALS['egw_info']['flags']['currentapp'] . '.merge',
|
||||
'merge_data' => $edit_attributes
|
||||
),
|
||||
// Merge in provided action last, so we can customize if needed (eg: default document)
|
||||
$action
|
||||
);
|
||||
$action['url'] = urldecode(http_build_query($edit_attributes));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2374,25 +2434,26 @@ abstract class Merge
|
||||
* Merge the selected IDs into the given document, save it to the VFS, then
|
||||
* either open it in the editor or have the browser download the file.
|
||||
*
|
||||
* @param String[]|null $ids Allows extending classes to process IDs in their own way. Leave null to pull from request.
|
||||
* @param string[]|null $ids Allows extending classes to process IDs in their own way. Leave null to pull from request.
|
||||
* @param Merge|null $document_merge Already instantiated Merge object to do the merge.
|
||||
* @param boolean|null $pdf Convert result to PDF
|
||||
* @throws Api\Exception
|
||||
* @throws Api\Exception\AssertionFailed
|
||||
*/
|
||||
public static function merge_entries(array $ids = null, Merge &$document_merge = null)
|
||||
public static function merge_entries(array $ids = null, Merge &$document_merge = null, $pdf = null)
|
||||
{
|
||||
if (is_null($document_merge) && class_exists($_REQUEST['merge']) && is_subclass_of($_REQUEST['merge'], 'EGroupware\\Api\\Storage\\Merge'))
|
||||
if(is_null($document_merge) && class_exists($_REQUEST['merge']) && is_subclass_of($_REQUEST['merge'], 'EGroupware\\Api\\Storage\\Merge'))
|
||||
{
|
||||
$document_merge = new $_REQUEST['merge']();
|
||||
}
|
||||
elseif (is_null($document_merge))
|
||||
elseif(is_null($document_merge))
|
||||
{
|
||||
$document_merge = new Api\Contacts\Merge();
|
||||
}
|
||||
|
||||
if(($error = $document_merge->check_document($_REQUEST['document'],'')))
|
||||
{
|
||||
$response->error($error);
|
||||
error_log(__METHOD__ . "({$_REQUEST['document']}) $error");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2405,35 +2466,33 @@ abstract class Merge
|
||||
$ids = self::get_all_ids($document_merge);
|
||||
}
|
||||
|
||||
$filename = $document_merge->get_filename($_REQUEST['document']);
|
||||
if(is_null($pdf))
|
||||
{
|
||||
$pdf = (boolean)$_REQUEST['pdf'];
|
||||
}
|
||||
|
||||
$filename = $document_merge->get_filename($_REQUEST['document'], $ids);
|
||||
$result = $document_merge->merge_file($_REQUEST['document'], $ids, $filename, '', $header);
|
||||
|
||||
if(!is_file($result) || !is_readable($result))
|
||||
{
|
||||
throw new Api\Exception\AssertionFailed("Unable to generate merge file\n". $result);
|
||||
throw new Api\Exception\AssertionFailed("Unable to generate merge file\n" . $result);
|
||||
}
|
||||
// Put it into the vfs using user's configured home dir if writable,
|
||||
// Put it into the vfs using user's preferred directory if writable,
|
||||
// or expected home dir (/home/username) if not
|
||||
$target = $_target = (Vfs::is_writable(Vfs::get_home_dir()) ?
|
||||
Vfs::get_home_dir() :
|
||||
"/home/{$GLOBALS['egw_info']['user']['account_lid']}"
|
||||
)."/$filename";
|
||||
$dupe_count = 0;
|
||||
while(is_file(Vfs::PREFIX.$target))
|
||||
{
|
||||
$dupe_count++;
|
||||
$target = Vfs::dirname($_target) . '/' .
|
||||
pathinfo($filename, PATHINFO_FILENAME) .
|
||||
' ('.($dupe_count + 1).')' . '.' .
|
||||
pathinfo($filename, PATHINFO_EXTENSION);
|
||||
}
|
||||
copy($result, Vfs::PREFIX.$target);
|
||||
$target = $document_merge->get_save_path($filename);
|
||||
|
||||
// Make sure we won't overwrite something already there
|
||||
$target = Vfs::make_unique($target);
|
||||
|
||||
copy($result, Vfs::PREFIX . $target);
|
||||
unlink($result);
|
||||
|
||||
// Find out what to do with it
|
||||
$editable_mimes = array();
|
||||
try {
|
||||
if (class_exists('EGroupware\\collabora\\Bo') &&
|
||||
try
|
||||
{
|
||||
if(class_exists('EGroupware\\collabora\\Bo') &&
|
||||
$GLOBALS['egw_info']['user']['apps']['collabora'] &&
|
||||
($discovery = \EGroupware\collabora\Bo::discover()) &&
|
||||
$GLOBALS['egw_info']['user']['preferences']['filemanager']['merge_open_handler'] != 'download'
|
||||
@ -2447,11 +2506,32 @@ abstract class Merge
|
||||
// ignore failed discovery
|
||||
unset($e);
|
||||
}
|
||||
if($editable_mimes[Vfs::mime_content_type($target)])
|
||||
|
||||
// PDF conversion
|
||||
if($editable_mimes[Vfs::mime_content_type($target)] && $pdf)
|
||||
{
|
||||
$error = '';
|
||||
$converted_path = '';
|
||||
$convert = new Conversion();
|
||||
$convert->convert($target, $converted_path, 'pdf', $error);
|
||||
|
||||
if($error)
|
||||
{
|
||||
error_log(__METHOD__ . "({$_REQUEST['document']}) $target => $converted_path Error in PDF conversion: $error");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove original
|
||||
Vfs::unlink($target);
|
||||
$target = $converted_path;
|
||||
}
|
||||
}
|
||||
if($editable_mimes[Vfs::mime_content_type($target)] &&
|
||||
!in_array(Vfs::mime_content_type($target), explode(',', $GLOBALS['egw_info']['user']['preferences']['filemanager']['collab_excluded_mimes'])))
|
||||
{
|
||||
\Egroupware\Api\Egw::redirect_link('/index.php', array(
|
||||
'menuaction' => 'collabora.EGroupware\\Collabora\\Ui.editor',
|
||||
'path'=> $target
|
||||
'path' => $target
|
||||
));
|
||||
}
|
||||
else
|
||||
@ -2461,14 +2541,80 @@ abstract class Merge
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a filename for the merged file
|
||||
* Generate a filename for the merged file, without extension
|
||||
*
|
||||
* Default is just the name of the template
|
||||
* Default filename is just the name of the template.
|
||||
* We use the placeholders from get_filename_placeholders() and the application's document filename preference
|
||||
* to generate a custom filename.
|
||||
*
|
||||
* @param string $document Template filename
|
||||
* @param string[] $ids List of IDs being merged
|
||||
* @return string
|
||||
*/
|
||||
protected function get_filename($document) : string
|
||||
protected function get_filename($document, $ids = []) : string
|
||||
{
|
||||
return '';
|
||||
$name = '';
|
||||
if(isset($GLOBALS['egw_info']['user']['preferences'][$this->get_app()][static::PREF_DOCUMENT_FILENAME]))
|
||||
{
|
||||
$pref = $GLOBALS['egw_info']['user']['preferences'][$this->get_app()][static::PREF_DOCUMENT_FILENAME];
|
||||
$placeholders = $this->get_filename_placeholders($document, $ids);
|
||||
|
||||
// Make values safe for VFS
|
||||
foreach($placeholders as &$value)
|
||||
{
|
||||
$value = Api\Mail::clean_subject_for_filename($value);
|
||||
}
|
||||
|
||||
// Do replacement
|
||||
$name = str_replace(
|
||||
array_keys($placeholders),
|
||||
array_values($placeholders),
|
||||
is_array($pref) ? implode(' ', $pref) : $pref
|
||||
);
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
protected function get_filename_placeholders($document, $ids)
|
||||
{
|
||||
$ext = '.' . pathinfo($document, PATHINFO_EXTENSION);
|
||||
$link_title = count($ids) == 1 ? Api\Link::title($this->get_app(), $ids[0]) : lang("multiple");
|
||||
$contact_title = count($ids) == 1 ? Api\Link::title($this->get_app(), $ids[0]) : lang("multiple");
|
||||
$current_date = str_replace('/', '-', Api\DateTime::to('now', Api\DateTime::$user_dateformat));
|
||||
|
||||
|
||||
$values = [
|
||||
'$$document$$' => basename($document, $ext),
|
||||
'$$link_title$$' => $link_title,
|
||||
'$$contact_title$$' => $contact_title,
|
||||
'$$current_date$$' => $current_date
|
||||
];
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a path where we can save the generated file
|
||||
* Takes into account user preference.
|
||||
*
|
||||
* @param string $filename The name of the generated file, including extension
|
||||
* @return string
|
||||
*/
|
||||
protected function get_save_path($filename) : string
|
||||
{
|
||||
// Default is home directory
|
||||
$target = (Vfs::is_writable(Vfs::get_home_dir()) ?
|
||||
Vfs::get_home_dir() :
|
||||
"/home/{$GLOBALS['egw_info']['user']['account_lid']}"
|
||||
);
|
||||
|
||||
// Check for a configured preferred directory
|
||||
if(($pref = $GLOBALS['egw_info']['user']['preferences']['filemanager'][Merge::PREF_STORE_LOCATION]) && Vfs::is_writable($pref))
|
||||
{
|
||||
$target = $pref;
|
||||
}
|
||||
|
||||
return $target . "/$filename";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2604,6 +2750,8 @@ abstract class Merge
|
||||
|
||||
// General information
|
||||
'date' => lang('Date'),
|
||||
'datetime' => lang('Date + time'),
|
||||
'time' => lang('Time'),
|
||||
'user/n_fn' => lang('Name of current user, all other contact fields are valid too'),
|
||||
'user/account_lid' => lang('Username'),
|
||||
|
||||
@ -2621,19 +2769,146 @@ abstract class Merge
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of common placeholders
|
||||
*
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function get_common_placeholder_list($prefix = '')
|
||||
{
|
||||
$placeholders = [
|
||||
'URLs' => [],
|
||||
'Egroupware links' => [],
|
||||
'General' => [],
|
||||
'Repeat' => [],
|
||||
'Commands' => []
|
||||
];
|
||||
// Iterate through the list & switch groups as we go
|
||||
// Hopefully a little better than assigning each field to a group
|
||||
$group = 'URLs';
|
||||
foreach($this->get_common_replacements() as $name => $label)
|
||||
{
|
||||
if(in_array($name, array('user/n_fn', 'user/account_lid')))
|
||||
{
|
||||
continue;
|
||||
} // don't show them, they're in 'User'
|
||||
|
||||
switch($name)
|
||||
{
|
||||
case 'links':
|
||||
$group = 'Egroupware links';
|
||||
break;
|
||||
case 'date':
|
||||
$group = 'General';
|
||||
break;
|
||||
case 'pagerepeat':
|
||||
$group = 'Repeat';
|
||||
break;
|
||||
case 'IF fieldname':
|
||||
$group = 'Commands';
|
||||
}
|
||||
$marker = $this->prefix($prefix, $name, '{');
|
||||
if(!array_filter($placeholders, function ($a) use ($marker)
|
||||
{
|
||||
return array_key_exists($marker, $a);
|
||||
}))
|
||||
{
|
||||
$placeholders[$group][] = [
|
||||
'value' => $marker,
|
||||
'label' => $label
|
||||
];
|
||||
}
|
||||
}
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of placeholders for the current user
|
||||
*/
|
||||
public function get_user_placeholder_list($prefix = '')
|
||||
{
|
||||
$contacts = new Api\Contacts\Merge();
|
||||
$replacements = $contacts->get_placeholder_list(($prefix ? $prefix . '/' : '') . 'user');
|
||||
unset($replacements['details']['{{' . ($prefix ? $prefix . '/' : '') . 'user/account_id}}']);
|
||||
$replacements = $contacts->get_placeholder_list($this->prefix($prefix, 'user'));
|
||||
unset($replacements['details'][$this->prefix($prefix, 'user/account_id', '{')]);
|
||||
$replacements['account'] = [
|
||||
'{{' . ($prefix ? $prefix . '/' : '') . 'user/account_id}}' => 'Account ID',
|
||||
'{{' . ($prefix ? $prefix . '/' : '') . 'user/account_lid}}' => 'Login ID'
|
||||
[
|
||||
'value' => $this->prefix($prefix, 'user/account_id', '{'),
|
||||
'label' => 'Account ID'
|
||||
],
|
||||
[
|
||||
'value' => $this->prefix($prefix, 'user/account_lid', '{'),
|
||||
'label' => 'Login ID'
|
||||
]
|
||||
];
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of placeholders for an application's customfields
|
||||
* If the customfield is a link to another application, we expand and add those placeholders as well
|
||||
*/
|
||||
protected function add_customfield_placeholders(&$placeholders, $prefix = '')
|
||||
{
|
||||
foreach(Customfields::get($this->get_app()) as $name => $field)
|
||||
{
|
||||
if(array_key_exists($field['type'], Api\Link::app_list()))
|
||||
{
|
||||
$app = self::get_app_class($field['type']);
|
||||
if($app)
|
||||
{
|
||||
$this->add_linked_placeholders($placeholders, $name, $app->get_placeholder_list('#' . $name));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$placeholders['customfields'][] = [
|
||||
'value' => $this->prefix($prefix, '#' . $name, '{'),
|
||||
'label' => $field['label'] . ($field['type'] == 'select-account' ? '*' : '')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of placeholders provided.
|
||||
*
|
||||
* Placeholders are grouped logically. Group key should have a user-friendly translation.
|
||||
* Override this method and specify the placeholders, as well as groups or a specific order
|
||||
*/
|
||||
public function get_placeholder_list($prefix = '')
|
||||
{
|
||||
$placeholders = [
|
||||
'placeholders' => []
|
||||
];
|
||||
|
||||
$this->add_customfield_placeholders($placeholders, $prefix);
|
||||
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add placeholders from another application into the given list of placeholders
|
||||
*
|
||||
* This is used for linked entries (like info_contact) and custom fields where the type is another application.
|
||||
* Here we adjust the group name, and add the group to the end of the placeholder list
|
||||
* @param array $placeholder_list Our placeholder list
|
||||
* @param string $base_name Name of the entry (eg: Contact, custom field name)
|
||||
* @param array $add_placeholder_groups Placeholder list from the other app. Placeholders should include any needed prefix
|
||||
*/
|
||||
protected function add_linked_placeholders(&$placeholder_list, $base_name, $add_placeholder_groups) : void
|
||||
{
|
||||
if(!$add_placeholder_groups)
|
||||
{
|
||||
// Skip empties
|
||||
return;
|
||||
}
|
||||
/*
|
||||
foreach($add_placeholder_groups as $group => $add_placeholders)
|
||||
{
|
||||
$placeholder_list[$base_name . ': ' . lang($group)] = $add_placeholders;
|
||||
}
|
||||
*/
|
||||
$placeholder_list[$base_name] = $add_placeholder_groups;
|
||||
}
|
||||
}
|
||||
|
@ -349,8 +349,8 @@ class Vfs extends Vfs\Base
|
||||
{
|
||||
//error_log(__METHOD__."(".print_r($base,true).",".print_r($options,true).",".print_r($exec,true).",".print_r($exec_params,true).")\n");
|
||||
|
||||
$type = $options['type']; // 'd', 'f' or 'F'
|
||||
$dirs_last = $options['depth']; // put content of dirs before the dir itself
|
||||
$type = $options['type'] ?? null; // 'd', 'f' or 'F'
|
||||
$dirs_last = !empty($options['depth']); // put content of dirs before the dir itself
|
||||
// show dirs on top by default, if no recursive listing (allways disabled if $type specified, as unnecessary)
|
||||
$dirsontop = !$type && (isset($options['dirsontop']) ? (boolean)$options['dirsontop'] : isset($options['maxdepth'])&&$options['maxdepth']>0);
|
||||
if ($dirsontop) $options['need_mime'] = true; // otherwise dirsontop can NOT work
|
||||
@ -386,7 +386,7 @@ class Vfs extends Vfs\Base
|
||||
$options['gid'] = 0;
|
||||
}
|
||||
}
|
||||
if ($options['order'] == 'mime')
|
||||
if (isset($options['order']) && $options['order'] === 'mime')
|
||||
{
|
||||
$options['need_mime'] = true; // we need to return the mime colum
|
||||
}
|
||||
@ -403,7 +403,7 @@ class Vfs extends Vfs\Base
|
||||
],
|
||||
]);
|
||||
|
||||
$url = $options['url'];
|
||||
$url = $options['url'] ?? null;
|
||||
|
||||
if (!is_array($base))
|
||||
{
|
||||
@ -422,7 +422,7 @@ class Vfs extends Vfs\Base
|
||||
$options['remove'] = count($base) == 1 ? count(explode('/',$path))-3+(int)(substr($path,-1)!='/') : 0;
|
||||
}
|
||||
$is_dir = is_dir($path);
|
||||
if ((int)$options['mindepth'] == 0 && (!$dirs_last || !$is_dir))
|
||||
if (empty($options['mindepth']) && (!$dirs_last || !$is_dir))
|
||||
{
|
||||
self::_check_add($options,$path,$result);
|
||||
}
|
||||
@ -434,11 +434,11 @@ class Vfs extends Vfs\Base
|
||||
{
|
||||
if ($fname == '.' || $fname == '..') continue; // ignore current and parent dir!
|
||||
|
||||
if (self::is_hidden($fname, $options['show-deleted']) && !$options['hidden']) continue; // ignore hidden files
|
||||
if (self::is_hidden($fname, $options['show-deleted'] ?? false) && !$options['hidden']) continue; // ignore hidden files
|
||||
|
||||
$file = self::concat($path, $fname);
|
||||
|
||||
if ((int)$options['mindepth'] <= 1)
|
||||
if (!isset($options['mindepth']) || (int)$options['mindepth'] <= 1)
|
||||
{
|
||||
self::_check_add($options,$file,$result);
|
||||
}
|
||||
@ -459,7 +459,7 @@ class Vfs extends Vfs\Base
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
if ($is_dir && (int)$options['mindepth'] == 0 && $dirs_last)
|
||||
if ($is_dir && empty($options['mindepth']) && $dirs_last)
|
||||
{
|
||||
self::_check_add($options,$path,$result);
|
||||
}
|
||||
@ -569,9 +569,9 @@ class Vfs extends Vfs\Base
|
||||
*/
|
||||
private static function _check_add($options,$path,&$result)
|
||||
{
|
||||
$type = $options['type']; // 'd' or 'f'
|
||||
$type = $options['type'] ?? null; // 'd' or 'f'
|
||||
|
||||
if ($options['url'])
|
||||
if (!empty($options['url']))
|
||||
{
|
||||
if (($stat = @lstat($path)))
|
||||
{
|
||||
@ -595,7 +595,7 @@ class Vfs extends Vfs\Base
|
||||
$stat['path'] = self::parse_url($path,PHP_URL_PATH);
|
||||
$stat['name'] = $options['remove'] > 0 ? implode('/',array_slice(explode('/',$stat['path']),$options['remove'])) : self::basename($path);
|
||||
|
||||
if ($options['mime'] || $options['need_mime'])
|
||||
if (!empty($options['mime']) || !empty($options['need_mime']))
|
||||
{
|
||||
$stat['mime'] = self::mime_content_type($path);
|
||||
}
|
||||
@ -642,7 +642,7 @@ class Vfs extends Vfs\Base
|
||||
return; // not create/modified in the spezified time
|
||||
}
|
||||
// do we return url or just vfs pathes
|
||||
if (!$options['url'])
|
||||
if (empty($options['url']))
|
||||
{
|
||||
$path = self::parse_url($path,PHP_URL_PATH);
|
||||
}
|
||||
@ -1227,7 +1227,7 @@ class Vfs extends Vfs\Base
|
||||
}
|
||||
}
|
||||
}
|
||||
return $component >= 0 ? $result[$component2str[$component]] : $result;
|
||||
return $component >= 0 ? ($result[$component2str[$component]] ?? null) : $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1240,7 +1240,7 @@ class Vfs extends Vfs\Base
|
||||
*/
|
||||
static function dirname($_url)
|
||||
{
|
||||
list($url,$query) = explode('?',$_url,2); // strip the query first, as it can contain slashes
|
||||
if (strpos($url=$_url, '?') !== false) list($url, $query) = explode('?',$_url,2); // strip the query first, as it can contain slashes
|
||||
|
||||
if ($url == '/' || $url[0] != '/' && self::parse_url($url,PHP_URL_PATH) == '/')
|
||||
{
|
||||
@ -1255,7 +1255,7 @@ class Vfs extends Vfs\Base
|
||||
array_push($parts,''); // scheme://host is wrong (no path), has to be scheme://host/
|
||||
}
|
||||
//error_log(__METHOD__."($url)=".implode('/',$parts).($query ? '?'.$query : ''));
|
||||
return implode('/',$parts).($query ? '?'.$query : '');
|
||||
return implode('/',$parts).(!empty($query) ? '?'.$query : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1283,7 +1283,7 @@ class Vfs extends Vfs\Base
|
||||
*/
|
||||
static function concat($_url,$relative)
|
||||
{
|
||||
list($url,$query) = explode('?',$_url,2);
|
||||
if (strpos($url=$_url, '?') !== false) list($url, $query) = explode('?',$_url,2);
|
||||
if (substr($url,-1) == '/') $url = substr($url,0,-1);
|
||||
$ret = ($relative === '' || $relative[0] == '/' ? $url.$relative : $url.'/'.$relative);
|
||||
|
||||
@ -2264,17 +2264,17 @@ class Vfs extends Vfs\Base
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$mime && is_dir($url))
|
||||
if (empty($mime) && is_dir($url))
|
||||
{
|
||||
$mime = self::DIR_MIME_TYPE;
|
||||
}
|
||||
// if we operate on the regular filesystem and the mime_content_type function is available --> use it
|
||||
if (!$mime && !$scheme && function_exists('mime_content_type'))
|
||||
if (empty($mime) && !$scheme && function_exists('mime_content_type'))
|
||||
{
|
||||
$mime = mime_content_type($path);
|
||||
}
|
||||
// using EGw's own mime magic (currently only checking the extension!)
|
||||
if (!$mime)
|
||||
if (empty($mime))
|
||||
{
|
||||
$mime = MimeMagic::filename2mime(self::parse_url($url,PHP_URL_PATH));
|
||||
}
|
||||
@ -2386,6 +2386,28 @@ class Vfs extends Vfs\Base
|
||||
}
|
||||
return self::_call_on_backend('get_minimum_file_id', array($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the path is unique, by appending (#) to the filename if it already exists
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return string The same path, but modified if it exists
|
||||
*/
|
||||
static function make_unique($path)
|
||||
{
|
||||
$filename = Vfs::basename($path);
|
||||
$dupe_count = 0;
|
||||
while(is_file(Vfs::PREFIX . $path))
|
||||
{
|
||||
$dupe_count++;
|
||||
$path = Vfs::dirname($path) . '/' .
|
||||
pathinfo($filename, PATHINFO_FILENAME) .
|
||||
' (' . ($dupe_count + 1) . ')' . '.' .
|
||||
pathinfo($filename, PATHINFO_EXTENSION);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
Vfs::init_static();
|
||||
|
@ -295,7 +295,7 @@ class Base
|
||||
'host' => $GLOBALS['egw_info']['user']['domain'],
|
||||
'home' => str_replace(array('\\\\', '\\'), array('', '/'), $GLOBALS['egw_info']['user']['homedirectory']),
|
||||
);
|
||||
$parts = array_merge(Vfs::parse_url($_path), Vfs::parse_url($path), $defaults);
|
||||
$parts = array_merge(Vfs::parse_url($_path), Vfs::parse_url($path) ?: [], $defaults);
|
||||
if(!$parts['host'])
|
||||
{
|
||||
// otherwise we get an invalid url (scheme:///path/to/something)!
|
||||
|
@ -1701,10 +1701,10 @@ class HTTP_WebDAV_Server
|
||||
/**
|
||||
* PUT method handler
|
||||
*
|
||||
* @param void
|
||||
* @param string $method='PUT'
|
||||
* @return void
|
||||
*/
|
||||
function http_PUT()
|
||||
function http_PUT(string $method='PUT')
|
||||
{
|
||||
if ($this->_check_lock_status($this->path)) {
|
||||
$options = Array();
|
||||
@ -1839,7 +1839,7 @@ class HTTP_WebDAV_Server
|
||||
}
|
||||
}
|
||||
|
||||
$stat = $this->PUT($options);
|
||||
$stat = $this->$method($options);
|
||||
|
||||
if ($stat === false) {
|
||||
$stat = "403 Forbidden";
|
||||
|
@ -21,7 +21,7 @@ use EGroupware\Api\Egw;
|
||||
|
||||
// E_STRICT in PHP 5.4 gives various strict warnings in working code, which can NOT be easy fixed in all use-cases :-(
|
||||
// Only variables should be assigned by reference, eg. soetemplate::tree_walk()
|
||||
// Declaration of <extended method> should be compatible with <parent method>, varios places where method parameters change
|
||||
// Declaration of <extended method> should be compatible with <parent method>, various places where method parameters change
|
||||
// --> switching it off for now, as it makes error-log unusable
|
||||
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
|
||||
|
||||
|
@ -16,7 +16,7 @@ use EGroupware\Api;
|
||||
/**
|
||||
* Translate message only if translation object is already loaded
|
||||
*
|
||||
* This function is usefull for exception handlers or early stages of the initialisation of the egw object,
|
||||
* This function is useful for exception handlers or early stages of the initialisation of the egw object,
|
||||
* as calling lang would try to load the translations, evtl. cause more errors, eg. because there's no db-connection.
|
||||
*
|
||||
* @param string $key message in englich with %1, %2, ... placeholders
|
||||
@ -36,7 +36,7 @@ function try_lang($key,$vars=null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clasify exception for a headline and log it to error_log, if not running as cli
|
||||
* Classify exception for a headline and log it to error_log, if not running as cli
|
||||
*
|
||||
* @param Exception|Error $e
|
||||
* @param string &$headline
|
||||
@ -82,7 +82,7 @@ function _egw_log_exception($e,&$headline=null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail a little bit more gracefully then an uncought exception
|
||||
* Fail a little more gracefully then an uncaught exception
|
||||
*
|
||||
* Does NOT return
|
||||
*
|
||||
@ -159,7 +159,7 @@ if (!isset($GLOBALS['egw_info']['flags']['no_exception_handler']) || $GLOBALS['e
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail a little bit more gracefully then a catchable fatal error, by throwing an exception
|
||||
* Fail a little more gracefully then a catchable fatal error, by throwing an exception
|
||||
*
|
||||
* @param int $errno level of the error raised: E_* constants
|
||||
* @param string $errstr error message
|
||||
@ -178,7 +178,7 @@ function egw_error_handler ($errno, $errstr, $errfile, $errline)
|
||||
|
||||
case E_WARNING:
|
||||
case E_USER_WARNING:
|
||||
// skip message for warnings supressed via @-error-control-operator (eg. @is_dir($path))
|
||||
// skip message for warnings suppressed via @-error-control-operator (eg. @is_dir($path))
|
||||
// can be commented out to get suppressed warnings too!
|
||||
if ((error_reporting() & $errno) && PHP_VERSION < 8.0)
|
||||
{
|
||||
|
@ -96,7 +96,7 @@ foreach(array('_COOKIE','_GET','_POST','_REQUEST','HTTP_GET_VARS','HTTP_POST_VAR
|
||||
}
|
||||
}
|
||||
// do the check for script-tags only for _GET and _POST or if we found something in _GET and _POST
|
||||
// speeds up the execusion a bit
|
||||
// speeds up the execution a bit
|
||||
if (isset($GLOBALS[$where]) && is_array($GLOBALS[$where]) && ($n < 3 || isset($GLOBALS['egw_unset_vars'])))
|
||||
{
|
||||
_check_script_tag($GLOBALS[$where],$where);
|
||||
@ -143,8 +143,8 @@ if (ini_get('register_globals'))
|
||||
*
|
||||
* Should be used for all external content, to guard against exploidts.
|
||||
*
|
||||
* PHP 7.0+ can be told not to instanciate any classes (and calling eg. it's destructor).
|
||||
* In fact it instanciates it as __PHP_Incomplete_Class without any methods and therefore disarming threads.
|
||||
* PHP 7.0+ can be told not to instantiate any classes (and calling eg. it's destructor).
|
||||
* In fact it instantiates it as __PHP_Incomplete_Class without any methods and therefore disarming threads.
|
||||
*
|
||||
* @param string $str
|
||||
* @return mixed
|
||||
|
@ -2322,6 +2322,8 @@ div.et2_toolbar_more h.ui-accordion-header.header_list-short span.ui-accordion-h
|
||||
.et2_toolbar_more .ui-accordion-header-active.header_list-short.ui-state-active span.ui-accordion-header-icon {
|
||||
background-position: bottom !important;
|
||||
margin-top: 2px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.et2_toolbar .et2_toolbar_more h .toolbar-admin-pref {
|
||||
background-image: url(../../../pixelegg/images/setup.svg);
|
||||
|
@ -24,7 +24,7 @@
|
||||
</hbox>
|
||||
</vbox>
|
||||
<styles>
|
||||
|
||||
/** Structural stuff **/
|
||||
#api\.insert_merge_placeholder_outer_box > #api\.insert_merge_placeholder_selects {
|
||||
flex: 1 1 80%;
|
||||
}
|
||||
@ -76,6 +76,11 @@
|
||||
border-radius: 0px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/** Cosmetics **/
|
||||
#api\.insert_merge_placeholder_outer_box option:first-letter {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
</styles>
|
||||
</template>
|
||||
</overlay>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<select id="placeholder_list"/>
|
||||
</vbox>
|
||||
<hrule/>
|
||||
<link-entry id="entry" label="Select entry"/>
|
||||
<link-entry id="entry" label="Select entry" only_app="addressbook"/>
|
||||
<hbox class="preview">
|
||||
<description id="preview_content"/>
|
||||
</hbox>
|
||||
@ -49,7 +49,7 @@
|
||||
flex-grow: 0;
|
||||
}
|
||||
div.et2_link_entry input.ui-autocomplete-input {
|
||||
width: 75%
|
||||
width: 70%
|
||||
}
|
||||
div.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset button, button#cancel, .et2_button {
|
||||
border: none;
|
||||
|
@ -22,11 +22,13 @@ require_once realpath(__DIR__.'/../WidgetBaseTest.php');
|
||||
|
||||
use EGroupware\Api\Etemplate;
|
||||
|
||||
class EntryTest extends \EGroupware\Api\Etemplate\WidgetBaseTest {
|
||||
class ContactEntryTest extends \EGroupware\Api\Etemplate\WidgetBaseTest
|
||||
{
|
||||
|
||||
const TEST_TEMPLATE = 'api.entry_test_contact';
|
||||
|
||||
public static function setUpBeforeClass() : void {
|
||||
public static function setUpBeforeClass() : void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace EGroupware\Api\Storage;
|
||||
|
||||
require_once __DIR__ . '/../LoggedInTest.php';
|
||||
use EGroupware\Api\LoggedInTest as LoggedInTest;
|
||||
|
||||
class CustomfieldsTest extends LoggedInTest
|
||||
@ -20,19 +21,19 @@ class CustomfieldsTest extends LoggedInTest
|
||||
protected $customfields = null;
|
||||
|
||||
protected $simple_field = array(
|
||||
'app' => self::APP,
|
||||
'name' => 'test_field',
|
||||
'label' => 'Custom field',
|
||||
'type' => 'text',
|
||||
'type2' => array(),
|
||||
'help' => 'Custom field created for automated testing by CustomfieldsTest',
|
||||
'values' => null,
|
||||
'len' => null,
|
||||
'rows' => null,
|
||||
'order' => null,
|
||||
'needed' => null,
|
||||
'private' => array()
|
||||
);
|
||||
'app' => self::APP,
|
||||
'name' => 'test_field',
|
||||
'label' => 'Custom field',
|
||||
'type' => 'text',
|
||||
'type2' => array(),
|
||||
'help' => 'Custom field created for automated testing by CustomfieldsTest',
|
||||
'values' => null,
|
||||
'len' => null,
|
||||
'rows' => null,
|
||||
'order' => null,
|
||||
'needed' => null,
|
||||
'private' => array()
|
||||
);
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
@ -209,35 +210,35 @@ class CustomfieldsTest extends LoggedInTest
|
||||
// Expected options, file
|
||||
return array(
|
||||
array(array(
|
||||
'' => 'Select',
|
||||
'Α'=> 'α Alpha',
|
||||
'Β'=> 'β Beta',
|
||||
'Γ'=> 'γ Gamma',
|
||||
'Δ'=> 'δ Delta',
|
||||
'Ε'=> 'ε Epsilon',
|
||||
'Ζ'=> 'ζ Zeta',
|
||||
'Η'=> 'η Eta',
|
||||
'Θ'=> 'θ Theta',
|
||||
'Ι'=> 'ι Iota',
|
||||
'Κ'=> 'κ Kappa',
|
||||
'Λ'=> 'λ Lambda',
|
||||
'Μ'=> 'μ Mu',
|
||||
'Ν'=> 'ν Nu',
|
||||
'Ξ'=> 'ξ Xi',
|
||||
'Ο'=> 'ο Omicron',
|
||||
'Π'=> 'π Pi',
|
||||
'Ρ'=> 'ρ Rho',
|
||||
'Σ'=> 'σ Sigma',
|
||||
'Τ'=> 'τ Tau',
|
||||
'Υ'=> 'υ Upsilon',
|
||||
'Φ'=> 'φ Phi',
|
||||
'Χ'=> 'χ Chi',
|
||||
'Ψ'=> 'ψ Psi',
|
||||
'Ω'=> 'ω Omega'
|
||||
), 'greek_options.php'),
|
||||
'' => 'Select',
|
||||
'Α' => 'α Alpha',
|
||||
'Β' => 'β Beta',
|
||||
'Γ' => 'γ Gamma',
|
||||
'Δ' => 'δ Delta',
|
||||
'Ε' => 'ε Epsilon',
|
||||
'Ζ' => 'ζ Zeta',
|
||||
'Η' => 'η Eta',
|
||||
'Θ' => 'θ Theta',
|
||||
'Ι' => 'ι Iota',
|
||||
'Κ' => 'κ Kappa',
|
||||
'Λ' => 'λ Lambda',
|
||||
'Μ' => 'μ Mu',
|
||||
'Ν' => 'ν Nu',
|
||||
'Ξ' => 'ξ Xi',
|
||||
'Ο' => 'ο Omicron',
|
||||
'Π' => 'π Pi',
|
||||
'Ρ' => 'ρ Rho',
|
||||
'Σ' => 'σ Sigma',
|
||||
'Τ' => 'τ Tau',
|
||||
'Υ' => 'υ Upsilon',
|
||||
'Φ' => 'φ Phi',
|
||||
'Χ' => 'χ Chi',
|
||||
'Ψ' => 'ψ Psi',
|
||||
'Ω' => 'ω Omega'
|
||||
), 'greek_options.php'),
|
||||
array(array(
|
||||
'View Subs' => "egw_open('','infolog','list',{action:'sp',action_id:widget.getRoot().getArrayMgr('content').getEntry('info_id')},'infolog','infolog');"
|
||||
), 'infolog_subs_option.php')
|
||||
'View Subs' => "egw_open('','infolog','list',{action:'sp',action_id:widget.getRoot().getArrayMgr('content').getEntry('info_id')},'infolog','infolog');"
|
||||
), 'infolog_subs_option.php')
|
||||
);
|
||||
}
|
||||
|
||||
@ -302,8 +303,8 @@ class CustomfieldsTest extends LoggedInTest
|
||||
protected function get_another_user()
|
||||
{
|
||||
$accounts = $GLOBALS['egw']->accounts->search(array(
|
||||
'type' => 'accounts'
|
||||
));
|
||||
'type' => 'accounts'
|
||||
));
|
||||
unset($accounts[$GLOBALS['egw_info']['user']['account_id']]);
|
||||
if(count($accounts) == 0)
|
||||
{
|
||||
|
@ -14,8 +14,10 @@
|
||||
namespace EGroupware\Api\Storage;
|
||||
|
||||
|
||||
require_once __DIR__ . '/../../src/Storage/Tracking.php';
|
||||
|
||||
class TestTracking extends Tracking {
|
||||
class TestTracking extends Tracking
|
||||
{
|
||||
|
||||
var $app = 'test';
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
namespace EGroupware\Api\Storage;
|
||||
|
||||
require_once __DIR__ . '/../LoggedInTest.php';
|
||||
require_once __DIR__ . '/TestTracking.php';
|
||||
|
||||
use EGroupware\Api;
|
||||
@ -22,18 +23,18 @@ class TrackingTest extends LoggedInTest
|
||||
const APP = 'test';
|
||||
|
||||
protected $simple_field = array(
|
||||
'app' => self::APP,
|
||||
'name' => 'test_field',
|
||||
'label' => 'Custom field',
|
||||
'type' => 'text',
|
||||
'type2' => array(),
|
||||
'help' => 'Custom field created for automated testing by CustomfieldsTest',
|
||||
'values' => null,
|
||||
'len' => null,
|
||||
'rows' => null,
|
||||
'order' => null,
|
||||
'needed' => null,
|
||||
'private' => array()
|
||||
'app' => self::APP,
|
||||
'name' => 'test_field',
|
||||
'label' => 'Custom field',
|
||||
'type' => 'text',
|
||||
'type2' => array(),
|
||||
'help' => 'Custom field created for automated testing by CustomfieldsTest',
|
||||
'values' => null,
|
||||
'len' => null,
|
||||
'rows' => null,
|
||||
'order' => null,
|
||||
'needed' => null,
|
||||
'private' => array()
|
||||
);
|
||||
|
||||
/**
|
||||
@ -57,8 +58,8 @@ class TrackingTest extends LoggedInTest
|
||||
|
||||
// Get another user
|
||||
$accounts = $GLOBALS['egw']->accounts->search(array(
|
||||
'type' => 'accounts'
|
||||
));
|
||||
'type' => 'accounts'
|
||||
));
|
||||
unset($accounts[$GLOBALS['egw_info']['user']['account_id']]);
|
||||
if(count($accounts) == 0)
|
||||
{
|
||||
|
@ -296,15 +296,18 @@ class SharingBase extends LoggedInTest
|
||||
*/
|
||||
protected function mountVersioned($path)
|
||||
{
|
||||
if (!class_exists('EGroupware\Stylite\Vfs\Versioning\StreamWrapper'))
|
||||
if(!class_exists('EGroupware\Stylite\Vfs\Versioning\StreamWrapper'))
|
||||
{
|
||||
$this->markTestSkipped("No versioning available");
|
||||
}
|
||||
if(substr($path, -1) == '/') $path = substr($path, 0, -1);
|
||||
if(substr($path, -1) == '/')
|
||||
{
|
||||
$path = substr($path, 0, -1);
|
||||
}
|
||||
$backup = Vfs::$is_root;
|
||||
Vfs::$is_root = true;
|
||||
$url = Versioning\StreamWrapper::PREFIX.$path;
|
||||
$this->assertTrue(Vfs::mount($url,$path), "Unable to mount $path as versioned");
|
||||
$url = Versioning\StreamWrapper::PREFIX . $path;
|
||||
$this->assertTrue(Vfs::mount($url, $path, false), "Unable to mount $path as versioned");
|
||||
Vfs::$is_root = $backup;
|
||||
|
||||
$this->mounts[] = $path;
|
||||
@ -362,8 +365,8 @@ class SharingBase extends LoggedInTest
|
||||
Vfs::chmod($path, 0750);
|
||||
Vfs::chown($path, $GLOBALS['egw_info']['user']['account_id']);
|
||||
|
||||
$url = \EGroupware\Stylite\Vfs\Merge\StreamWrapper::SCHEME.'://default'.$path.'?merge=' . realpath(__DIR__ . '/../fixtures/Vfs/filesystem_mount');
|
||||
$this->assertTrue(Vfs::mount($url,$path), "Unable to mount $url to $path");
|
||||
$url = \EGroupware\Stylite\Vfs\Merge\StreamWrapper::SCHEME . '://default' . $path . '?merge=' . realpath(__DIR__ . '/../fixtures/Vfs/filesystem_mount');
|
||||
$this->assertTrue(Vfs::mount($url, $path, false), "Unable to mount $url to $path");
|
||||
Vfs::$is_root = $backup;
|
||||
|
||||
$this->mounts[] = $path;
|
||||
|
@ -901,7 +901,7 @@ END:VALARM';
|
||||
{
|
||||
Api\Translation::add_app('calendar');
|
||||
// do not set actions for alarm type
|
||||
if ($params['data']['type'] == 6)
|
||||
if (isset($params['data']['type']) && $params['data']['type'] == 6)
|
||||
{
|
||||
if (!empty($params['data']['videoconference'])
|
||||
&& !self::isVideoconferenceDisabled())
|
||||
@ -917,6 +917,8 @@ END:VALARM';
|
||||
}
|
||||
return array();
|
||||
}
|
||||
if (!isset($params['data']['event_id'])) $params['data']['event_id'] = '';
|
||||
if (!isset($params['data']['user_id'])) $params['data']['user_id'] = '';
|
||||
return array(
|
||||
array(
|
||||
'id' => 'A',
|
||||
|
@ -313,7 +313,7 @@ class calendar_uiforms extends calendar_ui
|
||||
$msg = $this->export($content['id'],true);
|
||||
}
|
||||
// delete a recur-exception
|
||||
if ($content['recur_exception']['delete_exception'])
|
||||
if (!empty($content['recur_exception']['delete_exception']))
|
||||
{
|
||||
$date = key($content['recur_exception']['delete_exception']);
|
||||
// eT2 converts time to
|
||||
@ -338,7 +338,7 @@ class calendar_uiforms extends calendar_ui
|
||||
$update_type = 'edit';
|
||||
}
|
||||
// delete an alarm
|
||||
if ($content['alarm']['delete_alarm'])
|
||||
if (!empty($content['alarm']['delete_alarm']))
|
||||
{
|
||||
$id = key($content['alarm']['delete_alarm']);
|
||||
//echo "delete alarm $id"; _debug_array($content['alarm']['delete_alarm']);
|
||||
@ -1739,9 +1739,11 @@ class calendar_uiforms extends calendar_ui
|
||||
$lock_path = Vfs::app_entry_lock_path('calendar',$event['id']);
|
||||
$lock_owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
|
||||
|
||||
$scope = 'shared';
|
||||
$type = 'write';
|
||||
if (($preserv['lock_token'] = $event['lock_token'])) // already locked --> refresh the lock
|
||||
{
|
||||
Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',$type='write',true,false);
|
||||
Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope,$type,true,false);
|
||||
}
|
||||
if (($lock = Vfs::checkLock($lock_path)) && $lock['owner'] != $lock_owner)
|
||||
{
|
||||
@ -1753,7 +1755,7 @@ class calendar_uiforms extends calendar_ui
|
||||
{
|
||||
$preserv['lock_token'] = $lock['token'];
|
||||
}
|
||||
elseif(Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',$type='write',false,false))
|
||||
elseif(Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope,$type,false,false))
|
||||
{
|
||||
//We handle AJAX_REQUEST in client-side for unlocking the locked entry, in case of closing the entry by X button or close button
|
||||
}
|
||||
@ -2248,7 +2250,7 @@ class calendar_uiforms extends calendar_ui
|
||||
$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
elseif (!empty($event['button']))
|
||||
{
|
||||
//_debug_array($event);
|
||||
$button = key($event['button']);
|
||||
@ -2906,7 +2908,7 @@ class calendar_uiforms extends calendar_ui
|
||||
{
|
||||
throw new Api\Exception\NoPermission\Admin();
|
||||
}
|
||||
if ($_content)
|
||||
if (!empty($_content['button']))
|
||||
{
|
||||
$button = key($_content['button']);
|
||||
unset($_content['button']);
|
||||
|
@ -93,7 +93,7 @@ class calendar_uilist extends calendar_ui
|
||||
// handle a single button like actions
|
||||
foreach(array('delete','timesheet','document') as $button)
|
||||
{
|
||||
if ($_content['nm']['rows'][$button])
|
||||
if (!empty($_content['nm']['rows'][$button]))
|
||||
{
|
||||
$id = key($_content['nm']['rows'][$button]);
|
||||
$_content['nm']['action'] = $button;
|
||||
|
880
composer.lock
generated
880
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -253,7 +253,119 @@ Location: https://example.org/egroupware/groupdav.php/<username>/addressbook/123
|
||||
```
|
||||
</details>
|
||||
|
||||
* **PUT** requests with a ```Content-Type: application/json``` header allow modifying single resources
|
||||
<details>
|
||||
<summary>Example: POST request to create a new resource using flat attributes (JSON patch syntax) eg. for a simple Wordpress contact-form</summary>
|
||||
|
||||
```
|
||||
cat <<EOF | curl -i 'https://example.org/egroupware/groupdav.php/<username>/addressbook/' -X POST -d @- -H "Content-Type: application/json" --user <username>
|
||||
{
|
||||
"fullName": "First Tester",
|
||||
"name/personal": "First",
|
||||
"name/surname": "Tester",
|
||||
"organizations/org/name": "Test Organization",
|
||||
"emails/work": "test.user@test-user.org",
|
||||
"addresses/work/locality": "Test-Town",
|
||||
"addresses/work/postcode": "12345",
|
||||
"addresses/work/street": "Teststr. 123",
|
||||
"addresses/work/country": "Germany",
|
||||
"addresses/work/countryCode": "DE",
|
||||
"phones/tel_work": "+49 123 4567890",
|
||||
"online/url": "https://www.example.org/",
|
||||
"notes/note": "This is a note.",
|
||||
"egroupware.org:customfields/Test": "Content for Test"
|
||||
}
|
||||
EOF
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Location: https://example.org/egroupware/groupdav.php/<username>/addressbook/1234
|
||||
```
|
||||
</details>
|
||||
|
||||
* **PUT** requests with a ```Content-Type: application/json``` header allow modifying single resources (requires to specify all attributes!)
|
||||
|
||||
<details>
|
||||
<summary>Example: PUT request to update a resource</summary>
|
||||
|
||||
```
|
||||
cat <<EOF | curl -i 'https://example.org/egroupware/groupdav.php/<username>/addressbook/1234' -X PUT -d @- -H "Content-Type: application/json" --user <username>
|
||||
{
|
||||
"uid": "5638-8623c4830472a8ede9f9f8b30d435ea4",
|
||||
"prodId": "EGroupware Addressbook 21.1.001",
|
||||
"created": "2010-10-21T09:55:42Z",
|
||||
"updated": "2014-06-02T14:45:24Z",
|
||||
"name": [
|
||||
{ "type": "@type": "NameComponent", "personal", "value": "Default" },
|
||||
{ "type": "@type": "NameComponent", "surname", "value": "Tester" }
|
||||
],
|
||||
"fullName": { "value": "Default Tester" },
|
||||
....
|
||||
}
|
||||
EOF
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Example: PUT request with UID to update an existing resource or create it, if not exists</summary>
|
||||
|
||||
```
|
||||
cat <<EOF | curl -i 'https://example.org/egroupware/groupdav.php/<username>/addressbook/5638-8623c4830472a8ede9f9f8b30d435ea4' -X PUT -d @- -H "Content-Type: application/json" --user <username>
|
||||
{
|
||||
"uid": "5638-8623c4830472a8ede9f9f8b30d435ea4",
|
||||
"prodId": "EGroupware Addressbook 21.1.001",
|
||||
"created": "2010-10-21T09:55:42Z",
|
||||
"updated": "2014-06-02T14:45:24Z",
|
||||
"name": [
|
||||
{ "type": "@type": "NameComponent", "personal", "value": "Default" },
|
||||
{ "type": "@type": "NameComponent", "surname", "value": "Tester" }
|
||||
],
|
||||
"fullName": { "value": "Default Tester" },
|
||||
....
|
||||
}
|
||||
EOF
|
||||
```
|
||||
Update of an existing one:
|
||||
```
|
||||
HTTP/1.1 204 No Content
|
||||
```
|
||||
New contact:
|
||||
```
|
||||
HTTP/1.1 201 Created
|
||||
Location: https://example.org/egroupware/groupdav.php/<username>/addressbook/1234
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
* **PATCH** request with a ```Content-Type: application/json``` header allow to modify a single resource by only specifying changed attributes as a [PatchObject](https://www.rfc-editor.org/rfc/rfc8984.html#type-PatchObject)
|
||||
|
||||
<details>
|
||||
<summary>Example: PATCH request to modify a contact with partial data</summary>
|
||||
|
||||
```
|
||||
cat <<EOF | curl -i 'https://example.org/egroupware/groupdav.php/<username>/addressbook/1234' -X PATCH -d @- -H "Content-Type: application/json" --user <username>
|
||||
{
|
||||
"name": [
|
||||
{
|
||||
"@type": "NameComponent",
|
||||
"type": "personal",
|
||||
"value": "Testfirst"
|
||||
},
|
||||
{
|
||||
"@type": "NameComponent",
|
||||
"type": "surname",
|
||||
"value": "Username"
|
||||
}
|
||||
],
|
||||
"fullName": "Testfirst Username",
|
||||
"organizations/org/name": "Test-User.org",
|
||||
"emails/work/email": "test.user@test-user.org"
|
||||
}
|
||||
EOF
|
||||
|
||||
HTTP/1.1 204 No content
|
||||
```
|
||||
</details>
|
||||
|
||||
* **DELETE** requests delete single resources
|
||||
|
||||
@ -266,3 +378,11 @@ use ```<domain-name>:<name>``` like in JsCalendar
|
||||
* top-level objects need a ```@type``` attribute with one of the following values:
|
||||
```NameComponent```, ```Organization```, ```Title```, ```Phone```, ```Resource```, ```File```, ```ContactLanguage```,
|
||||
```Address```, ```StreetComponent```, ```Anniversary```, ```PersonalInformation```
|
||||
|
||||
### ToDos
|
||||
- [x] Addressbook
|
||||
- [ ] update of photos, keys, attachments
|
||||
- [ ] InfoLog
|
||||
- [ ] Calendar
|
||||
- [ ] relatedTo / links
|
||||
- [ ] storing not native supported attributes eg. localization
|
@ -161,27 +161,34 @@ class filemanager_hooks
|
||||
'forced' => 'yes',
|
||||
),
|
||||
'showusers' => array(
|
||||
'type' => 'select',
|
||||
'name' => 'showusers',
|
||||
'values' => $yes_no,
|
||||
'label' => lang('Show link "%1" in side box menu?',lang('Users and groups')),
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'forced' => 'yes',
|
||||
'type' => 'select',
|
||||
'name' => 'showusers',
|
||||
'values' => $yes_no,
|
||||
'label' => lang('Show link "%1" in side box menu?', lang('Users and groups')),
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'forced' => 'yes',
|
||||
),
|
||||
);
|
||||
|
||||
$settings[Api\Storage\Merge::PREF_STORE_LOCATION] = array(
|
||||
'type' => 'vfs_dir',
|
||||
'size' => 60,
|
||||
'label' => 'Directory for storing merged documents',
|
||||
'name' => Api\Storage\Merge::PREF_STORE_LOCATION,
|
||||
'help' => lang('When you merge entries into documents, they will be stored here. If no directory is provided, they will be stored in %1', Vfs::get_home_dir())
|
||||
);
|
||||
$settings['default_document'] = array(
|
||||
'type' => 'vfs_file',
|
||||
'size' => 60,
|
||||
'label' => 'Default document to insert entries',
|
||||
'name' => 'default_document',
|
||||
'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.',lang('filemanager')).' '.
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'name').' '.
|
||||
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
|
||||
'type' => 'vfs_file',
|
||||
'size' => 60,
|
||||
'label' => 'Default document to insert entries',
|
||||
'name' => 'default_document',
|
||||
'help' => lang('If you specify a document (full vfs path) here, %1 displays an extra document icon for each entry. That icon allows to download the specified document with the data inserted.', lang('filemanager')) . ' ' .
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'name') . ' ' .
|
||||
lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()),
|
||||
'run_lang' => false,
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
);
|
||||
$settings['document_dir'] = array(
|
||||
'type' => 'vfs_dirs',
|
||||
|
@ -106,11 +106,11 @@ class filemanager_select
|
||||
}
|
||||
}
|
||||
|
||||
$content['mime'] = key($sel_options['mime']);
|
||||
$content['mime'] = key($sel_options['mime'] ?? []);
|
||||
error_log(array2string($content['options-mime']));
|
||||
}
|
||||
}
|
||||
elseif(isset($content['button']))
|
||||
elseif(!empty($content['button']))
|
||||
{
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
@ -205,7 +205,7 @@ class filemanager_select
|
||||
|
||||
$sel_options['mime'] = $content['options-mime'];
|
||||
}
|
||||
elseif(isset($content['apps']))
|
||||
elseif(!empty($content['apps']))
|
||||
{
|
||||
$app = key($content['apps']);
|
||||
if ($app == 'home') $content['path'] = filemanager_ui::get_home_dir();
|
||||
|
@ -573,13 +573,11 @@ class filemanager_ui
|
||||
{
|
||||
$content['nm']['path'] = urldecode($content['nm']['path']);
|
||||
}
|
||||
if ($content['button'])
|
||||
if (!empty($content['button']))
|
||||
{
|
||||
if ($content['button'])
|
||||
{
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
}
|
||||
$button = key($content['button']);
|
||||
unset($content['button']);
|
||||
|
||||
switch ($button)
|
||||
{
|
||||
case 'upload':
|
||||
@ -1193,7 +1191,7 @@ class filemanager_ui
|
||||
//_debug_array($content);
|
||||
$path =& $content['path'];
|
||||
|
||||
$button = @key($content['button']);
|
||||
$button = @key($content['button'] ?? []);
|
||||
unset($content['button']);
|
||||
if(!$button && $content['sudo'] && $content['sudouser'])
|
||||
{
|
||||
@ -1347,9 +1345,9 @@ class filemanager_ui
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($content['eacl'] && $content['is_owner'])
|
||||
elseif (!empty($content['eacl']) && !empty($content['is_owner']))
|
||||
{
|
||||
if ($content['eacl']['delete'])
|
||||
if (!empty($content['eacl']['delete']))
|
||||
{
|
||||
$ino_owner = key($content['eacl']['delete']);
|
||||
list(, $owner) = explode('-',$ino_owner,2); // $owner is a group and starts with a minus!
|
||||
|
@ -94,7 +94,7 @@ class infolog_customfields extends admin_customfields
|
||||
$create = $fields['create'];
|
||||
unset($fields['create']);
|
||||
|
||||
if ($fields['delete'])
|
||||
if (!empty($fields['delete']))
|
||||
{
|
||||
$delete = key($fields['delete']);
|
||||
unset($fields['delete']);
|
||||
@ -178,7 +178,7 @@ class infolog_customfields extends admin_customfields
|
||||
$create = $status['create'];
|
||||
unset($status['create']);
|
||||
|
||||
if ($status['delete'])
|
||||
if (!empty($status['delete']))
|
||||
{
|
||||
$delete = key($status['delete']);
|
||||
unset($status['delete']);
|
||||
@ -276,7 +276,7 @@ class infolog_customfields extends admin_customfields
|
||||
unset($this->status[$content['type2']]);
|
||||
unset($this->status['defaults'][$content['type2']]);
|
||||
unset($this->group_owners[$content['type2']]);
|
||||
$content['type2'] = key($this->content_types);
|
||||
$content['type2'] = key($this->content_types ?? []);
|
||||
|
||||
// save changes to repository
|
||||
$this->save_repository();
|
||||
|
@ -116,7 +116,7 @@ class infolog_favorite_portlet extends home_favorite_portlet
|
||||
{
|
||||
$popup =& $values;
|
||||
}
|
||||
$values['nm']['multi_action'] .= '_' . key($popup[$multi_action . '_action']);
|
||||
$values['nm']['multi_action'] .= '_' . key($popup[$multi_action . '_action'] ?? []);
|
||||
if($multi_action == 'link')
|
||||
{
|
||||
$popup[$multi_action] = $popup['link']['app'] . ':'.$popup['link']['id'];
|
||||
|
@ -471,17 +471,27 @@ class infolog_hooks
|
||||
'admin' => False,
|
||||
);
|
||||
$settings['document_dir'] = array(
|
||||
'type' => 'vfs_dirs',
|
||||
'size' => 60,
|
||||
'label' => 'Directory with documents to insert entries',
|
||||
'name' => 'document_dir',
|
||||
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.',lang('infolog')).' '.
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','info_subject').' '.
|
||||
lang('The following document-types are supported:').'*.rtf, *.txt',
|
||||
'type' => 'vfs_dirs',
|
||||
'size' => 60,
|
||||
'label' => 'Directory with documents to insert entries',
|
||||
'name' => 'document_dir',
|
||||
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the data inserted.', lang('infolog')) . ' ' .
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'info_subject') . ' ' .
|
||||
lang('The following document-types are supported:') . '*.rtf, *.txt',
|
||||
'run_lang' => false,
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => '/templates/infolog',
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => '/templates/infolog',
|
||||
);
|
||||
$settings[Api\Storage\Merge::PREF_DOCUMENT_FILENAME] = array(
|
||||
'type' => 'taglist',
|
||||
'label' => 'Document download filename',
|
||||
'name' => 'document_download_name',
|
||||
'values' => Api\Storage\Merge::DOCUMENT_FILENAME_OPTIONS,
|
||||
'help' => 'Choose the default filename for downloaded documents.',
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => 'document',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -64,23 +64,42 @@ class infolog_merge extends Api\Storage\Merge
|
||||
* @param string &$content=null content to create some replacements only if they are use
|
||||
* @return array|boolean
|
||||
*/
|
||||
protected function get_replacements($id,&$content=null)
|
||||
protected function get_replacements($id, &$content = null)
|
||||
{
|
||||
if (!($replacements = $this->infolog_replacements($id, '', $content)))
|
||||
if(!($replacements = $this->infolog_replacements($id, '', $content)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override contact filename placeholder to use info_contact
|
||||
*
|
||||
* @param $document
|
||||
* @param $ids
|
||||
* @return array|void
|
||||
*/
|
||||
public function get_filename_placeholders($document, $ids)
|
||||
{
|
||||
$placeholders = parent::get_filename_placeholders($document, $ids);
|
||||
if(count($ids) == 1 && ($info = $this->bo->read($ids[0])))
|
||||
{
|
||||
$placeholders['$$contact_title$$'] = $info['info_contact']['title'] ??
|
||||
(is_array($info['info_contact']) && Link::title($info['info_contact']['app'], $info['info_contact']['id'])) ??
|
||||
'';
|
||||
}
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get infolog replacements
|
||||
*
|
||||
* @param int $id id of entry
|
||||
* @param string $prefix='' prefix like eg. 'erole'
|
||||
* @param string $prefix ='' prefix like eg. 'erole'
|
||||
* @return array|boolean
|
||||
*/
|
||||
public function infolog_replacements($id,$prefix='', &$content = '')
|
||||
public function infolog_replacements($id, $prefix = '', &$content = '')
|
||||
{
|
||||
$record = new infolog_egw_record($id);
|
||||
$info = array();
|
||||
@ -250,14 +269,56 @@ class infolog_merge extends Api\Storage\Merge
|
||||
echo '<tr><td>{{info_contact/#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n";
|
||||
}
|
||||
|
||||
echo '<tr><td colspan="4"><h3>'.lang('General fields:')."</h3></td></tr>";
|
||||
echo '<tr><td colspan="4"><h3>' . lang('General fields:') . "</h3></td></tr>";
|
||||
foreach($this->get_common_replacements() as $name => $label)
|
||||
{
|
||||
echo '<tr><td>{{'.$name.'}}</td><td colspan="3">'.$label."</td></tr>\n";
|
||||
echo '<tr><td>{{' . $name . '}}</td><td colspan="3">' . $label . "</td></tr>\n";
|
||||
}
|
||||
|
||||
echo "</table>\n";
|
||||
|
||||
echo $GLOBALS['egw']->framework->footer();
|
||||
}
|
||||
|
||||
public function get_placeholder_list($prefix = '')
|
||||
{
|
||||
$placeholders = parent::get_placeholder_list($prefix);
|
||||
|
||||
$tracking = new infolog_tracking($this->bo);
|
||||
$fields = array('info_id' => lang('Infolog ID'), 'pm_id' => lang('Project ID'),
|
||||
'project' => lang('Project name')) + $tracking->field2label + array('info_sum_timesheets' => lang('Used time'));
|
||||
Api\Translation::add_app('projectmanager');
|
||||
|
||||
$group = 'placeholders';
|
||||
foreach($fields as $name => $label)
|
||||
{
|
||||
if(in_array($name, array('custom')))
|
||||
{
|
||||
// dont show them
|
||||
continue;
|
||||
}
|
||||
$marker = $this->prefix($prefix, $name, '{');
|
||||
if(!array_filter($placeholders, function ($a) use ($marker)
|
||||
{
|
||||
return array_key_exists($marker, $a);
|
||||
}))
|
||||
{
|
||||
$placeholders[$group][] = [
|
||||
'value' => $marker,
|
||||
'label' => $label
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Add contact placeholders
|
||||
$insert_index = 1;
|
||||
$placeholders = array_slice($placeholders, 0, $insert_index, true) +
|
||||
[lang($tracking->field2label['info_from']) => []] +
|
||||
array_slice($placeholders, $insert_index, count($placeholders) - $insert_index, true);
|
||||
$contact_merge = new Api\Contacts\Merge();
|
||||
$contact = $contact_merge->get_placeholder_list('info_contact');
|
||||
$this->add_linked_placeholders($placeholders, lang($tracking->field2label['info_from']), $contact);
|
||||
|
||||
return $placeholders;
|
||||
}
|
||||
}
|
||||
|
@ -608,7 +608,7 @@ class infolog_ui
|
||||
}
|
||||
if(count($links))
|
||||
{
|
||||
$query['col_filter']['info_id'] = count($links) > 1 ? call_user_func_array('array_intersect', $links) : $links[$key ?? 'info_id'];
|
||||
$query['col_filter']['info_id'] = count($links) > 1 ? call_user_func_array('array_intersect', array_values($links)) : $links[$key ?? 'info_id'];
|
||||
}
|
||||
return $linked;
|
||||
}
|
||||
@ -831,7 +831,7 @@ class infolog_ui
|
||||
{
|
||||
$popup =& $values;
|
||||
}
|
||||
$values['nm']['multi_action'] .= '_' . key($popup[$multi_action . '_action']);
|
||||
$values['nm']['multi_action'] .= '_' . key($popup[$multi_action . '_action'] ?? []);
|
||||
if($multi_action == 'link')
|
||||
{
|
||||
$popup[$multi_action] = $popup['link']['app'] . ':'.$popup['link']['id'];
|
||||
@ -2498,7 +2498,7 @@ class infolog_ui
|
||||
if($content)
|
||||
{
|
||||
// Save
|
||||
$button = key($content['button']);
|
||||
$button = key($content['button'] ?? []);
|
||||
if($button == 'save' || $button == 'apply')
|
||||
{
|
||||
$this->bo->responsible_edit = array('info_status','info_percent','info_datecompleted');
|
||||
|
@ -551,7 +551,7 @@ class mail_compose
|
||||
}
|
||||
if ($sendOK)
|
||||
{
|
||||
$workingFolder = $activeFolder;
|
||||
$workingFolder = $activeFolder['mailbox'];
|
||||
$mode = 'compose';
|
||||
$idsForRefresh = array();
|
||||
if (isset($_content['mode']) && !empty($_content['mode']))
|
||||
|
@ -145,7 +145,7 @@ class mail_tree
|
||||
*/
|
||||
private static function isAccountNode ($_node)
|
||||
{
|
||||
list(,$leaf) = explode(self::DELIMITER, $_node);
|
||||
list(,$leaf) = explode(self::DELIMITER, $_node)+[null,null];
|
||||
if ($leaf || $_node == null) return false;
|
||||
return true;
|
||||
}
|
||||
@ -404,7 +404,7 @@ class mail_tree
|
||||
}
|
||||
$parents[] = $component;
|
||||
}
|
||||
if ($data['folderarray']['delimiter'] && $data['folderarray']['MAILBOX'])
|
||||
if (!empty($data['folderarray']['delimiter']) && !empty($data['folderarray']['MAILBOX']))
|
||||
{
|
||||
$path = explode($data['folderarray']['delimiter'], $data['folderarray']['MAILBOX']);
|
||||
$folderName = array_pop($path);
|
||||
@ -428,7 +428,7 @@ class mail_tree
|
||||
$data[Tree::IMAGE_FOLDER_OPEN] =
|
||||
$data [Tree::IMAGE_FOLDER_CLOSED] = basename(Api\Image::find('mail', 'dhtmlxtree/'."MailFolder".$key));
|
||||
}
|
||||
elseif(stripos(array2string($data['folderarray']['attributes']),'\noselect')!== false)
|
||||
elseif(!empty($data['folderarray']['attributes']) && stripos(array2string($data['folderarray']['attributes']),'\noselect') !== false)
|
||||
{
|
||||
$data[Tree::IMAGE_LEAF] = self::$leafImages['folderNoSelectClosed'];
|
||||
$data[Tree::IMAGE_FOLDER_OPEN] = self::$leafImages['folderNoSelectOpen'];
|
||||
@ -444,7 +444,7 @@ class mail_tree
|
||||
}
|
||||
|
||||
// Contains unseen mails for the folder
|
||||
$unseen = $data['folderarray']['counter']['UNSEEN'];
|
||||
$unseen = $data['folderarray']['counter']['UNSEEN'] ?? 0;
|
||||
|
||||
// if there's unseen mails then change the label and style
|
||||
// accordingly to indicate useen mails
|
||||
@ -477,7 +477,7 @@ class mail_tree
|
||||
foreach(Mail\Account::search(true, false) as $acc_id => $accObj)
|
||||
{
|
||||
if (!$accObj->is_imap()|| $_profileID && $acc_id != $_profileID) continue;
|
||||
$identity = self::getIdentityName(Mail\Account::identity_name($accObj,true,$GLOBALS['egw_info']['user']['acount_id'], true));
|
||||
$identity = self::getIdentityName(Mail\Account::identity_name($accObj,true, $GLOBALS['egw_info']['user']['account_id'], true));
|
||||
// Open top level folders for active account
|
||||
$openActiveAccount = $GLOBALS['egw_info']['user']['preferences']['mail']['ActiveProfileID'] == $acc_id?1:0;
|
||||
|
||||
|
@ -2445,10 +2445,29 @@ app.classes.mail = AppJS.extend(
|
||||
// we can NOT query global object manager for this.nm_index="nm", as we might not get the one from mail,
|
||||
// if other tabs are open, we have to query for obj_manager for "mail" and then it's child with id "nm"
|
||||
var obj_manager = egw_getObjectManager(this.appname).getObjectById(this.nm_index);
|
||||
let tree = this.et2.getWidgetById('nm[foldertree]');
|
||||
var that = this;
|
||||
var rvMain = false;
|
||||
if ((obj_manager && _elems.length>1 && obj_manager.getAllSelected() && !_action.paste) || _action.id=='readall')
|
||||
{
|
||||
try {
|
||||
let splitedID = [];
|
||||
let mailbox = '';
|
||||
// Avoid possibly doing select all action on not desired mailbox e.g. INBOX
|
||||
for (let n=0;n<_elems.length;n++)
|
||||
{
|
||||
splitedID = _elems[n].id.split("::");
|
||||
// find the mailbox from the constructed rowID, sometimes the rowID may not contain the app name
|
||||
mailbox = splitedID.length == 4?atob(splitedID[2]):atob(splitedID[3]);
|
||||
// drop the action if there's a mixedup mailbox found in the selected messages
|
||||
if (mailbox != tree.getSelectedNode().id.split("::")[1]) return;
|
||||
}
|
||||
}catch(e)
|
||||
{
|
||||
// continue
|
||||
}
|
||||
|
||||
|
||||
if (_confirm)
|
||||
{
|
||||
var buttons = [
|
||||
|
@ -68,7 +68,7 @@ class preferences_settings
|
||||
{
|
||||
$is_admin = $content['is_admin'] || $content['type'] != 'user';
|
||||
//error_log(__METHOD__."(".array2string($content).")");
|
||||
if ($content['button'])
|
||||
if (!empty($content['button']))
|
||||
{
|
||||
$button = key($content['button']);
|
||||
$appname = $content['old_appname'] ? $content['old_appname'] : 'common';
|
||||
|
@ -176,7 +176,7 @@ class resources_acl_ui
|
||||
$content = array('data' => array());
|
||||
}
|
||||
}
|
||||
elseif ($content['button'])
|
||||
elseif (!empty($content['button']))
|
||||
{
|
||||
$cats = new Categories($content['owner'] ? $content['owner'] : Categories::GLOBAL_ACCOUNT,'resources');
|
||||
|
||||
|
@ -32,7 +32,8 @@ class resources_reserve {
|
||||
$display_days = $_GET['planner_days'] ? $_GET['planner_days'] : 3;
|
||||
$planner_date = $_GET['date'] ? $_GET['date'] : strtotime('yesterday',$content['date'] ? $content['date'] : time());
|
||||
|
||||
if($_GET['confirm']) {
|
||||
if(!empty($_GET['confirm']))
|
||||
{
|
||||
$register_code = ($_GET['confirm'] && preg_match('/^[0-9a-f]{32}$/',$_GET['confirm'])) ? $_GET['confirm'] : false;
|
||||
if($register_code && $registration = registration_bo::confirm($register_code)) {
|
||||
// Get calendar through link
|
||||
@ -48,17 +49,19 @@ class resources_reserve {
|
||||
$planner_date = mktime(0,0,0,date('m',$data['start']),date('d',$data['start']),date('Y',$data['start']));
|
||||
$readonlys['__ALL__'] = true;
|
||||
$content = array(
|
||||
'resource' => key($data['participant_types']['r']),
|
||||
'resource' => key($data['participant_types']['r'] ?? []),
|
||||
'date' => $data['start'],
|
||||
'time' => $data['start'] - mktime(0,0,0,date('m',$data['start']),date('d',$data['start']),date('Y',$data['start'])),
|
||||
'quantity' => 0
|
||||
);
|
||||
calendar_so::split_status($data['participant_types']['r'][$content['resource']], $content['quantity'],$role);
|
||||
$data['msg']= '<div class="confirm">'.lang('Registration confirmed %1', Api\DateTime::to($data['start'])) .'</div>';
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
$data['msg']= '<div class="confirm">'.lang('Unable to process confirmation.').'</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->tmpl->read('resources.sitemgr_book');
|
||||
|
||||
|
@ -11,14 +11,21 @@
|
||||
|
||||
import path from 'path';
|
||||
import babel from '@babel/core';
|
||||
import { readFileSync, readdirSync, statSync } from "fs";
|
||||
import rimraf from 'rimraf';
|
||||
import { readFileSync, readdirSync, statSync, unlinkSync } from "fs";
|
||||
//import rimraf from 'rimraf';
|
||||
import { minify } from 'terser';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
|
||||
// Best practice: use this
|
||||
//rimraf.sync('./dist/');
|
||||
rimraf.sync('./chunks/');
|
||||
//rimraf.sync('./chunks/');
|
||||
|
||||
// remove only chunks older then 2 days, to allow UI to still load them and not require a reload / F5
|
||||
const rm_older = Date.now() - 48*3600000;
|
||||
readdirSync('./chunks').forEach(name => {
|
||||
const stat = statSync('./chunks/'+name);
|
||||
if (stat.atimeMs < rm_older) unlinkSync('./chunks/'+name);
|
||||
});
|
||||
|
||||
// Turn on minification
|
||||
const do_minify = false;
|
||||
|
@ -38,7 +38,7 @@ $db_backup = new Api\Db\Backup();
|
||||
$asyncservice = new Api\Asyncservice();
|
||||
|
||||
// download a backup, has to be before any output !!!
|
||||
if ($_POST['download'])
|
||||
if (!empty($_POST['download']))
|
||||
{
|
||||
$file = key($_POST['download']);
|
||||
$file = $db_backup->backup_dir.'/'.basename($file); // basename to now allow to change the dir
|
||||
@ -166,7 +166,7 @@ if ($_POST['upload'] && is_array($_FILES['uploaded']) && !$_FILES['uploaded']['e
|
||||
sprintf('%3.1f MB (%d)',$_FILES['uploaded']['size']/(1024*1024),$_FILES['uploaded']['size']).$md5));
|
||||
}
|
||||
// delete a backup
|
||||
if ($_POST['delete'])
|
||||
if (!empty($_POST['delete']))
|
||||
{
|
||||
$file = key($_POST['delete']);
|
||||
$file = $db_backup->backup_dir.'/'.basename($file); // basename to not allow to change the dir
|
||||
@ -174,7 +174,7 @@ if ($_POST['delete'])
|
||||
if (unlink($file)) $setup_tpl->set_var('error_msg',lang("backup '%1' deleted",$file));
|
||||
}
|
||||
// rename a backup
|
||||
if ($_POST['rename'])
|
||||
if (!empty($_POST['rename']))
|
||||
{
|
||||
$file = key($_POST['rename']);
|
||||
$new_name = $_POST['new_name'][$file];
|
||||
@ -190,7 +190,7 @@ if ($_POST['rename'])
|
||||
}
|
||||
}
|
||||
// restore a backup
|
||||
if ($_POST['restore'])
|
||||
if (!empty($_POST['restore']))
|
||||
{
|
||||
$file = key($_POST['restore']);
|
||||
$file = $db_backup->backup_dir.'/'.basename($file); // basename to not allow to change the dir
|
||||
@ -217,12 +217,12 @@ if ($_POST['restore'])
|
||||
}
|
||||
}
|
||||
// create a new scheduled backup
|
||||
if ($_POST['schedule'])
|
||||
if (!empty($_POST['schedule']))
|
||||
{
|
||||
$asyncservice->set_timer($_POST['times'],'db_backup-'.implode(':',$_POST['times']),'admin.admin_db_backup.do_backup','');
|
||||
}
|
||||
// cancel a scheduled backup
|
||||
if (is_array($_POST['cancel']))
|
||||
if (!empty($_POST['cancel']) && is_array($_POST['cancel']))
|
||||
{
|
||||
$id = key($_POST['cancel']);
|
||||
$asyncservice->cancel_timer($id);
|
||||
|
@ -191,17 +191,27 @@ class timesheet_hooks
|
||||
'admin' => False,
|
||||
);
|
||||
$settings['document_dir'] = array(
|
||||
'type' => 'vfs_dirs',
|
||||
'size' => 60,
|
||||
'label' => 'Directory with documents to insert entries',
|
||||
'name' => 'document_dir',
|
||||
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the %1 data inserted.', lang('timesheet')).' '.
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.','ts_title').' '.
|
||||
lang('The following document-types are supported:'). implode(',',Api\Storage\Merge::get_file_extensions()),
|
||||
'type' => 'vfs_dirs',
|
||||
'size' => 60,
|
||||
'label' => 'Directory with documents to insert entries',
|
||||
'name' => 'document_dir',
|
||||
'help' => lang('If you specify a directory (full vfs path) here, %1 displays an action for each document. That action allows to download the specified document with the %1 data inserted.', lang('timesheet')) . ' ' .
|
||||
lang('The document can contain placeholder like {{%1}}, to be replaced with the data.', 'ts_title') . ' ' .
|
||||
lang('The following document-types are supported:') . implode(',', Api\Storage\Merge::get_file_extensions()),
|
||||
'run_lang' => false,
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => '/templates/timesheet',
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => '/templates/timesheet',
|
||||
);
|
||||
$settings[Api\Storage\Merge::PREF_DOCUMENT_FILENAME] = array(
|
||||
'type' => 'taglist',
|
||||
'label' => 'Document download filename',
|
||||
'name' => 'document_download_name',
|
||||
'values' => Api\Storage\Merge::DOCUMENT_FILENAME_OPTIONS,
|
||||
'help' => 'Choose the default filename for downloaded documents.',
|
||||
'xmlrpc' => True,
|
||||
'admin' => False,
|
||||
'default' => 'document',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -207,14 +207,59 @@ class timesheet_merge extends Api\Storage\Merge
|
||||
$i++;
|
||||
}
|
||||
|
||||
echo '<tr><td colspan="4"><h3>'.lang('General fields:')."</h3></td></tr>";
|
||||
echo '<tr><td colspan="4"><h3>' . lang('General fields:') . "</h3></td></tr>";
|
||||
foreach($this->get_common_replacements() as $name => $label)
|
||||
{
|
||||
echo '<tr><td>{{'.$name.'}}</td><td colspan="3">'.$label."</td></tr>\n";
|
||||
echo '<tr><td>{{' . $name . '}}</td><td colspan="3">' . $label . "</td></tr>\n";
|
||||
}
|
||||
|
||||
echo "</table>\n";
|
||||
|
||||
echo $GLOBALS['egw']->framework->footer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of placeholders provided.
|
||||
*
|
||||
* Placeholders are grouped logically. Group key should have a user-friendly translation.
|
||||
*/
|
||||
public function get_placeholder_list($prefix = '')
|
||||
{
|
||||
$placeholders = array(
|
||||
'timesheet' => [],
|
||||
lang('Project') => []
|
||||
) + parent::get_placeholder_list($prefix);
|
||||
|
||||
$fields = array('ts_id' => lang('Timesheet ID')) + $this->bo->field2label + array(
|
||||
'ts_total' => lang('total'),
|
||||
'ts_created' => lang('Created'),
|
||||
'ts_modified' => lang('Modified'),
|
||||
);
|
||||
$group = 'timesheet';
|
||||
foreach($fields as $name => $label)
|
||||
{
|
||||
if(in_array($name, array('custom')))
|
||||
{
|
||||
// dont show them
|
||||
continue;
|
||||
}
|
||||
$marker = $this->prefix($prefix, $name, '{');
|
||||
if(!array_filter($placeholders, function ($a) use ($marker)
|
||||
{
|
||||
return array_key_exists($marker, $a);
|
||||
}))
|
||||
{
|
||||
$placeholders[$group][] = [
|
||||
'value' => $marker,
|
||||
'label' => $label
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Add project placeholders
|
||||
$pm_merge = new projectmanager_merge();
|
||||
$this->add_linked_placeholders($placeholders, lang('Project'), $pm_merge->get_placeholder_list('ts_project'));
|
||||
|
||||
return $placeholders;
|
||||
}
|
||||
}
|
||||
|
@ -80,26 +80,6 @@ class timesheet_tracking extends Api\Storage\Tracking
|
||||
parent::__construct('timesheet');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a notification-config value
|
||||
*
|
||||
* @param string $name
|
||||
* - 'copy' array of email addresses notifications should be copied too, can depend on $data
|
||||
* - 'lang' string lang code for copy mail
|
||||
* - 'sender' string send email address
|
||||
* @param array $data current entry
|
||||
* @param array $old =null old/last state of the entry or null for a new entry
|
||||
* @return mixed
|
||||
*/
|
||||
function get_config($name,$data,$old=null)
|
||||
{
|
||||
$timesheet = $data['ts_id'];
|
||||
|
||||
//$config = $this->timesheet->notification[$timesheet][$name] ? $this->timesheet->notification[$timesheet][$name] : $this->$timesheet->notification[0][$name];
|
||||
//no nitify configert (ToDo)
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subject for a given entry, reimplementation for get_subject in Api\Storage\Tracking
|
||||
*
|
||||
@ -107,9 +87,11 @@ class timesheet_tracking extends Api\Storage\Tracking
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $old
|
||||
* @param boolean $deleted =null can be set to true to let the tracking know the item got deleted or undeleted
|
||||
* @param int|string $receiver numeric account_id or email address
|
||||
* @return string
|
||||
*/
|
||||
function get_subject($data,$old)
|
||||
protected function get_subject($data,$old,$deleted=null,$receiver=null)
|
||||
{
|
||||
return '#'.$data['ts_id'].' - '.$data['ts_title'];
|
||||
}
|
||||
@ -119,9 +101,10 @@ class timesheet_tracking extends Api\Storage\Tracking
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $old
|
||||
* @param int|string $receiver nummeric account_id or email address
|
||||
* @return string
|
||||
*/
|
||||
function get_message($data,$old)
|
||||
protected function get_message($data,$old,$receiver=null)
|
||||
{
|
||||
if (!$data['ts_modified'] || !$old)
|
||||
{
|
||||
|
@ -879,8 +879,8 @@ class timesheet_ui extends timesheet_bo
|
||||
{
|
||||
$etpl = new Etemplate('timesheet.index');
|
||||
|
||||
if ($_GET['msg']) $msg = $_GET['msg'];
|
||||
if ($content['nm']['rows']['delete'])
|
||||
if (!empty($_GET['msg'])) $msg = $_GET['msg'];
|
||||
if (!empty($content['nm']['rows']['delete']))
|
||||
{
|
||||
$ts_id = key($content['nm']['rows']['delete']);
|
||||
if ($this->delete($ts_id))
|
||||
@ -892,13 +892,13 @@ class timesheet_ui extends timesheet_bo
|
||||
$msg = lang('Error deleting the entry!!!');
|
||||
}
|
||||
}
|
||||
if (is_array($content) && isset($content['nm']['rows']['document'])) // handle insert in default document button like an action
|
||||
if (is_array($content) && !empty($content['nm']['rows']['document'])) // handle insert in default document button like an action
|
||||
{
|
||||
$id = @key($content['nm']['rows']['document']);
|
||||
$content['nm']['action'] = 'document';
|
||||
$content['nm']['selected'] = array($id);
|
||||
}
|
||||
if ($content['nm']['action'])
|
||||
if (!empty($content['nm']['action']))
|
||||
{
|
||||
// remove sum-* rows from checked rows
|
||||
$content['nm']['selected'] = array_filter($content['nm']['selected'], function($id)
|
||||
@ -1309,7 +1309,7 @@ class timesheet_ui extends timesheet_bo
|
||||
$GLOBALS['egw']->redirect_link('/admin/index.php', null, 'admin');
|
||||
}
|
||||
}
|
||||
if (isset($content['statis']['delete']))
|
||||
if (!empty($content['statis']['delete']))
|
||||
{
|
||||
$id = key($content['statis']['delete']);
|
||||
if (isset($this->status_labels_config[$id]))
|
||||
|
Loading…
Reference in New Issue
Block a user