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_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_build_return_url(int $character_id = 0, string $item_source = '', string $item_search = '', bool $keep_item_panel = false): 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;
}
if ($keep_item_panel) {
$params['item_panel'] = '1';
}
return 'sccharacters.php' . ($params !== [] ? '?' . http_build_query($params) : '');
}
function sccharacters_attach_item(
PDO $db,
int $owner_auth_id,
int $character_id,
string $source,
int $source_id,
string $requested_category,
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;
}
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_note
) VALUES (
:character_id,
:source,
:scobjs_id,
NULL,
:slot,
:note
)'
);
$stmt_insert->execute([
'character_id' => $character_id,
'source' => 'base',
'scobjs_id' => $source_id,
'slot' => $category,
'note' => $note !== '' ? $note : null,
]);
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_note
) VALUES (
:character_id,
:source,
NULL,
:scitemcustom_id,
:slot,
:note
)'
);
$stmt_insert->execute([
'character_id' => $character_id,
'source' => 'custom',
'scitemcustom_id' => $source_id,
'slot' => $category,
'note' => $note !== '' ? $note : null,
]);
return true;
}
$error_message = 'Source d’objet invalide.';
return false;
}
$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 = sccharacters_clean_text($_POST['character_faction'] ?? '');
$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;
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;
}
$stmt = $db->prepare(
'INSERT INTO tbl_sccharacters (
cl_sccharacter_owner_auth_id,
cl_sccharacter_name,
cl_sccharacter_role,
cl_sccharacter_faction,
cl_sccharacter_avatar_url,
cl_sccharacter_description,
cl_sccharacter_notes,
cl_sccharacter_share_token,
cl_sccharacter_share_enabled
) VALUES (
:owner_auth_id,
:name,
:role,
:faction,
:avatar_url,
:description,
:notes,
:share_token,
:share_enabled
)'
);
$stmt->execute([
'owner_auth_id' => $current_owner_auth_id,
'name' => $name,
'role' => $role,
'faction' => $faction,
'avatar_url' => $avatar_url,
'description' => $description !== '' ? $description : null,
'notes' => $notes !== '' ? $notes : null,
'share_token' => sccharacters_generate_share_token($db),
'share_enabled' => $share_enabled,
]);
$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 = sccharacters_clean_text($_POST['character_faction'] ?? '');
$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;
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;
}
$stmt = $db->prepare(
'UPDATE tbl_sccharacters
SET cl_sccharacter_name = :name,
cl_sccharacter_role = :role,
cl_sccharacter_faction = :faction,
cl_sccharacter_avatar_url = :avatar_url,
cl_sccharacter_description = :description,
cl_sccharacter_notes = :notes,
cl_sccharacter_share_enabled = :share_enabled
WHERE cl_sccharacter_id = :id
AND cl_sccharacter_owner_auth_id = :owner_auth_id'
);
$stmt->execute([
'name' => $name,
'role' => $role,
'faction' => $faction,
'avatar_url' => $avatar_url,
'description' => $description !== '' ? $description : null,
'notes' => $notes !== '' ? $notes : null,
'share_enabled' => $share_enabled,
'id' => $character_id,
'owner_auth_id' => $current_owner_auth_id,
]);
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'] ?? '');
$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'] ?? '');
$error_message = null;
if (!sccharacters_attach_item(
$db,
$current_owner_auth_id,
$character_id,
'base',
$obj_id,
$requested_category,
$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));
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));
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'] ?? '');
$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'] ?? '');
$error_message = null;
if (!sccharacters_attach_item(
$db,
$current_owner_auth_id,
$character_id,
'custom',
$itemcustom_id,
$requested_category,
$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));
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));
exit;
}
if ($action === 'add_selected_items') {
$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'] ?? '');
$selected_items = $_POST['selected_items'] ?? [];
$return_url = sccharacters_build_return_url($character_id, $item_source_context, $item_search_context, true);
$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;
}
if (!is_array($selected_items) || $selected_items === []) {
auth_flash_set('error', 'Sélectionne au moins un objet avant de valider.');
header('Location: ' . $return_url);
exit;
}
$selected_items = array_values(array_unique(array_map('strval', $selected_items)));
$item_slots = isset($_POST['item_slot']) && is_array($_POST['item_slot']) ? $_POST['item_slot'] : [];
$item_notes = isset($_POST['item_note']) && is_array($_POST['item_note']) ? $_POST['item_note'] : [];
$added_count = 0;
$error_count = 0;
foreach ($selected_items as $item_key) {
if (!preg_match('/^(base|custom):(\d+)$/', $item_key, $matches)) {
$error_count++;
continue;
}
$source = (string) $matches[1];
$source_id = (int) $matches[2];
$requested_category = sccharacters_clean_text($item_slots[$item_key] ?? '');
$note = sccharacters_clean_text($item_notes[$item_key] ?? '');
$error_message = null;
if (sccharacters_attach_item(
$db,
$current_owner_auth_id,
$character_id,
$source,
$source_id,
$requested_category,
$note,
$error_message
)) {
$added_count++;
continue;
}
$error_count++;
}
if ($added_count <= 0) {
auth_flash_set('error', 'Aucun objet n’a pu être ajouté à la sélection.');
header('Location: ' . $return_url);
exit;
}
if ($error_count > 0) {
auth_flash_set('success', $added_count . ' objet(s) ajouté(s). Certains éléments sélectionnés ont été ignorés.');
header('Location: ' . $return_url);
exit;
}
auth_flash_set('success', $added_count . ' objet(s) ajouté(s) au personnage.');
header('Location: ' . $return_url);
exit;
}
if ($action === 'delete_character_item') {
$character_item_id = (int) ($_POST['character_item_id'] ?? 0);
$character_id = (int) ($_POST['character_id'] ?? 0);
$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,
]);
auth_flash_set('success', 'Objet retiré du personnage.');
header('Location: sccharacters.php?character=' . $character_id);
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_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_results = [];
if ($selected_character) {
if ($item_source === 'custom') {
$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";
$params = ['owner_auth_id' => $current_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
)";
$params['search'] = '%' . $item_search . '%';
}
$sql .= "
GROUP BY c.cl_scitemcustom_id
ORDER BY o.cl_scobjs_name ASC, c.cl_scitemcustom_id ASC
LIMIT 25";
$stmt_item_results = $db->prepare($sql);
$stmt_item_results->execute($params);
$item_results = $stmt_item_results->fetchAll();
} else {
$sql = "SELECT cl_scobjs_id, cl_scobjs_name, cl_scobjs_type, cl_scobjs_subtype, cl_scobjs_uuid
FROM tbl_scobjs
WHERE 1 = 1";
$params = [];
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
)";
$params['search'] = '%' . $item_search . '%';
}
$sql .= "
ORDER BY cl_scobjs_name ASC
LIMIT 25";
$stmt_item_results = $db->prepare($sql);
$stmt_item_results->execute($params);
$item_results = $stmt_item_results->fetchAll();
}
}
$selected_character_items = [];
$custom_stats_by_itemcustom = [];
if ($selected_character) {
$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
CASE WHEN TRIM(ci.cl_sccharacteritem_slot) = '' THEN 1 ELSE 0 END,
ci.cl_sccharacteritem_slot ASC,
COALESCE(oo.cl_scobjs_name, bo.cl_scobjs_name, 'ZZZ') 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_items_by_category = [];
foreach (array_keys($item_category_options) as $category_key) {
$selected_character_items_by_category[$category_key] = [];
}
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 = array_filter(
$selected_character_items_by_category,
static fn(array $items): bool => $items !== []
);
$create_character = [
'cl_sccharacter_name' => '',
'cl_sccharacter_role' => '',
'cl_sccharacter_faction' => '',
'cl_sccharacter_avatar_url' => '',
'cl_sccharacter_description' => '',
'cl_sccharacter_notes' => '',
'cl_sccharacter_share_enabled' => 0,
];
?>
PERSONNAGES | R.E.A.C.T. Admin
Personnages
Crée une fiche personnage, rattache-lui des objets depuis la Base d'Objets et tes Objets personnalisés, puis partage sa page publique uniquement via son lien dédié.
Connecté :
Rôle :
équipement(s)
Partage par lien uniquementPage publique dédiée
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é.
$category_items): ?>
objet(s)
◈
:
>
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 à « ».
La recherche est prête. Tu peux aussi laisser le champ vide pour parcourir les 25 premiers objets de la base.
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.