Autosave: 20260415-231211

This commit is contained in:
Flatlogic Bot 2026-04-15 23:12:11 +00:00
parent de33ca704e
commit 0965f47f80
6 changed files with 3084 additions and 0 deletions

View File

@ -18726,3 +18726,52 @@ CREATE TABLE IF NOT EXISTS tbl_page_access (
cl_allow_member TINYINT(1) NOT NULL DEFAULT 0,
cl_updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS tbl_sccharacters (
cl_sccharacter_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cl_sccharacter_owner_auth_id INT UNSIGNED NOT NULL,
cl_sccharacter_name VARCHAR(190) NOT NULL,
cl_sccharacter_role VARCHAR(190) NOT NULL DEFAULT '',
cl_sccharacter_faction VARCHAR(190) NOT NULL DEFAULT '',
cl_sccharacter_avatar_url VARCHAR(255) NOT NULL DEFAULT '',
cl_sccharacter_description TEXT DEFAULT NULL,
cl_sccharacter_notes TEXT DEFAULT NULL,
cl_sccharacter_share_token VARCHAR(64) NOT NULL,
cl_sccharacter_share_enabled TINYINT(1) NOT NULL DEFAULT 0,
cl_sccharacter_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
cl_sccharacter_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_sccharacter_share_token (cl_sccharacter_share_token),
KEY idx_sccharacter_owner (cl_sccharacter_owner_auth_id),
KEY idx_sccharacter_name (cl_sccharacter_name),
CONSTRAINT fk_sccharacter_owner_auth FOREIGN KEY (cl_sccharacter_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_sccharacteritems (
cl_sccharacteritem_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cl_sccharacteritem_character_id INT UNSIGNED NOT NULL,
cl_sccharacteritem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base',
cl_sccharacteritem_scobjs_id INT UNSIGNED DEFAULT NULL,
cl_sccharacteritem_scitemcustom_id INT(11) DEFAULT NULL,
cl_sccharacteritem_slot VARCHAR(120) NOT NULL DEFAULT '',
cl_sccharacteritem_note TEXT DEFAULT NULL,
cl_sccharacteritem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_sccharacteritem_character (cl_sccharacteritem_character_id),
KEY idx_sccharacteritem_scobjs (cl_sccharacteritem_scobjs_id),
KEY idx_sccharacteritem_scitemcustom (cl_sccharacteritem_scitemcustom_id),
CONSTRAINT fk_sccharacteritem_character FOREIGN KEY (cl_sccharacteritem_character_id)
REFERENCES tbl_sccharacters (cl_sccharacter_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT fk_sccharacteritem_scobjs FOREIGN KEY (cl_sccharacteritem_scobjs_id)
REFERENCES tbl_scobjs (cl_scobjs_id)
ON DELETE SET NULL
ON UPDATE CASCADE,
CONSTRAINT fk_sccharacteritem_scitemcustom FOREIGN KEY (cl_sccharacteritem_scitemcustom_id)
REFERENCES tbl_scitemcustom (cl_scitemcustom_id)
ON DELETE SET NULL
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -205,3 +205,52 @@ CREATE TABLE IF NOT EXISTS tbl_page_access (
cl_allow_member TINYINT(1) NOT NULL DEFAULT 0,
cl_updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS tbl_sccharacters (
cl_sccharacter_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cl_sccharacter_owner_auth_id INT UNSIGNED NOT NULL,
cl_sccharacter_name VARCHAR(190) NOT NULL,
cl_sccharacter_role VARCHAR(190) NOT NULL DEFAULT '',
cl_sccharacter_faction VARCHAR(190) NOT NULL DEFAULT '',
cl_sccharacter_avatar_url VARCHAR(255) NOT NULL DEFAULT '',
cl_sccharacter_description TEXT DEFAULT NULL,
cl_sccharacter_notes TEXT DEFAULT NULL,
cl_sccharacter_share_token VARCHAR(64) NOT NULL,
cl_sccharacter_share_enabled TINYINT(1) NOT NULL DEFAULT 0,
cl_sccharacter_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
cl_sccharacter_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_sccharacter_share_token (cl_sccharacter_share_token),
KEY idx_sccharacter_owner (cl_sccharacter_owner_auth_id),
KEY idx_sccharacter_name (cl_sccharacter_name),
CONSTRAINT fk_sccharacter_owner_auth FOREIGN KEY (cl_sccharacter_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_sccharacteritems (
cl_sccharacteritem_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
cl_sccharacteritem_character_id INT UNSIGNED NOT NULL,
cl_sccharacteritem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base',
cl_sccharacteritem_scobjs_id INT UNSIGNED DEFAULT NULL,
cl_sccharacteritem_scitemcustom_id INT(11) DEFAULT NULL,
cl_sccharacteritem_slot VARCHAR(120) NOT NULL DEFAULT '',
cl_sccharacteritem_note TEXT DEFAULT NULL,
cl_sccharacteritem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_sccharacteritem_character (cl_sccharacteritem_character_id),
KEY idx_sccharacteritem_scobjs (cl_sccharacteritem_scobjs_id),
KEY idx_sccharacteritem_scitemcustom (cl_sccharacteritem_scitemcustom_id),
CONSTRAINT fk_sccharacteritem_character FOREIGN KEY (cl_sccharacteritem_character_id)
REFERENCES tbl_sccharacters (cl_sccharacter_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT fk_sccharacteritem_scobjs FOREIGN KEY (cl_sccharacteritem_scobjs_id)
REFERENCES tbl_scobjs (cl_scobjs_id)
ON DELETE SET NULL
ON UPDATE CASCADE,
CONSTRAINT fk_sccharacteritem_scitemcustom FOREIGN KEY (cl_sccharacteritem_scitemcustom_id)
REFERENCES tbl_scitemcustom (cl_scitemcustom_id)
ON DELETE SET NULL
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@ -469,6 +469,7 @@ function auth_navigation_items(): array
['file' => 'scitems.php', 'label' => 'Base d\'Objets'],
['file' => 'scstatsitem.php', 'label' => 'Stats Item'],
['file' => 'scitemcustom.php', 'label' => 'Objets perso.'],
['file' => 'sccharacters.php', 'label' => 'Personnages'],
['file' => 'scmining.php', 'label' => 'Scanner Minage'],
['file' => 'scmanufactures.php', 'label' => 'Manufactures'],
['file' => 'scvaisseaux.php', 'label' => 'Vaisseaux'],

456
db/sccharacters.php Normal file
View File

@ -0,0 +1,456 @@
<?php
require_once __DIR__ . '/config.php';
function sccharacters_column_exists(PDO $db, string $table, string $column): bool
{
$stmt = $db->query("SHOW COLUMNS FROM `{$table}` LIKE " . $db->quote($column));
return (bool) $stmt->fetch();
}
function sccharacters_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 sccharacters_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 sccharacters_bootstrap(): void
{
static $bootstrapped = false;
if ($bootstrapped) {
return;
}
$db = db();
$db->exec(
"CREATE TABLE IF NOT EXISTS tbl_sccharacters (
cl_sccharacter_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
cl_sccharacter_owner_auth_id INT UNSIGNED NOT NULL,
cl_sccharacter_name VARCHAR(190) NOT NULL,
cl_sccharacter_role VARCHAR(190) NOT NULL DEFAULT '',
cl_sccharacter_faction VARCHAR(190) NOT NULL DEFAULT '',
cl_sccharacter_avatar_url VARCHAR(255) NOT NULL DEFAULT '',
cl_sccharacter_description TEXT DEFAULT NULL,
cl_sccharacter_notes TEXT DEFAULT NULL,
cl_sccharacter_share_token VARCHAR(64) NOT NULL,
cl_sccharacter_share_enabled TINYINT(1) NOT NULL DEFAULT 0,
cl_sccharacter_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
cl_sccharacter_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (cl_sccharacter_id),
UNIQUE KEY uq_sccharacter_share_token (cl_sccharacter_share_token),
KEY idx_sccharacter_owner (cl_sccharacter_owner_auth_id),
KEY idx_sccharacter_name (cl_sccharacter_name),
CONSTRAINT fk_sccharacter_owner_auth FOREIGN KEY (cl_sccharacter_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 (!sccharacters_column_exists($db, 'tbl_sccharacters', 'cl_sccharacter_owner_auth_id')) {
$db->exec(
'ALTER TABLE tbl_sccharacters
ADD COLUMN cl_sccharacter_owner_auth_id INT UNSIGNED NULL AFTER cl_sccharacter_id'
);
}
if (!sccharacters_column_exists($db, 'tbl_sccharacters', 'cl_sccharacter_share_token')) {
$db->exec(
"ALTER TABLE tbl_sccharacters
ADD COLUMN cl_sccharacter_share_token VARCHAR(64) NOT NULL DEFAULT '' AFTER cl_sccharacter_notes"
);
}
if (!sccharacters_column_exists($db, 'tbl_sccharacters', 'cl_sccharacter_share_enabled')) {
$db->exec(
'ALTER TABLE tbl_sccharacters
ADD COLUMN cl_sccharacter_share_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER cl_sccharacter_share_token'
);
}
if (!sccharacters_index_exists($db, 'tbl_sccharacters', 'idx_sccharacter_owner')) {
$db->exec(
'ALTER TABLE tbl_sccharacters
ADD INDEX idx_sccharacter_owner (cl_sccharacter_owner_auth_id)'
);
}
if (!sccharacters_index_exists($db, 'tbl_sccharacters', 'uq_sccharacter_share_token')) {
$db->exec(
'ALTER TABLE tbl_sccharacters
ADD UNIQUE KEY uq_sccharacter_share_token (cl_sccharacter_share_token)'
);
}
if (!sccharacters_foreign_key_exists($db, 'tbl_sccharacters', 'fk_sccharacter_owner_auth')) {
$db->exec(
'ALTER TABLE tbl_sccharacters
ADD CONSTRAINT fk_sccharacter_owner_auth FOREIGN KEY (cl_sccharacter_owner_auth_id)
REFERENCES tbl_auth (cl_auth_id)
ON DELETE CASCADE
ON UPDATE CASCADE'
);
}
$db->exec(
"CREATE TABLE IF NOT EXISTS tbl_sccharacteritems (
cl_sccharacteritem_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
cl_sccharacteritem_character_id INT UNSIGNED NOT NULL,
cl_sccharacteritem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base',
cl_sccharacteritem_scobjs_id INT UNSIGNED DEFAULT NULL,
cl_sccharacteritem_scitemcustom_id INT(11) DEFAULT NULL,
cl_sccharacteritem_slot VARCHAR(120) NOT NULL DEFAULT '',
cl_sccharacteritem_note TEXT DEFAULT NULL,
cl_sccharacteritem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (cl_sccharacteritem_id),
KEY idx_sccharacteritem_character (cl_sccharacteritem_character_id),
KEY idx_sccharacteritem_scobjs (cl_sccharacteritem_scobjs_id),
KEY idx_sccharacteritem_scitemcustom (cl_sccharacteritem_scitemcustom_id),
CONSTRAINT fk_sccharacteritem_character FOREIGN KEY (cl_sccharacteritem_character_id)
REFERENCES tbl_sccharacters (cl_sccharacter_id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT fk_sccharacteritem_scobjs FOREIGN KEY (cl_sccharacteritem_scobjs_id)
REFERENCES tbl_scobjs (cl_scobjs_id)
ON DELETE SET NULL
ON UPDATE CASCADE,
CONSTRAINT fk_sccharacteritem_scitemcustom FOREIGN KEY (cl_sccharacteritem_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 (!sccharacters_column_exists($db, 'tbl_sccharacteritems', 'cl_sccharacteritem_scobjs_id')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD COLUMN cl_sccharacteritem_scobjs_id INT UNSIGNED NULL AFTER cl_sccharacteritem_source'
);
}
if (!sccharacters_column_exists($db, 'tbl_sccharacteritems', 'cl_sccharacteritem_scitemcustom_id')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD COLUMN cl_sccharacteritem_scitemcustom_id INT(11) NULL AFTER cl_sccharacteritem_scobjs_id'
);
}
if (!sccharacters_index_exists($db, 'tbl_sccharacteritems', 'idx_sccharacteritem_character')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD INDEX idx_sccharacteritem_character (cl_sccharacteritem_character_id)'
);
}
if (!sccharacters_index_exists($db, 'tbl_sccharacteritems', 'idx_sccharacteritem_scobjs')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD INDEX idx_sccharacteritem_scobjs (cl_sccharacteritem_scobjs_id)'
);
}
if (!sccharacters_index_exists($db, 'tbl_sccharacteritems', 'idx_sccharacteritem_scitemcustom')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD INDEX idx_sccharacteritem_scitemcustom (cl_sccharacteritem_scitemcustom_id)'
);
}
if (!sccharacters_foreign_key_exists($db, 'tbl_sccharacteritems', 'fk_sccharacteritem_character')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD CONSTRAINT fk_sccharacteritem_character FOREIGN KEY (cl_sccharacteritem_character_id)
REFERENCES tbl_sccharacters (cl_sccharacter_id)
ON DELETE CASCADE
ON UPDATE CASCADE'
);
}
if (!sccharacters_foreign_key_exists($db, 'tbl_sccharacteritems', 'fk_sccharacteritem_scobjs')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD CONSTRAINT fk_sccharacteritem_scobjs FOREIGN KEY (cl_sccharacteritem_scobjs_id)
REFERENCES tbl_scobjs (cl_scobjs_id)
ON DELETE SET NULL
ON UPDATE CASCADE'
);
}
if (!sccharacters_foreign_key_exists($db, 'tbl_sccharacteritems', 'fk_sccharacteritem_scitemcustom')) {
$db->exec(
'ALTER TABLE tbl_sccharacteritems
ADD CONSTRAINT fk_sccharacteritem_scitemcustom FOREIGN KEY (cl_sccharacteritem_scitemcustom_id)
REFERENCES tbl_scitemcustom (cl_scitemcustom_id)
ON DELETE SET NULL
ON UPDATE CASCADE'
);
}
$stmt_missing_tokens = $db->query(
"SELECT cl_sccharacter_id
FROM tbl_sccharacters
WHERE cl_sccharacter_share_token IS NULL
OR cl_sccharacter_share_token = ''"
);
$stmt_update_token = $db->prepare(
'UPDATE tbl_sccharacters
SET cl_sccharacter_share_token = :token
WHERE cl_sccharacter_id = :id'
);
foreach ($stmt_missing_tokens->fetchAll() as $row) {
$stmt_update_token->execute([
'token' => sccharacters_generate_share_token($db),
'id' => (int) $row['cl_sccharacter_id'],
]);
}
$bootstrapped = true;
}
function sccharacters_generate_share_token(PDO $db): string
{
do {
$token = bin2hex(random_bytes(16));
$stmt = $db->prepare(
'SELECT cl_sccharacter_id
FROM tbl_sccharacters
WHERE cl_sccharacter_share_token = :token
LIMIT 1'
);
$stmt->execute(['token' => $token]);
} while ($stmt->fetch());
return $token;
}
function sccharacters_item_category_options(): array
{
return [
'weapon' => 'Armes',
'armor' => 'Armures',
'tools' => 'Outils',
'consumables' => 'Consommables',
'ammunition' => 'Munitions',
'attachments' => 'Accessoires',
'clothing' => 'Vêtements',
'cargo' => 'Cargo / Conteneurs',
'ship' => 'Composants / Véhicule',
'access' => 'Accès / Mobilier',
'misc' => 'Divers',
];
}
function sccharacters_item_category_label(string $category): string
{
$options = sccharacters_item_category_options();
return $options[$category] ?? $options['misc'];
}
function sccharacters_item_category_slug(string $value): string
{
$value = trim($value);
if ($value == '') {
return '';
}
if (function_exists('iconv')) {
$converted = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value);
if ($converted !== false) {
$value = $converted;
}
}
$value = strtolower($value);
$value = preg_replace('/[^a-z0-9]+/', '_', $value) ?? '';
return trim($value, '_');
}
function sccharacters_string_contains_any(string $haystack, array $needles): bool
{
foreach ($needles as $needle) {
if ($needle !== '' && strpos($haystack, $needle) !== false) {
return true;
}
}
return false;
}
function sccharacters_normalize_item_category(?string $value): string
{
$slug = sccharacters_item_category_slug((string) $value);
if ($slug === '' || in_array($slug, ['auto', 'automatic', 'automatique', 'default', 'defaut'], true)) {
return '';
}
$options = sccharacters_item_category_options();
if (isset($options[$slug])) {
return $slug;
}
$exact_aliases = [
'arme' => 'weapon',
'armes' => 'weapon',
'weapon' => 'weapon',
'weapons' => 'weapon',
'armure' => 'armor',
'armures' => 'armor',
'armor' => 'armor',
'armors' => 'armor',
'outil' => 'tools',
'outils' => 'tools',
'tool' => 'tools',
'tools' => 'tools',
'consommable' => 'consumables',
'consommables' => 'consumables',
'usable' => 'consumables',
'food' => 'consumables',
'drink' => 'consumables',
'munition' => 'ammunition',
'munitions' => 'ammunition',
'ammo' => 'ammunition',
'ammunition' => 'ammunition',
'accessoire' => 'attachments',
'accessoires' => 'attachments',
'attachment' => 'attachments',
'attachments' => 'attachments',
'vetement' => 'clothing',
'vetements' => 'clothing',
'clothing' => 'clothing',
'clothes' => 'clothing',
'cargo' => 'cargo',
'container' => 'cargo',
'conteneur' => 'cargo',
'conteneurs' => 'cargo',
'composant' => 'ship',
'composants' => 'ship',
'component' => 'ship',
'components' => 'ship',
'vaisseau' => 'ship',
'vaisseaux' => 'ship',
'vehicule' => 'ship',
'vehicules' => 'ship',
'acces' => 'access',
'access' => 'access',
'mobilier' => 'access',
'divers' => 'misc',
'misc' => 'misc',
'autre' => 'misc',
'autres' => 'misc',
'other' => 'misc',
];
if (isset($exact_aliases[$slug])) {
return $exact_aliases[$slug];
}
$contains_aliases = [
'weapon' => ['arme', 'weapon', 'pistol', 'rifle', 'shotgun', 'sniper', 'knife', 'blade'],
'armor' => ['armure', 'armor', 'plating'],
'tools' => ['outil', 'tool', 'tractor', 'mining', 'multitool', 'multi_tool', 'gadget'],
'consumables' => ['consommable', 'consumable', 'usable', 'food', 'drink', 'med', 'medical', 'heal'],
'ammunition' => ['munition', 'ammo', 'ammunition', 'magazine', 'grenade', 'rocket'],
'attachments' => ['attachment', 'accessoire', 'scope', 'optic', 'silencer', 'sight', 'barrel'],
'clothing' => ['clothing', 'vetement', 'apparel', 'outfit', 'char_clothing', 'char_head'],
'cargo' => ['cargo', 'container', 'crate', 'box'],
'ship' => ['component', 'composant', 'vaisseau', 'vehicule', 'thruster', 'quantum', 'powerplant', 'cooler', 'radar', 'sensor', 'shield', 'turret', 'missilelauncher', 'docking', 'flightcontroller', 'fueltank', 'fuelintake', 'aimodule'],
'access' => ['door', 'seat', 'access', 'display', 'controlpanel', 'dashboard', 'mobilier'],
];
foreach ($contains_aliases as $category => $needles) {
if (sccharacters_string_contains_any($slug, $needles)) {
return $category;
}
}
return '';
}
function sccharacters_guess_item_category(?string $type, ?string $subtype = null): string
{
$haystack = strtolower(trim(((string) $type) . ' ' . ((string) $subtype)));
$slug = sccharacters_item_category_slug($haystack);
if ($slug === '') {
return 'misc';
}
if (sccharacters_string_contains_any($slug, ['char_clothing', 'char_head', 'clothing', 'apparel', 'outfit', 'hat', 'beard', 'piercing'])) {
return 'clothing';
}
if (sccharacters_string_contains_any($slug, ['weaponattachment', 'attachment', 'scope', 'optic', 'sight', 'silencer', 'barrel', 'mag_mount'])) {
return 'attachments';
}
if (sccharacters_string_contains_any($slug, ['missile', 'ammo', 'ammunition', 'magazine', 'rocket'])) {
return 'ammunition';
}
if (sccharacters_string_contains_any($slug, ['armor', 'armure'])) {
return 'armor';
}
if (sccharacters_string_contains_any($slug, ['weapon', 'gun', 'rifle', 'pistol', 'shotgun', 'sniper', 'knife', 'blade'])) {
return 'weapon';
}
if (sccharacters_string_contains_any($slug, ['usable', 'consumable', 'food', 'drink', 'fps_consumable', 'med', 'medical'])) {
return 'consumables';
}
if (sccharacters_string_contains_any($slug, ['tool', 'outil', 'mining', 'tractor', 'gadget', 'utility'])) {
return 'tools';
}
if (sccharacters_string_contains_any($slug, ['cargo', 'container', 'crate', 'box'])) {
return 'cargo';
}
if (sccharacters_string_contains_any($slug, ['door', 'seat', 'access', 'display', 'controlpanel', 'dashboard', 'shopdisplay', 'player'])) {
return 'access';
}
if (sccharacters_string_contains_any($slug, ['thruster', 'quantum', 'powerplant', 'cooler', 'radar', 'sensor', 'shield', 'turret', 'flightcontroller', 'fueltank', 'fuelintake', 'a_module', 'aimodule', 'docking', 'attachedpart'])) {
return 'ship';
}
return 'misc';
}
function sccharacters_resolve_item_category(?string $storedValue, ?string $type, ?string $subtype = null): string
{
$normalized = sccharacters_normalize_item_category($storedValue);
if ($normalized !== '') {
return $normalized;
}
return sccharacters_guess_item_category($type, $subtype);
}

430
sccharacter.php Normal file
View File

@ -0,0 +1,430 @@
<?php
require_once __DIR__ . '/db/auth.php';
require_once __DIR__ . '/db/scstatsitem.php';
require_once __DIR__ . '/db/scitemcustom.php';
require_once __DIR__ . '/db/sccharacters.php';
auth_start_session();
auth_bootstrap();
scstatsitem_bootstrap();
scitemcustom_bootstrap();
sccharacters_bootstrap();
$db = db();
$share_token = trim((string) ($_GET['share'] ?? ''));
$character = null;
$character_items = [];
$custom_stats_by_itemcustom = [];
if ($share_token !== '') {
$stmt_character = $db->prepare(
'SELECT c.*, COALESCE(NULLIF(TRIM(a.cl_auth_user), \'\'), \'Inconnu\') AS cl_sccharacter_creator_name
FROM tbl_sccharacters c
LEFT JOIN tbl_auth a ON a.cl_auth_id = c.cl_sccharacter_owner_auth_id
WHERE c.cl_sccharacter_share_token = :share_token
AND c.cl_sccharacter_share_enabled = 1
LIMIT 1'
);
$stmt_character->execute(['share_token' => $share_token]);
$character = $stmt_character->fetch() ?: null;
}
if ($character) {
$stmt_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,
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_items->execute(['character_id' => (int) $character['cl_sccharacter_id']]);
$character_items = $stmt_items->fetchAll();
$custom_item_ids = [];
foreach ($character_items as $row) {
if (($row['cl_sccharacteritem_source'] ?? '') === 'custom' && !empty($row['cl_sccharacteritem_scitemcustom_id'])) {
$custom_item_ids[] = (int) $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_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_stats->execute($custom_item_ids);
foreach ($stmt_stats->fetchAll() as $stat_row) {
$itemcustom_id = (int) $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][] = $stat_row;
}
}
} else {
http_response_code(404);
}
$item_category_options = sccharacters_item_category_options();
$character_items_by_category = [];
foreach (array_keys($item_category_options) as $category_key) {
$character_items_by_category[$category_key] = [];
}
foreach ($character_items as $item_row) {
$is_custom = ($item_row['cl_sccharacteritem_source'] ?? '') === 'custom';
$type = $is_custom
? (string) ($item_row['cl_sccharacteritem_custom_type'] ?? '')
: (string) ($item_row['cl_sccharacteritem_base_type'] ?? '');
$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'] ?? ''),
$type,
$subtype
);
if (!isset($character_items_by_category[$category_key])) {
$character_items_by_category[$category_key] = [];
}
$character_items_by_category[$category_key][] = $item_row;
}
$character_items_by_category = array_filter(
$character_items_by_category,
static fn(array $items): bool => $items !== []
);
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo htmlspecialchars($character ? ((string) $character['cl_sccharacter_name'] . ' | Personnage partagé') : 'Personnage introuvable', ENT_QUOTES, 'UTF-8'); ?></title>
<style>
:root {
--primary: #a29b78;
--primary-soft: rgba(162, 155, 120, 0.18);
--primary-border: rgba(162, 155, 120, 0.3);
--bg: #080a0f;
--card: rgba(20, 24, 33, 0.88);
--text-main: #ece9df;
--text-soft: rgba(236, 233, 223, 0.72);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: radial-gradient(circle at top right, #1d2234 0%, #0a0d13 55%, #050608 100%);
color: var(--text-main);
font-family: Arial, Helvetica, sans-serif;
}
.page {
max-width: 1180px;
margin: 0 auto;
padding: 2rem 1rem 3rem;
}
.hero,
.card,
.message {
background: var(--card);
border: 1px solid var(--primary-border);
border-radius: 20px;
box-shadow: 0 22px 50px rgba(0,0,0,0.28);
backdrop-filter: blur(10px);
}
.hero {
padding: 1.4rem;
display: grid;
grid-template-columns: 120px minmax(0, 1fr);
gap: 1.2rem;
margin-bottom: 1.3rem;
}
.avatar,
.avatar-fallback {
width: 120px;
height: 120px;
border-radius: 28px;
object-fit: cover;
background: linear-gradient(145deg, rgba(162, 155, 120, 0.3), rgba(255,255,255,0.08));
border: 1px solid rgba(255,255,255,0.08);
}
.avatar-fallback {
display: flex;
align-items: center;
justify-content: center;
font-size: 2.2rem;
color: var(--primary);
font-weight: 700;
}
h1 { margin: 0 0 0.65rem; font-size: 2rem; }
p { line-height: 1.6; }
.meta,
.stats,
.tags {
display: flex;
gap: 0.55rem;
flex-wrap: wrap;
align-items: center;
}
.tag {
display: inline-flex;
align-items: center;
padding: 0.34rem 0.62rem;
border-radius: 999px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.08);
font-size: 0.84rem;
}
.tag-primary {
background: var(--primary-soft);
border-color: rgba(162, 155, 120, 0.35);
color: #f6eebf;
}
.muted { color: var(--text-soft); }
.equipment-sections {
display: flex;
flex-direction: column;
gap: 1rem;
}
.equipment-section {
display: flex;
flex-direction: column;
gap: 0.8rem;
}
.equipment-section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.8rem;
}
.equipment-section-title {
margin: 0;
color: var(--primary);
letter-spacing: 0.08em;
text-transform: uppercase;
font-size: 1rem;
}
.grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
}
.card {
padding: 1rem;
display: flex;
gap: 0.95rem;
align-items: flex-start;
}
.thumb,
.thumb-fallback {
width: 68px;
height: 68px;
border-radius: 18px;
object-fit: cover;
flex: 0 0 68px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.08);
}
.thumb-fallback {
display: flex;
align-items: center;
justify-content: center;
color: var(--primary);
font-size: 1.2rem;
}
.card-body { min-width: 0; }
.card-body h3 { margin: 0 0 0.45rem; font-size: 1.05rem; }
.stats { margin-top: 0.7rem; }
.stat {
padding: 0.32rem 0.55rem;
border-radius: 999px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.08);
font-size: 0.78rem;
}
.section-title {
margin: 1.2rem 0 0.8rem;
color: var(--primary);
letter-spacing: 0.08em;
text-transform: uppercase;
font-size: 1rem;
}
.message {
padding: 1.4rem;
text-align: center;
}
@media (max-width: 860px) {
.hero,
.grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="page">
<?php if (!$character): ?>
<div class="message">
<h1>Personnage introuvable</h1>
<p class="muted">Ce lien public est invalide, désactivé ou nest plus disponible.</p>
</div>
<?php else: ?>
<?php
$avatar = trim((string) ($character['cl_sccharacter_avatar_url'] ?? ''));
$initial = function_exists('mb_substr')
? mb_strtoupper(mb_substr((string) $character['cl_sccharacter_name'], 0, 1, 'UTF-8'), 'UTF-8')
: strtoupper(substr((string) $character['cl_sccharacter_name'], 0, 1));
?>
<section class="hero">
<?php if ($avatar !== ''): ?>
<img class="avatar" src="<?php echo htmlspecialchars($avatar, ENT_QUOTES, 'UTF-8'); ?>" alt="Avatar de <?php echo htmlspecialchars((string) $character['cl_sccharacter_name'], ENT_QUOTES, 'UTF-8'); ?>" loading="lazy" onerror="this.replaceWith(Object.assign(document.createElement('div'), {className:'avatar-fallback', textContent:'<?php echo htmlspecialchars($initial, ENT_QUOTES, 'UTF-8'); ?>'}));">
<?php else: ?>
<div class="avatar-fallback"><?php echo htmlspecialchars($initial, ENT_QUOTES, 'UTF-8'); ?></div>
<?php endif; ?>
<div>
<h1><?php echo htmlspecialchars((string) $character['cl_sccharacter_name'], ENT_QUOTES, 'UTF-8'); ?></h1>
<div class="meta">
<?php if (trim((string) $character['cl_sccharacter_role']) !== ''): ?>
<span class="tag tag-primary"><?php echo htmlspecialchars((string) $character['cl_sccharacter_role'], ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
<?php if (trim((string) $character['cl_sccharacter_faction']) !== ''): ?>
<span class="tag"><?php echo htmlspecialchars((string) $character['cl_sccharacter_faction'], ENT_QUOTES, 'UTF-8'); ?></span>
<?php endif; ?>
<span class="tag muted">Créé par <?php echo htmlspecialchars((string) $character['cl_sccharacter_creator_name'], ENT_QUOTES, 'UTF-8'); ?></span>
<span class="tag muted"><?php echo count($character_items); ?> équipement(s)</span>
</div>
<p><?php echo nl2br(htmlspecialchars(trim((string) $character['cl_sccharacter_description']) !== '' ? (string) $character['cl_sccharacter_description'] : 'Aucune description publique fournie pour ce personnage.', ENT_QUOTES, 'UTF-8')); ?></p>
</div>
</section>
<h2 class="section-title">Équipement</h2>
<?php if ($character_items === []): ?>
<div class="message">
<p class="muted">Ce personnage na pas encore déquipement attribué.</p>
</div>
<?php else: ?>
<div class="equipment-sections">
<?php foreach ($character_items_by_category as $category_key => $category_items): ?>
<section class="equipment-section">
<div class="equipment-section-head">
<h3 class="equipment-section-title"><?php echo htmlspecialchars(sccharacters_item_category_label((string) $category_key), ENT_QUOTES, 'UTF-8'); ?></h3>
<span class="tag muted"><?php echo count($category_items); ?> objet(s)</span>
</div>
<div class="grid">
<?php foreach ($category_items as $item_row): ?>
<?php
$is_custom = ($item_row['cl_sccharacteritem_source'] ?? '') === 'custom';
$name = $is_custom
? (string) ($item_row['cl_sccharacteritem_custom_name'] ?? 'Objet personnalisé indisponible')
: (string) ($item_row['cl_sccharacteritem_base_name'] ?? 'Objet indisponible');
$type = $is_custom
? (string) ($item_row['cl_sccharacteritem_custom_type'] ?? '')
: (string) ($item_row['cl_sccharacteritem_base_type'] ?? '');
$subtype = $is_custom
? (string) ($item_row['cl_sccharacteritem_custom_subtype'] ?? '')
: (string) ($item_row['cl_sccharacteritem_base_subtype'] ?? '');
$uuid = $is_custom
? (string) ($item_row['cl_sccharacteritem_custom_uuid'] ?? '')
: (string) ($item_row['cl_sccharacteritem_base_uuid'] ?? '');
$category = sccharacters_resolve_item_category(
(string) ($item_row['cl_sccharacteritem_slot'] ?? ''),
$type,
$subtype
);
$note = trim((string) ($item_row['cl_sccharacteritem_note'] ?? ''));
$stats = $is_custom ? ($custom_stats_by_itemcustom[(int) ($item_row['cl_sccharacteritem_scitemcustom_id'] ?? 0)] ?? []) : [];
?>
<article class="card">
<?php if ($uuid !== ''): ?>
<img class="thumb" src="https://cstone.space/uifimages/<?php echo htmlspecialchars($uuid, ENT_QUOTES, 'UTF-8'); ?>.png" alt="Aperçu de <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8'); ?>" loading="lazy" onerror="this.replaceWith(Object.assign(document.createElement('div'), {className:'thumb-fallback', textContent:'◈'}));">
<?php else: ?>
<div class="thumb-fallback"></div>
<?php endif; ?>
<div class="card-body">
<h3><?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8'); ?></h3>
<div class="tags">
<span class="tag <?php echo $is_custom ? 'tag-primary' : ''; ?>"><?php echo $is_custom ? 'Objet perso.' : 'Base dobjets'; ?></span>
<span class="tag"><?php echo htmlspecialchars(sccharacters_item_category_label($category), ENT_QUOTES, 'UTF-8'); ?></span>
<?php if ($type !== ''): ?><span class="tag muted"><?php echo htmlspecialchars($type, ENT_QUOTES, 'UTF-8'); ?></span><?php endif; ?>
<?php if ($subtype !== ''): ?><span class="tag muted"><?php echo htmlspecialchars($subtype, ENT_QUOTES, 'UTF-8'); ?></span><?php endif; ?>
</div>
<?php if ($stats !== []): ?>
<div class="stats">
<?php foreach ($stats as $stat_row): ?>
<?php
$sign = (string) ($stat_row['cl_scitemcustomstat_sign'] ?? '');
$value = rtrim(rtrim(number_format((float) ($stat_row['cl_scitemcustomstat_value'] ?? 0), 2, '.', ''), '0'), '.');
if ($value === '') {
$value = '0';
}
?>
<span class="stat"><?php echo htmlspecialchars((string) $stat_row['cl_scstatsitem_name'] . ' : ' . $sign . $value . ' ' . (string) $stat_row['cl_scstatsitem_unit'], ENT_QUOTES, 'UTF-8'); ?></span>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if ($note !== ''): ?>
<p class="muted"><?php echo nl2br(htmlspecialchars($note, ENT_QUOTES, 'UTF-8')); ?></p>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</body>
</html>

2099
sccharacters.php Normal file

File diff suppressed because it is too large Load Diff