diff --git a/database/full.sql b/database/full.sql
index 652e82f..d6d5046 100644
--- a/database/full.sql
+++ b/database/full.sql
@@ -18787,3 +18787,53 @@ CREATE TABLE IF NOT EXISTS tbl_sccharacteritems (
ON DELETE SET NULL
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+
+CREATE TABLE IF NOT EXISTS tbl_scmanutentions (
+ cl_scmanutention_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ cl_scmanutention_owner_auth_id INT UNSIGNED NOT NULL,
+ cl_scmanutention_title VARCHAR(190) NOT NULL,
+ cl_scmanutention_type VARCHAR(120) NOT NULL DEFAULT '',
+ cl_scmanutention_subtype VARCHAR(120) NOT NULL DEFAULT '',
+ cl_scmanutention_description TEXT DEFAULT NULL,
+ cl_scmanutention_share_token VARCHAR(64) NOT NULL,
+ cl_scmanutention_share_enabled TINYINT(1) NOT NULL DEFAULT 0,
+ cl_scmanutention_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ cl_scmanutention_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uq_scmanutention_share_token (cl_scmanutention_share_token),
+ KEY idx_scmanutention_owner (cl_scmanutention_owner_auth_id),
+ KEY idx_scmanutention_title (cl_scmanutention_title),
+ CONSTRAINT fk_scmanutention_owner_auth FOREIGN KEY (cl_scmanutention_owner_auth_id)
+ REFERENCES tbl_auth (cl_auth_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS tbl_scmanutentionitems (
+ cl_scmanutentionitem_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ cl_scmanutentionitem_manutention_id INT UNSIGNED NOT NULL,
+ cl_scmanutentionitem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base',
+ cl_scmanutentionitem_scobjs_id INT UNSIGNED DEFAULT NULL,
+ cl_scmanutentionitem_scitemcustom_id INT(11) DEFAULT NULL,
+ cl_scmanutentionitem_quantity INT UNSIGNED NOT NULL DEFAULT 1,
+ cl_scmanutentionitem_extra_info TEXT DEFAULT NULL,
+ cl_scmanutentionitem_sort_order INT UNSIGNED NOT NULL DEFAULT 0,
+ cl_scmanutentionitem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ cl_scmanutentionitem_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ KEY idx_scmanutentionitem_sheet (cl_scmanutentionitem_manutention_id),
+ KEY idx_scmanutentionitem_scobjs (cl_scmanutentionitem_scobjs_id),
+ KEY idx_scmanutentionitem_scitemcustom (cl_scmanutentionitem_scitemcustom_id),
+ KEY idx_scmanutentionitem_sheet_sort (cl_scmanutentionitem_manutention_id, cl_scmanutentionitem_sort_order, cl_scmanutentionitem_id),
+ CONSTRAINT fk_scmanutentionitem_sheet FOREIGN KEY (cl_scmanutentionitem_manutention_id)
+ REFERENCES tbl_scmanutentions (cl_scmanutention_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ CONSTRAINT fk_scmanutentionitem_scobjs FOREIGN KEY (cl_scmanutentionitem_scobjs_id)
+ REFERENCES tbl_scobjs (cl_scobjs_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE,
+ CONSTRAINT fk_scmanutentionitem_scitemcustom FOREIGN KEY (cl_scmanutentionitem_scitemcustom_id)
+ REFERENCES tbl_scitemcustom (cl_scitemcustom_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/database/schema.sql b/database/schema.sql
index f50adea..9a6a064 100644
--- a/database/schema.sql
+++ b/database/schema.sql
@@ -260,3 +260,53 @@ CREATE TABLE IF NOT EXISTS tbl_sccharacteritems (
ON DELETE SET NULL
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+
+CREATE TABLE IF NOT EXISTS tbl_scmanutentions (
+ cl_scmanutention_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ cl_scmanutention_owner_auth_id INT UNSIGNED NOT NULL,
+ cl_scmanutention_title VARCHAR(190) NOT NULL,
+ cl_scmanutention_type VARCHAR(120) NOT NULL DEFAULT '',
+ cl_scmanutention_subtype VARCHAR(120) NOT NULL DEFAULT '',
+ cl_scmanutention_description TEXT DEFAULT NULL,
+ cl_scmanutention_share_token VARCHAR(64) NOT NULL,
+ cl_scmanutention_share_enabled TINYINT(1) NOT NULL DEFAULT 0,
+ cl_scmanutention_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ cl_scmanutention_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ UNIQUE KEY uq_scmanutention_share_token (cl_scmanutention_share_token),
+ KEY idx_scmanutention_owner (cl_scmanutention_owner_auth_id),
+ KEY idx_scmanutention_title (cl_scmanutention_title),
+ CONSTRAINT fk_scmanutention_owner_auth FOREIGN KEY (cl_scmanutention_owner_auth_id)
+ REFERENCES tbl_auth (cl_auth_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS tbl_scmanutentionitems (
+ cl_scmanutentionitem_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ cl_scmanutentionitem_manutention_id INT UNSIGNED NOT NULL,
+ cl_scmanutentionitem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base',
+ cl_scmanutentionitem_scobjs_id INT UNSIGNED DEFAULT NULL,
+ cl_scmanutentionitem_scitemcustom_id INT(11) DEFAULT NULL,
+ cl_scmanutentionitem_quantity INT UNSIGNED NOT NULL DEFAULT 1,
+ cl_scmanutentionitem_extra_info TEXT DEFAULT NULL,
+ cl_scmanutentionitem_sort_order INT UNSIGNED NOT NULL DEFAULT 0,
+ cl_scmanutentionitem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ cl_scmanutentionitem_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ KEY idx_scmanutentionitem_sheet (cl_scmanutentionitem_manutention_id),
+ KEY idx_scmanutentionitem_scobjs (cl_scmanutentionitem_scobjs_id),
+ KEY idx_scmanutentionitem_scitemcustom (cl_scmanutentionitem_scitemcustom_id),
+ KEY idx_scmanutentionitem_sheet_sort (cl_scmanutentionitem_manutention_id, cl_scmanutentionitem_sort_order, cl_scmanutentionitem_id),
+ CONSTRAINT fk_scmanutentionitem_sheet FOREIGN KEY (cl_scmanutentionitem_manutention_id)
+ REFERENCES tbl_scmanutentions (cl_scmanutention_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ CONSTRAINT fk_scmanutentionitem_scobjs FOREIGN KEY (cl_scmanutentionitem_scobjs_id)
+ REFERENCES tbl_scobjs (cl_scobjs_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE,
+ CONSTRAINT fk_scmanutentionitem_scitemcustom FOREIGN KEY (cl_scmanutentionitem_scitemcustom_id)
+ REFERENCES tbl_scitemcustom (cl_scitemcustom_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/db/auth.php b/db/auth.php
index 3ea1f13..046bd48 100644
--- a/db/auth.php
+++ b/db/auth.php
@@ -470,6 +470,7 @@ function auth_navigation_items(): array
['file' => 'scstatsitem.php', 'label' => 'Stats Item'],
['file' => 'scitemcustom.php', 'label' => 'Objets perso.'],
['file' => 'sccharacters.php', 'label' => 'Personnages'],
+ ['file' => 'scmanutention.php', 'label' => 'Manutention'],
['file' => 'scmining.php', 'label' => 'Scanner Minage'],
['file' => 'scmanufactures.php', 'label' => 'Manufactures'],
['file' => 'scvaisseaux.php', 'label' => 'Vaisseaux'],
diff --git a/db/scmanutention.php b/db/scmanutention.php
new file mode 100644
index 0000000..3b33803
--- /dev/null
+++ b/db/scmanutention.php
@@ -0,0 +1,762 @@
+query("SHOW COLUMNS FROM `{$table}` LIKE " . $db->quote($column));
+
+ return (bool) $stmt->fetch();
+}
+
+function scmanutention_index_exists(PDO $db, string $table, string $index): bool
+{
+ $stmt = $db->query("SHOW INDEX FROM `{$table}` WHERE Key_name = " . $db->quote($index));
+
+ return (bool) $stmt->fetch();
+}
+
+function scmanutention_foreign_key_exists(PDO $db, string $table, string $constraint): bool
+{
+ $stmt = $db->prepare(
+ "SELECT COUNT(*)
+ FROM information_schema.TABLE_CONSTRAINTS
+ WHERE TABLE_SCHEMA = DATABASE()
+ AND TABLE_NAME = :table_name
+ AND CONSTRAINT_NAME = :constraint_name
+ AND CONSTRAINT_TYPE = 'FOREIGN KEY'"
+ );
+ $stmt->execute([
+ 'table_name' => $table,
+ 'constraint_name' => $constraint,
+ ]);
+
+ return (int) $stmt->fetchColumn() > 0;
+}
+
+function scmanutention_bootstrap(): void
+{
+ static $bootstrapped = false;
+
+ if ($bootstrapped) {
+ return;
+ }
+
+ $db = db();
+
+ $db->exec(
+ "CREATE TABLE IF NOT EXISTS tbl_scmanutentions (
+ cl_scmanutention_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ cl_scmanutention_owner_auth_id INT UNSIGNED NOT NULL,
+ cl_scmanutention_title VARCHAR(190) NOT NULL,
+ cl_scmanutention_type VARCHAR(120) NOT NULL DEFAULT '',
+ cl_scmanutention_subtype VARCHAR(120) NOT NULL DEFAULT '',
+ cl_scmanutention_description TEXT DEFAULT NULL,
+ cl_scmanutention_share_token VARCHAR(64) NOT NULL,
+ cl_scmanutention_share_enabled TINYINT(1) NOT NULL DEFAULT 0,
+ cl_scmanutention_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ cl_scmanutention_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (cl_scmanutention_id),
+ UNIQUE KEY uq_scmanutention_share_token (cl_scmanutention_share_token),
+ KEY idx_scmanutention_owner (cl_scmanutention_owner_auth_id),
+ KEY idx_scmanutention_title (cl_scmanutention_title),
+ CONSTRAINT fk_scmanutention_owner_auth FOREIGN KEY (cl_scmanutention_owner_auth_id)
+ REFERENCES tbl_auth (cl_auth_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
+ );
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_owner_auth_id')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentions
+ ADD COLUMN cl_scmanutention_owner_auth_id INT UNSIGNED NULL AFTER cl_scmanutention_id'
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_type')) {
+ $db->exec(
+ "ALTER TABLE tbl_scmanutentions
+ ADD COLUMN cl_scmanutention_type VARCHAR(120) NOT NULL DEFAULT '' AFTER cl_scmanutention_title"
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_subtype')) {
+ $db->exec(
+ "ALTER TABLE tbl_scmanutentions
+ ADD COLUMN cl_scmanutention_subtype VARCHAR(120) NOT NULL DEFAULT '' AFTER cl_scmanutention_type"
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_description')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentions
+ ADD COLUMN cl_scmanutention_description TEXT NULL AFTER cl_scmanutention_subtype'
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_share_token')) {
+ $db->exec(
+ "ALTER TABLE tbl_scmanutentions
+ ADD COLUMN cl_scmanutention_share_token VARCHAR(64) NOT NULL DEFAULT '' AFTER cl_scmanutention_description"
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_share_enabled')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentions
+ ADD COLUMN cl_scmanutention_share_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER cl_scmanutention_share_token'
+ );
+ }
+
+ if (!scmanutention_index_exists($db, 'tbl_scmanutentions', 'idx_scmanutention_owner')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentions
+ ADD INDEX idx_scmanutention_owner (cl_scmanutention_owner_auth_id)'
+ );
+ }
+
+ if (!scmanutention_index_exists($db, 'tbl_scmanutentions', 'idx_scmanutention_title')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentions
+ ADD INDEX idx_scmanutention_title (cl_scmanutention_title)'
+ );
+ }
+
+ if (!scmanutention_index_exists($db, 'tbl_scmanutentions', 'uq_scmanutention_share_token')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentions
+ ADD UNIQUE KEY uq_scmanutention_share_token (cl_scmanutention_share_token)'
+ );
+ }
+
+ if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentions', 'fk_scmanutention_owner_auth')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentions
+ ADD CONSTRAINT fk_scmanutention_owner_auth FOREIGN KEY (cl_scmanutention_owner_auth_id)
+ REFERENCES tbl_auth (cl_auth_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE'
+ );
+ }
+
+ $db->exec(
+ "CREATE TABLE IF NOT EXISTS tbl_scmanutentionitems (
+ cl_scmanutentionitem_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ cl_scmanutentionitem_manutention_id INT UNSIGNED NOT NULL,
+ cl_scmanutentionitem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base',
+ cl_scmanutentionitem_scobjs_id INT UNSIGNED DEFAULT NULL,
+ cl_scmanutentionitem_scitemcustom_id INT(11) DEFAULT NULL,
+ cl_scmanutentionitem_quantity INT UNSIGNED NOT NULL DEFAULT 1,
+ cl_scmanutentionitem_extra_info TEXT DEFAULT NULL,
+ cl_scmanutentionitem_sort_order INT UNSIGNED NOT NULL DEFAULT 0,
+ cl_scmanutentionitem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ cl_scmanutentionitem_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (cl_scmanutentionitem_id),
+ KEY idx_scmanutentionitem_sheet (cl_scmanutentionitem_manutention_id),
+ KEY idx_scmanutentionitem_scobjs (cl_scmanutentionitem_scobjs_id),
+ KEY idx_scmanutentionitem_scitemcustom (cl_scmanutentionitem_scitemcustom_id),
+ KEY idx_scmanutentionitem_sheet_sort (cl_scmanutentionitem_manutention_id, cl_scmanutentionitem_sort_order, cl_scmanutentionitem_id),
+ CONSTRAINT fk_scmanutentionitem_sheet FOREIGN KEY (cl_scmanutentionitem_manutention_id)
+ REFERENCES tbl_scmanutentions (cl_scmanutention_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ CONSTRAINT fk_scmanutentionitem_scobjs FOREIGN KEY (cl_scmanutentionitem_scobjs_id)
+ REFERENCES tbl_scobjs (cl_scobjs_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE,
+ CONSTRAINT fk_scmanutentionitem_scitemcustom FOREIGN KEY (cl_scmanutentionitem_scitemcustom_id)
+ REFERENCES tbl_scitemcustom (cl_scitemcustom_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
+ );
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_source')) {
+ $db->exec(
+ "ALTER TABLE tbl_scmanutentionitems
+ ADD COLUMN cl_scmanutentionitem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base' AFTER cl_scmanutentionitem_manutention_id"
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_scobjs_id')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD COLUMN cl_scmanutentionitem_scobjs_id INT UNSIGNED NULL AFTER cl_scmanutentionitem_source'
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_scitemcustom_id')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD COLUMN cl_scmanutentionitem_scitemcustom_id INT(11) NULL AFTER cl_scmanutentionitem_scobjs_id'
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_quantity')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD COLUMN cl_scmanutentionitem_quantity INT UNSIGNED NOT NULL DEFAULT 1 AFTER cl_scmanutentionitem_scitemcustom_id'
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_extra_info')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD COLUMN cl_scmanutentionitem_extra_info TEXT NULL AFTER cl_scmanutentionitem_quantity'
+ );
+ }
+
+ if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_sort_order')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD COLUMN cl_scmanutentionitem_sort_order INT UNSIGNED NOT NULL DEFAULT 0 AFTER cl_scmanutentionitem_extra_info'
+ );
+ }
+
+ if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_sheet')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD INDEX idx_scmanutentionitem_sheet (cl_scmanutentionitem_manutention_id)'
+ );
+ }
+
+ if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_scobjs')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD INDEX idx_scmanutentionitem_scobjs (cl_scmanutentionitem_scobjs_id)'
+ );
+ }
+
+ if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_scitemcustom')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD INDEX idx_scmanutentionitem_scitemcustom (cl_scmanutentionitem_scitemcustom_id)'
+ );
+ }
+
+ if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_sheet_sort')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD INDEX idx_scmanutentionitem_sheet_sort (cl_scmanutentionitem_manutention_id, cl_scmanutentionitem_sort_order, cl_scmanutentionitem_id)'
+ );
+ }
+
+ if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentionitems', 'fk_scmanutentionitem_sheet')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD CONSTRAINT fk_scmanutentionitem_sheet FOREIGN KEY (cl_scmanutentionitem_manutention_id)
+ REFERENCES tbl_scmanutentions (cl_scmanutention_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE'
+ );
+ }
+
+ if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentionitems', 'fk_scmanutentionitem_scobjs')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD CONSTRAINT fk_scmanutentionitem_scobjs FOREIGN KEY (cl_scmanutentionitem_scobjs_id)
+ REFERENCES tbl_scobjs (cl_scobjs_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE'
+ );
+ }
+
+ if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentionitems', 'fk_scmanutentionitem_scitemcustom')) {
+ $db->exec(
+ 'ALTER TABLE tbl_scmanutentionitems
+ ADD CONSTRAINT fk_scmanutentionitem_scitemcustom FOREIGN KEY (cl_scmanutentionitem_scitemcustom_id)
+ REFERENCES tbl_scitemcustom (cl_scitemcustom_id)
+ ON DELETE SET NULL
+ ON UPDATE CASCADE'
+ );
+ }
+
+ $stmt_missing_tokens = $db->query(
+ "SELECT cl_scmanutention_id
+ FROM tbl_scmanutentions
+ WHERE cl_scmanutention_share_token = ''
+ OR cl_scmanutention_share_token IS NULL"
+ );
+
+ foreach ($stmt_missing_tokens->fetchAll(PDO::FETCH_COLUMN) as $sheet_id) {
+ $stmt_update = $db->prepare(
+ 'UPDATE tbl_scmanutentions
+ SET cl_scmanutention_share_token = :token
+ WHERE cl_scmanutention_id = :id'
+ );
+ $stmt_update->execute([
+ 'token' => scmanutention_generate_share_token(),
+ 'id' => (int) $sheet_id,
+ ]);
+ }
+
+ $bootstrapped = true;
+}
+
+function scmanutention_generate_share_token(int $length = 32): string
+{
+ $length = max(16, min(64, $length));
+
+ return bin2hex(random_bytes((int) ceil($length / 2)));
+}
+
+function scmanutention_clean_text(?string $value): string
+{
+ return trim((string) $value);
+}
+
+function scmanutention_normalize_quantity($value): int
+{
+ $quantity = (int) $value;
+
+ if ($quantity <= 0) {
+ return 1;
+ }
+
+ return min($quantity, 999999);
+}
+
+function scmanutention_is_valid_source(string $value): bool
+{
+ return in_array($value, ['base', 'custom'], true);
+}
+
+function scmanutention_escape_like(string $value): string
+{
+ return strtr($value, [
+ '\\' => '\\\\',
+ '%' => '\\%',
+ '_' => '\\_',
+ ]);
+}
+
+function scmanutention_sheet_share_url(string $token): string
+{
+ $is_https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
+ || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
+ || ((string) ($_SERVER['SERVER_PORT'] ?? '') === '443');
+
+ $scheme = $is_https ? 'https' : 'http';
+ $host = trim((string) ($_SERVER['HTTP_HOST'] ?? ''));
+
+ if ($host === '') {
+ $host = '127.0.0.1';
+ }
+
+ return $scheme . '://' . $host . '/scmanutentionpublic.php?share=' . rawurlencode($token);
+}
+
+function scmanutention_find_owned_sheet(PDO $db, int $sheet_id, int $owner_auth_id): ?array
+{
+ if ($sheet_id <= 0 || $owner_auth_id <= 0) {
+ return null;
+ }
+
+ $stmt = $db->prepare(
+ 'SELECT *
+ FROM tbl_scmanutentions
+ WHERE cl_scmanutention_id = :id
+ AND cl_scmanutention_owner_auth_id = :owner_auth_id
+ LIMIT 1'
+ );
+ $stmt->execute([
+ 'id' => $sheet_id,
+ 'owner_auth_id' => $owner_auth_id,
+ ]);
+
+ $row = $stmt->fetch();
+
+ return $row ?: null;
+}
+
+function scmanutention_find_public_sheet_by_token(PDO $db, string $share_token): ?array
+{
+ $share_token = trim($share_token);
+ if ($share_token === '') {
+ return null;
+ }
+
+ $stmt = $db->prepare(
+ 'SELECT m.*, COALESCE(NULLIF(TRIM(a.cl_auth_user), \'\'), \'Inconnu\') AS cl_scmanutention_owner_name
+ FROM tbl_scmanutentions m
+ LEFT JOIN tbl_auth a ON a.cl_auth_id = m.cl_scmanutention_owner_auth_id
+ WHERE m.cl_scmanutention_share_token = :share_token
+ AND m.cl_scmanutention_share_enabled = 1
+ LIMIT 1'
+ );
+ $stmt->execute(['share_token' => $share_token]);
+
+ $row = $stmt->fetch();
+
+ return $row ?: null;
+}
+
+function scmanutention_next_item_sort_order(PDO $db, int $sheet_id): int
+{
+ $stmt = $db->prepare(
+ 'SELECT COALESCE(MAX(cl_scmanutentionitem_sort_order), 0)
+ FROM tbl_scmanutentionitems
+ WHERE cl_scmanutentionitem_manutention_id = :sheet_id'
+ );
+ $stmt->execute(['sheet_id' => $sheet_id]);
+
+ return ((int) $stmt->fetchColumn()) + 1;
+}
+
+function scmanutention_reindex_items(PDO $db, int $sheet_id): void
+{
+ if ($sheet_id <= 0) {
+ return;
+ }
+
+ $stmt = $db->prepare(
+ 'SELECT cl_scmanutentionitem_id
+ FROM tbl_scmanutentionitems
+ WHERE cl_scmanutentionitem_manutention_id = :sheet_id
+ ORDER BY cl_scmanutentionitem_sort_order ASC, cl_scmanutentionitem_id ASC'
+ );
+ $stmt->execute(['sheet_id' => $sheet_id]);
+ $ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
+
+ $position = 1;
+ $stmt_update = $db->prepare(
+ 'UPDATE tbl_scmanutentionitems
+ SET cl_scmanutentionitem_sort_order = :sort_order
+ WHERE cl_scmanutentionitem_id = :id'
+ );
+
+ foreach ($ids as $id) {
+ $stmt_update->execute([
+ 'sort_order' => $position++,
+ 'id' => (int) $id,
+ ]);
+ }
+}
+
+function scmanutention_find_owned_item(PDO $db, int $item_id, int $owner_auth_id): ?array
+{
+ if ($item_id <= 0 || $owner_auth_id <= 0) {
+ return null;
+ }
+
+ $stmt = $db->prepare(
+ "SELECT
+ mi.*,
+ m.cl_scmanutention_owner_auth_id
+ FROM tbl_scmanutentionitems mi
+ INNER JOIN tbl_scmanutentions m ON m.cl_scmanutention_id = mi.cl_scmanutentionitem_manutention_id
+ WHERE mi.cl_scmanutentionitem_id = :item_id
+ AND m.cl_scmanutention_owner_auth_id = :owner_auth_id
+ LIMIT 1"
+ );
+ $stmt->execute([
+ 'item_id' => $item_id,
+ 'owner_auth_id' => $owner_auth_id,
+ ]);
+
+ $row = $stmt->fetch();
+
+ return $row ?: null;
+}
+
+function scmanutention_validate_item_reference(PDO $db, int $owner_auth_id, string $source, int $scobjs_id, int $scitemcustom_id): ?array
+{
+ if (!scmanutention_is_valid_source($source)) {
+ return null;
+ }
+
+ if ($source === 'base') {
+ if ($scobjs_id <= 0) {
+ return null;
+ }
+
+ $stmt = $db->prepare(
+ 'SELECT cl_scobjs_id, cl_scobjs_name, cl_scobjs_type, cl_scobjs_subtype, cl_scobjs_uuid, cl_scobjs_rarity
+ FROM tbl_scobjs
+ WHERE cl_scobjs_id = :id
+ LIMIT 1'
+ );
+ $stmt->execute(['id' => $scobjs_id]);
+ $row = $stmt->fetch();
+
+ if (!$row) {
+ return null;
+ }
+
+ return [
+ 'source' => 'base',
+ 'scobjs_id' => (int) $row['cl_scobjs_id'],
+ 'scitemcustom_id' => 0,
+ 'name' => (string) $row['cl_scobjs_name'],
+ 'type' => (string) ($row['cl_scobjs_type'] ?? ''),
+ 'subtype' => (string) ($row['cl_scobjs_subtype'] ?? ''),
+ 'uuid' => (string) ($row['cl_scobjs_uuid'] ?? ''),
+ 'rarity' => (string) ($row['cl_scobjs_rarity'] ?? ''),
+ ];
+ }
+
+ if ($scitemcustom_id <= 0 || $owner_auth_id <= 0) {
+ return null;
+ }
+
+ $stmt = $db->prepare(
+ "SELECT
+ c.cl_scitemcustom_id,
+ o.cl_scobjs_id,
+ o.cl_scobjs_name,
+ o.cl_scobjs_type,
+ o.cl_scobjs_subtype,
+ o.cl_scobjs_uuid,
+ o.cl_scobjs_rarity
+ FROM tbl_scitemcustom c
+ INNER JOIN tbl_scobjs o ON o.cl_scobjs_id = c.cl_scitemcustom_obj_id
+ WHERE c.cl_scitemcustom_id = :itemcustom_id
+ AND c.cl_scitemcustom_owner_auth_id = :owner_auth_id
+ LIMIT 1"
+ );
+ $stmt->execute([
+ 'itemcustom_id' => $scitemcustom_id,
+ 'owner_auth_id' => $owner_auth_id,
+ ]);
+ $row = $stmt->fetch();
+
+ if (!$row) {
+ return null;
+ }
+
+ return [
+ 'source' => 'custom',
+ 'scobjs_id' => (int) $row['cl_scobjs_id'],
+ 'scitemcustom_id' => (int) $row['cl_scitemcustom_id'],
+ 'name' => (string) $row['cl_scobjs_name'],
+ 'type' => (string) ($row['cl_scobjs_type'] ?? ''),
+ 'subtype' => (string) ($row['cl_scobjs_subtype'] ?? ''),
+ 'uuid' => (string) ($row['cl_scobjs_uuid'] ?? ''),
+ 'rarity' => (string) ($row['cl_scobjs_rarity'] ?? ''),
+ ];
+}
+
+function scmanutention_search_available_items(PDO $db, int $owner_auth_id, string $query, int $limit = 12): array
+{
+ $query = trim($query);
+ if ($query === '') {
+ return [];
+ }
+
+ $escaped = scmanutention_escape_like($query);
+ $exact = $escaped;
+ $prefix = $escaped . '%';
+ $contains = '%' . $escaped . '%';
+ $limit = max(1, min(30, $limit));
+
+ $sql = "
+ SELECT *
+ FROM (
+ SELECT
+ CONCAT('base:', o.cl_scobjs_id) AS result_key,
+ 'base' AS result_source,
+ o.cl_scobjs_id AS result_scobjs_id,
+ NULL AS result_scitemcustom_id,
+ o.cl_scobjs_name AS result_name,
+ COALESCE(o.cl_scobjs_type, '') AS result_type,
+ COALESCE(o.cl_scobjs_subtype, '') AS result_subtype,
+ COALESCE(o.cl_scobjs_uuid, '') AS result_uuid,
+ COALESCE(o.cl_scobjs_rarity, '') AS result_rarity
+ FROM tbl_scobjs o
+ WHERE (
+ o.cl_scobjs_name LIKE :contains_name_base
+ OR o.cl_scobjs_type LIKE :contains_type_base
+ OR o.cl_scobjs_subtype LIKE :contains_subtype_base
+ OR o.cl_scobjs_uuid LIKE :contains_uuid_base
+ )
+
+ UNION ALL
+
+ SELECT
+ CONCAT('custom:', c.cl_scitemcustom_id) AS result_key,
+ 'custom' AS result_source,
+ o.cl_scobjs_id AS result_scobjs_id,
+ c.cl_scitemcustom_id AS result_scitemcustom_id,
+ o.cl_scobjs_name AS result_name,
+ COALESCE(o.cl_scobjs_type, '') AS result_type,
+ COALESCE(o.cl_scobjs_subtype, '') AS result_subtype,
+ COALESCE(o.cl_scobjs_uuid, '') AS result_uuid,
+ COALESCE(o.cl_scobjs_rarity, '') AS result_rarity
+ FROM tbl_scitemcustom c
+ INNER JOIN tbl_scobjs o ON o.cl_scobjs_id = c.cl_scitemcustom_obj_id
+ WHERE c.cl_scitemcustom_owner_auth_id = :owner_auth_id
+ AND (
+ o.cl_scobjs_name LIKE :contains_name_custom
+ OR o.cl_scobjs_type LIKE :contains_type_custom
+ OR o.cl_scobjs_subtype LIKE :contains_subtype_custom
+ OR o.cl_scobjs_uuid LIKE :contains_uuid_custom
+ )
+ ) search_results
+ ORDER BY
+ CASE
+ WHEN result_name = :exact_name THEN 0
+ WHEN result_name LIKE :prefix_name THEN 1
+ WHEN result_source = 'custom' THEN 2
+ WHEN result_uuid = :exact_uuid THEN 3
+ WHEN result_uuid LIKE :prefix_uuid THEN 4
+ WHEN result_type LIKE :prefix_type THEN 5
+ WHEN result_subtype LIKE :prefix_subtype THEN 6
+ ELSE 7
+ END ASC,
+ CHAR_LENGTH(result_name) ASC,
+ result_name ASC,
+ result_key ASC
+ LIMIT {$limit}";
+
+ $stmt = $db->prepare($sql);
+ $stmt->execute([
+ 'owner_auth_id' => $owner_auth_id,
+ 'contains_name_base' => $contains,
+ 'contains_type_base' => $contains,
+ 'contains_subtype_base' => $contains,
+ 'contains_uuid_base' => $contains,
+ 'contains_name_custom' => $contains,
+ 'contains_type_custom' => $contains,
+ 'contains_subtype_custom' => $contains,
+ 'contains_uuid_custom' => $contains,
+ 'exact_name' => $exact,
+ 'prefix_name' => $prefix,
+ 'exact_uuid' => $exact,
+ 'prefix_uuid' => $prefix,
+ 'prefix_type' => $prefix,
+ 'prefix_subtype' => $prefix,
+ ]);
+
+ return $stmt->fetchAll() ?: [];
+}
+
+
+function scmanutention_sortable_item_name(array $item_row): string
+{
+ $name = (string) ((($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom')
+ ? ($item_row['cl_scmanutentionitem_custom_name'] ?? '')
+ : ($item_row['cl_scmanutentionitem_base_name'] ?? ''));
+ $name = trim($name);
+
+ if ($name === '') {
+ return '';
+ }
+
+ if (function_exists('iconv')) {
+ $ascii_name = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $name);
+ if ($ascii_name !== false) {
+ $name = $ascii_name;
+ }
+ }
+
+ return function_exists('mb_strtolower')
+ ? mb_strtolower($name, 'UTF-8')
+ : strtolower($name);
+}
+
+function scmanutention_fetch_items(PDO $db, int $sheet_id): array
+{
+ if ($sheet_id <= 0) {
+ return [];
+ }
+
+ scmanutention_reindex_items($db, $sheet_id);
+
+ $stmt = $db->prepare(
+ "SELECT
+ mi.*,
+ bo.cl_scobjs_name AS cl_scmanutentionitem_base_name,
+ bo.cl_scobjs_type AS cl_scmanutentionitem_base_type,
+ bo.cl_scobjs_subtype AS cl_scmanutentionitem_base_subtype,
+ bo.cl_scobjs_uuid AS cl_scmanutentionitem_base_uuid,
+ bo.cl_scobjs_rarity AS cl_scmanutentionitem_base_rarity,
+ co.cl_scitemcustom_id AS cl_scmanutentionitem_custom_ref_id,
+ oo.cl_scobjs_name AS cl_scmanutentionitem_custom_name,
+ oo.cl_scobjs_type AS cl_scmanutentionitem_custom_type,
+ oo.cl_scobjs_subtype AS cl_scmanutentionitem_custom_subtype,
+ oo.cl_scobjs_uuid AS cl_scmanutentionitem_custom_uuid,
+ oo.cl_scobjs_rarity AS cl_scmanutentionitem_custom_rarity
+ FROM tbl_scmanutentionitems mi
+ LEFT JOIN tbl_scobjs bo ON bo.cl_scobjs_id = mi.cl_scmanutentionitem_scobjs_id
+ LEFT JOIN tbl_scitemcustom co ON co.cl_scitemcustom_id = mi.cl_scmanutentionitem_scitemcustom_id
+ LEFT JOIN tbl_scobjs oo ON oo.cl_scobjs_id = co.cl_scitemcustom_obj_id
+ WHERE mi.cl_scmanutentionitem_manutention_id = :sheet_id
+ ORDER BY mi.cl_scmanutentionitem_sort_order ASC, mi.cl_scmanutentionitem_id ASC"
+ );
+ $stmt->execute(['sheet_id' => $sheet_id]);
+
+ $items = $stmt->fetchAll() ?: [];
+ foreach ($items as $index => &$item_row) {
+ $item_row['__alpha_sort_name'] = scmanutention_sortable_item_name($item_row);
+ $item_row['__alpha_sort_index'] = $index;
+ }
+ unset($item_row);
+
+ usort($items, static function (array $left, array $right): int {
+ $name_compare = strnatcasecmp((string) ($left['__alpha_sort_name'] ?? ''), (string) ($right['__alpha_sort_name'] ?? ''));
+ if ($name_compare !== 0) {
+ return $name_compare;
+ }
+
+ $sort_order_compare = ((int) ($left['cl_scmanutentionitem_sort_order'] ?? 0)) <=> ((int) ($right['cl_scmanutentionitem_sort_order'] ?? 0));
+ if ($sort_order_compare !== 0) {
+ return $sort_order_compare;
+ }
+
+ $id_compare = ((int) ($left['cl_scmanutentionitem_id'] ?? 0)) <=> ((int) ($right['cl_scmanutentionitem_id'] ?? 0));
+ if ($id_compare !== 0) {
+ return $id_compare;
+ }
+
+ return ((int) ($left['__alpha_sort_index'] ?? 0)) <=> ((int) ($right['__alpha_sort_index'] ?? 0));
+ });
+
+ foreach ($items as &$item_row) {
+ unset($item_row['__alpha_sort_name'], $item_row['__alpha_sort_index']);
+ }
+ unset($item_row);
+
+ return $items;
+}
+
+function scmanutention_fetch_custom_stats_map(PDO $db, array $item_rows): array
+{
+ $custom_ids = [];
+ foreach ($item_rows as $row) {
+ if (($row['cl_scmanutentionitem_source'] ?? '') === 'custom' && !empty($row['cl_scmanutentionitem_scitemcustom_id'])) {
+ $custom_ids[] = (int) $row['cl_scmanutentionitem_scitemcustom_id'];
+ }
+ }
+
+ $custom_ids = array_values(array_unique(array_filter($custom_ids)));
+ if ($custom_ids === []) {
+ return [];
+ }
+
+ $placeholders = implode(',', array_fill(0, count($custom_ids), '?'));
+ $stmt = $db->prepare(
+ "SELECT
+ cs.cl_scitemcustomstat_itemcustom_id,
+ st.cl_scstatsitem_name,
+ st.cl_scstatsitem_unit,
+ cs.cl_scitemcustomstat_sign,
+ cs.cl_scitemcustomstat_value
+ FROM tbl_scitemcustomstat cs
+ INNER JOIN tbl_scstatsitem st ON st.cl_scstatsitem_id = cs.cl_scitemcustomstat_stat_id
+ WHERE cs.cl_scitemcustomstat_itemcustom_id IN ({$placeholders})
+ ORDER BY st.cl_scstatsitem_name ASC, cs.cl_scitemcustomstat_id ASC"
+ );
+ $stmt->execute($custom_ids);
+
+ $stats_map = [];
+ foreach ($stmt->fetchAll() as $row) {
+ $itemcustom_id = (int) $row['cl_scitemcustomstat_itemcustom_id'];
+ if (!isset($stats_map[$itemcustom_id])) {
+ $stats_map[$itemcustom_id] = [];
+ }
+ $stats_map[$itemcustom_id][] = $row;
+ }
+
+ return $stats_map;
+}
diff --git a/scmanutention.php b/scmanutention.php
new file mode 100644
index 0000000..2216a13
--- /dev/null
+++ b/scmanutention.php
@@ -0,0 +1,1678 @@
+prepare(
+ 'SELECT cl_auth_id
+ FROM tbl_auth
+ WHERE cl_auth_user = :user
+ LIMIT 1'
+ );
+ $stmt->execute(['user' => $session_user]);
+
+ return (int) $stmt->fetchColumn();
+}
+
+function scmanutention_redirect(int $sheet_id = 0, string $anchor = ''): void
+{
+ $location = 'scmanutention.php';
+ if ($sheet_id > 0) {
+ $location .= '?sheet=' . $sheet_id;
+ }
+ if ($anchor !== '') {
+ $location .= '#' . rawurlencode(ltrim($anchor, '#'));
+ }
+
+ header('Location: ' . $location);
+ exit;
+}
+
+function scmanutention_rarity_class(string $rarity): string
+{
+ $rarity = strtoupper(trim($rarity));
+
+ return in_array($rarity, ['L', 'E', 'R', 'U', 'C'], true) ? 'rarity-' . $rarity : '';
+}
+
+function scmanutention_item_name(array $item_row): string
+{
+ $is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
+
+ return (string) ($is_custom
+ ? ($item_row['cl_scmanutentionitem_custom_name'] ?? '')
+ : ($item_row['cl_scmanutentionitem_base_name'] ?? ''));
+}
+
+function scmanutention_item_type(array $item_row): string
+{
+ $is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
+
+ return (string) ($is_custom
+ ? ($item_row['cl_scmanutentionitem_custom_type'] ?? '')
+ : ($item_row['cl_scmanutentionitem_base_type'] ?? ''));
+}
+
+function scmanutention_item_subtype(array $item_row): string
+{
+ $is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
+
+ return (string) ($is_custom
+ ? ($item_row['cl_scmanutentionitem_custom_subtype'] ?? '')
+ : ($item_row['cl_scmanutentionitem_base_subtype'] ?? ''));
+}
+
+function scmanutention_item_uuid(array $item_row): string
+{
+ $is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
+
+ return (string) ($is_custom
+ ? ($item_row['cl_scmanutentionitem_custom_uuid'] ?? '')
+ : ($item_row['cl_scmanutentionitem_base_uuid'] ?? ''));
+}
+
+function scmanutention_item_rarity(array $item_row): string
+{
+ $is_custom = ($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom';
+
+ return (string) ($is_custom
+ ? ($item_row['cl_scmanutentionitem_custom_rarity'] ?? '')
+ : ($item_row['cl_scmanutentionitem_base_rarity'] ?? ''));
+}
+
+function scmanutention_format_stat_preview(array $stat_row): string
+{
+ $sign = (string) ($stat_row['cl_scitemcustomstat_sign'] ?? '');
+ $prefix = $sign === '-' ? '-' : ($sign === '+' ? '+' : '');
+ $value = (float) ($stat_row['cl_scitemcustomstat_value'] ?? 0);
+ $formatted = number_format($value, 2, '.', '');
+ $formatted = rtrim(rtrim($formatted, '0'), '.');
+ if ($formatted === '') {
+ $formatted = '0';
+ }
+
+ $unit = trim((string) ($stat_row['cl_scstatsitem_unit'] ?? ''));
+
+ return trim((string) ($stat_row['cl_scstatsitem_name'] ?? '') . ' : ' . $prefix . $formatted . ($unit !== '' ? ' ' . $unit : ''));
+}
+
+$flash = auth_flash_get();
+$flash_type = $flash['type'] ?? '';
+$flash_message = $flash['message'] ?? '';
+
+$db = db();
+$csrf_token = auth_csrf_token();
+$current_owner_auth_id = scmanutention_current_owner_auth_id($db);
+$current_role_label = auth_role_label(auth_current_role());
+$current_session_user = auth_current_user();
+
+if ($current_owner_auth_id <= 0) {
+ auth_flash_set('error', 'Utilisateur introuvable. Merci de vous reconnecter.');
+ header('Location: logout.php');
+ exit;
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'GET' && (string) ($_GET['ajax'] ?? '') === 'item_suggestions') {
+ header('Content-Type: application/json; charset=utf-8');
+
+ $query = trim((string) ($_GET['q'] ?? ''));
+ $items = array_map(static function (array $row): array {
+ return [
+ 'key' => (string) $row['result_key'],
+ 'source' => (string) $row['result_source'],
+ 'sourceLabel' => ((string) $row['result_source']) === 'custom' ? 'Objet perso' : 'Base d\'objets',
+ 'scobjs_id' => (int) ($row['result_scobjs_id'] ?? 0),
+ 'scitemcustom_id' => (int) ($row['result_scitemcustom_id'] ?? 0),
+ 'name' => (string) ($row['result_name'] ?? ''),
+ 'type' => (string) ($row['result_type'] ?? ''),
+ 'subtype' => (string) ($row['result_subtype'] ?? ''),
+ 'uuid' => (string) ($row['result_uuid'] ?? ''),
+ 'rarity' => (string) ($row['result_rarity'] ?? ''),
+ ];
+ }, scmanutention_search_available_items($db, $current_owner_auth_id, $query, 14));
+
+ echo json_encode(['items' => $items], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+ exit;
+}
+
+$selected_sheet_id = isset($_GET['sheet']) ? (int) $_GET['sheet'] : 0;
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $csrf = isset($_POST['csrf_token']) ? (string) $_POST['csrf_token'] : null;
+ if (!auth_validate_csrf($csrf)) {
+ auth_flash_set('error', 'Jeton CSRF invalide.');
+ scmanutention_redirect($selected_sheet_id);
+ }
+
+ $action = trim((string) ($_POST['action'] ?? ''));
+
+ if ($action === 'create_sheet') {
+ $title = scmanutention_clean_text($_POST['title'] ?? '');
+ $type = scmanutention_clean_text($_POST['type'] ?? '');
+ $subtype = scmanutention_clean_text($_POST['subtype'] ?? '');
+ $description = scmanutention_clean_text($_POST['description'] ?? '');
+ $share_enabled = isset($_POST['share_enabled']) ? 1 : 0;
+
+ if ($title === '') {
+ auth_flash_set('error', 'Le nom de la fiche est obligatoire.');
+ scmanutention_redirect();
+ }
+
+ $stmt = $db->prepare(
+ 'INSERT INTO tbl_scmanutentions (
+ cl_scmanutention_owner_auth_id,
+ cl_scmanutention_title,
+ cl_scmanutention_type,
+ cl_scmanutention_subtype,
+ cl_scmanutention_description,
+ cl_scmanutention_share_token,
+ cl_scmanutention_share_enabled
+ ) VALUES (
+ :owner_auth_id,
+ :title,
+ :type,
+ :subtype,
+ :description,
+ :share_token,
+ :share_enabled
+ )'
+ );
+ $stmt->execute([
+ 'owner_auth_id' => $current_owner_auth_id,
+ 'title' => $title,
+ 'type' => $type,
+ 'subtype' => $subtype,
+ 'description' => $description !== '' ? $description : null,
+ 'share_token' => scmanutention_generate_share_token(),
+ 'share_enabled' => $share_enabled,
+ ]);
+
+ $new_sheet_id = (int) $db->lastInsertId();
+ auth_flash_set('success', 'Fiche créée avec succès.');
+ scmanutention_redirect($new_sheet_id, 'sheet-settings');
+ }
+
+ if ($action === 'update_sheet') {
+ $sheet_id = (int) ($_POST['sheet_id'] ?? 0);
+ $sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
+ if (!$sheet) {
+ auth_flash_set('error', 'Fiche introuvable.');
+ scmanutention_redirect();
+ }
+
+ $title = scmanutention_clean_text($_POST['title'] ?? '');
+ $type = scmanutention_clean_text($_POST['type'] ?? '');
+ $subtype = scmanutention_clean_text($_POST['subtype'] ?? '');
+ $description = scmanutention_clean_text($_POST['description'] ?? '');
+ $share_enabled = isset($_POST['share_enabled']) ? 1 : 0;
+
+ if ($title === '') {
+ auth_flash_set('error', 'Le nom de la fiche est obligatoire.');
+ scmanutention_redirect($sheet_id, 'sheet-settings');
+ }
+
+ $stmt = $db->prepare(
+ 'UPDATE tbl_scmanutentions
+ SET cl_scmanutention_title = :title,
+ cl_scmanutention_type = :type,
+ cl_scmanutention_subtype = :subtype,
+ cl_scmanutention_description = :description,
+ cl_scmanutention_share_enabled = :share_enabled
+ WHERE cl_scmanutention_id = :sheet_id'
+ );
+ $stmt->execute([
+ 'title' => $title,
+ 'type' => $type,
+ 'subtype' => $subtype,
+ 'description' => $description !== '' ? $description : null,
+ 'share_enabled' => $share_enabled,
+ 'sheet_id' => $sheet_id,
+ ]);
+
+ auth_flash_set('success', 'Fiche mise à jour.');
+ scmanutention_redirect($sheet_id, 'sheet-settings');
+ }
+
+ if ($action === 'regenerate_share') {
+ $sheet_id = (int) ($_POST['sheet_id'] ?? 0);
+ $sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
+ if (!$sheet) {
+ auth_flash_set('error', 'Fiche introuvable.');
+ scmanutention_redirect();
+ }
+
+ $stmt = $db->prepare(
+ 'UPDATE tbl_scmanutentions
+ SET cl_scmanutention_share_token = :share_token
+ WHERE cl_scmanutention_id = :sheet_id'
+ );
+ $stmt->execute([
+ 'share_token' => scmanutention_generate_share_token(),
+ 'sheet_id' => $sheet_id,
+ ]);
+
+ auth_flash_set('success', 'Lien public régénéré.');
+ scmanutention_redirect($sheet_id, 'sheet-settings');
+ }
+
+ if ($action === 'delete_sheet') {
+ $sheet_id = (int) ($_POST['sheet_id'] ?? 0);
+ $sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
+ if (!$sheet) {
+ auth_flash_set('error', 'Fiche introuvable.');
+ scmanutention_redirect();
+ }
+
+ $stmt = $db->prepare('DELETE FROM tbl_scmanutentions WHERE cl_scmanutention_id = :sheet_id');
+ $stmt->execute(['sheet_id' => $sheet_id]);
+
+ auth_flash_set('success', 'Fiche supprimée.');
+ scmanutention_redirect();
+ }
+
+ if ($action === 'add_item') {
+ $sheet_id = (int) ($_POST['sheet_id'] ?? 0);
+ $sheet = scmanutention_find_owned_sheet($db, $sheet_id, $current_owner_auth_id);
+ if (!$sheet) {
+ auth_flash_set('error', 'Fiche introuvable.');
+ scmanutention_redirect();
+ }
+
+ $source = trim((string) ($_POST['item_source'] ?? ''));
+ $scobjs_id = (int) ($_POST['item_scobjs_id'] ?? 0);
+ $scitemcustom_id = (int) ($_POST['item_scitemcustom_id'] ?? 0);
+ $quantity = scmanutention_normalize_quantity($_POST['quantity'] ?? 1);
+ $extra_info = scmanutention_clean_text($_POST['extra_info'] ?? '');
+
+ $item_reference = scmanutention_validate_item_reference($db, $current_owner_auth_id, $source, $scobjs_id, $scitemcustom_id);
+ if (!$item_reference) {
+ auth_flash_set('error', 'Objet invalide ou inaccessible.');
+ scmanutention_redirect($sheet_id, 'item-add-card');
+ }
+
+ $stmt = $db->prepare(
+ 'INSERT INTO tbl_scmanutentionitems (
+ cl_scmanutentionitem_manutention_id,
+ cl_scmanutentionitem_source,
+ cl_scmanutentionitem_scobjs_id,
+ cl_scmanutentionitem_scitemcustom_id,
+ cl_scmanutentionitem_quantity,
+ cl_scmanutentionitem_extra_info,
+ cl_scmanutentionitem_sort_order
+ ) VALUES (
+ :sheet_id,
+ :source,
+ :scobjs_id,
+ :scitemcustom_id,
+ :quantity,
+ :extra_info,
+ :sort_order
+ )'
+ );
+ $stmt->execute([
+ 'sheet_id' => $sheet_id,
+ 'source' => $item_reference['source'],
+ 'scobjs_id' => $item_reference['scobjs_id'] > 0 ? $item_reference['scobjs_id'] : null,
+ 'scitemcustom_id' => $item_reference['scitemcustom_id'] > 0 ? $item_reference['scitemcustom_id'] : null,
+ 'quantity' => $quantity,
+ 'extra_info' => $extra_info !== '' ? $extra_info : null,
+ 'sort_order' => scmanutention_next_item_sort_order($db, $sheet_id),
+ ]);
+
+ auth_flash_set('success', 'Objet ajouté à la fiche.');
+ scmanutention_redirect($sheet_id, 'sheet-items');
+ }
+
+ if ($action === 'update_item') {
+ $item_id = (int) ($_POST['item_id'] ?? 0);
+ $item_row = scmanutention_find_owned_item($db, $item_id, $current_owner_auth_id);
+ if (!$item_row) {
+ auth_flash_set('error', 'Ligne introuvable.');
+ scmanutention_redirect($selected_sheet_id);
+ }
+
+ $sheet_id = (int) $item_row['cl_scmanutentionitem_manutention_id'];
+ $source = trim((string) ($_POST['item_source'] ?? ''));
+ $scobjs_id = (int) ($_POST['item_scobjs_id'] ?? 0);
+ $scitemcustom_id = (int) ($_POST['item_scitemcustom_id'] ?? 0);
+ $quantity = scmanutention_normalize_quantity($_POST['quantity'] ?? 1);
+ $extra_info = scmanutention_clean_text($_POST['extra_info'] ?? '');
+
+ $item_reference = scmanutention_validate_item_reference($db, $current_owner_auth_id, $source, $scobjs_id, $scitemcustom_id);
+ if (!$item_reference) {
+ auth_flash_set('error', 'Objet invalide ou inaccessible.');
+ scmanutention_redirect($sheet_id, 'manutention-item-' . $item_id);
+ }
+
+ $stmt = $db->prepare(
+ 'UPDATE tbl_scmanutentionitems
+ SET cl_scmanutentionitem_source = :source,
+ cl_scmanutentionitem_scobjs_id = :scobjs_id,
+ cl_scmanutentionitem_scitemcustom_id = :scitemcustom_id,
+ cl_scmanutentionitem_quantity = :quantity,
+ cl_scmanutentionitem_extra_info = :extra_info
+ WHERE cl_scmanutentionitem_id = :item_id'
+ );
+ $stmt->execute([
+ 'source' => $item_reference['source'],
+ 'scobjs_id' => $item_reference['scobjs_id'] > 0 ? $item_reference['scobjs_id'] : null,
+ 'scitemcustom_id' => $item_reference['scitemcustom_id'] > 0 ? $item_reference['scitemcustom_id'] : null,
+ 'quantity' => $quantity,
+ 'extra_info' => $extra_info !== '' ? $extra_info : null,
+ 'item_id' => $item_id,
+ ]);
+
+ auth_flash_set('success', 'Ligne mise à jour.');
+ scmanutention_redirect($sheet_id, 'manutention-item-' . $item_id);
+ }
+
+ if ($action === 'delete_item') {
+ $item_id = (int) ($_POST['item_id'] ?? 0);
+ $item_row = scmanutention_find_owned_item($db, $item_id, $current_owner_auth_id);
+ if (!$item_row) {
+ auth_flash_set('error', 'Ligne introuvable.');
+ scmanutention_redirect($selected_sheet_id);
+ }
+
+ $sheet_id = (int) $item_row['cl_scmanutentionitem_manutention_id'];
+ $stmt = $db->prepare('DELETE FROM tbl_scmanutentionitems WHERE cl_scmanutentionitem_id = :item_id');
+ $stmt->execute(['item_id' => $item_id]);
+ scmanutention_reindex_items($db, $sheet_id);
+
+ auth_flash_set('success', 'Ligne supprimée.');
+ scmanutention_redirect($sheet_id, 'sheet-items');
+ }
+
+ auth_flash_set('error', 'Action inconnue.');
+ scmanutention_redirect($selected_sheet_id);
+}
+
+$stmt_sheets = $db->prepare(
+ 'SELECT
+ m.*,
+ COUNT(mi.cl_scmanutentionitem_id) AS cl_item_total
+ FROM tbl_scmanutentions m
+ LEFT JOIN tbl_scmanutentionitems mi ON mi.cl_scmanutentionitem_manutention_id = m.cl_scmanutention_id
+ WHERE m.cl_scmanutention_owner_auth_id = :owner_auth_id
+ GROUP BY m.cl_scmanutention_id
+ ORDER BY m.cl_scmanutention_updated_at DESC, m.cl_scmanutention_id DESC'
+);
+$stmt_sheets->execute(['owner_auth_id' => $current_owner_auth_id]);
+$sheets = $stmt_sheets->fetchAll() ?: [];
+
+if ($selected_sheet_id <= 0 && !empty($sheets)) {
+ $selected_sheet_id = (int) $sheets[0]['cl_scmanutention_id'];
+}
+
+$selected_sheet = $selected_sheet_id > 0
+ ? scmanutention_find_owned_sheet($db, $selected_sheet_id, $current_owner_auth_id)
+ : null;
+
+$selected_items = $selected_sheet
+ ? scmanutention_fetch_items($db, (int) $selected_sheet['cl_scmanutention_id'])
+ : [];
+$custom_stats_map = scmanutention_fetch_custom_stats_map($db, $selected_items);
+$page_access_widget = auth_render_page_access_widget('scmanutention.php', 'Manutention');
+?>
+
+
+
+
+
+ Manutention
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Aucune fiche sélectionnée
+ Crée une fiche ou sélectionne-en une dans la colonne de gauche pour commencer à ajouter des objets.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ objet(s)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ajouter un objet
+ Recherche interactive inspirée de la page objets perso : base d’objets + objets personnalisés, avec quantité et info libre.
+
+
+
+
+ Objets de la fiche
+
+ Cette fiche ne contient encore aucun objet. Utilise la recherche ci-dessus pour commencer.
+
+
+
+
+
+
+
+
+
; ?>.png)
+
+
+
+ x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modifier la ligne
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scmanutentionpublic.php b/scmanutentionpublic.php
new file mode 100644
index 0000000..718e2ec
--- /dev/null
+++ b/scmanutentionpublic.php
@@ -0,0 +1,322 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Fiche introuvable
+ Cette fiche n’existe pas, n’est plus publique, ou le lien partagé n’est plus valide.
+
+
+
+
+
+
+
+
+
+
+
+ objet(s)
+ Publié par
+
+
+
+
+ Cette fiche publique ne contient pas encore de description.
+
+
+
+
+ Contenu de la fiche
+
+ Aucun objet n’est actuellement listé sur cette fiche.
+
+
+
+
+
+
+
+
; ?>.png)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+