feat: config_games XML tag descriptions + jump link; support optional fields; steam_workshop remove adapter terminology

Agent-Logs-Url: https://github.com/GameServerPanel/GSP/sessions/7c776773-fa8f-4f5d-afec-ff62cf7b2bba

Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-05-04 20:34:14 +00:00 committed by GitHub
parent e338eafb36
commit e30c6ac25d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 237 additions and 52 deletions

View file

@ -23,6 +23,7 @@
*/
require_once("server_config_parser.php");
require_once(__DIR__ . "/xml_tag_descriptions.php");
/**
* Safely convert any config value (string, NULL, or array from SimpleXML) to a
@ -264,6 +265,14 @@ function config_games_print_editor_css()
.xml-raw-section textarea{width:100%;min-height:300px;font-family:monospace;font-size:0.85rem;background:#0c0c0c;color:#eee;border:1px solid #3a3a3a;border-radius:4px;padding:8px}
.xml-raw-warning{background:#2d2200;border:1px solid #7a5a00;border-radius:4px;padding:8px 12px;color:#f0c050;font-size:0.85rem;margin-bottom:6px}
.xml-section-header{margin:20px 0 4px;font-size:0.8rem;color:#888;text-transform:uppercase;letter-spacing:0.1em;border-bottom:1px solid #2a2a2a;padding-bottom:4px}
.xml-node__desc{font-size:0.82rem;color:#aaa;background:#0e0e0e;border-left:3px solid #2a4a7a;padding:6px 10px;margin:6px 0 8px;border-radius:0 4px 4px 0}
.xml-node__options{margin:4px 0 4px 12px;padding:0;list-style:disc inside}
.xml-node__options li{margin-bottom:2px}
.xml-node__options code{color:#7eb3f0;background:rgba(30,100,200,0.12);padding:1px 4px;border-radius:3px}
.xml-node__example{display:block;margin-top:4px;color:#888}
.xml-node__example code{color:#a0d0a0;background:rgba(30,150,50,0.1);padding:1px 4px;border-radius:3px}
.xml-jump-link{display:inline-block;margin-bottom:12px;padding:6px 14px;background:#1c6dd0;color:#fff;border-radius:4px;text-decoration:none;font-size:0.9rem}
.xml-jump-link:hover{background:#1f7aec;text-decoration:none}
</style>
CSS;
}
@ -296,12 +305,34 @@ function config_games_render_node(SimpleXMLElement $node, array $ancestors, arra
? "<span class='xml-node__badge xml-node__badge--required'>required</span>"
: "<span class='xml-node__badge xml-node__badge--optional'>optional</span>";
// Look up per-tag description from the descriptions helper.
$tagDescriptions = config_games_tag_descriptions();
$tagDesc = $tagDescriptions[$name] ?? null;
$html = "<div class='{$nodeClass}'>";
$actionId = 'node_action_' . substr(md5($safePath . $index), 0, 8);
$html .= "<div class='xml-node__header'><div><div class='xml-node__title'>{$safeLabel}{$badge}</div><div class='xml-node__path'>{$displayPath}</div></div>";
$html .= "<div class='xml-node__actions'><label for=\"{$actionId}\">Action</label>";
$html .= "<select id=\"{$actionId}\" name=\"nodes[{$safeNodeKey}][action]\"><option value='keep'>Save Changes</option><option value='remove'>Remove Node</option></select>";
$html .= "<button type='submit' name='save_xml' value='1' class='xml-node__apply'>Apply</button></div></div>";
if ($tagDesc !== null) {
$safeDesc = htmlspecialchars($tagDesc['desc'], ENT_QUOTES, 'UTF-8');
$html .= "<div class='xml-node__desc'>{$safeDesc}";
if (!empty($tagDesc['options'])) {
$html .= "<ul class='xml-node__options'>";
foreach ($tagDesc['options'] as $optVal => $optLabel) {
$safeOptVal = htmlspecialchars((string)$optVal, ENT_QUOTES, 'UTF-8');
$safeOptLabel = htmlspecialchars($optLabel, ENT_QUOTES, 'UTF-8');
$html .= "<li><code>{$safeOptVal}</code> &ndash; {$safeOptLabel}</li>";
}
$html .= "</ul>";
}
if (!empty($tagDesc['example'])) {
$safeExample = htmlspecialchars($tagDesc['example'], ENT_QUOTES, 'UTF-8');
$html .= "<span class='xml-node__example'>Example: <code>{$safeExample}</code></span>";
}
$html .= "</div>";
}
$html .= "<div class='xml-node__body'>";
$html .= "<input type='hidden' name=\"nodes[{$safeNodeKey}][path]\" value=\"{$safePath}\">";
$html .= "<input type='hidden' name=\"nodes[{$safeNodeKey}][has_children]\" value=\"" . ($hasChildren ? '1' : '0') . "\">";
@ -667,7 +698,8 @@ function exec_ogp_module() {
</table>\n";
if ( isset($_GET['home_cfg_id']) )
{
{
echo "<p><a class='xml-jump-link' href='#xml-editor-section'>&#x2193; Jump to XML Editor</a></p>";
$home_cfg_id = trim($_GET['home_cfg_id']);
$cfg_info = $db->getGameCfg($home_cfg_id);
@ -721,6 +753,7 @@ function exec_ogp_module() {
print_failure(get_lang_f("error_when_handling_file",$config_file));
} else {
$raw_xml_content = htmlspecialchars(file_get_contents($config_file), ENT_QUOTES, 'UTF-8');
echo "<div id='xml-editor-section'>";
echo "<form action='?m=config_games&amp;home_cfg_id=".$home_cfg_id."' method='post'>";
echo "<input type='hidden' name='home_cfg_id' value='".(int)$home_cfg_id."'>";
echo "<button type='submit' name='save_xml' value='1' class='xml-global-save xml-global-save--top'>".get_lang('save')."</button>";
@ -738,6 +771,7 @@ function exec_ogp_module() {
echo "<div class='xml-actions' style='margin-top:8px'><button type='submit' name='save_xml' value='1' class='xml-global-save'>Save Raw XML</button></div>";
echo "</div>";
echo "</form>";
echo "</div>"; // #xml-editor-section
}
}
}

View file

@ -0,0 +1,155 @@
<?php
/*
* OGP / GSP Config Games XML tag descriptions
*
* Each entry maps an XML element name to an array with:
* 'desc' Short human-readable description of what the element does.
* 'options' (optional) array of allowed values with labels.
* 'example' (optional) A short illustrative value.
*
* Sourced from the OGP XML documentation and the schema in
* modules/config_games/schema_server_config.xml.
*/
/**
* Return the description map for all known game-config XML tags.
*
* @return array<string, array{desc: string, options?: array<string,string>, example?: string}>
*/
function config_games_tag_descriptions(): array
{
return [
'game_key' => [
'desc' => 'Unique lowercase identifier for this game configuration. Used internally to link homes, mods, and Workshop profiles.',
'example' => 'arma3_linux64',
],
'protocol' => [
'desc' => 'Query protocol used to monitor server status.',
'options' => [
'lgsl' => 'LGSL Lightweight Game Server Library',
'gameq' => 'GameQ PHP-based multi-protocol server query',
'' => 'None (server is not queryable)',
],
],
'lgsl_query_name' => [
'desc' => 'LGSL type string identifying the game in the LGSL library (only used when protocol is "lgsl").',
'example' => 'arma3',
],
'gameq_query_name' => [
'desc' => 'GameQ protocol name identifying the game (only used when protocol is "gameq").',
'example' => 'arma3',
],
'installer' => [
'desc' => 'Installer/updater helper used to download and update game server files.',
'options' => [
'steam' => 'SteamCMD / HLDSUpdateTool',
'steamcmd' => 'SteamCMD (explicit)',
'' => 'None (manual installation)',
],
],
'game_name' => [
'desc' => 'Display name shown in the panel UI for this game.',
'example' => 'Arma 3',
],
'server_exec_name' => [
'desc' => 'Filename of the server executable (without path). The panel uses this to detect whether the server process is running.',
'example' => 'arma3server_x64',
],
'query_port' => [
'desc' => 'Port offset added to the main port to obtain the query port used for status monitoring.',
'example' => '1',
],
'cli_template' => [
'desc' => 'Command-line template used to launch the server. Supports placeholder tokens such as %ip%, %port%, %slots%.',
'example' => '-ip=%ip% -port=%port% -maxPlayers=%slots%',
],
'cli_params' => [
'desc' => 'Container for individual <param> child elements that define configurable launch parameters shown in the panel UI.',
],
'reserve_ports' => [
'desc' => 'Additional sequential ports (beyond the main port) that must be reserved for this server instance.',
'example' => '3',
],
'cli_allow_chars' => [
'desc' => 'Extra characters that are safe to include in command-line parameter values (extends the default whitelist).',
'example' => '@_-.',
],
'maps_location' => [
'desc' => 'Path inside the server directory where map files are stored. The panel uses this to populate map-selection dropdowns.',
'example' => 'Maps',
],
'map_list' => [
'desc' => 'Hardcoded comma-separated list of map names when maps cannot be read from disk.',
],
'console_log' => [
'desc' => 'Relative path to the server log file that the panel displays in the console viewer.',
'example' => 'logs/console.log',
],
'exe_location' => [
'desc' => 'Subdirectory within the server installation where the executable resides. Leave empty if the executable is at the root.',
'example' => 'Binaries/Win64',
],
'max_user_amount' => [
'desc' => 'Maximum player slots the panel will allow for this game type. Enforced in the panel UI when creating or editing a server.',
'example' => '64',
],
'control_protocol' => [
'desc' => 'Protocol used to send RCON/admin commands to the running server.',
'options' => [
'rcon' => 'RCON (remote console)',
'rconhl' => 'Half-Life RCON',
'' => 'None',
],
],
'control_protocol_type' => [
'desc' => 'Sub-type or variant that further qualifies the control protocol.',
'example' => 'rcon_password',
],
'mods' => [
'desc' => 'Container element for mod definitions. Child <mod> elements describe each variant of the server configuration.',
],
'replace_texts' => [
'desc' => 'Container for text-replacement rules applied to config files on the server.',
],
'server_params' => [
'desc' => 'Additional fixed parameters appended verbatim to the server launch command.',
],
'custom_fields' => [
'desc' => 'Container for admin-defined extra fields displayed in the server control panel.',
],
'list_players_command' => [
'desc' => 'RCON command sent to the server to retrieve the current player list.',
'example' => 'players',
],
'player_info_regex' => [
'desc' => 'Regular expression used to parse each line of the player-list response into name and other fields.',
],
'player_info' => [
'desc' => 'Defines which capture groups from player_info_regex map to player attributes (name, score, ping, etc.).',
],
'player_commands' => [
'desc' => 'Container for RCON commands that can be executed on individual players (kick, ban, etc.).',
],
'pre_install' => [
'desc' => 'Shell/batch script executed on the agent BEFORE the game server files are installed or updated.',
],
'post_install' => [
'desc' => 'Shell/batch script executed on the agent AFTER the game server files are installed or updated.',
],
'pre_start' => [
'desc' => 'Shell/batch script executed on the agent BEFORE the game server process is started.',
],
'post_start' => [
'desc' => 'Shell/batch script executed on the agent AFTER the game server process has started.',
],
'environment_variables' => [
'desc' => 'Container for environment variables that are set in the server process environment at startup.',
],
'lock_files' => [
'desc' => 'Files that should not be overwritten when updating the server. Paths are relative to the server installation directory.',
],
'configuration_files' => [
'desc' => 'Container listing server configuration files that the panel can display and edit via the config-file editor.',
],
];
}

View file

@ -7,7 +7,7 @@ return [
'button_save' => 'Save settings',
'button_cancel' => 'Back to list',
'label_feature_flag' => 'Enable scheduled Workshop updates for this server',
'label_adapter' => 'Game adapter',
'label_adapter' => 'Game type',
'label_interval' => 'Update interval (minutes)',
'label_interval_hint' => 'Runs on the agent scheduler. Allowed range: 15360 minutes.',
'label_staging_dir' => 'Staging directory (optional)',
@ -17,24 +17,24 @@ return [
'label_mod_import' => 'Workshop IDs list (one "id,@ModName" per line)',
'hint_mod_import' => 'Paste from Modlist.txt or import from a collection. IDs are sanitized automatically.',
'hint_admin_only' => 'Managed by your administrator.',
'adapter_locked_note' => 'This adapter is enforced for the current game type by your administrator.',
'admin_heading_game_mapping' => 'Adapter mapping by game type',
'admin_subheading_game_mapping' => 'Pick which adapter becomes the default whenever a server of that game opens the Workshop UI.',
'adapter_locked_note' => 'The game type for this server is managed by your administrator.',
'admin_heading_game_mapping' => 'Game type mapping',
'admin_subheading_game_mapping' => 'Pick which game configuration becomes the default whenever a server of that game opens the Workshop UI.',
'admin_col_game_key' => 'Game key',
'admin_col_adapter' => 'Adapter',
'admin_col_adapter' => 'Game configuration',
'admin_no_game_keys' => 'No server configuration XML files were detected.',
'admin_heading_adapters' => 'Available adapters',
'admin_heading_adapters' => 'Available game configurations',
'admin_col_key' => 'Key',
'admin_col_mods_dir' => 'Mods directory',
'admin_col_notes' => 'Notes',
'admin_heading_per_game' => 'Per-game adapters',
'admin_subheading_per_game' => 'Each game should have its own adapter XML for Steam Workshop automation.',
'admin_heading_per_game' => 'Per-game Workshop configurations',
'admin_subheading_per_game' => 'Each game should have its own Workshop configuration to control how mods are installed.',
'admin_col_status' => 'Status',
'admin_col_updated' => 'Last updated',
'admin_col_actions' => 'Actions',
'admin_heading_edit_adapter' => 'Editing adapter for %s',
'admin_hint_select_game' => 'Select a game in the table above to edit or create its adapter.',
'status_no_adapter' => 'No adapter defined',
'admin_heading_edit_adapter' => 'Editing Workshop configuration for %s',
'admin_hint_select_game' => 'Select a game in the table above to edit or create its Workshop configuration.',
'status_no_adapter' => 'Not configured',
'status_enabled' => 'Enabled',
'status_disabled' => 'Disabled',
'status_hot_reload' => 'Hot reload ready',
@ -55,15 +55,15 @@ return [
'install_staging' => 'Download to staging only (manual apply)',
'action_queue_for_restart' => 'Queue for restart',
'action_hot_reload_if_supported' => 'Hot reload if the adapter allows it',
'summary_adapter' => 'Adapter',
'summary_adapter' => 'Game',
'summary_interval' => 'Interval',
'summary_mods' => 'Mods',
'summary_last_saved' => 'Last saved',
'summary_hot_reload' => 'Hot reload',
'raw_definition_label' => 'Raw Workshop list',
'message_mappings_saved' => 'Adapter mappings saved.',
'message_adapter_saved' => 'Adapter saved.',
'message_adapter_deleted' => 'Adapter deleted.',
'message_mappings_saved' => 'Game type mappings saved.',
'message_adapter_saved' => 'Game configuration saved.',
'message_adapter_deleted' => 'Game configuration deleted.',
'error_admin_only' => 'Administrator access required.',
'mod_picker_heading' => 'Workshop library',
'mod_picker_hint' => 'Search Steam Workshop and add mods to keep them synced automatically.',
@ -87,15 +87,15 @@ return [
'mod_picker_request_label' => 'Submitting request',
'mod_picker_request_hint' => 'Exact Steam URL preview. The input shows the text that will be submitted.',
'mod_picker_request_input_label' => 'Workshop query preview',
'error_game_key_required' => 'Select a valid game key before editing the adapter.',
'error_adapter_delete_failed' => 'Adapter could not be deleted.',
'error_game_key_required' => 'Select a valid game key before editing the Workshop configuration.',
'error_adapter_delete_failed' => 'Game configuration could not be deleted.',
'button_edit_adapter' => 'Edit',
'button_create_adapter' => 'Create',
'button_delete_adapter' => 'Delete',
'button_save_adapter' => 'Save adapter',
'confirm_delete_adapter' => 'Delete this adapter? Servers mapped to it will fall back to defaults.',
'button_save_adapter' => 'Save game configuration',
'confirm_delete_adapter' => 'Delete this game configuration? Servers mapped to it will fall back to defaults.',
'label_game_key' => 'Game key',
'label_adapter_name' => 'Adapter display name',
'label_adapter_name' => 'Game display name',
'label_adapter_app_id' => 'Steam App ID',
'label_adapter_mods_dir' => 'Mods directory',
'label_adapter_keys_dir' => 'Keys directory (optional)',

View file

@ -52,7 +52,7 @@ declare(strict_types=1);
<span class="sw-game-label__name"><?php echo htmlspecialchars($row['game_name']); ?></span>
<span class="sw-badge sw-badge--app">App ID <?php echo htmlspecialchars($row['app_id']); ?></span>
<?php if ($row['exists']): ?>
<span class="sw-badge sw-badge--custom"><?php echo htmlspecialchars($lang['badge_custom_xml'] ?? 'Custom XML'); ?></span>
<span class="sw-badge sw-badge--custom"><?php echo htmlspecialchars($lang['badge_custom_xml'] ?? 'Custom config'); ?></span>
<?php endif; ?>
</div>
<div class="sw-game-variants">
@ -61,7 +61,7 @@ declare(strict_types=1);
<?php endforeach; ?>
</div>
</div>
<small class="sw-game-label__hint"><?php echo htmlspecialchars($lang['admin_hint_inline_edit'] ?? 'Use the toggle to edit the XML inline.'); ?></small>
<small class="sw-game-label__hint"><?php echo htmlspecialchars($lang['admin_hint_inline_edit'] ?? 'Use the toggle to configure this game inline.'); ?></small>
</td>
<td>
<select form="sw-mapping-form" name="mapping[<?php echo htmlspecialchars($groupKey); ?>]">
@ -86,13 +86,13 @@ declare(strict_types=1);
</td>
<td class="sw-actions">
<button type="button" class="btn secondary js-toggle-adapter" data-target="<?php echo htmlspecialchars($formId); ?>" aria-expanded="<?php echo $isOpen ? 'true' : 'false'; ?>">
<?php echo htmlspecialchars($row['exists'] ? ($lang['button_edit_adapter'] ?? 'Edit adapter') : ($lang['button_create_adapter'] ?? 'Create adapter')); ?>
<?php echo htmlspecialchars($row['exists'] ? ($lang['button_edit_adapter'] ?? 'Edit') : ($lang['button_create_adapter'] ?? 'Create')); ?>
</button>
<?php if ($row['exists']): ?>
<form method="post" class="sw-inline-delete">
<input type="hidden" name="admin_action" value="delete_adapter">
<input type="hidden" name="game_key" value="<?php echo htmlspecialchars($primaryKey); ?>">
<button type="submit" class="btn danger" onclick="return confirm('<?php echo htmlspecialchars($lang['confirm_delete_adapter'] ?? 'Delete this adapter?'); ?>');">
<button type="submit" class="btn danger" onclick="return confirm('<?php echo htmlspecialchars($lang['confirm_delete_adapter'] ?? 'Delete this game configuration?'); ?>');">
<?php echo htmlspecialchars($lang['button_delete_adapter'] ?? 'Delete'); ?>
</button>
</form>
@ -111,7 +111,7 @@ declare(strict_types=1);
<input type="text" value="<?php echo htmlspecialchars($form['game_key']); ?>" readonly>
</label>
<label>
<?php echo htmlspecialchars($lang['label_adapter_name'] ?? 'Adapter display name'); ?>
<?php echo htmlspecialchars($lang['label_adapter_name'] ?? 'Game display name'); ?>
<input type="text" name="adapter[name]" value="<?php echo htmlspecialchars($form['name']); ?>" required>
</label>
<label>
@ -143,7 +143,7 @@ declare(strict_types=1);
</label>
<div class="sw-form__actions">
<button class="btn primary" type="submit"><?php echo htmlspecialchars($lang['button_save_adapter'] ?? 'Save adapter'); ?></button>
<button class="btn primary" type="submit"><?php echo htmlspecialchars($lang['button_save_adapter'] ?? 'Save game configuration'); ?></button>
<button type="button" class="btn js-toggle-adapter" data-target="<?php echo htmlspecialchars($formId); ?>"><?php echo htmlspecialchars($lang['button_cancel'] ?? 'Cancel'); ?></button>
</div>
</form>
@ -159,12 +159,12 @@ declare(strict_types=1);
<button class="btn primary" type="submit" form="sw-mapping-form"><?php echo htmlspecialchars($lang['button_save']); ?></button>
</div>
<h3><?php echo htmlspecialchars($lang['admin_heading_adapters'] ?? 'Available adapters'); ?></h3>
<h3><?php echo htmlspecialchars($lang['admin_heading_adapters'] ?? 'Available game configurations'); ?></h3>
<table class="table sw-mods__table">
<thead>
<tr>
<th><?php echo htmlspecialchars($lang['admin_col_key'] ?? 'Key'); ?></th>
<th><?php echo htmlspecialchars($lang['summary_adapter']); ?></th>
<th><?php echo htmlspecialchars($lang['summary_adapter'] ?? 'Game'); ?></th>
<th>Steam App ID</th>
<th><?php echo htmlspecialchars($lang['admin_col_mods_dir'] ?? 'Mods Dir'); ?></th>
<th><?php echo htmlspecialchars($lang['summary_hot_reload']); ?></th>

View file

@ -21,10 +21,10 @@ $currentAdapterName = $adapterOptions[$formConfig['adapter_key']] ?? strtoupper(
</label>
<label>
<span><?php echo htmlspecialchars($lang['label_adapter']); ?></span>
<span><?php echo htmlspecialchars($lang['label_adapter'] ?? 'Game type'); ?></span>
<?php if ($adapterLocked): ?>
<input type="text" value="<?php echo htmlspecialchars($currentAdapterName); ?>" disabled />
<small><?php echo htmlspecialchars($lang['adapter_locked_note'] ?? 'This adapter is managed by the administrator.'); ?></small>
<small><?php echo htmlspecialchars($lang['adapter_locked_note'] ?? 'The game type for this server is managed by the administrator.'); ?></small>
<?php else: ?>
<select name="workshop[adapter_key]">
<?php foreach ((array)$adapterOptions as $key => $label): ?>

View file

@ -36,10 +36,17 @@ function exec_ogp_module() {
if(isset($_POST["submit"])){
$email = $_POST["email"];
$email = isset($_POST["email"]) ? trim($_POST["email"]) : '';
$gameserver = $_POST['gameserver'];
$subject = get_lang('support').": ".$_POST["subject"];
$message = $_POST["message"];
$subjectRaw = isset($_POST["subject"]) ? trim($_POST["subject"]) : '';
$subject = get_lang('support') . ($subjectRaw !== '' ? ": " . $subjectRaw : '');
$message = isset($_POST["message"]) ? trim($_POST["message"]) : '';
if ($message === '') {
$errMsg = get_lang('message_must_be_filled_out');
$errTitle = get_lang('error');
echo "<script>$(document).ready(function(){\$('#dialog').html('<p><img src=\"modules/support/images/error.png\">" . htmlspecialchars($errMsg, ENT_QUOTES) . "</p>').attr('title', '" . htmlspecialchars($errTitle, ENT_QUOTES) . "').dialog();});</script>";
} else {
//TICKET SUBMITTED, POST ON DISCORD and log
//logger
@ -59,8 +66,8 @@ if (!empty($webhook)) {
}
//end discord
$content = get_lang_f('support_email_content', $user['users_login'], $email, $gameserver, $message);
if( mymail($email, $subject, $content, $settings, $user['users_login']) == TRUE )
$content = get_lang_f('support_email_content', $user['users_login'], $email, $gameserver, $message);
if ($email === '' || mymail($email, $subject, $content, $settings, $user['users_login']) == TRUE)
{
?>
<script type="text/javascript">
@ -70,7 +77,8 @@ if (!empty($webhook)) {
</script>
<?php
}
} // End else
} // end else (message not empty)
} // end if submit
echo '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />';
echo "<h2>".get_lang('support')."</h2>";
echo '
@ -95,7 +103,7 @@ if (!empty($webhook)) {
if(!isset($user['users_email']) or $user['users_email'] == "")
{
echo get_lang('email_address').':
echo get_lang('email_address').' <em>(optional)</em>:
<br />
<input type="text" name="email" id="email" style="width: 250px;" />
<br />
@ -106,7 +114,7 @@ if (!empty($webhook)) {
echo '<input type="hidden" name="email" id="email" value="'.$user['users_email'].'" />';
}
echo get_lang('subject').':
echo get_lang('subject').' <em>(optional)</em>:
<br />
<input type="text" name="subject" id="subject" style="width: 250px;" />
<br />
@ -123,20 +131,8 @@ if (!empty($webhook)) {
<script type="text/javascript">
function validateForm()
{
var $email=document.forms["contactForm"]["email"].value;
var $subject=document.forms["contactForm"]["subject"].value;
var $message=document.forms["contactForm"]["message"].value;
if ($email==null || $email=="")
{
$('#dialog').html('<p><img src="modules/support/images/error.png" ><?php print_lang('email_must_be_filled_out'); ?></p>').attr('title', '<?php print_lang('error'); ?>').dialog();
return false;
}
else if ($subject==null || $subject=="")
{
$('#dialog').html('<p><img src="modules/support/images/error.png" ><?php print_lang('subject_must_be_filled_out'); ?></p>').attr('title', '<?php print_lang('error'); ?>').dialog();
return false;
}
else if ($message==null || $message=="")
if ($message==null || $message=="")
{
$('#dialog').html('<p><img src="modules/support/images/error.png" ><?php print_lang('message_must_be_filled_out'); ?></p>').attr('title', '<?php print_lang('error'); ?>').dialog();
return false;