Merge pull request #153 from GameServerPanel/copilot/add-server-content-workshop
This commit is contained in:
commit
211875aaf3
12 changed files with 854 additions and 3 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2026-05-18
|
## 2026-05-18
|
||||||
|
- **Server Content Workshop Phase 1 in addonsmanager:** Added a new `Workshop Content` flow under Server Content with per-home Workshop ID storage, ID validation/deduplication, install/update/remove/update-all actions, manifest-based script handoff (`gsp_server_content/workshop_manifest.json`), safe placeholder workshop scripts for Linux/Cygwin, and schema support via `server_content_workshop` plus `addons.addon_type VARCHAR(32)`.
|
||||||
- **Updater layout hardening + pre-update patch framework:** Reworked `modules/administration/panel_update.php` to resolve explicit GSP root/Panel/Website paths, run mandatory preflight checks, self-update updater files before main sync when drift is detected, and apply ordered required patches from `modules/update/patches/` with DB/local state tracking. Backup/rollback now includes both Panel + Website archives and root `version.json`, logs moved to root `logs/update_trace.log`, and the admin Update UI now exposes preflight, patch apply, Apache path scan/fix, backup, update, and rollback actions.
|
- **Updater layout hardening + pre-update patch framework:** Reworked `modules/administration/panel_update.php` to resolve explicit GSP root/Panel/Website paths, run mandatory preflight checks, self-update updater files before main sync when drift is detected, and apply ordered required patches from `modules/update/patches/` with DB/local state tracking. Backup/rollback now includes both Panel + Website archives and root `version.json`, logs moved to root `logs/update_trace.log`, and the admin Update UI now exposes preflight, patch apply, Apache path scan/fix, backup, update, and rollback actions.
|
||||||
- **Billing runtime relocation + portable path bootstrap:** Re-homed storefront runtime to `Panel/modules/billing`, added portable runtime helpers (`billing_bootstrap.php`, `site_config.php`, `site_config.example.php`) with env/local override support for base path and panel path, normalized critical storefront redirects/links to computed billing URLs, and added `Website` compatibility wrappers for key billing entrypoints.
|
- **Billing runtime relocation + portable path bootstrap:** Re-homed storefront runtime to `Panel/modules/billing`, added portable runtime helpers (`billing_bootstrap.php`, `site_config.php`, `site_config.example.php`) with env/local override support for base path and panel path, normalized critical storefront redirects/links to computed billing URLs, and added `Website` compatibility wrappers for key billing entrypoints.
|
||||||
- **Panel updater panel-subtree safety:** Hardened updater logic to treat repository `/panel` as the update source when present (ZIP + git flows) so root-level docs/examples/scripts are no longer candidates for panel file overwrite during updates.
|
- **Panel updater panel-subtree safety:** Hardened updater logic to treat repository `/panel` as the update source when present (ZIP + git flows) so root-level docs/examples/scripts are no longer candidates for panel file overwrite during updates.
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,4 @@
|
||||||
- Add a panel settings health check that validates reCAPTCHA site/secret keys against active panel/storefront domains and warns admins before registration users see widget errors.
|
- Add a panel settings health check that validates reCAPTCHA site/secret keys against active panel/storefront domains and warns admins before registration users see widget errors.
|
||||||
- Add an automated deployment check that fails when `Website/timestamp.txt` and `modules/billing/timestamp.txt` diverge after storefront/content changes.
|
- Add an automated deployment check that fails when `Website/timestamp.txt` and `modules/billing/timestamp.txt` diverge after storefront/content changes.
|
||||||
- Add an admin preview/diff panel for Apache path repairs so staff can review exact vhost line changes before confirming `Fix Apache Paths`.
|
- Add an admin preview/diff panel for Apache path repairs so staff can review exact vhost line changes before confirming `Fix Apache Paths`.
|
||||||
|
- Add Phase 2 Workshop Content UX in `addonsmanager`: browse/search/select Workshop items with metadata while reusing the Phase 1 per-home saved-ID action pipeline.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Server Content Workshop – Phase 1
|
||||||
|
|
||||||
|
Phase 1 adds manual Workshop ID support inside the existing `addonsmanager` module (user-facing label: **Server Content**).
|
||||||
|
|
||||||
|
## Scope (Phase 1)
|
||||||
|
|
||||||
|
- No Steam Workshop browser/search UI yet.
|
||||||
|
- No Steam scraping.
|
||||||
|
- User enters comma-separated numeric Workshop IDs.
|
||||||
|
- Panel validates IDs, removes duplicates, and stores them per server home.
|
||||||
|
- Panel lists saved IDs and supports:
|
||||||
|
- Install New
|
||||||
|
- Update Selected
|
||||||
|
- Remove Selected
|
||||||
|
- Update All
|
||||||
|
- Panel generates a per-server manifest at:
|
||||||
|
- `%home_path%/gsp_server_content/workshop_manifest.json`
|
||||||
|
- Panel runs an approved script path (safe default or game-specific config), never user-supplied command/path.
|
||||||
|
|
||||||
|
## Security model
|
||||||
|
|
||||||
|
- Ownership check: non-admin users can only access homes assigned to them; admins can access any home.
|
||||||
|
- Actions are scoped to one `home_id`.
|
||||||
|
- IDs must be numeric only.
|
||||||
|
- Script path is not user-editable.
|
||||||
|
- Manifest path is validated to remain under server home.
|
||||||
|
- Remove is non-destructive in the generic scripts (preserve/move behavior for Phase 1).
|
||||||
|
- All actions are logged through panel logging.
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
Phase 1 introduces:
|
||||||
|
|
||||||
|
- `OGP_DB_PREFIXserver_content_workshop`
|
||||||
|
|
||||||
|
and keeps `OGP_DB_PREFIXaddons.addon_type` at `VARCHAR(32)` so `workshop` is valid.
|
||||||
|
|
||||||
|
## Game/admin config TODO (next phase hardening)
|
||||||
|
|
||||||
|
Each game should define and document:
|
||||||
|
|
||||||
|
- `workshop_app_id`
|
||||||
|
- Linux workshop script path
|
||||||
|
- Windows/Cygwin workshop script path
|
||||||
|
- target install location
|
||||||
|
- restart/update behavior
|
||||||
|
|
||||||
|
## Phase 2 (not included here)
|
||||||
|
|
||||||
|
- Workshop browsing/search/select UI
|
||||||
|
- richer metadata/title lookups
|
||||||
|
- per-game install adapters and deeper status reporting
|
||||||
|
|
@ -111,6 +111,7 @@ require_once("modules/config_games/server_config_parser.php");
|
||||||
require_once("protocol/lgsl/lgsl_protocol.php");
|
require_once("protocol/lgsl/lgsl_protocol.php");
|
||||||
// Central category map — all valid addon_type values and their labels.
|
// Central category map — all valid addon_type values and their labels.
|
||||||
require_once(dirname(__FILE__) . '/server_content_categories.php');
|
require_once(dirname(__FILE__) . '/server_content_categories.php');
|
||||||
|
require_once(dirname(__FILE__) . '/server_content_helpers.php');
|
||||||
|
|
||||||
function exec_ogp_module() {
|
function exec_ogp_module() {
|
||||||
|
|
||||||
|
|
@ -325,6 +326,11 @@ function exec_ogp_module() {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($addon_type === 'workshop') {
|
||||||
|
scm_ensure_workshop_schema($db);
|
||||||
|
$view->refresh('?m=addonsmanager&p=workshop_content&home_id='.(int)$home_id.'&mod_id='.(int)$mod_id.'&ip='.urlencode((string)$ip).'&port='.urlencode((string)$port), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<h2><?php echo htmlentities($home_info['home_name'])." ".get_lang($addon_type) ;?></h2>
|
<h2><?php echo htmlentities($home_info['home_name'])." ".get_lang($addon_type) ;?></h2>
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
// Module general information
|
// Module general information
|
||||||
$module_title = "Server Content Manager";
|
$module_title = "Server Content Manager";
|
||||||
$module_version = "2.0";
|
$module_version = "2.1";
|
||||||
$db_version = 2;
|
$db_version = 3;
|
||||||
$module_required = TRUE;
|
$module_required = TRUE;
|
||||||
$module_menus = array(
|
$module_menus = array(
|
||||||
array( 'subpage' => 'addons_manager', 'name' => 'Server Content Manager', 'group' => 'admin' )
|
array( 'subpage' => 'addons_manager', 'name' => 'Server Content Manager', 'group' => 'admin' )
|
||||||
|
|
@ -48,4 +48,28 @@ $install_queries[1] = array(
|
||||||
"ALTER TABLE `".OGP_DB_PREFIX."addons`
|
"ALTER TABLE `".OGP_DB_PREFIX."addons`
|
||||||
MODIFY `addon_type` VARCHAR(32) NOT NULL;"
|
MODIFY `addon_type` VARCHAR(32) NOT NULL;"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ── db_version 3 : workshop item selections per server home ───────────────────
|
||||||
|
$install_queries[2] = array(
|
||||||
|
"CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."server_content_workshop` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`home_id` INT NOT NULL,
|
||||||
|
`home_cfg_id` INT NOT NULL,
|
||||||
|
`remote_server_id` INT NULL,
|
||||||
|
`workshop_app_id` VARCHAR(32) NULL,
|
||||||
|
`workshop_item_id` VARCHAR(64) NOT NULL,
|
||||||
|
`title` VARCHAR(255) NULL,
|
||||||
|
`install_state` VARCHAR(32) NOT NULL DEFAULT 'selected',
|
||||||
|
`last_installed_at` DATETIME NULL,
|
||||||
|
`last_updated_at` DATETIME NULL,
|
||||||
|
`last_error` TEXT NULL,
|
||||||
|
`created_by` INT NULL,
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` DATETIME NULL,
|
||||||
|
UNIQUE KEY `uniq_home_workshop_item` (`home_id`, `workshop_item_id`),
|
||||||
|
KEY `idx_home_id` (`home_id`),
|
||||||
|
KEY `idx_home_cfg_id` (`home_cfg_id`),
|
||||||
|
KEY `idx_install_state` (`install_state`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
|
||||||
|
);
|
||||||
?>
|
?>
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<page key="user_addons" file="user_addons.php" access="admin,user" />
|
<page key="user_addons" file="user_addons.php" access="admin,user" />
|
||||||
<page key="addons_manager" file="addons_manager.php" access="admin" />
|
<page key="addons_manager" file="addons_manager.php" access="admin" />
|
||||||
<page key="addons" file="addons_installer.php" access="admin,user" />
|
<page key="addons" file="addons_installer.php" access="admin,user" />
|
||||||
|
<page key="workshop_content" file="workshop_content.php" access="admin,user" />
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MANIFEST_PATH="${1:-}"
|
||||||
|
if [[ -z "$MANIFEST_PATH" ]]; then
|
||||||
|
echo "Usage: $0 <manifest_path>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$MANIFEST_PATH" ]]; then
|
||||||
|
echo "Manifest not found: $MANIFEST_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MANIFEST_DIR="$(dirname "$MANIFEST_PATH")"
|
||||||
|
WORKSHOP_DIR="${MANIFEST_DIR}/workshop"
|
||||||
|
REMOVED_DIR="${WORKSHOP_DIR}/removed"
|
||||||
|
LOG_FILE="${MANIFEST_DIR}/workshop_phase1.log"
|
||||||
|
|
||||||
|
mkdir -p "$WORKSHOP_DIR" "$REMOVED_DIR"
|
||||||
|
|
||||||
|
ACTION="$(python3 - <<'PY' "$MANIFEST_PATH"
|
||||||
|
import json,sys
|
||||||
|
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
||||||
|
data=json.load(f)
|
||||||
|
print(data.get("action",""))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
ITEMS="$(python3 - <<'PY' "$MANIFEST_PATH"
|
||||||
|
import json,sys
|
||||||
|
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
||||||
|
data=json.load(f)
|
||||||
|
items=data.get("items",[])
|
||||||
|
print(",".join(str(x) for x in items if str(x).isdigit()))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] workshop_phase1 action=${ACTION} manifest=${MANIFEST_PATH}"
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] workshop_phase1 items=${ITEMS}"
|
||||||
|
} >> "$LOG_FILE"
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
install|update)
|
||||||
|
# TODO: Replace with game-specific SteamCMD workshop install/update logic.
|
||||||
|
# Example flow:
|
||||||
|
# 1) Use workshop_app_id + item IDs from the manifest.
|
||||||
|
# 2) Download/refresh content into a controlled staging folder.
|
||||||
|
# 3) Copy/sync approved files into the game server content path.
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
# Phase 1 safety behavior: avoid destructive delete.
|
||||||
|
# TODO: move/disable per-item content using game-specific mapping rules.
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] remove requested; preserving files (non-destructive phase 1)." >> "$LOG_FILE"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown workshop action: ${ACTION}" >> "$LOG_FILE"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MANIFEST_PATH="${1:-}"
|
||||||
|
if [[ -z "$MANIFEST_PATH" ]]; then
|
||||||
|
echo "Usage: $0 <manifest_path>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$MANIFEST_PATH" ]]; then
|
||||||
|
echo "Manifest not found: $MANIFEST_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MANIFEST_DIR="$(dirname "$MANIFEST_PATH")"
|
||||||
|
WORKSHOP_DIR="${MANIFEST_DIR}/workshop"
|
||||||
|
REMOVED_DIR="${WORKSHOP_DIR}/removed"
|
||||||
|
LOG_FILE="${MANIFEST_DIR}/workshop_phase1_windows.log"
|
||||||
|
|
||||||
|
mkdir -p "$WORKSHOP_DIR" "$REMOVED_DIR"
|
||||||
|
|
||||||
|
ACTION="$(python3 - <<'PY' "$MANIFEST_PATH"
|
||||||
|
import json,sys
|
||||||
|
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
||||||
|
data=json.load(f)
|
||||||
|
print(data.get("action",""))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
ITEMS="$(python3 - <<'PY' "$MANIFEST_PATH"
|
||||||
|
import json,sys
|
||||||
|
with open(sys.argv[1], "r", encoding="utf-8") as f:
|
||||||
|
data=json.load(f)
|
||||||
|
items=data.get("items",[])
|
||||||
|
print(",".join(str(x) for x in items if str(x).isdigit()))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] workshop_phase1_windows action=${ACTION} manifest=${MANIFEST_PATH}"
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] workshop_phase1_windows items=${ITEMS}"
|
||||||
|
} >> "$LOG_FILE"
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
install|update)
|
||||||
|
# TODO: Replace with game-specific SteamCMD workshop install/update logic for Cygwin environments.
|
||||||
|
;;
|
||||||
|
remove)
|
||||||
|
# Phase 1 safety behavior: avoid destructive delete.
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] remove requested; preserving files (non-destructive phase 1)." >> "$LOG_FILE"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown workshop action: ${ACTION}" >> "$LOG_FILE"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
200
Panel/modules/addonsmanager/server_content_helpers.php
Normal file
200
Panel/modules/addonsmanager/server_content_helpers.php
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* GSP - Server Content helpers (addonsmanager)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('SCM_WORKSHOP_SCRIPT_LINUX_DEFAULT')) {
|
||||||
|
define('SCM_WORKSHOP_SCRIPT_LINUX_DEFAULT', '/home/gameserver/OGP_User_Files/modules/addonsmanager/scripts/workshop/generic_steam_workshop_linux.sh');
|
||||||
|
}
|
||||||
|
if (!defined('SCM_WORKSHOP_SCRIPT_WINDOWS_DEFAULT')) {
|
||||||
|
define('SCM_WORKSHOP_SCRIPT_WINDOWS_DEFAULT', '/home/gameserver/OGP_User_Files/modules/addonsmanager/scripts/workshop/generic_steam_workshop_windows_cygwin.sh');
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_ensure_workshop_schema($db)
|
||||||
|
{
|
||||||
|
static $schema_checked = false;
|
||||||
|
if ($schema_checked) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$schema_checked = true;
|
||||||
|
|
||||||
|
$db->query("ALTER TABLE `".OGP_DB_PREFIX."addons` MODIFY `addon_type` VARCHAR(32) NOT NULL");
|
||||||
|
return (bool)$db->query(
|
||||||
|
"CREATE TABLE IF NOT EXISTS `".OGP_DB_PREFIX."server_content_workshop` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
`home_id` INT NOT NULL,
|
||||||
|
`home_cfg_id` INT NOT NULL,
|
||||||
|
`remote_server_id` INT NULL,
|
||||||
|
`workshop_app_id` VARCHAR(32) NULL,
|
||||||
|
`workshop_item_id` VARCHAR(64) NOT NULL,
|
||||||
|
`title` VARCHAR(255) NULL,
|
||||||
|
`install_state` VARCHAR(32) NOT NULL DEFAULT 'selected',
|
||||||
|
`last_installed_at` DATETIME NULL,
|
||||||
|
`last_updated_at` DATETIME NULL,
|
||||||
|
`last_error` TEXT NULL,
|
||||||
|
`created_by` INT NULL,
|
||||||
|
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` DATETIME NULL,
|
||||||
|
UNIQUE KEY `uniq_home_workshop_item` (`home_id`, `workshop_item_id`),
|
||||||
|
KEY `idx_home_id` (`home_id`),
|
||||||
|
KEY `idx_home_cfg_id` (`home_cfg_id`),
|
||||||
|
KEY `idx_install_state` (`install_state`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_get_home_for_user($db, $home_id, $user_id)
|
||||||
|
{
|
||||||
|
$home_id = (int)$home_id;
|
||||||
|
$user_id = (int)$user_id;
|
||||||
|
if ($home_id <= 0 || $user_id <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($db->isAdmin($user_id)) {
|
||||||
|
return $db->getGameHome($home_id);
|
||||||
|
}
|
||||||
|
return $db->getUserGameHome($user_id, $home_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_get_workshop_saved_count($db, $home_id)
|
||||||
|
{
|
||||||
|
$home_id = (int)$home_id;
|
||||||
|
if ($home_id <= 0 || !scm_ensure_workshop_schema($db)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
$rows = $db->resultQuery(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM `".OGP_DB_PREFIX."server_content_workshop` WHERE home_id=".$home_id." AND install_state<>'removed'"
|
||||||
|
);
|
||||||
|
if (!is_array($rows) || !isset($rows[0]['cnt'])) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (int)$rows[0]['cnt'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_get_workshop_rows($db, $home_id)
|
||||||
|
{
|
||||||
|
$home_id = (int)$home_id;
|
||||||
|
if ($home_id <= 0 || !scm_ensure_workshop_schema($db)) {
|
||||||
|
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"
|
||||||
|
);
|
||||||
|
return is_array($rows) ? $rows : array();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_parse_workshop_ids($raw, &$invalid = array())
|
||||||
|
{
|
||||||
|
$invalid = array();
|
||||||
|
$ids = array();
|
||||||
|
$parts = explode(',', (string)$raw);
|
||||||
|
foreach ((array)$parts as $part) {
|
||||||
|
$value = trim((string)$part);
|
||||||
|
if ($value === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!preg_match('/^[0-9]+$/', $value)) {
|
||||||
|
$invalid[] = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ids[$value] = $value;
|
||||||
|
}
|
||||||
|
return array_values($ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_parse_selected_workshop_ids($selected)
|
||||||
|
{
|
||||||
|
$ids = array();
|
||||||
|
if (!is_array($selected)) {
|
||||||
|
return $ids;
|
||||||
|
}
|
||||||
|
foreach ($selected as $item_id) {
|
||||||
|
$item_id = trim((string)$item_id);
|
||||||
|
if ($item_id !== '' && preg_match('/^[0-9]+$/', $item_id)) {
|
||||||
|
$ids[$item_id] = $item_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values($ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_h($value)
|
||||||
|
{
|
||||||
|
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_is_windows_home(array $home_info)
|
||||||
|
{
|
||||||
|
$game_key = isset($home_info['game_key']) ? strtolower((string)$home_info['game_key']) : '';
|
||||||
|
$cfg_file = isset($home_info['home_cfg_file']) ? strtolower((string)$home_info['home_cfg_file']) : '';
|
||||||
|
return (strpos($game_key, 'win') !== false) || (strpos($cfg_file, 'win') !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_path_is_under_home($home_path, $candidate_path)
|
||||||
|
{
|
||||||
|
$home_path = rtrim(clean_path((string)$home_path), '/');
|
||||||
|
$candidate_path = clean_path((string)$candidate_path);
|
||||||
|
if ($home_path === '' || $candidate_path === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return strpos($candidate_path.'/', $home_path.'/') === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_get_workshop_manifest_path(array $home_info)
|
||||||
|
{
|
||||||
|
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
|
||||||
|
$manifest_path = clean_path($home_path . '/gsp_server_content/workshop_manifest.json');
|
||||||
|
if (!scm_path_is_under_home($home_path, $manifest_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $manifest_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_extract_workshop_app_id($server_xml)
|
||||||
|
{
|
||||||
|
$candidates = array(
|
||||||
|
'workshop_app_id',
|
||||||
|
'workshop_appid',
|
||||||
|
'steam_workshop_app_id',
|
||||||
|
'steam_workshop_appid',
|
||||||
|
);
|
||||||
|
foreach ((array)$candidates as $candidate) {
|
||||||
|
if (isset($server_xml->$candidate)) {
|
||||||
|
$value = trim((string)$server_xml->$candidate);
|
||||||
|
if ($value !== '' && preg_match('/^[0-9]+$/', $value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_get_workshop_script_path(array $home_info, $server_xml)
|
||||||
|
{
|
||||||
|
$key = scm_is_windows_home($home_info) ? 'workshop_script_windows' : 'workshop_script_linux';
|
||||||
|
if (isset($server_xml->$key)) {
|
||||||
|
$xml_path = trim((string)$server_xml->$key);
|
||||||
|
if ($xml_path !== '' && preg_match('/^[^\\r\\n\\0]+$/', $xml_path)) {
|
||||||
|
return $xml_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scm_is_windows_home($home_info) ? SCM_WORKSHOP_SCRIPT_WINDOWS_DEFAULT : SCM_WORKSHOP_SCRIPT_LINUX_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_get_csrf_token()
|
||||||
|
{
|
||||||
|
if (empty($_SESSION['addonsmanager_workshop_csrf'])) {
|
||||||
|
$_SESSION['addonsmanager_workshop_csrf'] = md5(uniqid((string)mt_rand(), true));
|
||||||
|
}
|
||||||
|
return $_SESSION['addonsmanager_workshop_csrf'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_validate_csrf_token($token)
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['addonsmanager_workshop_csrf'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return hash_equals((string)$_SESSION['addonsmanager_workshop_csrf'], (string)$token);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
// Central category map — load so we can iterate all types dynamically.
|
// Central category map — load so we can iterate all types dynamically.
|
||||||
require_once(dirname(__FILE__) . '/server_content_categories.php');
|
require_once(dirname(__FILE__) . '/server_content_categories.php');
|
||||||
|
require_once(dirname(__FILE__) . '/server_content_helpers.php');
|
||||||
|
|
||||||
function exec_ogp_module() {
|
function exec_ogp_module() {
|
||||||
global $db;
|
global $db;
|
||||||
|
|
@ -44,8 +45,9 @@ function exec_ogp_module() {
|
||||||
}
|
}
|
||||||
if ($home_info)
|
if ($home_info)
|
||||||
{
|
{
|
||||||
|
scm_ensure_workshop_schema($db);
|
||||||
$home_cfg_id = $home_info['home_cfg_id'];
|
$home_cfg_id = $home_info['home_cfg_id'];
|
||||||
echo "<h2>".get_lang('user_addons').": ".htmlentities($home_info['home_name'])."</h2>\n".
|
echo "<h2>Server Content: ".htmlentities($home_info['home_name'])."</h2>\n".
|
||||||
"<table class='center' >\n".
|
"<table class='center' >\n".
|
||||||
"<tr>\n";
|
"<tr>\n";
|
||||||
|
|
||||||
|
|
@ -58,6 +60,24 @@ function exec_ogp_module() {
|
||||||
|
|
||||||
foreach ((array)$categories as $type_key => $type_label)
|
foreach ((array)$categories as $type_key => $type_label)
|
||||||
{
|
{
|
||||||
|
if ($type_key === 'workshop')
|
||||||
|
{
|
||||||
|
$workshop_count = scm_get_workshop_saved_count($db, (int)$home_id);
|
||||||
|
if ($printed_any_cell)
|
||||||
|
echo "</td><td>\n";
|
||||||
|
else
|
||||||
|
echo "<td>\n";
|
||||||
|
$printed_any_cell = true;
|
||||||
|
echo "<a href='?m=addonsmanager&p=workshop_content" .
|
||||||
|
"&home_id=" . (int)$home_id .
|
||||||
|
"&mod_id=" . (int)$mod_id .
|
||||||
|
"&ip=" . htmlspecialchars($ip) .
|
||||||
|
"&port=" . htmlspecialchars($port) . "'>" .
|
||||||
|
"Workshop Content (" . (int)$workshop_count . ")" .
|
||||||
|
"</a>\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$items = $db->resultQuery(
|
$items = $db->resultQuery(
|
||||||
"SELECT DISTINCT addon_id, name, game_name " .
|
"SELECT DISTINCT addon_id, name, game_name " .
|
||||||
"FROM OGP_DB_PREFIXaddons " .
|
"FROM OGP_DB_PREFIXaddons " .
|
||||||
|
|
|
||||||
277
Panel/modules/addonsmanager/workshop_action.php
Normal file
277
Panel/modules/addonsmanager/workshop_action.php
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* GSP - Workshop Content actions (Phase 1)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once("includes/lib_remote.php");
|
||||||
|
require_once("modules/config_games/server_config_parser.php");
|
||||||
|
require_once(dirname(__FILE__) . '/server_content_helpers.php');
|
||||||
|
|
||||||
|
function scm_workshop_log_action($db, $home_id, $user_id, $message)
|
||||||
|
{
|
||||||
|
$db->logger("server_content_workshop home_id=".(int)$home_id." user_id=".(int)$user_id." ".$message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_workshop_update_rows_state($db, $home_id, array $item_ids, $state, $error = null, $mark_install = false, $mark_update = false)
|
||||||
|
{
|
||||||
|
if (empty($item_ids)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$escaped_ids = array();
|
||||||
|
foreach ($item_ids as $item_id) {
|
||||||
|
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
|
||||||
|
}
|
||||||
|
$set = array(
|
||||||
|
"install_state='" . $db->realEscapeSingle($state) . "'",
|
||||||
|
"updated_at=NOW()",
|
||||||
|
);
|
||||||
|
if ($mark_install) {
|
||||||
|
$set[] = "last_installed_at=NOW()";
|
||||||
|
}
|
||||||
|
if ($mark_update) {
|
||||||
|
$set[] = "last_updated_at=NOW()";
|
||||||
|
}
|
||||||
|
if ($error === null) {
|
||||||
|
$set[] = "last_error=NULL";
|
||||||
|
} else {
|
||||||
|
$set[] = "last_error='" . $db->realEscapeSingle($error) . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = "UPDATE `".OGP_DB_PREFIX."server_content_workshop`
|
||||||
|
SET ".implode(", ", $set)."
|
||||||
|
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")";
|
||||||
|
return (bool)$db->query($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_workshop_filter_existing_ids($db, $home_id, array $item_ids)
|
||||||
|
{
|
||||||
|
if (empty($item_ids)) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
$escaped_ids = array();
|
||||||
|
foreach ($item_ids as $item_id) {
|
||||||
|
$escaped_ids[] = "'" . $db->realEscapeSingle((string)$item_id) . "'";
|
||||||
|
}
|
||||||
|
$rows = $db->resultQuery(
|
||||||
|
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
|
||||||
|
WHERE home_id=".(int)$home_id." AND workshop_item_id IN (".implode(",", $escaped_ids).")"
|
||||||
|
);
|
||||||
|
$allowed = array();
|
||||||
|
if (is_array($rows)) {
|
||||||
|
foreach ((array)$rows as $row) {
|
||||||
|
$allowed[(string)$row['workshop_item_id']] = (string)$row['workshop_item_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values($allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_workshop_write_manifest_and_run($db, array $home_info, $server_xml, $action, array $item_ids, &$error = '')
|
||||||
|
{
|
||||||
|
$error = '';
|
||||||
|
if (empty($item_ids)) {
|
||||||
|
$error = 'No Workshop IDs were selected for this action.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$manifest_path = scm_get_workshop_manifest_path($home_info);
|
||||||
|
if ($manifest_path === false) {
|
||||||
|
$error = 'Manifest path validation failed for this server home.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$script_path = scm_get_workshop_script_path($home_info, $server_xml);
|
||||||
|
$script_path = trim((string)$script_path);
|
||||||
|
if ($script_path === '' || !preg_match('/^[^\\r\\n\\0]+$/', $script_path)) {
|
||||||
|
$error = 'Workshop script path is invalid.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
|
||||||
|
if (!scm_path_is_under_home($home_path, $manifest_path)) {
|
||||||
|
$error = 'Manifest path is outside of the server home.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$manifest_dir = dirname($manifest_path);
|
||||||
|
$manifest = array(
|
||||||
|
'action' => (string)$action,
|
||||||
|
'home_id' => (int)$home_info['home_id'],
|
||||||
|
'home_cfg_id' => (int)$home_info['home_cfg_id'],
|
||||||
|
'workshop_app_id' => scm_extract_workshop_app_id($server_xml),
|
||||||
|
'items' => array_values($item_ids),
|
||||||
|
);
|
||||||
|
$manifest_json = json_encode($manifest);
|
||||||
|
if ($manifest_json === false) {
|
||||||
|
$error = 'Failed to encode workshop manifest JSON.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$remote = new OGPRemoteLibrary(
|
||||||
|
$home_info['agent_ip'],
|
||||||
|
$home_info['agent_port'],
|
||||||
|
$home_info['encryption_key'],
|
||||||
|
$home_info['timeout']
|
||||||
|
);
|
||||||
|
|
||||||
|
$remote->exec("mkdir -p " . escapeshellarg($manifest_dir));
|
||||||
|
if ((int)$remote->remote_writefile($manifest_path, $manifest_json) !== 1) {
|
||||||
|
$error = 'Failed to write workshop manifest to remote server.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((int)$remote->rfile_exists($script_path) !== 1) {
|
||||||
|
$error = 'Configured workshop script not found on agent host: ' . $script_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = "bash " . escapeshellarg($script_path) . " " . escapeshellarg($manifest_path) . " ; echo __GSP_WORKSHOP_EXIT:$?";
|
||||||
|
$output = $remote->exec($command);
|
||||||
|
if (!is_string($output) || $output === '') {
|
||||||
|
$error = 'Workshop script did not return an execution status.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!preg_match('/__GSP_WORKSHOP_EXIT:(\d+)/', $output, $matches)) {
|
||||||
|
$error = 'Workshop script exit marker not found in output.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$exit_code = (int)$matches[1];
|
||||||
|
if ($exit_code !== 0) {
|
||||||
|
$error = 'Workshop script failed (exit '.$exit_code.'): '.trim($output);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scm_workshop_handle_action($db, array $home_info, $user_id, $action, $raw_ids, array $selected_ids, &$message, &$is_error)
|
||||||
|
{
|
||||||
|
$message = '';
|
||||||
|
$is_error = true;
|
||||||
|
if (!scm_ensure_workshop_schema($db)) {
|
||||||
|
$message = 'Workshop schema migration failed.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$home_id = (int)$home_info['home_id'];
|
||||||
|
$user_id = (int)$user_id;
|
||||||
|
$server_xml = read_server_config(SERVER_CONFIG_LOCATION . "/" . $home_info['home_cfg_file']);
|
||||||
|
if ($server_xml === false) {
|
||||||
|
$message = 'Unable to read server configuration for workshop action.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'install_new') {
|
||||||
|
$invalid = array();
|
||||||
|
$item_ids = scm_parse_workshop_ids($raw_ids, $invalid);
|
||||||
|
if (!empty($invalid)) {
|
||||||
|
$message = 'Invalid Workshop IDs: ' . implode(', ', $invalid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty($item_ids)) {
|
||||||
|
$message = 'Enter at least one numeric Workshop ID.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($item_ids as $item_id) {
|
||||||
|
$query = "INSERT INTO `".OGP_DB_PREFIX."server_content_workshop`
|
||||||
|
(home_id, home_cfg_id, remote_server_id, workshop_app_id, workshop_item_id, install_state, created_by, created_at, updated_at)
|
||||||
|
VALUES (
|
||||||
|
".$home_id.",
|
||||||
|
".(int)$home_info['home_cfg_id'].",
|
||||||
|
".(int)$home_info['remote_server_id'].",
|
||||||
|
'".$db->realEscapeSingle(scm_extract_workshop_app_id($server_xml))."',
|
||||||
|
'".$db->realEscapeSingle($item_id)."',
|
||||||
|
'selected',
|
||||||
|
".$user_id.",
|
||||||
|
NOW(),
|
||||||
|
NOW()
|
||||||
|
)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
home_cfg_id=VALUES(home_cfg_id),
|
||||||
|
remote_server_id=VALUES(remote_server_id),
|
||||||
|
workshop_app_id=VALUES(workshop_app_id),
|
||||||
|
install_state='selected',
|
||||||
|
last_error=NULL,
|
||||||
|
updated_at=NOW()";
|
||||||
|
$db->query($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
|
||||||
|
$error = '';
|
||||||
|
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'install', $item_ids, $error);
|
||||||
|
if ($ok) {
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, true, true);
|
||||||
|
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." status=success");
|
||||||
|
$is_error = false;
|
||||||
|
$message = 'Workshop IDs installed successfully.';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
|
||||||
|
scm_workshop_log_action($db, $home_id, $user_id, "install_new ids=".implode(',', $item_ids)." status=failed error=".$error);
|
||||||
|
$message = $error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'update_selected' || $action === 'remove_selected') {
|
||||||
|
$item_ids = scm_workshop_filter_existing_ids($db, $home_id, scm_parse_selected_workshop_ids($selected_ids));
|
||||||
|
if (empty($item_ids)) {
|
||||||
|
$message = 'Select one or more saved Workshop IDs.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$target_action = ($action === 'remove_selected') ? 'remove' : 'update';
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
|
||||||
|
$error = '';
|
||||||
|
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, $target_action, $item_ids, $error);
|
||||||
|
if ($ok) {
|
||||||
|
if ($target_action === 'remove') {
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'removed', null, false, true);
|
||||||
|
} else {
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
|
||||||
|
}
|
||||||
|
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." status=success");
|
||||||
|
$is_error = false;
|
||||||
|
$message = ($target_action === 'remove') ? 'Selected Workshop IDs marked removed.' : 'Selected Workshop IDs updated successfully.';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
|
||||||
|
scm_workshop_log_action($db, $home_id, $user_id, $action." ids=".implode(',', $item_ids)." status=failed error=".$error);
|
||||||
|
$message = $error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'update_all') {
|
||||||
|
$rows = $db->resultQuery(
|
||||||
|
"SELECT workshop_item_id FROM `".OGP_DB_PREFIX."server_content_workshop`
|
||||||
|
WHERE home_id=".$home_id." AND install_state<>'removed'"
|
||||||
|
);
|
||||||
|
$item_ids = array();
|
||||||
|
if (is_array($rows)) {
|
||||||
|
foreach ((array)$rows as $row) {
|
||||||
|
$item_ids[] = (string)$row['workshop_item_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$item_ids = scm_parse_selected_workshop_ids($item_ids);
|
||||||
|
if (empty($item_ids)) {
|
||||||
|
$message = 'No Workshop IDs are currently saved for this server.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installing', null, false, false);
|
||||||
|
$error = '';
|
||||||
|
$ok = scm_workshop_write_manifest_and_run($db, $home_info, $server_xml, 'update', $item_ids, $error);
|
||||||
|
if ($ok) {
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'installed', null, false, true);
|
||||||
|
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." status=success");
|
||||||
|
$is_error = false;
|
||||||
|
$message = 'All saved Workshop IDs updated successfully.';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
scm_workshop_update_rows_state($db, $home_id, $item_ids, 'failed', $error, false, false);
|
||||||
|
scm_workshop_log_action($db, $home_id, $user_id, "update_all ids=".implode(',', $item_ids)." status=failed error=".$error);
|
||||||
|
$message = $error;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = 'Invalid workshop action.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
148
Panel/modules/addonsmanager/workshop_content.php
Normal file
148
Panel/modules/addonsmanager/workshop_content.php
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* GSP - Server Content Workshop page (Phase 1)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(dirname(__FILE__) . '/server_content_helpers.php');
|
||||||
|
require_once(dirname(__FILE__) . '/workshop_action.php');
|
||||||
|
|
||||||
|
function exec_ogp_module() {
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$user_id = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 0;
|
||||||
|
$home_id = isset($_REQUEST['home_id']) ? (int)$_REQUEST['home_id'] : 0;
|
||||||
|
$mod_id = isset($_REQUEST['mod_id']) ? (int)$_REQUEST['mod_id'] : 0;
|
||||||
|
$ip = isset($_REQUEST['ip']) ? (string)$_REQUEST['ip'] : '';
|
||||||
|
$port = isset($_REQUEST['port']) ? (string)$_REQUEST['port'] : '';
|
||||||
|
|
||||||
|
if ($home_id <= 0 || $user_id <= 0) {
|
||||||
|
print_failure(get_lang('no_rights'));
|
||||||
|
echo create_back_button("addonsmanager","user_addons");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$home_info = scm_get_home_for_user($db, $home_id, $user_id);
|
||||||
|
if ($home_info === false) {
|
||||||
|
print_failure(get_lang('no_rights'));
|
||||||
|
echo create_back_button("addonsmanager","user_addons");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scm_ensure_workshop_schema($db)) {
|
||||||
|
print_failure('Failed to initialize Workshop Content storage.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = '';
|
||||||
|
$is_error = false;
|
||||||
|
$entered_ids = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$posted_home_id = isset($_POST['home_id']) ? (int)$_POST['home_id'] : 0;
|
||||||
|
$csrf_token = isset($_POST['workshop_csrf']) ? (string)$_POST['workshop_csrf'] : '';
|
||||||
|
$entered_ids = isset($_POST['workshop_ids']) ? (string)$_POST['workshop_ids'] : '';
|
||||||
|
$selected_ids = isset($_POST['selected_ids']) ? $_POST['selected_ids'] : array();
|
||||||
|
$action = isset($_POST['workshop_action']) ? (string)$_POST['workshop_action'] : '';
|
||||||
|
|
||||||
|
if ($posted_home_id !== $home_id) {
|
||||||
|
$is_error = true;
|
||||||
|
$message = 'Invalid server context for workshop action.';
|
||||||
|
}
|
||||||
|
elseif (!scm_validate_csrf_token($csrf_token)) {
|
||||||
|
$is_error = true;
|
||||||
|
$message = 'Invalid CSRF token for workshop action.';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
scm_workshop_handle_action($db, $home_info, $user_id, $action, $entered_ids, (array)$selected_ids, $message, $is_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = scm_get_workshop_rows($db, $home_id);
|
||||||
|
$csrf_token = scm_get_csrf_token();
|
||||||
|
|
||||||
|
echo "<h2>Workshop Content: ".scm_h($home_info['home_name'])."</h2>";
|
||||||
|
if ($message !== '') {
|
||||||
|
if ($is_error) {
|
||||||
|
print_failure($message);
|
||||||
|
} else {
|
||||||
|
print_success($message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<table class='center'>
|
||||||
|
<tr><td align='right'><strong>Server Name:</strong></td><td align='left'><?php echo scm_h($home_info['home_name']); ?></td></tr>
|
||||||
|
<tr><td align='right'><strong>Game Name:</strong></td><td align='left'><?php echo scm_h($home_info['game_name']); ?></td></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<form method='post' action=''>
|
||||||
|
<input type='hidden' name='m' value='addonsmanager' />
|
||||||
|
<input type='hidden' name='p' value='workshop_content' />
|
||||||
|
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
|
||||||
|
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
|
||||||
|
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
|
||||||
|
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
|
||||||
|
<input type='hidden' name='workshop_csrf' value='<?php echo scm_h($csrf_token); ?>' />
|
||||||
|
|
||||||
|
<table class='center'>
|
||||||
|
<tr>
|
||||||
|
<td align='right'><strong>Enter Workshop IDs</strong></td>
|
||||||
|
<td align='left'>
|
||||||
|
<input type='text' name='workshop_ids' size='72' value='<?php echo scm_h($entered_ids); ?>' placeholder='1234567890, 9876543210, 555555555' />
|
||||||
|
</td>
|
||||||
|
<td align='left'>
|
||||||
|
<button type='submit' name='workshop_action' value='install_new'>Install New</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<table class='center'>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Workshop ID</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>State</th>
|
||||||
|
<th>Last Installed</th>
|
||||||
|
<th>Last Updated</th>
|
||||||
|
<th>Last Error</th>
|
||||||
|
</tr>
|
||||||
|
<?php if (empty($rows)): ?>
|
||||||
|
<tr><td colspan='7' class='info'>No Workshop IDs saved for this server yet.</td></tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ((array)$rows as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td><input type='checkbox' name='selected_ids[]' value='<?php echo scm_h($row['workshop_item_id']); ?>'></td>
|
||||||
|
<td><?php echo scm_h($row['workshop_item_id']); ?></td>
|
||||||
|
<td><?php echo scm_h($row['title']); ?></td>
|
||||||
|
<td><?php echo scm_h($row['install_state']); ?></td>
|
||||||
|
<td><?php echo scm_h($row['last_installed_at']); ?></td>
|
||||||
|
<td><?php echo scm_h($row['last_updated_at']); ?></td>
|
||||||
|
<td><?php echo scm_h($row['last_error']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<table class='center'>
|
||||||
|
<tr>
|
||||||
|
<td><button type='submit' name='workshop_action' value='update_selected'>Update Selected</button></td>
|
||||||
|
<td><button type='submit' name='workshop_action' value='remove_selected'>Remove Selected</button></td>
|
||||||
|
<td><button type='submit' name='workshop_action' value='update_all'>Update All</button></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method='get' action=''>
|
||||||
|
<input type='hidden' name='m' value='addonsmanager' />
|
||||||
|
<input type='hidden' name='p' value='user_addons' />
|
||||||
|
<input type='hidden' name='home_id' value='<?php echo (int)$home_id; ?>' />
|
||||||
|
<input type='hidden' name='mod_id' value='<?php echo (int)$mod_id; ?>' />
|
||||||
|
<input type='hidden' name='ip' value='<?php echo scm_h($ip); ?>' />
|
||||||
|
<input type='hidden' name='port' value='<?php echo scm_h($port); ?>' />
|
||||||
|
<input type='submit' value='Back to Server Content' />
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue