diff --git a/Panel/modules/addonsmanager/addons_manager.php b/Panel/modules/addonsmanager/addons_manager.php
index c4682553..d818cb64 100644
--- a/Panel/modules/addonsmanager/addons_manager.php
+++ b/Panel/modules/addonsmanager/addons_manager.php
@@ -81,6 +81,7 @@ function exec_ogp_module() {
'url' => $fields['url'],
'path' => $fields['path'],
'workshop_item_id' => $fields['workshop_item_id'],
+ 'workshop_app_id' => $fields['workshop_app_id'],
'target_path_template' => $fields['target_path_template'],
'post_script' => $fields['post_script'],
'config_edit_rule' => $fields['config_edit_rule'],
diff --git a/Panel/modules/addonsmanager/module.php b/Panel/modules/addonsmanager/module.php
index eac60521..9b2161e0 100644
--- a/Panel/modules/addonsmanager/module.php
+++ b/Panel/modules/addonsmanager/module.php
@@ -25,13 +25,16 @@
* (allow_user_workshop_ids, max_workshop_ids, required_workshop_ids,
* blocked_workshop_ids); add content_id column to
* server_content_workshop so user installs link to their template
+ * 7 – add Phase 1 Workshop runtime tracking columns to
+ * server_content_workshop (install_path, install_strategy, enabled,
+ * load_order)
*
*/
// Module general information
$module_title = "Server Content Manager";
-$module_version = "2.4";
-$db_version = 6;
+$module_version = "2.5";
+$db_version = 7;
$module_required = TRUE;
$module_menus = array(
array( 'subpage' => 'addons_manager', 'name' => 'Server Content Manager', 'group' => 'admin' )
@@ -260,4 +263,31 @@ $install_queries[5] = array(
return true;
},
);
+// ── db_version 7 : Workshop Phase 1 runtime tracking columns ────────────────
+$install_queries[6] = array(
+ function ($db) {
+ $prefix = OGP_DB_PREFIX;
+ $table = $db->realEscapeSingle($prefix . 'server_content_workshop');
+ $columns = array(
+ 'install_path' => "VARCHAR(512) NULL AFTER `title`",
+ 'install_strategy' => "VARCHAR(64) NULL AFTER `install_path`",
+ 'enabled' => "TINYINT(1) NOT NULL DEFAULT 1 AFTER `install_strategy`",
+ 'load_order' => "INT NOT NULL DEFAULT 0 AFTER `enabled`",
+ );
+ foreach ($columns as $col => $definition) {
+ $check = $db->resultQuery(
+ "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = '{$table}'
+ AND COLUMN_NAME = '" . $db->realEscapeSingle($col) . "'"
+ );
+ if (empty($check)) {
+ if (!$db->query("ALTER TABLE `{$prefix}server_content_workshop` ADD COLUMN `{$col}` {$definition}")) {
+ return false;
+ }
+ }
+ }
+ return true;
+ },
+);
?>
diff --git a/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_linux.sh b/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_linux.sh
index e63912e5..2f29207d 100755
--- a/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_linux.sh
+++ b/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_linux.sh
@@ -69,6 +69,19 @@ def ensure_under_home(path_value):
return target
+def safe_folder_name(value, fallback):
+ text = str(value or '').strip()
+ if not text or '..' in text or '/' in text or '\\' in text or '\x00' in text:
+ return fallback
+ return text
+
+
+def truthy(value):
+ if isinstance(value, bool):
+ return value
+ return str(value).strip().lower() in ('1', 'yes', 'true', 'on')
+
+
def resolve_steamcmd(explicit_path=''):
candidates = []
explicit_path = str(explicit_path or '').strip()
@@ -104,6 +117,26 @@ def sync_copy(src, dst):
shutil.copy2(source_entry, target_entry)
+def copy_bikeys(mod_path, keys_target, workshop_id):
+ if not os.path.isdir(mod_path):
+ return 0
+ keys_target = ensure_under_home(keys_target)
+ os.makedirs(keys_target, exist_ok=True)
+ copied = 0
+ for root, dirs, files in os.walk(mod_path):
+ for filename in files:
+ if not filename.lower().endswith('.bikey'):
+ continue
+ source_file = os.path.join(root, filename)
+ target_file = os.path.join(keys_target, filename)
+ shutil.copy2(source_file, target_file)
+ copied += 1
+ log(f"workshop_id={workshop_id} key={target_file}", 'Copying Key')
+ if copied == 0:
+ log(f"workshop_id={workshop_id} no .bikey files found; continuing", 'Copying Key')
+ return copied
+
+
try:
with open(manifest_path, 'r', encoding='utf-8') as handle:
manifest = json.load(handle)
@@ -122,13 +155,17 @@ try:
server_root = ensure_under_home(extra.get('server_root') or home_root)
steamcmd_path = resolve_steamcmd(extra.get('steamcmd_path') or '')
post_install_script = str(extra.get('post_install_script') or '').strip()
+ item_details = manifest.get('item_details') or extra.get('item_details') or {}
+ default_install_strategy = str(manifest.get('install_strategy') or extra.get('install_strategy') or 'copy_to_mod_folder').strip()
default_download_dir = extra.get('workshop_download_dir') or os.path.join(server_root, 'steamapps', 'workshop', 'content', workshop_app_id or steam_app_id)
action_label = 'Queued' if action in ('install', 'update', 'check_updates') else action
log(f"action={action} manifest={manifest_path} steam_app_id={steam_app_id or 'n/a'} workshop_app_id={workshop_app_id or 'n/a'}", action_label)
for workshop_id in items:
- folder_name = str(extra.get('optional_folder_name') or '').strip() or ('@' + workshop_id)
+ detail = item_details.get(workshop_id) or item_details.get(str(workshop_id)) or {}
+ folder_name = safe_folder_name(detail.get('folder_name') or extra.get('optional_folder_name') or '', '@' + workshop_id)
+ install_strategy = str(detail.get('install_strategy') or default_install_strategy).strip() or 'copy_to_mod_folder'
template_values = {
'HOME_ID': manifest.get('home_id', ''),
'SERVER_ROOT': server_root,
@@ -139,11 +176,13 @@ try:
'FOLDER_NAME': folder_name,
'MOD_FOLDER': folder_name,
}
- target_template = str(extra.get('target_path_template') or '{SERVER_ROOT}/{MOD_FOLDER}')
- target_path = str(extra.get('target_path_resolved') or '').strip()
+ target_template = str(detail.get('target_path_template') or extra.get('target_path_template') or '{SERVER_ROOT}/{MOD_FOLDER}')
+ target_path = str(detail.get('target_path_resolved') or extra.get('target_path_resolved') or '').strip()
if len(items) != 1 or not target_path:
target_path = render_template(target_template, template_values)
target_path = ensure_under_home(target_path)
+ keys_target_path = str(detail.get('keys_target_path') or extra.get('keys_target_path') or os.path.join(server_root, 'keys'))
+ should_copy_keys = truthy(detail.get('copy_keys', extra.get('copy_keys', install_strategy in ('dayz_mod_folder', 'arma_mod_folder'))))
download_dir = ensure_under_home(render_template(default_download_dir, template_values))
source_path = os.path.join(download_dir, workshop_id)
@@ -169,9 +208,11 @@ try:
fail(f"SteamCMD did not create the expected Workshop cache path: {source_path}")
if action != 'check_updates':
- log(f"workshop_id={workshop_id} install_path={target_path}", 'Extracting/Copying')
+ log(f"workshop_id={workshop_id} strategy={install_strategy} install_path={target_path}", 'Extracting/Copying')
sync_copy(source_path, target_path)
log(f"workshop_id={workshop_id} final_folder_path={target_path}", 'Applying Folder Name')
+ if should_copy_keys:
+ copy_bikeys(target_path, keys_target_path, workshop_id)
if post_install_script:
log(f"workshop_id={workshop_id} cwd={server_root}", 'Running Post-install Script')
post_result = subprocess.run(['bash', '-lc', post_install_script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=server_root)
diff --git a/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_windows_cygwin.sh b/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_windows_cygwin.sh
index 011a4f1e..c8700564 100755
--- a/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_windows_cygwin.sh
+++ b/Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_windows_cygwin.sh
@@ -69,6 +69,19 @@ def ensure_under_home(path_value):
return target
+def safe_folder_name(value, fallback):
+ text = str(value or '').strip()
+ if not text or '..' in text or '/' in text or '\\' in text or '\x00' in text:
+ return fallback
+ return text
+
+
+def truthy(value):
+ if isinstance(value, bool):
+ return value
+ return str(value).strip().lower() in ('1', 'yes', 'true', 'on')
+
+
def resolve_steamcmd(explicit_path=''):
candidates = []
explicit_path = str(explicit_path or '').strip()
@@ -105,6 +118,26 @@ def sync_copy(src, dst):
shutil.copy2(source_entry, target_entry)
+def copy_bikeys(mod_path, keys_target, workshop_id):
+ if not os.path.isdir(mod_path):
+ return 0
+ keys_target = ensure_under_home(keys_target)
+ os.makedirs(keys_target, exist_ok=True)
+ copied = 0
+ for root, dirs, files in os.walk(mod_path):
+ for filename in files:
+ if not filename.lower().endswith('.bikey'):
+ continue
+ source_file = os.path.join(root, filename)
+ target_file = os.path.join(keys_target, filename)
+ shutil.copy2(source_file, target_file)
+ copied += 1
+ log(f"workshop_id={workshop_id} key={target_file}", 'Copying Key')
+ if copied == 0:
+ log(f"workshop_id={workshop_id} no .bikey files found; continuing", 'Copying Key')
+ return copied
+
+
try:
with open(manifest_path, 'r', encoding='utf-8') as handle:
manifest = json.load(handle)
@@ -123,13 +156,17 @@ try:
server_root = ensure_under_home(extra.get('server_root') or home_root)
steamcmd_path = resolve_steamcmd(extra.get('steamcmd_path') or '')
post_install_script = str(extra.get('post_install_script') or '').strip()
+ item_details = manifest.get('item_details') or extra.get('item_details') or {}
+ default_install_strategy = str(manifest.get('install_strategy') or extra.get('install_strategy') or 'copy_to_mod_folder').strip()
default_download_dir = extra.get('workshop_download_dir') or os.path.join(server_root, 'steamapps', 'workshop', 'content', workshop_app_id or steam_app_id)
action_label = 'Queued' if action in ('install', 'update', 'check_updates') else action
log(f"action={action} manifest={manifest_path} steam_app_id={steam_app_id or 'n/a'} workshop_app_id={workshop_app_id or 'n/a'}", action_label)
for workshop_id in items:
- folder_name = str(extra.get('optional_folder_name') or '').strip() or ('@' + workshop_id)
+ detail = item_details.get(workshop_id) or item_details.get(str(workshop_id)) or {}
+ folder_name = safe_folder_name(detail.get('folder_name') or extra.get('optional_folder_name') or '', '@' + workshop_id)
+ install_strategy = str(detail.get('install_strategy') or default_install_strategy).strip() or 'copy_to_mod_folder'
template_values = {
'HOME_ID': manifest.get('home_id', ''),
'SERVER_ROOT': server_root,
@@ -140,11 +177,13 @@ try:
'FOLDER_NAME': folder_name,
'MOD_FOLDER': folder_name,
}
- target_template = str(extra.get('target_path_template') or '{SERVER_ROOT}/{MOD_FOLDER}')
- target_path = str(extra.get('target_path_resolved') or '').strip()
+ target_template = str(detail.get('target_path_template') or extra.get('target_path_template') or '{SERVER_ROOT}/{MOD_FOLDER}')
+ target_path = str(detail.get('target_path_resolved') or extra.get('target_path_resolved') or '').strip()
if len(items) != 1 or not target_path:
target_path = render_template(target_template, template_values)
target_path = ensure_under_home(target_path)
+ keys_target_path = str(detail.get('keys_target_path') or extra.get('keys_target_path') or os.path.join(server_root, 'keys'))
+ should_copy_keys = truthy(detail.get('copy_keys', extra.get('copy_keys', install_strategy in ('dayz_mod_folder', 'arma_mod_folder'))))
download_dir = ensure_under_home(render_template(default_download_dir, template_values))
source_path = os.path.join(download_dir, workshop_id)
@@ -170,9 +209,11 @@ try:
fail(f"SteamCMD did not create the expected Workshop cache path: {source_path}")
if action != 'check_updates':
- log(f"workshop_id={workshop_id} install_path={target_path}", 'Extracting/Copying')
+ log(f"workshop_id={workshop_id} strategy={install_strategy} install_path={target_path}", 'Extracting/Copying')
sync_copy(source_path, target_path)
log(f"workshop_id={workshop_id} final_folder_path={target_path}", 'Applying Folder Name')
+ if should_copy_keys:
+ copy_bikeys(target_path, keys_target_path, workshop_id)
if post_install_script:
log(f"workshop_id={workshop_id} cwd={server_root}", 'Running Post-install Script')
post_result = subprocess.run(['bash', '-lc', post_install_script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=server_root)
diff --git a/Panel/modules/addonsmanager/server_content_helpers.php b/Panel/modules/addonsmanager/server_content_helpers.php
index 7b6cb46b..9a3f8757 100644
--- a/Panel/modules/addonsmanager/server_content_helpers.php
+++ b/Panel/modules/addonsmanager/server_content_helpers.php
@@ -30,6 +30,10 @@ function scm_ensure_workshop_schema($db)
`workshop_app_id` VARCHAR(32) NULL,
`workshop_item_id` VARCHAR(64) NOT NULL,
`title` VARCHAR(255) NULL,
+ `install_path` VARCHAR(512) NULL,
+ `install_strategy` VARCHAR(64) NULL,
+ `enabled` TINYINT(1) NOT NULL DEFAULT 1,
+ `load_order` INT NOT NULL DEFAULT 0,
`install_state` VARCHAR(32) NOT NULL DEFAULT 'selected',
`last_installed_at` DATETIME NULL,
`last_updated_at` DATETIME NULL,
@@ -60,6 +64,25 @@ function scm_ensure_workshop_schema($db)
);
}
+ $workshop_columns = array(
+ 'install_path' => "VARCHAR(512) NULL AFTER `title`",
+ 'install_strategy' => "VARCHAR(64) NULL AFTER `install_path`",
+ 'enabled' => "TINYINT(1) NOT NULL DEFAULT 1 AFTER `install_strategy`",
+ 'load_order' => "INT NOT NULL DEFAULT 0 AFTER `enabled`",
+ );
+ foreach ($workshop_columns as $col => $definition) {
+ $escaped_col = $db->realEscapeSingle($col);
+ $col_check = $db->resultQuery(
+ "SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = '{$wk_table}'
+ AND COLUMN_NAME = '{$escaped_col}'"
+ );
+ if (empty($col_check)) {
+ $db->query("ALTER TABLE `".OGP_DB_PREFIX."server_content_workshop` ADD COLUMN `{$col}` {$definition}");
+ }
+ }
+
return $ok;
}
@@ -98,28 +121,49 @@ function scm_get_workshop_rows($db, $home_id)
return array();
}
$rows = $db->resultQuery(
- "SELECT * FROM `".OGP_DB_PREFIX."server_content_workshop` WHERE home_id=".$home_id." ORDER BY created_at DESC, workshop_item_id ASC"
+ "SELECT * FROM `".OGP_DB_PREFIX."server_content_workshop` WHERE home_id=".$home_id." ORDER BY load_order ASC, created_at DESC, workshop_item_id ASC"
);
return is_array($rows) ? $rows : array();
}
+function scm_extract_workshop_item_id($value)
+{
+ $value = trim((string)$value);
+ if ($value === '') {
+ return '';
+ }
+ if (preg_match('/^[0-9]{1,20}$/', $value)) {
+ return ltrim($value, '0') === '' ? '' : $value;
+ }
+ if (preg_match('/[?&]id=([0-9]{1,20})(?:[^0-9]|$)/i', $value, $matches)) {
+ return ltrim($matches[1], '0') === '' ? '' : $matches[1];
+ }
+ if (preg_match('/steamcommunity\.com\/(?:sharedfiles|workshop)\/filedetails\/\?[^ \t\r\n]*id=([0-9]{1,20})/i', $value, $matches)) {
+ return ltrim($matches[1], '0') === '' ? '' : $matches[1];
+ }
+ return '';
+}
+
function scm_parse_workshop_ids($raw, &$invalid = array())
{
$invalid = array();
$ids = array();
- // Accept IDs separated by commas, newlines, or a mix of both.
- $normalized = str_replace(array("\r\n", "\r", "\n"), ',', (string)$raw);
- $parts = explode(',', $normalized);
+ // Accept IDs or full Steam Workshop URLs separated by commas, whitespace,
+ // or newlines. Customer input is reduced to numeric IDs before it reaches
+ // manifests, shell commands, or install paths.
+ $normalized = preg_replace('/[\r\n\t ]+/', ',', (string)$raw);
+ $parts = explode(',', (string)$normalized);
foreach ((array)$parts as $part) {
$value = trim((string)$part);
if ($value === '') {
continue;
}
- if (!preg_match('/^[0-9]+$/', $value)) {
+ $item_id = scm_extract_workshop_item_id($value);
+ if ($item_id === '') {
$invalid[] = $value;
continue;
}
- $ids[$value] = $value;
+ $ids[$item_id] = $item_id;
}
return array_values($ids);
}
@@ -472,11 +516,11 @@ function scm_validate_workshop_user_ids($raw_ids, &$message = '')
$invalid = array();
$ids = scm_parse_workshop_ids($raw_ids, $invalid);
if (!empty($invalid)) {
- $message = 'Invalid Workshop IDs: ' . implode(', ', $invalid);
+ $message = 'Invalid Workshop item entries. Use a numeric Workshop ID or a Steam Workshop URL: ' . implode(', ', $invalid);
return false;
}
if (empty($ids)) {
- $message = 'Enter at least one numeric Workshop ID.';
+ $message = 'Enter at least one Steam Workshop ID or Workshop URL.';
return false;
}
$message = '';
@@ -604,6 +648,46 @@ function scm_build_workshop_runtime_context($db, array $home_info, $server_xml,
);
}
+function scm_detect_workshop_install_strategy(array $home_info, $server_xml, array $template = array())
+{
+ if (!empty($template['install_strategy'])) {
+ $strategy = trim((string)$template['install_strategy']);
+ if (preg_match('/^[a-z0-9_\-]+$/i', $strategy)) {
+ return strtolower($strategy);
+ }
+ }
+ foreach (array('workshop_install_strategy', 'install_strategy') as $tag) {
+ if (isset($server_xml->$tag)) {
+ $strategy = trim((string)$server_xml->$tag);
+ if ($strategy !== '' && preg_match('/^[a-z0-9_\-]+$/i', $strategy)) {
+ return strtolower($strategy);
+ }
+ }
+ }
+ $game_key = strtolower((string)(isset($home_info['game_key']) ? $home_info['game_key'] : ''));
+ $cfg_file = strtolower((string)(isset($home_info['home_cfg_file']) ? $home_info['home_cfg_file'] : ''));
+ $name = strtolower((string)(isset($home_info['game_name']) ? $home_info['game_name'] : ''));
+ $haystack = $game_key . ' ' . $cfg_file . ' ' . $name;
+ if (strpos($haystack, 'dayz') !== false) {
+ return 'dayz_mod_folder';
+ }
+ if (strpos($haystack, 'arma') !== false) {
+ return 'arma_mod_folder';
+ }
+ return 'copy_to_mod_folder';
+}
+
+function scm_workshop_should_copy_keys($server_xml, $install_strategy)
+{
+ foreach (array('workshop_copy_keys', 'copy_workshop_keys') as $tag) {
+ if (isset($server_xml->$tag)) {
+ $value = strtolower(trim((string)$server_xml->$tag));
+ return in_array($value, array('1', 'yes', 'true', 'on'), true);
+ }
+ }
+ return in_array((string)$install_strategy, array('dayz_mod_folder', 'arma_mod_folder'), true);
+}
+
function scm_build_placeholder_map(array $home_info, array $server_context = array(), array $overrides = array())
{
$home_id = (int)(isset($home_info['home_id']) ? $home_info['home_id'] : 0);
diff --git a/Panel/modules/addonsmanager/workshop_action.php b/Panel/modules/addonsmanager/workshop_action.php
index f4d735ef..5bb5c6cf 100644
--- a/Panel/modules/addonsmanager/workshop_action.php
+++ b/Panel/modules/addonsmanager/workshop_action.php
@@ -67,9 +67,92 @@ function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
return array_values($allowed);
}
-function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '', array $extra_manifest = array())
+function scm_workshop_get_content_template($db, $addon_id)
+{
+ $addon_id = (int)$addon_id;
+ if ($addon_id <= 0) {
+ return array();
+ }
+ scm_ensure_phase2_schema($db);
+ $rows = $db->resultQuery(
+ "SELECT addon_id, name, workshop_app_id, target_path_template, optional_folder_name,
+ post_script, launch_param_additions, content_version, description
+ FROM `" . OGP_DB_PREFIX . "addons`
+ WHERE addon_id=" . $addon_id . " AND install_method='steam_workshop'
+ LIMIT 1"
+ );
+ return (is_array($rows) && !empty($rows)) ? $rows[0] : array();
+}
+
+function scm_workshop_build_manifest_context($db, array $home_info, $server_xml, array $item_ids, array $template = array())
+{
+ $install_strategy = scm_detect_workshop_install_strategy($home_info, $server_xml, $template);
+ $copy_keys = scm_workshop_should_copy_keys($server_xml, $install_strategy);
+ $item_details = array();
+ $resolved_app_id = '';
+ $steam_app_id = '';
+ $template_payload = array(
+ 'workshop_app_id' => isset($template['workshop_app_id']) ? (string)$template['workshop_app_id'] : '',
+ 'target_path_template' => isset($template['target_path_template']) ? (string)$template['target_path_template'] : '',
+ 'optional_folder_name' => isset($template['optional_folder_name']) ? (string)$template['optional_folder_name'] : '',
+ );
+
+ foreach ($item_ids as $item_id) {
+ $payload = $template_payload;
+ $payload['workshop_item_id'] = (string)$item_id;
+ // A fixed optional folder name is safe only when installing one item. For
+ // multi-item installs, use @
Enter a Steam Workshop URL or numeric item ID. GSP stores only the numeric Workshop ID and uses the Server Content template for game-specific install behavior.
"; if ($message !== '') { if ($is_error) { @@ -114,10 +116,10 @@ function exec_ogp_module() {| Workshop Item IDs | +Workshop URLs / IDs |
-
- Enter one or more Steam Workshop IDs, one per line or comma-separated. Example for Arma 3 CBA_A3: 450814997
+
+ Enter one or more Steam Workshop URLs or numeric IDs, one per line, comma-separated, or space-separated. Example for Arma 3 CBA_A3: https://steamcommunity.com/sharedfiles/filedetails/?id=450814997
|
@@ -131,20 +133,26 @@ function exec_ogp_module() { | Workshop ID | Title | +Enabled | +Order | State | +Install Path | Last Installed | Last Updated | Last Error | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| No Workshop IDs saved for this server yet. | |||||||||||||
| No Workshop items saved for this server yet. | |||||||||||||
| '> | + | + | + | @@ -173,4 +181,3 @@ function exec_ogp_module() { resultQuery( - "SELECT p.`id` - FROM " . sw_monitor_table('steam_workshop_game_profiles') . " p - JOIN " . sw_monitor_table('config_homes') . " c ON c.`game_key` = p.`config_name` - JOIN " . sw_monitor_table('server_homes') . " s ON s.`home_cfg_id` = c.`home_cfg_id` - WHERE s.home_id = " . (int)$server_home['home_id'] . " - AND p.enabled = 1 - LIMIT 1" -); - -if (!empty($_sw_profile)) { - $module_buttons[] = " - | |||||||||