query("SHOW COLUMNS FROM `{$table}` LIKE " . $db->quote($column)); return (bool) $stmt->fetch(); } function scmanutention_index_exists(PDO $db, string $table, string $index): bool { $stmt = $db->query("SHOW INDEX FROM `{$table}` WHERE Key_name = " . $db->quote($index)); return (bool) $stmt->fetch(); } function scmanutention_foreign_key_exists(PDO $db, string $table, string $constraint): bool { $stmt = $db->prepare( "SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table_name AND CONSTRAINT_NAME = :constraint_name AND CONSTRAINT_TYPE = 'FOREIGN KEY'" ); $stmt->execute([ 'table_name' => $table, 'constraint_name' => $constraint, ]); return (int) $stmt->fetchColumn() > 0; } function scmanutention_bootstrap(): void { static $bootstrapped = false; if ($bootstrapped) { return; } $db = db(); $db->exec( "CREATE TABLE IF NOT EXISTS tbl_scmanutentions ( cl_scmanutention_id INT UNSIGNED NOT NULL AUTO_INCREMENT, cl_scmanutention_owner_auth_id INT UNSIGNED NOT NULL, cl_scmanutention_title VARCHAR(190) NOT NULL, cl_scmanutention_type VARCHAR(120) NOT NULL DEFAULT '', cl_scmanutention_subtype VARCHAR(120) NOT NULL DEFAULT '', cl_scmanutention_description TEXT DEFAULT NULL, cl_scmanutention_share_token VARCHAR(64) NOT NULL, cl_scmanutention_share_enabled TINYINT(1) NOT NULL DEFAULT 0, cl_scmanutention_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, cl_scmanutention_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (cl_scmanutention_id), UNIQUE KEY uq_scmanutention_share_token (cl_scmanutention_share_token), KEY idx_scmanutention_owner (cl_scmanutention_owner_auth_id), KEY idx_scmanutention_title (cl_scmanutention_title), CONSTRAINT fk_scmanutention_owner_auth FOREIGN KEY (cl_scmanutention_owner_auth_id) REFERENCES tbl_auth (cl_auth_id) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_owner_auth_id')) { $db->exec( 'ALTER TABLE tbl_scmanutentions ADD COLUMN cl_scmanutention_owner_auth_id INT UNSIGNED NULL AFTER cl_scmanutention_id' ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_type')) { $db->exec( "ALTER TABLE tbl_scmanutentions ADD COLUMN cl_scmanutention_type VARCHAR(120) NOT NULL DEFAULT '' AFTER cl_scmanutention_title" ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_subtype')) { $db->exec( "ALTER TABLE tbl_scmanutentions ADD COLUMN cl_scmanutention_subtype VARCHAR(120) NOT NULL DEFAULT '' AFTER cl_scmanutention_type" ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_description')) { $db->exec( 'ALTER TABLE tbl_scmanutentions ADD COLUMN cl_scmanutention_description TEXT NULL AFTER cl_scmanutention_subtype' ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_share_token')) { $db->exec( "ALTER TABLE tbl_scmanutentions ADD COLUMN cl_scmanutention_share_token VARCHAR(64) NOT NULL DEFAULT '' AFTER cl_scmanutention_description" ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentions', 'cl_scmanutention_share_enabled')) { $db->exec( 'ALTER TABLE tbl_scmanutentions ADD COLUMN cl_scmanutention_share_enabled TINYINT(1) NOT NULL DEFAULT 0 AFTER cl_scmanutention_share_token' ); } if (!scmanutention_index_exists($db, 'tbl_scmanutentions', 'idx_scmanutention_owner')) { $db->exec( 'ALTER TABLE tbl_scmanutentions ADD INDEX idx_scmanutention_owner (cl_scmanutention_owner_auth_id)' ); } if (!scmanutention_index_exists($db, 'tbl_scmanutentions', 'idx_scmanutention_title')) { $db->exec( 'ALTER TABLE tbl_scmanutentions ADD INDEX idx_scmanutention_title (cl_scmanutention_title)' ); } if (!scmanutention_index_exists($db, 'tbl_scmanutentions', 'uq_scmanutention_share_token')) { $db->exec( 'ALTER TABLE tbl_scmanutentions ADD UNIQUE KEY uq_scmanutention_share_token (cl_scmanutention_share_token)' ); } if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentions', 'fk_scmanutention_owner_auth')) { $db->exec( 'ALTER TABLE tbl_scmanutentions ADD CONSTRAINT fk_scmanutention_owner_auth FOREIGN KEY (cl_scmanutention_owner_auth_id) REFERENCES tbl_auth (cl_auth_id) ON DELETE CASCADE ON UPDATE CASCADE' ); } $db->exec( "CREATE TABLE IF NOT EXISTS tbl_scmanutentionitems ( cl_scmanutentionitem_id INT UNSIGNED NOT NULL AUTO_INCREMENT, cl_scmanutentionitem_manutention_id INT UNSIGNED NOT NULL, cl_scmanutentionitem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base', cl_scmanutentionitem_scobjs_id INT UNSIGNED DEFAULT NULL, cl_scmanutentionitem_scitemcustom_id INT(11) DEFAULT NULL, cl_scmanutentionitem_quantity INT UNSIGNED NOT NULL DEFAULT 1, cl_scmanutentionitem_extra_info TEXT DEFAULT NULL, cl_scmanutentionitem_sort_order INT UNSIGNED NOT NULL DEFAULT 0, cl_scmanutentionitem_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, cl_scmanutentionitem_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (cl_scmanutentionitem_id), KEY idx_scmanutentionitem_sheet (cl_scmanutentionitem_manutention_id), KEY idx_scmanutentionitem_scobjs (cl_scmanutentionitem_scobjs_id), KEY idx_scmanutentionitem_scitemcustom (cl_scmanutentionitem_scitemcustom_id), KEY idx_scmanutentionitem_sheet_sort (cl_scmanutentionitem_manutention_id, cl_scmanutentionitem_sort_order, cl_scmanutentionitem_id), CONSTRAINT fk_scmanutentionitem_sheet FOREIGN KEY (cl_scmanutentionitem_manutention_id) REFERENCES tbl_scmanutentions (cl_scmanutention_id) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_scmanutentionitem_scobjs FOREIGN KEY (cl_scmanutentionitem_scobjs_id) REFERENCES tbl_scobjs (cl_scobjs_id) ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT fk_scmanutentionitem_scitemcustom FOREIGN KEY (cl_scmanutentionitem_scitemcustom_id) REFERENCES tbl_scitemcustom (cl_scitemcustom_id) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_source')) { $db->exec( "ALTER TABLE tbl_scmanutentionitems ADD COLUMN cl_scmanutentionitem_source ENUM('base', 'custom') NOT NULL DEFAULT 'base' AFTER cl_scmanutentionitem_manutention_id" ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_scobjs_id')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD COLUMN cl_scmanutentionitem_scobjs_id INT UNSIGNED NULL AFTER cl_scmanutentionitem_source' ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_scitemcustom_id')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD COLUMN cl_scmanutentionitem_scitemcustom_id INT(11) NULL AFTER cl_scmanutentionitem_scobjs_id' ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_quantity')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD COLUMN cl_scmanutentionitem_quantity INT UNSIGNED NOT NULL DEFAULT 1 AFTER cl_scmanutentionitem_scitemcustom_id' ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_extra_info')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD COLUMN cl_scmanutentionitem_extra_info TEXT NULL AFTER cl_scmanutentionitem_quantity' ); } if (!scmanutention_column_exists($db, 'tbl_scmanutentionitems', 'cl_scmanutentionitem_sort_order')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD COLUMN cl_scmanutentionitem_sort_order INT UNSIGNED NOT NULL DEFAULT 0 AFTER cl_scmanutentionitem_extra_info' ); } if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_sheet')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD INDEX idx_scmanutentionitem_sheet (cl_scmanutentionitem_manutention_id)' ); } if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_scobjs')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD INDEX idx_scmanutentionitem_scobjs (cl_scmanutentionitem_scobjs_id)' ); } if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_scitemcustom')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD INDEX idx_scmanutentionitem_scitemcustom (cl_scmanutentionitem_scitemcustom_id)' ); } if (!scmanutention_index_exists($db, 'tbl_scmanutentionitems', 'idx_scmanutentionitem_sheet_sort')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD INDEX idx_scmanutentionitem_sheet_sort (cl_scmanutentionitem_manutention_id, cl_scmanutentionitem_sort_order, cl_scmanutentionitem_id)' ); } if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentionitems', 'fk_scmanutentionitem_sheet')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD CONSTRAINT fk_scmanutentionitem_sheet FOREIGN KEY (cl_scmanutentionitem_manutention_id) REFERENCES tbl_scmanutentions (cl_scmanutention_id) ON DELETE CASCADE ON UPDATE CASCADE' ); } if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentionitems', 'fk_scmanutentionitem_scobjs')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD CONSTRAINT fk_scmanutentionitem_scobjs FOREIGN KEY (cl_scmanutentionitem_scobjs_id) REFERENCES tbl_scobjs (cl_scobjs_id) ON DELETE SET NULL ON UPDATE CASCADE' ); } if (!scmanutention_foreign_key_exists($db, 'tbl_scmanutentionitems', 'fk_scmanutentionitem_scitemcustom')) { $db->exec( 'ALTER TABLE tbl_scmanutentionitems ADD CONSTRAINT fk_scmanutentionitem_scitemcustom FOREIGN KEY (cl_scmanutentionitem_scitemcustom_id) REFERENCES tbl_scitemcustom (cl_scitemcustom_id) ON DELETE SET NULL ON UPDATE CASCADE' ); } $stmt_missing_tokens = $db->query( "SELECT cl_scmanutention_id FROM tbl_scmanutentions WHERE cl_scmanutention_share_token = '' OR cl_scmanutention_share_token IS NULL" ); foreach ($stmt_missing_tokens->fetchAll(PDO::FETCH_COLUMN) as $sheet_id) { $stmt_update = $db->prepare( 'UPDATE tbl_scmanutentions SET cl_scmanutention_share_token = :token WHERE cl_scmanutention_id = :id' ); $stmt_update->execute([ 'token' => scmanutention_generate_share_token(), 'id' => (int) $sheet_id, ]); } $bootstrapped = true; } function scmanutention_generate_share_token(int $length = 32): string { $length = max(16, min(64, $length)); return bin2hex(random_bytes((int) ceil($length / 2))); } function scmanutention_clean_text(?string $value): string { return trim((string) $value); } function scmanutention_normalize_quantity($value): int { $quantity = (int) $value; if ($quantity <= 0) { return 1; } return min($quantity, 999999); } function scmanutention_is_valid_source(string $value): bool { return in_array($value, ['base', 'custom'], true); } function scmanutention_escape_like(string $value): string { return strtr($value, [ '\\' => '\\\\', '%' => '\\%', '_' => '\\_', ]); } function scmanutention_sheet_share_url(string $token): string { $is_https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') || ((string) ($_SERVER['SERVER_PORT'] ?? '') === '443'); $scheme = $is_https ? 'https' : 'http'; $host = trim((string) ($_SERVER['HTTP_HOST'] ?? '')); if ($host === '') { $host = '127.0.0.1'; } return $scheme . '://' . $host . '/scmanutentionpublic.php?share=' . rawurlencode($token); } function scmanutention_find_owned_sheet(PDO $db, int $sheet_id, int $owner_auth_id): ?array { if ($sheet_id <= 0 || $owner_auth_id <= 0) { return null; } $stmt = $db->prepare( 'SELECT * FROM tbl_scmanutentions WHERE cl_scmanutention_id = :id AND cl_scmanutention_owner_auth_id = :owner_auth_id LIMIT 1' ); $stmt->execute([ 'id' => $sheet_id, 'owner_auth_id' => $owner_auth_id, ]); $row = $stmt->fetch(); return $row ?: null; } function scmanutention_find_public_sheet_by_token(PDO $db, string $share_token): ?array { $share_token = trim($share_token); if ($share_token === '') { return null; } $stmt = $db->prepare( 'SELECT m.*, COALESCE(NULLIF(TRIM(a.cl_auth_user), \'\'), \'Inconnu\') AS cl_scmanutention_owner_name FROM tbl_scmanutentions m LEFT JOIN tbl_auth a ON a.cl_auth_id = m.cl_scmanutention_owner_auth_id WHERE m.cl_scmanutention_share_token = :share_token AND m.cl_scmanutention_share_enabled = 1 LIMIT 1' ); $stmt->execute(['share_token' => $share_token]); $row = $stmt->fetch(); return $row ?: null; } function scmanutention_next_item_sort_order(PDO $db, int $sheet_id): int { $stmt = $db->prepare( 'SELECT COALESCE(MAX(cl_scmanutentionitem_sort_order), 0) FROM tbl_scmanutentionitems WHERE cl_scmanutentionitem_manutention_id = :sheet_id' ); $stmt->execute(['sheet_id' => $sheet_id]); return ((int) $stmt->fetchColumn()) + 1; } function scmanutention_reindex_items(PDO $db, int $sheet_id): void { if ($sheet_id <= 0) { return; } $stmt = $db->prepare( 'SELECT cl_scmanutentionitem_id FROM tbl_scmanutentionitems WHERE cl_scmanutentionitem_manutention_id = :sheet_id ORDER BY cl_scmanutentionitem_sort_order ASC, cl_scmanutentionitem_id ASC' ); $stmt->execute(['sheet_id' => $sheet_id]); $ids = $stmt->fetchAll(PDO::FETCH_COLUMN); $position = 1; $stmt_update = $db->prepare( 'UPDATE tbl_scmanutentionitems SET cl_scmanutentionitem_sort_order = :sort_order WHERE cl_scmanutentionitem_id = :id' ); foreach ($ids as $id) { $stmt_update->execute([ 'sort_order' => $position++, 'id' => (int) $id, ]); } } function scmanutention_find_owned_item(PDO $db, int $item_id, int $owner_auth_id): ?array { if ($item_id <= 0 || $owner_auth_id <= 0) { return null; } $stmt = $db->prepare( "SELECT mi.*, m.cl_scmanutention_owner_auth_id FROM tbl_scmanutentionitems mi INNER JOIN tbl_scmanutentions m ON m.cl_scmanutention_id = mi.cl_scmanutentionitem_manutention_id WHERE mi.cl_scmanutentionitem_id = :item_id AND m.cl_scmanutention_owner_auth_id = :owner_auth_id LIMIT 1" ); $stmt->execute([ 'item_id' => $item_id, 'owner_auth_id' => $owner_auth_id, ]); $row = $stmt->fetch(); return $row ?: null; } function scmanutention_validate_item_reference(PDO $db, int $owner_auth_id, string $source, int $scobjs_id, int $scitemcustom_id): ?array { if (!scmanutention_is_valid_source($source)) { return null; } if ($source === 'base') { if ($scobjs_id <= 0) { return null; } $stmt = $db->prepare( 'SELECT cl_scobjs_id, cl_scobjs_name, cl_scobjs_type, cl_scobjs_subtype, cl_scobjs_uuid, cl_scobjs_rarity FROM tbl_scobjs WHERE cl_scobjs_id = :id LIMIT 1' ); $stmt->execute(['id' => $scobjs_id]); $row = $stmt->fetch(); if (!$row) { return null; } return [ 'source' => 'base', 'scobjs_id' => (int) $row['cl_scobjs_id'], 'scitemcustom_id' => 0, 'name' => (string) $row['cl_scobjs_name'], 'type' => (string) ($row['cl_scobjs_type'] ?? ''), 'subtype' => (string) ($row['cl_scobjs_subtype'] ?? ''), 'uuid' => (string) ($row['cl_scobjs_uuid'] ?? ''), 'rarity' => (string) ($row['cl_scobjs_rarity'] ?? ''), ]; } if ($scitemcustom_id <= 0 || $owner_auth_id <= 0) { return null; } $stmt = $db->prepare( "SELECT c.cl_scitemcustom_id, o.cl_scobjs_id, o.cl_scobjs_name, o.cl_scobjs_type, o.cl_scobjs_subtype, o.cl_scobjs_uuid, o.cl_scobjs_rarity FROM tbl_scitemcustom c INNER JOIN tbl_scobjs o ON o.cl_scobjs_id = c.cl_scitemcustom_obj_id WHERE c.cl_scitemcustom_id = :itemcustom_id AND c.cl_scitemcustom_owner_auth_id = :owner_auth_id LIMIT 1" ); $stmt->execute([ 'itemcustom_id' => $scitemcustom_id, 'owner_auth_id' => $owner_auth_id, ]); $row = $stmt->fetch(); if (!$row) { return null; } return [ 'source' => 'custom', 'scobjs_id' => (int) $row['cl_scobjs_id'], 'scitemcustom_id' => (int) $row['cl_scitemcustom_id'], 'name' => (string) $row['cl_scobjs_name'], 'type' => (string) ($row['cl_scobjs_type'] ?? ''), 'subtype' => (string) ($row['cl_scobjs_subtype'] ?? ''), 'uuid' => (string) ($row['cl_scobjs_uuid'] ?? ''), 'rarity' => (string) ($row['cl_scobjs_rarity'] ?? ''), ]; } function scmanutention_search_available_items(PDO $db, int $owner_auth_id, string $query, ?int $limit = 25, int $offset = 0): array { $query = trim($query); if ($query === '') { return []; } $escaped = scmanutention_escape_like($query); $exact = $escaped; $prefix = $escaped . '%'; $contains = '%' . $escaped . '%'; $limit_clause = ''; $offset = max(0, $offset); if ($limit !== null && $limit > 0) { $limit = max(1, min(100, $limit)); $limit_clause = ' LIMIT ' . (int) $limit . ' OFFSET ' . (int) $offset; } $sql = " SELECT * FROM ( SELECT CONCAT('base:', o.cl_scobjs_id) AS result_key, 'base' AS result_source, o.cl_scobjs_id AS result_scobjs_id, NULL AS result_scitemcustom_id, o.cl_scobjs_name AS result_name, COALESCE(o.cl_scobjs_type, '') AS result_type, COALESCE(o.cl_scobjs_subtype, '') AS result_subtype, COALESCE(o.cl_scobjs_uuid, '') AS result_uuid, COALESCE(o.cl_scobjs_rarity, '') AS result_rarity FROM tbl_scobjs o WHERE ( o.cl_scobjs_name LIKE :contains_name_base OR o.cl_scobjs_type LIKE :contains_type_base OR o.cl_scobjs_subtype LIKE :contains_subtype_base OR o.cl_scobjs_uuid LIKE :contains_uuid_base ) UNION ALL SELECT CONCAT('custom:', c.cl_scitemcustom_id) AS result_key, 'custom' AS result_source, o.cl_scobjs_id AS result_scobjs_id, c.cl_scitemcustom_id AS result_scitemcustom_id, o.cl_scobjs_name AS result_name, COALESCE(o.cl_scobjs_type, '') AS result_type, COALESCE(o.cl_scobjs_subtype, '') AS result_subtype, COALESCE(o.cl_scobjs_uuid, '') AS result_uuid, COALESCE(o.cl_scobjs_rarity, '') AS result_rarity FROM tbl_scitemcustom c INNER JOIN tbl_scobjs o ON o.cl_scobjs_id = c.cl_scitemcustom_obj_id WHERE c.cl_scitemcustom_owner_auth_id = :owner_auth_id AND ( o.cl_scobjs_name LIKE :contains_name_custom OR o.cl_scobjs_type LIKE :contains_type_custom OR o.cl_scobjs_subtype LIKE :contains_subtype_custom OR o.cl_scobjs_uuid LIKE :contains_uuid_custom ) ) search_results ORDER BY CASE WHEN result_name = :exact_name THEN 0 WHEN result_name LIKE :prefix_name THEN 1 WHEN result_source = 'custom' THEN 2 WHEN result_uuid = :exact_uuid THEN 3 WHEN result_uuid LIKE :prefix_uuid THEN 4 WHEN result_type LIKE :prefix_type THEN 5 WHEN result_subtype LIKE :prefix_subtype THEN 6 ELSE 7 END ASC, CHAR_LENGTH(result_name) ASC, result_name ASC, result_key ASC {$limit_clause}"; $stmt = $db->prepare($sql); $stmt->execute([ 'owner_auth_id' => $owner_auth_id, 'contains_name_base' => $contains, 'contains_type_base' => $contains, 'contains_subtype_base' => $contains, 'contains_uuid_base' => $contains, 'contains_name_custom' => $contains, 'contains_type_custom' => $contains, 'contains_subtype_custom' => $contains, 'contains_uuid_custom' => $contains, 'exact_name' => $exact, 'prefix_name' => $prefix, 'exact_uuid' => $exact, 'prefix_uuid' => $prefix, 'prefix_type' => $prefix, 'prefix_subtype' => $prefix, ]); return $stmt->fetchAll() ?: []; } function scmanutention_sortable_item_name(array $item_row): string { $name = (string) ((($item_row['cl_scmanutentionitem_source'] ?? '') === 'custom') ? ($item_row['cl_scmanutentionitem_custom_name'] ?? '') : ($item_row['cl_scmanutentionitem_base_name'] ?? '')); $name = trim($name); if ($name === '') { return ''; } if (function_exists('iconv')) { $ascii_name = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $name); if ($ascii_name !== false) { $name = $ascii_name; } } return function_exists('mb_strtolower') ? mb_strtolower($name, 'UTF-8') : strtolower($name); } function scmanutention_fetch_items(PDO $db, int $sheet_id): array { if ($sheet_id <= 0) { return []; } scmanutention_reindex_items($db, $sheet_id); $stmt = $db->prepare( "SELECT mi.*, bo.cl_scobjs_name AS cl_scmanutentionitem_base_name, bo.cl_scobjs_type AS cl_scmanutentionitem_base_type, bo.cl_scobjs_subtype AS cl_scmanutentionitem_base_subtype, bo.cl_scobjs_uuid AS cl_scmanutentionitem_base_uuid, bo.cl_scobjs_rarity AS cl_scmanutentionitem_base_rarity, co.cl_scitemcustom_id AS cl_scmanutentionitem_custom_ref_id, oo.cl_scobjs_name AS cl_scmanutentionitem_custom_name, oo.cl_scobjs_type AS cl_scmanutentionitem_custom_type, oo.cl_scobjs_subtype AS cl_scmanutentionitem_custom_subtype, oo.cl_scobjs_uuid AS cl_scmanutentionitem_custom_uuid, oo.cl_scobjs_rarity AS cl_scmanutentionitem_custom_rarity FROM tbl_scmanutentionitems mi LEFT JOIN tbl_scobjs bo ON bo.cl_scobjs_id = mi.cl_scmanutentionitem_scobjs_id LEFT JOIN tbl_scitemcustom co ON co.cl_scitemcustom_id = mi.cl_scmanutentionitem_scitemcustom_id LEFT JOIN tbl_scobjs oo ON oo.cl_scobjs_id = co.cl_scitemcustom_obj_id WHERE mi.cl_scmanutentionitem_manutention_id = :sheet_id ORDER BY mi.cl_scmanutentionitem_sort_order ASC, mi.cl_scmanutentionitem_id ASC" ); $stmt->execute(['sheet_id' => $sheet_id]); $items = $stmt->fetchAll() ?: []; foreach ($items as $index => &$item_row) { $item_row['__alpha_sort_name'] = scmanutention_sortable_item_name($item_row); $item_row['__alpha_sort_index'] = $index; } unset($item_row); usort($items, static function (array $left, array $right): int { $name_compare = strnatcasecmp((string) ($left['__alpha_sort_name'] ?? ''), (string) ($right['__alpha_sort_name'] ?? '')); if ($name_compare !== 0) { return $name_compare; } $sort_order_compare = ((int) ($left['cl_scmanutentionitem_sort_order'] ?? 0)) <=> ((int) ($right['cl_scmanutentionitem_sort_order'] ?? 0)); if ($sort_order_compare !== 0) { return $sort_order_compare; } $id_compare = ((int) ($left['cl_scmanutentionitem_id'] ?? 0)) <=> ((int) ($right['cl_scmanutentionitem_id'] ?? 0)); if ($id_compare !== 0) { return $id_compare; } return ((int) ($left['__alpha_sort_index'] ?? 0)) <=> ((int) ($right['__alpha_sort_index'] ?? 0)); }); foreach ($items as &$item_row) { unset($item_row['__alpha_sort_name'], $item_row['__alpha_sort_index']); } unset($item_row); return $items; } function scmanutention_fetch_custom_stats_map(PDO $db, array $item_rows): array { $custom_ids = []; foreach ($item_rows as $row) { if (($row['cl_scmanutentionitem_source'] ?? '') === 'custom' && !empty($row['cl_scmanutentionitem_scitemcustom_id'])) { $custom_ids[] = (int) $row['cl_scmanutentionitem_scitemcustom_id']; } } $custom_ids = array_values(array_unique(array_filter($custom_ids))); if ($custom_ids === []) { return []; } $placeholders = implode(',', array_fill(0, count($custom_ids), '?')); $stmt = $db->prepare( "SELECT cs.cl_scitemcustomstat_itemcustom_id, st.cl_scstatsitem_name, st.cl_scstatsitem_unit, cs.cl_scitemcustomstat_sign, cs.cl_scitemcustomstat_value FROM tbl_scitemcustomstat cs INNER JOIN tbl_scstatsitem st ON st.cl_scstatsitem_id = cs.cl_scitemcustomstat_stat_id WHERE cs.cl_scitemcustomstat_itemcustom_id IN ({$placeholders}) ORDER BY st.cl_scstatsitem_name ASC, cs.cl_scitemcustomstat_id ASC" ); $stmt->execute($custom_ids); $stats_map = []; foreach ($stmt->fetchAll() as $row) { $itemcustom_id = (int) $row['cl_scitemcustomstat_itemcustom_id']; if (!isset($stats_map[$itemcustom_id])) { $stats_map[$itemcustom_id] = []; } $stats_map[$itemcustom_id][] = $row; } return $stats_map; } function scmanutention_fetch_custom_stats_preview_map(PDO $db, array $custom_ids): array { $custom_ids = array_values(array_unique(array_map('intval', array_filter($custom_ids)))); if ($custom_ids === []) { return []; } $placeholders = implode(',', array_fill(0, count($custom_ids), '?')); $stmt = $db->prepare( "SELECT cs.cl_scitemcustomstat_itemcustom_id, st.cl_scstatsitem_name, st.cl_scstatsitem_unit, cs.cl_scitemcustomstat_sign, cs.cl_scitemcustomstat_value FROM tbl_scitemcustomstat cs INNER JOIN tbl_scstatsitem st ON st.cl_scstatsitem_id = cs.cl_scitemcustomstat_stat_id WHERE cs.cl_scitemcustomstat_itemcustom_id IN ({$placeholders}) ORDER BY st.cl_scstatsitem_name ASC, cs.cl_scitemcustomstat_id ASC" ); $stmt->execute($custom_ids); $stats_map = []; foreach ($stmt->fetchAll() as $row) { $itemcustom_id = (int) $row['cl_scitemcustomstat_itemcustom_id']; if (!isset($stats_map[$itemcustom_id])) { $stats_map[$itemcustom_id] = []; } $stats_map[$itemcustom_id][] = $row; } return $stats_map; }