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 sccharacters_clean_text(?string $value): string { return trim((string) $value); } function sccharacters_normalize_item_quantity($value): ?int { if ($value === null) { return null; } $value = trim((string) $value); if ($value === '' || !preg_match('/^\d+$/', $value)) { return null; } $quantity = (int) $value; if ($quantity <= 0) { return null; } return min($quantity, 999999); } function sccharacters_is_valid_url(string $value): bool { if ($value === '') { return true; } return filter_var($value, FILTER_VALIDATE_URL) !== false; } function sccharacters_excerpt(string $value, int $limit = 120): string { $value = trim($value); if ($value === '') { return ''; } if (function_exists('mb_strimwidth')) { return mb_strimwidth($value, 0, $limit, '…', 'UTF-8'); } if (strlen($value) <= $limit) { return $value; } return rtrim(substr($value, 0, max(0, $limit - 1))) . '…'; } function sccharacters_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 . '/sccharacter.php?share=' . rawurlencode($token); } function sccharacters_find_owned_character(PDO $db, int $character_id, int $owner_auth_id): ?array { if ($character_id <= 0 || $owner_auth_id <= 0) { return null; } $stmt = $db->prepare( 'SELECT * FROM tbl_sccharacters WHERE cl_sccharacter_id = :id AND cl_sccharacter_owner_auth_id = :owner_auth_id LIMIT 1' ); $stmt->execute([ 'id' => $character_id, 'owner_auth_id' => $owner_auth_id, ]); $row = $stmt->fetch(); return $row ?: null; } function sccharacters_allowed_item_per_page(): array { return [25, 50, 100, 200]; } function sccharacters_normalize_item_per_page($value): int { $per_page = (int) $value; $allowed = sccharacters_allowed_item_per_page(); return in_array($per_page, $allowed, true) ? $per_page : 50; } function sccharacters_build_return_url( int $character_id = 0, string $item_source = '', string $item_search = '', bool $keep_item_panel = false, int $item_page = 1, int $item_per_page = 50 ): string { $params = []; if ($character_id > 0) { $params['character'] = $character_id; } if (in_array($item_source, ['base', 'custom'], true)) { $params['item_source'] = $item_source; } if ($item_search !== '') { $params['item_search'] = $item_search; } $item_page = max(1, $item_page); if ($item_page > 1) { $params['item_page'] = $item_page; } $item_per_page = sccharacters_normalize_item_per_page($item_per_page); if ($item_per_page !== 50) { $params['item_per_page'] = $item_per_page; } if ($keep_item_panel) { $params['item_panel'] = '1'; } return 'sccharacters.php' . ($params !== [] ? '?' . http_build_query($params) : ''); } function sccharacters_staged_items_session_key(int $owner_auth_id, int $character_id): string { return $owner_auth_id . ':' . $character_id; } function sccharacters_get_staged_items(int $owner_auth_id, int $character_id): array { if ($owner_auth_id <= 0 || $character_id <= 0) { return []; } if (!isset($_SESSION['sccharacters_staged_items']) || !is_array($_SESSION['sccharacters_staged_items'])) { $_SESSION['sccharacters_staged_items'] = []; } $session_key = sccharacters_staged_items_session_key($owner_auth_id, $character_id); $items = $_SESSION['sccharacters_staged_items'][$session_key] ?? []; return is_array($items) ? $items : []; } function sccharacters_save_staged_items(int $owner_auth_id, int $character_id, array $items): void { if (!isset($_SESSION['sccharacters_staged_items']) || !is_array($_SESSION['sccharacters_staged_items'])) { $_SESSION['sccharacters_staged_items'] = []; } $session_key = sccharacters_staged_items_session_key($owner_auth_id, $character_id); if ($owner_auth_id <= 0 || $character_id <= 0 || $items === []) { unset($_SESSION['sccharacters_staged_items'][$session_key]); return; } $_SESSION['sccharacters_staged_items'][$session_key] = $items; } function sccharacters_parse_item_key(string $item_key): ?array { if (!preg_match('/^(base|custom):(\d+)$/', $item_key, $matches)) { return null; } return [ 'source' => (string) $matches[1], 'source_id' => (int) $matches[2], ]; } function sccharacters_merge_selected_items(array $staged_items, array $selected_items, array $item_slots, array $item_notes, array $item_quantities = []): array { $selected_items = array_values(array_unique(array_map('strval', $selected_items))); $staged_count = 0; $error_count = 0; foreach ($selected_items as $item_key) { $parsed = sccharacters_parse_item_key($item_key); if (!$parsed) { $error_count++; continue; } $staged_items[$item_key] = [ 'source' => $parsed['source'], 'source_id' => $parsed['source_id'], 'category' => sccharacters_clean_text($item_slots[$item_key] ?? ''), 'note' => sccharacters_clean_text($item_notes[$item_key] ?? ''), 'quantity' => sccharacters_normalize_item_quantity($item_quantities[$item_key] ?? null), ]; $staged_count++; } return [ 'items' => $staged_items, 'staged_count' => $staged_count, 'error_count' => $error_count, ]; } function sccharacters_fetch_staged_item_details(PDO $db, int $owner_auth_id, array $staged_items): array { $details = []; $base_ids = []; $custom_ids = []; foreach ($staged_items as $item_key => $item_data) { $parsed = sccharacters_parse_item_key((string) $item_key); if (!$parsed) { continue; } if ($parsed['source'] === 'custom') { $custom_ids[] = $parsed['source_id']; } else { $base_ids[] = $parsed['source_id']; } } $base_ids = array_values(array_unique(array_filter($base_ids))); $custom_ids = array_values(array_unique(array_filter($custom_ids))); if ($base_ids !== []) { $placeholders = implode(',', array_fill(0, count($base_ids), '?')); $stmt = $db->prepare( "SELECT cl_scobjs_id, cl_scobjs_name, cl_scobjs_type, cl_scobjs_subtype, cl_scobjs_uuid FROM tbl_scobjs WHERE cl_scobjs_id IN ({$placeholders})" ); $stmt->execute($base_ids); foreach ($stmt->fetchAll() as $row) { $item_key = 'base:' . (int) $row['cl_scobjs_id']; $details[$item_key] = [ 'item_key' => $item_key, 'source' => 'base', 'source_id' => (int) $row['cl_scobjs_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'] ?? ''), 'is_custom' => false, 'stat_count' => null, ]; } } if ($custom_ids !== []) { $placeholders = implode(',', array_fill(0, count($custom_ids), '?')); $stmt = $db->prepare( "SELECT c.cl_scitemcustom_id, o.cl_scobjs_name, o.cl_scobjs_type, o.cl_scobjs_subtype, o.cl_scobjs_uuid, COUNT(cs.cl_scitemcustomstat_id) AS cl_scitemcustom_stat_count FROM tbl_scitemcustom c INNER JOIN tbl_scobjs o ON o.cl_scobjs_id = c.cl_scitemcustom_obj_id LEFT JOIN tbl_scitemcustomstat cs ON cs.cl_scitemcustomstat_itemcustom_id = c.cl_scitemcustom_id WHERE c.cl_scitemcustom_owner_auth_id = ? AND c.cl_scitemcustom_id IN ({$placeholders}) GROUP BY c.cl_scitemcustom_id" ); $stmt->execute(array_merge([$owner_auth_id], $custom_ids)); foreach ($stmt->fetchAll() as $row) { $item_key = 'custom:' . (int) $row['cl_scitemcustom_id']; $details[$item_key] = [ 'item_key' => $item_key, 'source' => 'custom', 'source_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'] ?? ''), 'is_custom' => true, 'stat_count' => (int) ($row['cl_scitemcustom_stat_count'] ?? 0), ]; } } return $details; } function sccharacters_fetch_owned_character_item_ids(PDO $db, int $owner_auth_id, int $character_id): array { if ($character_id <= 0 || $owner_auth_id <= 0) { return []; } sccharacters_reindex_character_items($db, $character_id); $stmt = $db->prepare( 'SELECT ci.cl_sccharacteritem_id FROM tbl_sccharacteritems ci INNER JOIN tbl_sccharacters c ON c.cl_sccharacter_id = ci.cl_sccharacteritem_character_id WHERE ci.cl_sccharacteritem_character_id = :character_id AND c.cl_sccharacter_owner_auth_id = :owner_auth_id ORDER BY ci.cl_sccharacteritem_sort_order ASC, ci.cl_sccharacteritem_id ASC' ); $stmt->execute([ 'character_id' => $character_id, 'owner_auth_id' => $owner_auth_id, ]); return array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN)); } function sccharacters_save_character_item_order(PDO $db, int $character_id, array $ordered_item_ids): void { if ($character_id <= 0 || $ordered_item_ids === []) { return; } $stmt = $db->prepare( 'UPDATE tbl_sccharacteritems SET cl_sccharacteritem_sort_order = :sort_order WHERE cl_sccharacteritem_character_id = :character_id AND cl_sccharacteritem_id = :item_id' ); $db->beginTransaction(); try { $position = 1; foreach ($ordered_item_ids as $item_id) { $stmt->execute([ 'sort_order' => $position, 'character_id' => $character_id, 'item_id' => (int) $item_id, ]); $position++; } $db->commit(); } catch (Throwable $exception) { if ($db->inTransaction()) { $db->rollBack(); } throw $exception; } } function sccharacters_move_owned_character_item( PDO $db, int $owner_auth_id, int $character_id, int $character_item_id, string $direction ): bool { $ordered_item_ids = sccharacters_fetch_owned_character_item_ids($db, $owner_auth_id, $character_id); if ($ordered_item_ids === []) { return false; } $current_index = array_search($character_item_id, $ordered_item_ids, true); if ($current_index === false) { return false; } $target_index = $current_index; $last_index = count($ordered_item_ids) - 1; switch ($direction) { case 'top': $target_index = 0; break; case 'up': $target_index = max(0, $current_index - 1); break; case 'down': $target_index = min($last_index, $current_index + 1); break; case 'bottom': $target_index = $last_index; break; default: return false; } if ($target_index === $current_index) { return true; } $moving_item_id = $ordered_item_ids[$current_index]; array_splice($ordered_item_ids, $current_index, 1); array_splice($ordered_item_ids, $target_index, 0, [$moving_item_id]); sccharacters_save_character_item_order($db, $character_id, $ordered_item_ids); return true; } function sccharacters_fetch_owned_character_item_rows(PDO $db, int $owner_auth_id, int $character_id): array { if ($character_id <= 0 || $owner_auth_id <= 0) { return []; } sccharacters_reindex_character_items($db, $character_id); $stmt = $db->prepare( "SELECT ci.cl_sccharacteritem_id, ci.cl_sccharacteritem_slot, ci.cl_sccharacteritem_source, bo.cl_scobjs_type AS cl_sccharacteritem_base_type, bo.cl_scobjs_subtype AS cl_sccharacteritem_base_subtype, oo.cl_scobjs_type AS cl_sccharacteritem_custom_type, oo.cl_scobjs_subtype AS cl_sccharacteritem_custom_subtype FROM tbl_sccharacteritems ci INNER JOIN tbl_sccharacters c ON c.cl_sccharacter_id = ci.cl_sccharacteritem_character_id LEFT JOIN tbl_scobjs bo ON bo.cl_scobjs_id = ci.cl_sccharacteritem_scobjs_id LEFT JOIN tbl_scitemcustom co ON co.cl_scitemcustom_id = ci.cl_sccharacteritem_scitemcustom_id LEFT JOIN tbl_scobjs oo ON oo.cl_scobjs_id = co.cl_scitemcustom_obj_id WHERE ci.cl_sccharacteritem_character_id = :character_id AND c.cl_sccharacter_owner_auth_id = :owner_auth_id ORDER BY ci.cl_sccharacteritem_sort_order ASC, ci.cl_sccharacteritem_id ASC" ); $stmt->execute([ 'character_id' => $character_id, 'owner_auth_id' => $owner_auth_id, ]); return $stmt->fetchAll(); } function sccharacters_group_item_ids_by_category(array $item_rows): array { $grouped = []; foreach ($item_rows as $item_row) { $is_custom = ($item_row['cl_sccharacteritem_source'] ?? '') === 'custom'; $item_type = $is_custom ? (string) ($item_row['cl_sccharacteritem_custom_type'] ?? '') : (string) ($item_row['cl_sccharacteritem_base_type'] ?? ''); $item_subtype = $is_custom ? (string) ($item_row['cl_sccharacteritem_custom_subtype'] ?? '') : (string) ($item_row['cl_sccharacteritem_base_subtype'] ?? ''); $category_key = sccharacters_resolve_item_category( (string) ($item_row['cl_sccharacteritem_slot'] ?? ''), $item_type, $item_subtype ); if (!isset($grouped[$category_key])) { $grouped[$category_key] = []; } $grouped[$category_key][] = (int) ($item_row['cl_sccharacteritem_id'] ?? 0); } return $grouped; } function sccharacters_flatten_grouped_item_ids(array $grouped_item_ids, array $category_order): array { $ordered_ids = []; $sorted_groups = sccharacters_sort_items_by_category_order($grouped_item_ids, $category_order); foreach ($sorted_groups as $category_item_ids) { foreach ($category_item_ids as $item_id) { $item_id = (int) $item_id; if ($item_id > 0) { $ordered_ids[] = $item_id; } } } return $ordered_ids; } function sccharacters_move_owned_character_category( PDO $db, int $owner_auth_id, int $character_id, string $category_key, string $direction ): bool { $character = sccharacters_find_owned_character($db, $character_id, $owner_auth_id); if (!$character) { return false; } $grouped_item_ids = sccharacters_group_item_ids_by_category( sccharacters_fetch_owned_character_item_rows($db, $owner_auth_id, $character_id) ); if (!isset($grouped_item_ids[$category_key])) { return false; } $category_order = sccharacters_character_category_order($character); $visible_order = []; foreach ($category_order as $ordered_category_key) { if (isset($grouped_item_ids[$ordered_category_key])) { $visible_order[] = $ordered_category_key; } } if ($visible_order === []) { return false; } $current_index = array_search($category_key, $visible_order, true); if ($current_index === false) { return false; } $target_index = $current_index; if ($direction === 'up') { $target_index = max(0, $current_index - 1); } elseif ($direction === 'down') { $target_index = min(count($visible_order) - 1, $current_index + 1); } else { return false; } if ($target_index === $current_index) { return true; } $moving_category_key = $visible_order[$current_index]; array_splice($visible_order, $current_index, 1); array_splice($visible_order, $target_index, 0, [$moving_category_key]); $visible_lookup = array_fill_keys(array_keys($grouped_item_ids), true); $visible_pointer = 0; $rebuilt_order = []; foreach ($category_order as $ordered_category_key) { if (isset($visible_lookup[$ordered_category_key])) { $rebuilt_order[] = $visible_order[$visible_pointer] ?? $ordered_category_key; $visible_pointer++; } else { $rebuilt_order[] = $ordered_category_key; } } sccharacters_save_character_category_order($db, $character_id, $rebuilt_order); sccharacters_save_character_item_order( $db, $character_id, sccharacters_flatten_grouped_item_ids($grouped_item_ids, $rebuilt_order) ); return true; } function sccharacters_reorder_owned_character_items( PDO $db, int $owner_auth_id, int $character_id, string $category_key, array $ordered_item_ids ): bool { $character = sccharacters_find_owned_character($db, $character_id, $owner_auth_id); if (!$character) { return false; } $grouped_item_ids = sccharacters_group_item_ids_by_category( sccharacters_fetch_owned_character_item_rows($db, $owner_auth_id, $character_id) ); if (!isset($grouped_item_ids[$category_key])) { return false; } $current_item_ids = array_map('intval', $grouped_item_ids[$category_key]); $posted_item_ids = array_values(array_filter(array_map('intval', $ordered_item_ids), static fn ($item_id) => $item_id > 0)); sort($current_item_ids); $sorted_posted = $posted_item_ids; sort($sorted_posted); if ($posted_item_ids === [] || $sorted_posted !== $current_item_ids) { return false; } $grouped_item_ids[$category_key] = $posted_item_ids; sccharacters_save_character_item_order( $db, $character_id, sccharacters_flatten_grouped_item_ids($grouped_item_ids, sccharacters_character_category_order($character)) ); return true; } function sccharacters_attach_item( PDO $db, int $owner_auth_id, int $character_id, string $source, int $source_id, string $requested_category, ?int $quantity, string $note, ?string &$error_message = null ): bool { if ($character_id <= 0 || $owner_auth_id <= 0 || $source_id <= 0) { $error_message = 'Paramètres d’ajout invalides.'; return false; } $character = sccharacters_find_owned_character($db, $character_id, $owner_auth_id); if (!$character) { $error_message = 'Personnage introuvable.'; return false; } $sort_order = sccharacters_next_item_sort_order($db, $character_id); if ($source === 'base') { $stmt_source = $db->prepare( 'SELECT cl_scobjs_id AS item_id, cl_scobjs_type, cl_scobjs_subtype FROM tbl_scobjs WHERE cl_scobjs_id = :id LIMIT 1' ); $stmt_source->execute(['id' => $source_id]); $item_row = $stmt_source->fetch(); if (!$item_row) { $error_message = 'Objet de base introuvable.'; return false; } $category = sccharacters_resolve_item_category( $requested_category, (string) ($item_row['cl_scobjs_type'] ?? ''), (string) ($item_row['cl_scobjs_subtype'] ?? '') ); $stmt_insert = $db->prepare( 'INSERT INTO tbl_sccharacteritems ( cl_sccharacteritem_character_id, cl_sccharacteritem_source, cl_sccharacteritem_scobjs_id, cl_sccharacteritem_scitemcustom_id, cl_sccharacteritem_slot, cl_sccharacteritem_quantity, cl_sccharacteritem_note, cl_sccharacteritem_sort_order ) VALUES ( :character_id, :source, :scobjs_id, NULL, :slot, :quantity, :note, :sort_order )' ); $stmt_insert->execute([ 'character_id' => $character_id, 'source' => 'base', 'scobjs_id' => $source_id, 'slot' => $category, 'quantity' => $quantity, 'note' => $note !== '' ? $note : null, 'sort_order' => $sort_order, ]); return true; } if ($source === 'custom') { $stmt_source = $db->prepare( 'SELECT c.cl_scitemcustom_id AS item_id, o.cl_scobjs_type, o.cl_scobjs_subtype FROM tbl_scitemcustom c INNER JOIN tbl_scobjs o ON o.cl_scobjs_id = c.cl_scitemcustom_obj_id WHERE c.cl_scitemcustom_id = :id AND c.cl_scitemcustom_owner_auth_id = :owner_auth_id LIMIT 1' ); $stmt_source->execute([ 'id' => $source_id, 'owner_auth_id' => $owner_auth_id, ]); $item_row = $stmt_source->fetch(); if (!$item_row) { $error_message = 'Objet personnalisé introuvable ou non autorisé.'; return false; } $category = sccharacters_resolve_item_category( $requested_category, (string) ($item_row['cl_scobjs_type'] ?? ''), (string) ($item_row['cl_scobjs_subtype'] ?? '') ); $stmt_insert = $db->prepare( 'INSERT INTO tbl_sccharacteritems ( cl_sccharacteritem_character_id, cl_sccharacteritem_source, cl_sccharacteritem_scobjs_id, cl_sccharacteritem_scitemcustom_id, cl_sccharacteritem_slot, cl_sccharacteritem_quantity, cl_sccharacteritem_note, cl_sccharacteritem_sort_order ) VALUES ( :character_id, :source, NULL, :scitemcustom_id, :slot, :quantity, :note, :sort_order )' ); $stmt_insert->execute([ 'character_id' => $character_id, 'source' => 'custom', 'scitemcustom_id' => $source_id, 'slot' => $category, 'quantity' => $quantity, 'note' => $note !== '' ? $note : null, 'sort_order' => $sort_order, ]); return true; } $error_message = 'Source d’objet invalide.'; return false; } function sccharacters_update_character_item( PDO $db, int $owner_auth_id, int $character_id, int $character_item_id, string $requested_category, ?int $quantity, string $note, ?string &$error_message = null ): bool { if ($character_id <= 0 || $owner_auth_id <= 0 || $character_item_id <= 0) { $error_message = 'Paramètres de mise à jour invalides.'; return false; } $stmt_item = $db->prepare( "SELECT ci.cl_sccharacteritem_id, ci.cl_sccharacteritem_source, bo.cl_scobjs_type AS cl_sccharacteritem_base_type, bo.cl_scobjs_subtype AS cl_sccharacteritem_base_subtype, oo.cl_scobjs_type AS cl_sccharacteritem_custom_type, oo.cl_scobjs_subtype AS cl_sccharacteritem_custom_subtype FROM tbl_sccharacteritems ci INNER JOIN tbl_sccharacters c ON c.cl_sccharacter_id = ci.cl_sccharacteritem_character_id LEFT JOIN tbl_scobjs bo ON bo.cl_scobjs_id = ci.cl_sccharacteritem_scobjs_id LEFT JOIN tbl_scitemcustom co ON co.cl_scitemcustom_id = ci.cl_sccharacteritem_scitemcustom_id LEFT JOIN tbl_scobjs oo ON oo.cl_scobjs_id = co.cl_scitemcustom_obj_id WHERE ci.cl_sccharacteritem_id = :character_item_id AND c.cl_sccharacter_id = :character_id AND c.cl_sccharacter_owner_auth_id = :owner_auth_id LIMIT 1" ); $stmt_item->execute([ 'character_item_id' => $character_item_id, 'character_id' => $character_id, 'owner_auth_id' => $owner_auth_id, ]); $item_row = $stmt_item->fetch(); if (!$item_row) { $error_message = 'Objet introuvable ou non autorisé.'; return false; } $is_custom = (string) ($item_row['cl_sccharacteritem_source'] ?? '') === 'custom'; $item_type = $is_custom ? (string) ($item_row['cl_sccharacteritem_custom_type'] ?? '') : (string) ($item_row['cl_sccharacteritem_base_type'] ?? ''); $item_subtype = $is_custom ? (string) ($item_row['cl_sccharacteritem_custom_subtype'] ?? '') : (string) ($item_row['cl_sccharacteritem_base_subtype'] ?? ''); $category = sccharacters_resolve_item_category($requested_category, $item_type, $item_subtype); $stmt_update = $db->prepare( 'UPDATE tbl_sccharacteritems SET cl_sccharacteritem_slot = :slot, cl_sccharacteritem_quantity = :quantity, cl_sccharacteritem_note = :note WHERE cl_sccharacteritem_id = :character_item_id AND cl_sccharacteritem_character_id = :character_id' ); $stmt_update->execute([ 'slot' => $category, 'quantity' => $quantity, 'note' => $note !== '' ? $note : null, 'character_item_id' => $character_item_id, 'character_id' => $character_id, ]); return true; } $flash = auth_flash_get(); $flash_type = $flash['type'] ?? ''; $flash_message = $flash['message'] ?? ''; $db = db(); $csrf_token = auth_csrf_token(); $current_owner_auth_id = sccharacters_current_owner_auth_id($db); $current_session_user = auth_current_user(); $current_session_role = auth_current_role(); $role_label = auth_role_label($current_session_role); if ($current_owner_auth_id <= 0) { auth_flash_set('error', 'Impossible d’identifier le compte connecté.'); header('Location: index.php'); exit; } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $submitted_csrf = (string) ($_POST['csrf_token'] ?? ''); if (!auth_validate_csrf($submitted_csrf)) { auth_flash_set('error', 'Jeton CSRF invalide.'); header('Location: sccharacters.php'); exit; } $action = (string) ($_POST['action'] ?? ''); if ($action === 'create_character') { $name = sccharacters_clean_text($_POST['character_name'] ?? ''); $role = sccharacters_clean_text($_POST['character_role'] ?? ''); $faction = ''; $org_rsi_url = sccharacters_clean_text($_POST['character_org_rsi_url'] ?? ''); $player_handle = sccharacters_clean_text($_POST['character_player_handle'] ?? ''); $is_player = $player_handle !== '' ? 1 : 0; $avatar_url = sccharacters_clean_text($_POST['character_avatar_url'] ?? ''); $description = sccharacters_clean_text($_POST['character_description'] ?? ''); $notes = sccharacters_clean_text($_POST['character_notes'] ?? ''); $share_enabled = isset($_POST['character_share_enabled']) ? 1 : 0; $is_pinned = isset($_POST['character_is_pinned']) ? 1 : 0; $posted_category_order_state = sccharacters_clean_text($_POST['category_order_state'] ?? ''); $posted_item_order_state_raw = trim((string) ($_POST['item_order_state'] ?? '')); if ($name === '') { auth_flash_set('error', 'Le nom du personnage est obligatoire.'); header('Location: sccharacters.php?mode=create'); exit; } if (!sccharacters_is_valid_url($avatar_url)) { auth_flash_set('error', 'L’URL de l’avatar n’est pas valide.'); header('Location: sccharacters.php?mode=create'); exit; } if (!sccharacters_is_valid_url($org_rsi_url)) { auth_flash_set('error', 'L’URL RSI de l’organisation n’est pas valide.'); header('Location: sccharacters.php?mode=create'); exit; } $stmt = $db->prepare( 'INSERT INTO tbl_sccharacters ( cl_sccharacter_owner_auth_id, cl_sccharacter_name, cl_sccharacter_role, cl_sccharacter_faction, cl_sccharacter_org_rsi_url, cl_sccharacter_is_player, cl_sccharacter_player_handle, cl_sccharacter_avatar_url, cl_sccharacter_description, cl_sccharacter_notes, cl_sccharacter_share_token, cl_sccharacter_share_enabled, cl_sccharacter_is_pinned ) VALUES ( :owner_auth_id, :name, :role, :faction, :org_rsi_url, :is_player, :player_handle, :avatar_url, :description, :notes, :share_token, :share_enabled, :is_pinned )' ); $stmt->execute([ 'owner_auth_id' => $current_owner_auth_id, 'name' => $name, 'role' => $role, 'faction' => $faction, 'org_rsi_url' => $org_rsi_url, 'is_player' => $is_player, 'player_handle' => $player_handle, 'avatar_url' => $avatar_url, 'description' => $description !== '' ? $description : null, 'notes' => $notes !== '' ? $notes : null, 'share_token' => sccharacters_generate_share_token($db), 'share_enabled' => $share_enabled, 'is_pinned' => $is_pinned, ]); $new_character_id = (int) $db->lastInsertId(); auth_flash_set('success', 'Personnage créé avec succès.'); header('Location: sccharacters.php?character=' . $new_character_id); exit; } if ($action === 'update_character') { $character_id = (int) ($_POST['character_id'] ?? 0); $character = sccharacters_find_owned_character($db, $character_id, $current_owner_auth_id); if (!$character) { auth_flash_set('error', 'Personnage introuvable.'); header('Location: sccharacters.php'); exit; } $name = sccharacters_clean_text($_POST['character_name'] ?? ''); $role = sccharacters_clean_text($_POST['character_role'] ?? ''); $faction = ''; $org_rsi_url = sccharacters_clean_text($_POST['character_org_rsi_url'] ?? ''); $player_handle = sccharacters_clean_text($_POST['character_player_handle'] ?? ''); $is_player = $player_handle !== '' ? 1 : 0; $avatar_url = sccharacters_clean_text($_POST['character_avatar_url'] ?? ''); $description = sccharacters_clean_text($_POST['character_description'] ?? ''); $notes = sccharacters_clean_text($_POST['character_notes'] ?? ''); $share_enabled = isset($_POST['character_share_enabled']) ? 1 : 0; $is_pinned = isset($_POST['character_is_pinned']) ? 1 : 0; $posted_category_order_state = sccharacters_clean_text($_POST['category_order_state'] ?? ''); $posted_item_order_state_raw = trim((string) ($_POST['item_order_state'] ?? '')); if ($name === '') { auth_flash_set('error', 'Le nom du personnage est obligatoire.'); header('Location: sccharacters.php?character=' . $character_id); exit; } if (!sccharacters_is_valid_url($avatar_url)) { auth_flash_set('error', 'L’URL de l’avatar n’est pas valide.'); header('Location: sccharacters.php?character=' . $character_id); exit; } if (!sccharacters_is_valid_url($org_rsi_url)) { auth_flash_set('error', 'L’URL RSI de l’organisation n’est pas valide.'); header('Location: sccharacters.php?character=' . $character_id); exit; } $stmt = $db->prepare( 'UPDATE tbl_sccharacters SET cl_sccharacter_name = :name, cl_sccharacter_role = :role, cl_sccharacter_faction = :faction, cl_sccharacter_org_rsi_url = :org_rsi_url, cl_sccharacter_is_player = :is_player, cl_sccharacter_player_handle = :player_handle, cl_sccharacter_avatar_url = :avatar_url, cl_sccharacter_description = :description, cl_sccharacter_notes = :notes, cl_sccharacter_share_enabled = :share_enabled, cl_sccharacter_is_pinned = :is_pinned WHERE cl_sccharacter_id = :id AND cl_sccharacter_owner_auth_id = :owner_auth_id' ); $stmt->execute([ 'name' => $name, 'role' => $role, 'faction' => $faction, 'org_rsi_url' => $org_rsi_url, 'is_player' => $is_player, 'player_handle' => $player_handle, 'avatar_url' => $avatar_url, 'description' => $description !== '' ? $description : null, 'notes' => $notes !== '' ? $notes : null, 'share_enabled' => $share_enabled, 'is_pinned' => $is_pinned, 'id' => $character_id, 'owner_auth_id' => $current_owner_auth_id, ]); $current_item_rows = sccharacters_fetch_owned_character_item_rows($db, $current_owner_auth_id, $character_id); $grouped_item_ids = sccharacters_group_item_ids_by_category($current_item_rows); $current_visible_category_keys = array_keys( sccharacters_sort_items_by_category_order($grouped_item_ids, sccharacters_character_category_order($character)) ); $submitted_visible_category_order = $current_visible_category_keys; if ($posted_category_order_state !== '') { $candidate_category_order = array_values(array_filter( array_map('sccharacters_clean_text', preg_split('/\s*,\s*/', $posted_category_order_state, -1, PREG_SPLIT_NO_EMPTY) ?: []), static fn (string $category_key): bool => $category_key !== '' )); if ($candidate_category_order !== []) { $candidate_sorted = $candidate_category_order; $current_sorted = $current_visible_category_keys; sort($candidate_sorted); sort($current_sorted); if ($candidate_sorted === $current_sorted) { $submitted_visible_category_order = $candidate_category_order; } } } $submitted_item_order_state = []; if ($posted_item_order_state_raw !== '') { $decoded_item_order_state = json_decode($posted_item_order_state_raw, true); if (is_array($decoded_item_order_state)) { $submitted_item_order_state = $decoded_item_order_state; } } foreach ($grouped_item_ids as $category_key => $current_category_item_ids) { $current_category_item_ids = array_values(array_map('intval', $current_category_item_ids)); $candidate_category_item_ids = $submitted_item_order_state[$category_key] ?? null; if (!is_array($candidate_category_item_ids)) { $grouped_item_ids[$category_key] = $current_category_item_ids; continue; } $candidate_category_item_ids = array_values(array_filter( array_map('intval', $candidate_category_item_ids), static fn (int $item_id): bool => $item_id > 0 )); $sorted_candidate_item_ids = $candidate_category_item_ids; $sorted_current_category_item_ids = $current_category_item_ids; sort($sorted_candidate_item_ids); sort($sorted_current_category_item_ids); if ($candidate_category_item_ids !== [] && $sorted_candidate_item_ids === $sorted_current_category_item_ids) { $grouped_item_ids[$category_key] = $candidate_category_item_ids; } else { $grouped_item_ids[$category_key] = $current_category_item_ids; } } $persisted_category_order = sccharacters_character_category_order($character); $visible_category_lookup = array_fill_keys(array_keys($grouped_item_ids), true); $visible_pointer = 0; $rebuilt_category_order = []; foreach ($persisted_category_order as $ordered_category_key) { if (isset($visible_category_lookup[$ordered_category_key])) { $rebuilt_category_order[] = $submitted_visible_category_order[$visible_pointer] ?? $ordered_category_key; $visible_pointer++; } else { $rebuilt_category_order[] = $ordered_category_key; } } sccharacters_save_character_category_order($db, $character_id, $rebuilt_category_order); sccharacters_save_character_item_order( $db, $character_id, sccharacters_flatten_grouped_item_ids($grouped_item_ids, $rebuilt_category_order) ); auth_flash_set('success', 'Personnage mis à jour.'); header('Location: sccharacters.php?character=' . $character_id); exit; } if ($action === 'delete_character') { $character_id = (int) ($_POST['character_id'] ?? 0); $character = sccharacters_find_owned_character($db, $character_id, $current_owner_auth_id); if (!$character) { auth_flash_set('error', 'Personnage introuvable.'); header('Location: sccharacters.php'); exit; } $stmt = $db->prepare( 'DELETE FROM tbl_sccharacters WHERE cl_sccharacter_id = :id AND cl_sccharacter_owner_auth_id = :owner_auth_id' ); $stmt->execute([ 'id' => $character_id, 'owner_auth_id' => $current_owner_auth_id, ]); auth_flash_set('success', 'Personnage supprimé.'); header('Location: sccharacters.php'); exit; } if ($action === 'regenerate_share_token') { $character_id = (int) ($_POST['character_id'] ?? 0); $character = sccharacters_find_owned_character($db, $character_id, $current_owner_auth_id); if (!$character) { auth_flash_set('error', 'Personnage introuvable.'); header('Location: sccharacters.php'); exit; } $stmt = $db->prepare( 'UPDATE tbl_sccharacters SET cl_sccharacter_share_token = :token WHERE cl_sccharacter_id = :id AND cl_sccharacter_owner_auth_id = :owner_auth_id' ); $stmt->execute([ 'token' => sccharacters_generate_share_token($db), 'id' => $character_id, 'owner_auth_id' => $current_owner_auth_id, ]); auth_flash_set('success', 'Lien public régénéré.'); header('Location: sccharacters.php?character=' . $character_id); exit; } if ($action === 'add_base_item') { $character_id = (int) ($_POST['character_id'] ?? 0); $obj_id = (int) ($_POST['base_obj_id'] ?? 0); $requested_category = sccharacters_clean_text($_POST['item_slot'] ?? ''); $quantity = sccharacters_normalize_item_quantity($_POST['item_quantity'] ?? null); $note = sccharacters_clean_text($_POST['item_note'] ?? ''); $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'base'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); $item_page_context = max(1, (int) ($_POST['item_page_context'] ?? 1)); $item_per_page_context = sccharacters_normalize_item_per_page($_POST['item_per_page_context'] ?? 50); $error_message = null; if (!sccharacters_attach_item( $db, $current_owner_auth_id, $character_id, 'base', $obj_id, $requested_category, $quantity, $note, $error_message )) { auth_flash_set('error', $error_message ?? 'Impossible d’ajouter l’objet.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, true, $item_page_context, $item_per_page_context)); exit; } auth_flash_set('success', 'Objet de la base ajouté au personnage.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, true, $item_page_context, $item_per_page_context)); exit; } if ($action === 'add_custom_item') { $character_id = (int) ($_POST['character_id'] ?? 0); $itemcustom_id = (int) ($_POST['custom_item_id'] ?? 0); $requested_category = sccharacters_clean_text($_POST['item_slot'] ?? ''); $quantity = sccharacters_normalize_item_quantity($_POST['item_quantity'] ?? null); $note = sccharacters_clean_text($_POST['item_note'] ?? ''); $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'custom'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); $item_page_context = max(1, (int) ($_POST['item_page_context'] ?? 1)); $item_per_page_context = sccharacters_normalize_item_per_page($_POST['item_per_page_context'] ?? 50); $error_message = null; if (!sccharacters_attach_item( $db, $current_owner_auth_id, $character_id, 'custom', $itemcustom_id, $requested_category, $quantity, $note, $error_message )) { auth_flash_set('error', $error_message ?? 'Impossible d’ajouter l’objet.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, true, $item_page_context, $item_per_page_context)); exit; } auth_flash_set('success', 'Objet personnalisé ajouté au personnage.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, true, $item_page_context, $item_per_page_context)); exit; } if ($action === 'update_character_item') { $character_item_id = (int) ($_POST['character_item_id'] ?? 0); $character_id = (int) ($_POST['character_id'] ?? 0); $requested_category = sccharacters_clean_text($_POST['item_slot'] ?? ''); $quantity = sccharacters_normalize_item_quantity($_POST['item_quantity'] ?? null); $note = sccharacters_clean_text($_POST['item_note'] ?? ''); $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'base'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); $item_page_context = max(1, (int) ($_POST['item_page_context'] ?? 1)); $item_per_page_context = sccharacters_normalize_item_per_page($_POST['item_per_page_context'] ?? 50); $item_panel_context = (string) ($_POST['item_panel_context'] ?? '') === '1'; $error_message = null; if (!sccharacters_update_character_item( $db, $current_owner_auth_id, $character_id, $character_item_id, $requested_category, $quantity, $note, $error_message )) { auth_flash_set('error', $error_message ?? 'Impossible de mettre à jour cet objet.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); exit; } auth_flash_set('success', 'Objet mis à jour.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); exit; } if ($action === 'move_character_category') { $character_id = (int) ($_POST['character_id'] ?? 0); $category_key = sccharacters_clean_text($_POST['category_key'] ?? ''); $move_direction = sccharacters_clean_text($_POST['move_direction'] ?? ''); $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'base'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); $item_page_context = max(1, (int) ($_POST['item_page_context'] ?? 1)); $item_per_page_context = sccharacters_normalize_item_per_page($_POST['item_per_page_context'] ?? 50); $item_panel_context = (string) ($_POST['item_panel_context'] ?? '') === '1'; if (!sccharacters_move_owned_character_category($db, $current_owner_auth_id, $character_id, $category_key, $move_direction)) { auth_flash_set('error', 'Impossible de déplacer cette catégorie.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); exit; } auth_flash_set('success', 'Ordre des catégories mis à jour.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); exit; } if ($action === 'reorder_character_items') { $character_id = (int) ($_POST['character_id'] ?? 0); $category_key = sccharacters_clean_text($_POST['category_key'] ?? ''); $ordered_item_ids = preg_split('/\s*,\s*/', (string) ($_POST['ordered_item_ids'] ?? ''), -1, PREG_SPLIT_NO_EMPTY) ?: []; $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'base'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); $item_page_context = max(1, (int) ($_POST['item_page_context'] ?? 1)); $item_per_page_context = sccharacters_normalize_item_per_page($_POST['item_per_page_context'] ?? 50); $item_panel_context = (string) ($_POST['item_panel_context'] ?? '') === '1'; if (!sccharacters_reorder_owned_character_items($db, $current_owner_auth_id, $character_id, $category_key, $ordered_item_ids)) { auth_flash_set('error', 'Impossible de réordonner les objets.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); exit; } auth_flash_set('success', 'Ordre des objets mis à jour.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); exit; } if ($action === 'delete_character_item') { $character_item_id = (int) ($_POST['character_item_id'] ?? 0); $character_id = (int) ($_POST['character_id'] ?? 0); $item_source_context = sccharacters_clean_text($_POST['item_source_context'] ?? 'base'); $item_search_context = sccharacters_clean_text($_POST['item_search_context'] ?? ''); $item_page_context = max(1, (int) ($_POST['item_page_context'] ?? 1)); $item_per_page_context = sccharacters_normalize_item_per_page($_POST['item_per_page_context'] ?? 50); $item_panel_context = (string) ($_POST['item_panel_context'] ?? '') === '1'; $stmt = $db->prepare( 'DELETE ci FROM tbl_sccharacteritems ci INNER JOIN tbl_sccharacters c ON c.cl_sccharacter_id = ci.cl_sccharacteritem_character_id WHERE ci.cl_sccharacteritem_id = :character_item_id AND c.cl_sccharacter_id = :character_id AND c.cl_sccharacter_owner_auth_id = :owner_auth_id' ); $stmt->execute([ 'character_item_id' => $character_item_id, 'character_id' => $character_id, 'owner_auth_id' => $current_owner_auth_id, ]); sccharacters_reindex_character_items($db, $character_id); auth_flash_set('success', 'Objet retiré du personnage.'); header('Location: ' . sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, $item_panel_context, $item_page_context, $item_per_page_context)); exit; } } $stmt_characters = $db->prepare( 'SELECT c.*, COUNT(ci.cl_sccharacteritem_id) AS cl_sccharacter_item_count FROM tbl_sccharacters c LEFT JOIN tbl_sccharacteritems ci ON ci.cl_sccharacteritem_character_id = c.cl_sccharacter_id WHERE c.cl_sccharacter_owner_auth_id = :owner_auth_id GROUP BY c.cl_sccharacter_id ORDER BY c.cl_sccharacter_is_pinned DESC, c.cl_sccharacter_updated_at DESC, c.cl_sccharacter_name ASC' ); $stmt_characters->execute(['owner_auth_id' => $current_owner_auth_id]); $characters = $stmt_characters->fetchAll(); $character_lookup = []; foreach ($characters as $character_row) { $character_lookup[(int) $character_row['cl_sccharacter_id']] = $character_row; } $mode = (string) ($_GET['mode'] ?? ''); $selected_character_id = (int) ($_GET['character'] ?? 0); $selected_character = null; if ($selected_character_id > 0 && isset($character_lookup[$selected_character_id])) { $selected_character = $character_lookup[$selected_character_id]; } elseif ($characters !== []) { $selected_character = $characters[0]; $selected_character_id = (int) $selected_character['cl_sccharacter_id']; } $create_panel_open = ($mode === 'create') || ($characters === []); $item_source = sccharacters_clean_text($_GET['item_source'] ?? 'base'); if (!in_array($item_source, ['base', 'custom'], true)) { $item_source = 'base'; } $item_search = sccharacters_clean_text($_GET['item_search'] ?? ''); $item_panel_open = (string) ($_GET['item_panel'] ?? '') === '1'; $item_page = max(1, (int) ($_GET['item_page'] ?? 1)); $item_per_page = sccharacters_normalize_item_per_page($_GET['item_per_page'] ?? 50); $item_results = []; $item_total_results = 0; $item_total_pages = 1; $item_result_offset_start = 0; $item_result_offset_end = 0; if ($selected_character) { if ($item_source === 'custom') { $count_sql = "SELECT COUNT(DISTINCT c.cl_scitemcustom_id) 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"; $params = ['owner_auth_id' => $current_owner_auth_id]; if ($item_search !== '') { $count_sql .= " AND ( o.cl_scobjs_name LIKE :search OR o.cl_scobjs_type LIKE :search OR o.cl_scobjs_subtype LIKE :search OR o.cl_scobjs_uuid LIKE :search )"; $params['search'] = '%' . $item_search . '%'; } $stmt_item_count = $db->prepare($count_sql); $stmt_item_count->execute($params); $item_total_results = (int) $stmt_item_count->fetchColumn(); $item_total_pages = max(1, (int) ceil($item_total_results / $item_per_page)); $item_page = min($item_page, $item_total_pages); $item_offset = max(0, ($item_page - 1) * $item_per_page); $sql = "SELECT c.cl_scitemcustom_id, o.cl_scobjs_name, o.cl_scobjs_type, o.cl_scobjs_subtype, o.cl_scobjs_uuid, COUNT(cs.cl_scitemcustomstat_id) AS cl_scitemcustom_stat_count FROM tbl_scitemcustom c INNER JOIN tbl_scobjs o ON o.cl_scobjs_id = c.cl_scitemcustom_obj_id LEFT JOIN tbl_scitemcustomstat cs ON cs.cl_scitemcustomstat_itemcustom_id = c.cl_scitemcustom_id WHERE c.cl_scitemcustom_owner_auth_id = :owner_auth_id"; if ($item_search !== '') { $sql .= " AND ( o.cl_scobjs_name LIKE :search OR o.cl_scobjs_type LIKE :search OR o.cl_scobjs_subtype LIKE :search OR o.cl_scobjs_uuid LIKE :search )"; } $sql .= " GROUP BY c.cl_scitemcustom_id ORDER BY o.cl_scobjs_name ASC, c.cl_scitemcustom_id ASC LIMIT " . (int) $item_per_page . " OFFSET " . (int) $item_offset; $stmt_item_results = $db->prepare($sql); $stmt_item_results->execute($params); $item_results = $stmt_item_results->fetchAll(); } else { $count_sql = "SELECT COUNT(*) FROM tbl_scobjs WHERE 1 = 1"; $params = []; if ($item_search !== '') { $count_sql .= " AND ( cl_scobjs_name LIKE :search OR cl_scobjs_type LIKE :search OR cl_scobjs_subtype LIKE :search OR cl_scobjs_uuid LIKE :search )"; $params['search'] = '%' . $item_search . '%'; } $stmt_item_count = $db->prepare($count_sql); $stmt_item_count->execute($params); $item_total_results = (int) $stmt_item_count->fetchColumn(); $item_total_pages = max(1, (int) ceil($item_total_results / $item_per_page)); $item_page = min($item_page, $item_total_pages); $item_offset = max(0, ($item_page - 1) * $item_per_page); $sql = "SELECT cl_scobjs_id, cl_scobjs_name, cl_scobjs_type, cl_scobjs_subtype, cl_scobjs_uuid FROM tbl_scobjs WHERE 1 = 1"; if ($item_search !== '') { $sql .= " AND ( cl_scobjs_name LIKE :search OR cl_scobjs_type LIKE :search OR cl_scobjs_subtype LIKE :search OR cl_scobjs_uuid LIKE :search )"; } $sql .= " ORDER BY cl_scobjs_name ASC LIMIT " . (int) $item_per_page . " OFFSET " . (int) $item_offset; $stmt_item_results = $db->prepare($sql); $stmt_item_results->execute($params); $item_results = $stmt_item_results->fetchAll(); } if ($item_total_results > 0) { $item_result_offset_start = (($item_page - 1) * $item_per_page) + 1; $item_result_offset_end = min($item_total_results, $item_result_offset_start + count($item_results) - 1); } } $selected_character_items = []; $custom_stats_by_itemcustom = []; if ($selected_character) { sccharacters_reindex_character_items($db, (int) $selected_character['cl_sccharacter_id']); $stmt_character_items = $db->prepare( "SELECT ci.*, bo.cl_scobjs_name AS cl_sccharacteritem_base_name, bo.cl_scobjs_type AS cl_sccharacteritem_base_type, bo.cl_scobjs_subtype AS cl_sccharacteritem_base_subtype, bo.cl_scobjs_uuid AS cl_sccharacteritem_base_uuid, co.cl_scitemcustom_id AS cl_sccharacteritem_custom_ref_id, oo.cl_scobjs_name AS cl_sccharacteritem_custom_name, oo.cl_scobjs_type AS cl_sccharacteritem_custom_type, oo.cl_scobjs_subtype AS cl_sccharacteritem_custom_subtype, oo.cl_scobjs_uuid AS cl_sccharacteritem_custom_uuid FROM tbl_sccharacteritems ci LEFT JOIN tbl_scobjs bo ON bo.cl_scobjs_id = ci.cl_sccharacteritem_scobjs_id LEFT JOIN tbl_scitemcustom co ON co.cl_scitemcustom_id = ci.cl_sccharacteritem_scitemcustom_id LEFT JOIN tbl_scobjs oo ON oo.cl_scobjs_id = co.cl_scitemcustom_obj_id WHERE ci.cl_sccharacteritem_character_id = :character_id ORDER BY ci.cl_sccharacteritem_sort_order ASC, ci.cl_sccharacteritem_id ASC" ); $stmt_character_items->execute(['character_id' => (int) $selected_character['cl_sccharacter_id']]); $selected_character_items = $stmt_character_items->fetchAll(); $custom_item_ids = []; foreach ($selected_character_items as $character_item_row) { if (($character_item_row['cl_sccharacteritem_source'] ?? '') === 'custom' && !empty($character_item_row['cl_sccharacteritem_scitemcustom_id'])) { $custom_item_ids[] = (int) $character_item_row['cl_sccharacteritem_scitemcustom_id']; } } $custom_item_ids = array_values(array_unique(array_filter($custom_item_ids))); if ($custom_item_ids !== []) { $placeholders = implode(',', array_fill(0, count($custom_item_ids), '?')); $stmt_custom_stats = $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_custom_stats->execute($custom_item_ids); foreach ($stmt_custom_stats->fetchAll() as $custom_stat_row) { $itemcustom_id = (int) $custom_stat_row['cl_scitemcustomstat_itemcustom_id']; if (!isset($custom_stats_by_itemcustom[$itemcustom_id])) { $custom_stats_by_itemcustom[$itemcustom_id] = []; } $custom_stats_by_itemcustom[$itemcustom_id][] = $custom_stat_row; } } } $item_category_options = sccharacters_item_category_options(); $selected_character_category_order = $selected_character ? sccharacters_character_category_order($selected_character) : sccharacters_default_category_order(); $selected_character_items_by_category = []; foreach ($selected_character_items as $character_item_row) { $is_custom = ($character_item_row['cl_sccharacteritem_source'] ?? '') === 'custom'; $item_type = $is_custom ? (string) ($character_item_row['cl_sccharacteritem_custom_type'] ?? '') : (string) ($character_item_row['cl_sccharacteritem_base_type'] ?? ''); $item_subtype = $is_custom ? (string) ($character_item_row['cl_sccharacteritem_custom_subtype'] ?? '') : (string) ($character_item_row['cl_sccharacteritem_base_subtype'] ?? ''); $category_key = sccharacters_resolve_item_category( (string) ($character_item_row['cl_sccharacteritem_slot'] ?? ''), $item_type, $item_subtype ); if (!isset($selected_character_items_by_category[$category_key])) { $selected_character_items_by_category[$category_key] = []; } $selected_character_items_by_category[$category_key][] = $character_item_row; } $selected_character_items_by_category = sccharacters_sort_items_by_category_order( $selected_character_items_by_category, $selected_character_category_order ); $selected_character_category_order_meta = []; $visible_category_keys = array_keys($selected_character_items_by_category); $visible_category_count = count($visible_category_keys); foreach ($visible_category_keys as $visible_category_index => $visible_category_key) { $selected_character_category_order_meta[$visible_category_key] = [ 'is_first' => $visible_category_index === 0, 'is_last' => $visible_category_index === ($visible_category_count - 1), ]; } $selected_character_visible_category_order_state = implode(',', $visible_category_keys); $selected_character_item_order_state = []; foreach ($selected_character_items_by_category as $category_key => $category_items) { $selected_character_item_order_state[$category_key] = array_values(array_map( static fn (array $item_row): int => (int) ($item_row['cl_sccharacteritem_id'] ?? 0), $category_items )); } $selected_character_item_order_state_json = json_encode( $selected_character_item_order_state, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); if (!is_string($selected_character_item_order_state_json)) { $selected_character_item_order_state_json = '{}'; } $create_character = [ 'cl_sccharacter_name' => '', 'cl_sccharacter_role' => '', 'cl_sccharacter_faction' => '', 'cl_sccharacter_org_rsi_url' => '', 'cl_sccharacter_is_player' => 0, 'cl_sccharacter_player_handle' => '', 'cl_sccharacter_avatar_url' => '', 'cl_sccharacter_description' => '', 'cl_sccharacter_notes' => '', 'cl_sccharacter_share_enabled' => 0, 'cl_sccharacter_is_pinned' => 0, ]; $selected_character_org_tag = $selected_character ? sccharacters_resolve_org_tag($selected_character) : ''; $selected_character_has_player_handle = $selected_character ? sccharacters_has_player_handle($selected_character) : false; $item_has_previous_page = $item_page > 1; $item_has_next_page = $item_page < $item_total_pages; $item_query_base_params = []; if ($selected_character) { $item_query_base_params = [ 'character' => (int) $selected_character['cl_sccharacter_id'], 'item_panel' => 1, 'item_source' => $item_source, ]; if ($item_search !== '') { $item_query_base_params['item_search'] = $item_search; } if ($item_per_page !== 50) { $item_query_base_params['item_per_page'] = $item_per_page; } } ?> PERSONNAGES | R.E.A.C.T. Admin

R.E.A.C.T. Characters Control

Niveau d'accès :

Avatar de <?php echo htmlspecialchars((string) $selected_character['cl_sccharacter_name'], ENT_QUOTES, 'UTF-8'); ?>

📌
Rôle / Classe
Tag organisation
Handle
équipement(s)

Modifier la fiche

Bloc repliable pour garder la fiche visible sans laisser le formulaire ouvert en permanence.

+
>
>

Équipement attribué

Bloc repliable pour consulter rapidement l’équipement et le refermer quand tu veux dégager la vue.

+
Aucun équipement attribué Utilise le module de recherche plus bas pour ajouter un objet de la base ou un objet personnalisé.
Tu peux réorganiser librement les catégories et les objets ci-dessous, puis enregistrer quand tu veux.
$category_items): ?> false, 'is_last' => false]; ?>

objet(s)
Aperçu de <?php echo htmlspecialchars($item_name, ENT_QUOTES, 'UTF-8'); ?>

:
Glisser-déposer pour réordonner dans cette catégorie
>

Ajouter des objets

Recherche repliable : tu l’ouvres quand tu équipes le personnage, tu la refermes dès que c’est fait.

+
> >
Aucun objet personnalisé ne correspond à « ». Commence par créer des objets dans l’onglet « Objets perso. » pour pouvoir les attribuer à tes personnages. Aucun objet de la base ne correspond à « ». Lance une recherche ou parcours le catalogue page par page avec le sélecteur de résultats.
Résultats de recherche
sur résultat(s) · page /
Le bouton + ajoute l’objet immédiatement au personnage sans casser ta recherche en cours.
Aperçu de <?php echo htmlspecialchars($result_name, ENT_QUOTES, 'UTF-8'); ?>
UUID : stat(s)
Navigation dans les résultats
Aucun personnage sélectionné Crée un personnage via le bloc repliable de gauche, puis sélectionne-le dans la liste pour gérer sa fiche et son équipement.