Placeholders list common UI

Should reduce maintenance by using a common UI to show placeholders based on the placeholder list
This commit is contained in:
nathan 2021-10-12 14:18:18 -06:00
parent 809e718f1a
commit 9e2bb05ded
7 changed files with 253 additions and 350 deletions

View File

@ -155,119 +155,6 @@ class Merge extends Api\Storage\Merge
return $replacements;
}
/**
* Generate table with replacements for the preferences
*
*/
public function show_replacements()
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('Addressbook').' - '.lang('Replacements for inserting contacts into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = (bool)$_GET['nonavbar'];
ob_start();
echo "<table width='90%' align='center'>\n";
echo '<tr><td colspan="4"><h3>'.lang('Contact fields:')."</h3></td></tr>";
$n = 0;
foreach($this->contacts->contact_fields as $name => $label)
{
if (in_array($name,array('tid','label','geo'))) continue; // dont show them, as they are not used in the UI atm.
if (in_array($name,array('email','org_name','tel_work','url')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.$label.'</td>';
if($name == 'cat_id')
{
if ($n&1) echo "</tr>\n";
echo '<td>{{categories}}</td><td>'.lang('Category path').'</td>';
$n++;
}
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach($this->contacts->customfields as $name => $field)
{
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>'.lang('General fields:')."</h3></td></tr>";
foreach(array(
'link' => lang('HTML link to the current record'),
'links' => lang('Titles of any entries linked to the current record, excluding attached files'),
'attachments' => lang('List of files linked to the current record'),
'links_attachments' => lang('Links and attached files'),
'links/[appname]' => lang('Links to specified application. Example: {{links/infolog}}'),
'date' => lang('Date'),
'user/n_fn' => lang('Name of current user, all other contact fields are valid too'),
'user/account_lid' => lang('Username'),
'pagerepeat' => lang('For serial letter use this tag. Put the content, you want to repeat between two Tags.'),
'label' => lang('Use this tag for addresslabels. Put the content, you want to repeat, between two tags.'),
'labelplacement' => lang('Tag to mark positions for address labels'),
'IF fieldname' => lang('Example {{IF n_prefix~Mr~Hello Mr.~Hello Ms.}} - search the field "n_prefix", for "Mr", if found, write Hello Mr., else write Hello Ms.'),
'NELF' => lang('Example {{NELF role}} - if field role is not empty, you will get a new line with the value of field role'),
'NENVLF' => lang('Example {{NENVLF role}} - if field role is not empty, set a LF without any value of the field'),
'LETTERPREFIX' => lang('Example {{LETTERPREFIX}} - Gives a letter prefix without double spaces, if the title is empty for example'),
'LETTERPREFIXCUSTOM' => lang('Example {{LETTERPREFIXCUSTOM n_prefix title n_family}} - Example: Mr Dr. James Miller'),
) as $name => $label)
{
echo '<tr><td>{{'.$name.'}}</td><td colspan="3">'.$label."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>'.lang('EPL Only').":</h3></td></tr>";
echo '<tr><td>{{share}}</td><td colspan="3">'.lang('Public sharing URL')."</td></tr>\n";
Api\Translation::add_app('calendar');
echo '<tr><td colspan="4"><h3>'.lang('Calendar fields:')." # = 1, 2, ..., 20, -1</h3></td></tr>";
foreach(array(
'title' => lang('Title'),
'description' => lang('Description'),
'participants' => lang('Participants'),
'location' => lang('Location'),
'start' => lang('Start').': '.lang('Date').'+'.lang('Time'),
'startday' => lang('Start').': '.lang('Weekday'),
'startdate'=> lang('Start').': '.lang('Date'),
'starttime'=> lang('Start').': '.lang('Time'),
'end' => lang('End').': '.lang('Date').'+'.lang('Time'),
'endday' => lang('End').': '.lang('Weekday'),
'enddate' => lang('End').': '.lang('Date'),
'endtime' => lang('End').': '.lang('Time'),
'duration' => lang('Duration'),
'category' => lang('Category'),
'priority' => lang('Priority'),
'updated' => lang('Updated'),
'recur_type' => lang('Repetition'),
'access' => lang('Access').': '.lang('public').', '.lang('private'),
'owner' => lang('Owner'),
) as $name => $label)
{
if(in_array($name, array('start',
'end')) && $n & 1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if(!($n & 1))
{
echo '<tr>';
}
echo '<td>{{calendar/#/' . $name . '}}</td><td>' . $label . '</td>';
if($n & 1)
{
echo "</tr>\n";
}
$n++;
}
echo "</table>\n";
$GLOBALS['egw']->framework->render(ob_get_clean());
}
/**
* Get a list of placeholders provided.
*
@ -352,10 +239,58 @@ class Merge extends Api\Storage\Merge
'label' => "Formatted private address"
];
$placeholders['EPL only'][] = [
'value' => $this->prefix($prefix, 'share', '{'),
'label' => 'Public sharing URL'
];
$this->add_customfield_placeholders($placeholders, $prefix);
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
$this->add_calendar_placeholders($placeholders, $prefix);
}
return $placeholders;
}
protected function add_calendar_placeholders(&$placeholders, $prefix)
{
Api\Translation::add_app('calendar');
// NB: The -1 is actually 1, a non-breaking hyphen to avoid UI issues where we split on -
$group = lang('Calendar fields:') . " # = 1, 2, ..., 20, 1";
foreach(array(
'title' => lang('Title'),
'description' => lang('Description'),
'participants' => lang('Participants'),
'location' => lang('Location'),
'start' => lang('Start') . ': ' . lang('Date') . '+' . lang('Time'),
'startday' => lang('Start') . ': ' . lang('Weekday'),
'startdate' => lang('Start') . ': ' . lang('Date'),
'starttime' => lang('Start') . ': ' . lang('Time'),
'end' => lang('End') . ': ' . lang('Date') . '+' . lang('Time'),
'endday' => lang('End') . ': ' . lang('Weekday'),
'enddate' => lang('End') . ': ' . lang('Date'),
'endtime' => lang('End') . ': ' . lang('Time'),
'duration' => lang('Duration'),
'category' => lang('Category'),
'priority' => lang('Priority'),
'updated' => lang('Updated'),
'recur_type' => lang('Repetition'),
'access' => lang('Access') . ': ' . lang('public') . ', ' . lang('private'),
'owner' => lang('Owner'),
) as $name => $label)
{
$placeholders[$group][] = array(
'value' => $this->prefix(($prefix ? $prefix . '/' : '') . 'calendar/#', $name, '{'),
'label' => $label
);
}
}
/**
* Get insert-in-document action with optional default document on top
*

View File

@ -13,6 +13,7 @@
namespace EGroupware\Api\Storage;
use ceLTIc\LTI\Service\Result;
use DOMDocument;
use EGroupware\Api;
use EGroupware\Api\Vfs;
@ -433,11 +434,11 @@ abstract class Merge
*
* Uses egw_link system to get link titles
*
* @param app Name of current app
* @param id ID of current entry
* @param only_app Restrict links to only given application
* @param exclude Exclude links to these applications
* @param style String One of:
* @param string app Name of current app
* @param string id ID of current entry
* @param string only_app Restrict links to only given application
* @param string[] exclude Exclude links to these applications
* @param string style One of:
* 'title' - plain text, just the title of the link
* 'link' - URL to the entry
* 'href' - HREF tag wrapped around the title
@ -1785,7 +1786,7 @@ abstract class Merge
/**
* Get the replacements for any entry specified by app & id
*
* @param stribg $app
* @param string $app
* @param string $id
* @param string $content
* @return array
@ -1846,10 +1847,10 @@ abstract class Merge
/**
* Process special flags, such as IF or NELF
*
* @param content Text to be examined and changed
* @param replacements array of markers => replacement
* @param string content Text to be examined and changed
* @param array replacements array of markers => replacement
*
* @return changed content
* @return string changed content
*/
private function process_commands($content, $replacements)
{
@ -2031,7 +2032,7 @@ abstract class Merge
* @param array $ids array with contact id(s)
* @param string $name ='' name to use for downloaded document
* @param string $dirs comma or whitespace separated directories, used if $document is a relative path
* @param Array $header File name, mime & filesize if you want to send a header
* @param array $header File name, mime & filesize if you want to send a header
*
* @return string with error-message on error
* @throws Api\Exception
@ -2546,8 +2547,8 @@ abstract class Merge
* back to the client like the other documents. Merging for a single selected
* contact opens a compose window, multiple contacts just sends.
*
* @param Array &$action Action to be modified for mail
* @param Array $file Array of information about the document from Api\Vfs::find
* @param array &$action Action to be modified for mail
* @param array $file Array of information about the document from Api\Vfs::find
* @return void
*/
private static function document_mail_action(array &$action, $file)
@ -2580,8 +2581,8 @@ abstract class Merge
* Set up a document action so the generated file is saved and opened in
* the collabora editor (if collabora is available)
*
* @param Array &$action Action to be modified for editor
* @param Array $file Array of information about the document from Api\Vfs::find
* @param array &$action Action to be modified for editor
* @param array $file Array of information about the document from Api\Vfs::find
* @return void
*/
private static function document_editable_action(array &$action, $file)
@ -3198,4 +3199,80 @@ abstract class Merge
return $settings;
}
/**
* Show replacement placeholders for the app
*
* Generates a page that shows all the available placeholders for this appliction. By default,
* we have all placeholders generated in get_placeholder_list() (including any custom fields)
* as well as the common and current user placeholders.
*
* By overridding show_replacements_hook(), extending classes can override without having to
* re-implement everything.
*/
public function show_replacements()
{
$template_name = 'api.show_replacements';
$content = $sel_options = $readonlys = $preserve = array();
$content['appname'] = $this->get_app();
$content['placeholders'] = $this->remap_replacement_list($this->get_placeholder_list());
$content['extra'] = array();
$content['common'] = $this->remap_replacement_list($this->get_common_placeholder_list());
$content['user'] = $this->remap_replacement_list($this->get_user_placeholder_list());
$this->show_replacements_hook($template_name, $content, $sel_options, $readonlys);
$etemplate = new Api\Etemplate($template_name);
$etemplate->exec('filemanager.filemanager_ui.file', $content, $sel_options, $readonlys, $preserve, 2);
}
/**
* Helper function for show_replacements() to change the output of get_placeholder_list() into somethig
* more suited for etemplate repeating rows.
*
* @param $list
* @return array
*/
protected function remap_replacement_list($list, $title_prefix = '')
{
$new_list = [];
foreach($list as $group_title => $group_placeholders)
{
if(is_array($group_placeholders) && !array_key_exists('0', $group_placeholders))
{
// Limit how far we go through linked entries
if($title_prefix)
{
continue;
}
$new_list = array_merge($new_list, $this->remap_replacement_list($group_placeholders, $group_title));
}
else
{
$new_list[] = [
'title' => ($title_prefix ? $title_prefix . ': ' : '') . $group_title,
'placeholders' => $group_placeholders
];
}
}
return $new_list;
}
/**
* Hook for extending apps to customise the replacements UI without having to override the whole method.
*
* This can include detailed descriptions or instructions, documentation of tables and custom stuff
* Set $content['extra_template'] to a template ID with extra descriptions or instructions and it will be
* added into the main template.
*
* @param string $template_name
* @param $content
* @param $sel_options
* @param $readonlys
*/
protected function show_replacements_hook(&$template_name, &$content, &$sel_options, &$readonlys)
{
}
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2//EN" "http://www.egroupware.org/etemplate2.dtd">
<!-- $Id$ -->
<overlay>
<template id="api.show_replacements.placeholder_list">
<description id="title" class="title"/>
<grid id="placeholders" width="100%">
<columns>
<column width="30%"/>
<column/>
</columns>
<rows>
<row>
<description id="${row}[value]"/>
<description id="${row}[label]"/>
</row>
</rows>
</grid>
</template>
<template id="api.show_replacements" template="" lang="" group="0" version="21.1.001">
<vbox>
<description value="Placeholders" class="group title"/>
<box id="placeholders">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
<template template="@extra_template"/>
<details title="Common">
<description value="Common" class="group title"/>
<box id="common">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
</details>
<details title="Current user">
<description value="Current user" class="group title"/>
<box id="user">
<box id="${row}">
<template template="api.show_replacements.placeholder_list"/>
</box>
</box>
</details>
</vbox>
<styles>
.et2_details_title, .title {
display: inline-block;
font-weight: bold;
font-size: 130%;
margin-top: 2ex;
}
.et2_details_title, .group {
margin-top: 3ex;
font-size: 150%;
}
/** Cosmetics **/
#api-show_replacements_title:first-letter, .title {
text-transform: capitalize;
}
</styles>
</template>
</overlay>

View File

@ -260,73 +260,16 @@ class filemanager_merge extends Api\Storage\Merge
}
/**
* Generate table with replacements for the Api\Preferences
* Hook for extending apps to customise the replacements UI without having to override the whole method
*
* @param string $template_name
* @param $content
* @param $sel_options
* @param $readonlys
*/
public function show_replacements()
protected function show_replacements_hook(&$template_name, &$content, &$sel_options, &$readonlys)
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('filemanager').' - '.lang('Replacements for inserting entries into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = false;
echo $GLOBALS['egw']->framework->header();
echo "<table width='90%' align='center'>\n";
echo '<tr><td colspan="4"><h3>'.lang('Filemanager fields:')."</h3></td></tr>";
$n = 0;
$fields = array(
'name' => 'name',
'path' => 'Absolute path',
'rel_path' => 'Path relative to current directory',
'folder' => 'Containing folder',
'folder_file' => 'Containing folder and file name',
'url' => 'url',
'webdav_url' => 'External path using webdav',
'link' => 'Clickable link to file',
'comment' => 'comment',
'mtime' => 'modified',
'ctime' => 'created',
'mime' => 'Type',
'hsize' => 'Size',
'size' => 'Size (in bytes)',
);
foreach($fields as $name => $label)
{
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.lang($label).'</td>';
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach(Api\Storage\Customfields::get('filemanager') as $name => $field)
{
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>'.lang('Application fields').":</h3></td></tr>";
echo '<tr><td colspan="4">'.lang('For files linked to an application entry (inside /apps/appname/id/) the placeholders for that application are also available. See the specific application for a list of available placeholders.').'</td></tr>';
echo '<tr><td colspan="4"><h3>'.lang('General fields:')."</h3></td></tr>";
foreach(array(
'date' => lang('Date'),
'user/n_fn' => lang('Name of current user, all other contact fields are valid too'),
'user/account_lid' => lang('Username'),
'pagerepeat' => lang('For serial letter use this tag. Put the content, you want to repeat between two Tags.'),
'label' => lang('Use this tag for addresslabels. Put the content, you want to repeat, between two tags.'),
'labelplacement' => lang('Tag to mark positions for address labels'),
'IF fieldname' => lang('Example {{IF n_prefix~Mr~Hello Mr.~Hello Ms.}} - search the field "n_prefix", for "Mr", if found, write Hello Mr., else write Hello Ms.'),
'NELF' => lang('Example {{NELF role}} - if field role is not empty, you will get a new line with the value of field role'),
'NENVLF' => lang('Example {{NENVLF role}} - if field role is not empty, set a LF without any value of the field'),
'LETTERPREFIX' => lang('Example {{LETTERPREFIX}} - Gives a letter prefix without double spaces, if the title is empty for example'),
'LETTERPREFIXCUSTOM' => lang('Example {{LETTERPREFIXCUSTOM n_prefix title n_family}} - Example: Mr Dr. James Miller'),
) as $name => $label)
{
echo '<tr><td>{{' . $name . '}}</td><td colspan="3">' . $label . "</td></tr>\n";
}
echo "</table>\n";
echo $GLOBALS['egw']->framework->footer();
$content['extra_template'] = 'filemanager.replacements';
}
/**

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2//EN" "http://www.egroupware.org/etemplate2.dtd">
<!-- This template adds the extra bits to the replacements list UI -->
<overlay>
<template id="filemanager.replacements">
<vbox>
<description class="title" value="Application fields"/>
<description
value="For files linked to an application entry (inside /apps/appname/id/) the placeholders for that application are also available. See the specific application for a list of available placeholders."/>
</vbox>
</template>
</overlay>

View File

@ -198,98 +198,20 @@ class infolog_merge extends Api\Storage\Merge
return $info;
}
/**
* Generate table with replacements for the Api\Preferences
*
*/
public function show_replacements()
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('infolog').' - '.lang('Replacements for inserting entries into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = false;
echo $GLOBALS['egw']->framework->header();
echo "<table width='90%' align='center'>\n";
echo '<tr><td colspan="4"><h3>'.lang('Infolog fields:')."</h3></td></tr>";
$n = 0;
$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');
foreach($fields as $name => $label)
{
if (in_array($name,array('custom'))) continue; // dont show them
if (in_array($name,array('info_subject', 'info_des')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.lang($label).'</td>';
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
$contact_custom = false;
foreach($this->bo->customfields as $name => $field)
{
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label'].($field['type'] == 'select-account' ? '*':'')."</td></tr>\n";
if($field['type'] == 'select-account') $contact_custom = true;
}
if($contact_custom)
{
echo '<tr><td /><td colspan="3">* '.lang('Addressbook placeholders available'). '</td></tr>';
}
echo '<tr><td colspan="4"><h3>'.lang('Parent').":</h3></td></tr>";
echo '<tr><td>{{info_id_parent/info_subject}}</td><td colspan="3">'.lang('All other %1 fields are valid',lang('infolog'))."</td></tr>\n";
echo '<tr><td colspan="4"><h3>'.lang('Contact fields').':</h3></td></tr>';
$i = 0;
foreach($this->contacts->contact_fields as $name => $label)
{
if (in_array($name,array('tid','label','geo'))) continue; // dont show them, as they are not used in the UI atm.
if (in_array($name,array('email','org_name','tel_work','url')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$i++;
}
if (!($i&1)) echo '<tr>';
echo '<td>{{info_contact/'.$name.'}}</td><td>'.$label.'</td>';
if ($i&1) echo "</tr>\n";
$i++;
}
echo '<tr><td colspan="4">'.lang('Owner contact fields also available under info_owner/...').'</td></tr>';
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach($this->contacts->customfields as $name => $field)
{
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>";
foreach($this->get_common_replacements() as $name => $label)
{
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);
$placeholders = array(
'infolog' => [],
lang('parent') => [],
lang($tracking->field2label['info_from']) => []
) + parent::get_placeholder_list($prefix);
$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';
$group = 'infolog';
foreach($fields as $name => $label)
{
if(in_array($name, array('custom')))
@ -310,15 +232,22 @@ class infolog_merge extends Api\Storage\Merge
}
}
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
// 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($this->prefix($prefix, 'info_contact'));
$this->add_linked_placeholders($placeholders, lang($tracking->field2label['info_from']), $contact);
// Add parent placeholders
$this->add_linked_placeholders(
$placeholders,
lang('parent'),
$this->get_placeholder_list(($prefix ? $prefix . '/' : '') . 'info_id_parent')
);
}
return $placeholders;
}
}

View File

@ -156,68 +156,6 @@ class timesheet_merge extends Api\Storage\Merge
return $info;
}
/**
* Generate table with replacements for the Api\Preferences
*
*/
public function show_replacements()
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('timesheet').' - '.lang('Replacements for inserting entries into documents');
$GLOBALS['egw_info']['flags']['nonavbar'] = false;
echo $GLOBALS['egw']->framework->header();
echo "<table width='90%' align='center'>\n";
echo '<tr><td colspan="4"><h3>'.lang('Timesheet fields:')."</h3></td></tr>";
$n = 0;
$fields = array('ts_id' => lang('Timesheet ID')) + $this->bo->field2label + array(
'ts_total' => lang('total'),
'ts_created' => lang('Created'),
'ts_modified' => lang('Modified'),
);
foreach($fields as $name => $label)
{
if (in_array($name,array('pl_id','customfields'))) continue; // dont show them
if (in_array($name,array('ts_title', 'ts_description')) && $n&1) // main values, which should be in the first column
{
echo "</tr>\n";
$n++;
}
if (!($n&1)) echo '<tr>';
echo '<td>{{'.$name.'}}</td><td>'.lang($label).'</td>';
if ($n&1) echo "</tr>\n";
$n++;
}
echo '<tr><td colspan="4"><h3>'.lang('Custom fields').":</h3></td></tr>";
foreach($this->bo->customfields as $name => $field)
{
echo '<tr><td>{{#'.$name.'}}</td><td colspan="3">'.$field['label']."</td></tr>\n";
}
echo '<tr><td colspan="4"><h3>'.lang('Project fields').':</h3></td></tr>';
$pm_merge = new projectmanager_merge();
$i = 0;
foreach($pm_merge->projectmanager_fields as $name => $label)
{
if (!($i&1)) echo '<tr>';
echo '<td>{{ts_project/'.$name.'}}</td><td>'.$label.'</td>';
if ($i&1) echo "</tr>\n";
$i++;
}
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 "</table>\n";
echo $GLOBALS['egw']->framework->footer();
}
/**
* Get a list of placeholders provided.
*
@ -256,10 +194,14 @@ class timesheet_merge extends Api\Storage\Merge
}
}
// Don't add any linked placeholders if we're not at the top level
// This avoids potential recursion
if(!$prefix)
{
// Add project placeholders
$pm_merge = new projectmanager_merge();
$this->add_linked_placeholders($placeholders, lang('Project'), $pm_merge->get_placeholder_list('ts_project'));
}
return $placeholders;
}
}