conn = $conn; } /** * เพิ่มโหนดใหม่ * * @param string $name ชื่อของโหนด * @param int $level ระดับของโหนด * @param int|null $parentId ID ของโหนดพ่อ (null สำหรับโหนดระดับบนสุด) * @param string|null $externalId ID ภายนอกสำหรับเชื่อมโยงกับระบบอื่น * @param string|null $data ข้อมูลเพิ่มเติมในรูปแบบ JSON * @return int|false ID ของโหนดที่เพิ่ม หรือ false ถ้าไม่สำเร็จ */ public function addNode($name, $level, $parentId = null, $externalId = null, $data = null) { // หาลำดับของโหนดตาม parent_id $nodeOrder = $this->getNextNodeOrder($parentId); $stmt = $this->conn->prepare("INSERT INTO tree_nodes (name, level, parent_id, node_order, external_id, data) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->bind_param("siisss", $name, $level, $parentId, $nodeOrder, $externalId, $data); if ($stmt->execute()) { return $stmt->insert_id; } else { return false; } } /** * หาลำดับถัดไปของโหนดภายใต้พ่อเดียวกัน * * @param int|null $parentId ID ของโหนดพ่อ * @return int ลำดับถัดไป */ private function getNextNodeOrder($parentId) { if ($parentId === null) { $query = "SELECT MAX(node_order) AS max_order FROM tree_nodes WHERE parent_id IS NULL"; $result = $this->conn->query($query); } else { $stmt = $this->conn->prepare("SELECT MAX(node_order) AS max_order FROM tree_nodes WHERE parent_id = ?"); $stmt->bind_param("i", $parentId); $stmt->execute(); $result = $stmt->get_result(); } if ($row = $result->fetch_assoc()) { return ($row['max_order'] !== null) ? $row['max_order'] + 1 : 0; } return 0; } /** * อัพเดตโหนด * * @param int $id ID ของโหนดที่ต้องการอัพเดต * @param array $data ข้อมูลที่ต้องการอัพเดต [key => value] * @return bool สำเร็จหรือไม่ */ public function updateNode($id, $data) { $allowedFields = ['name', 'level', 'parent_id', 'node_order', 'external_id', 'data']; $updates = []; $paramTypes = ''; $paramValues = []; foreach ($data as $key => $value) { if (in_array($key, $allowedFields)) { $updates[] = "$key = ?"; // กำหนดชนิดของพารามิเตอร์ if ($key === 'level' || $key === 'parent_id' || $key === 'node_order') { $paramTypes .= 'i'; // integer } else { $paramTypes .= 's'; // string } $paramValues[] = $value; } } if (empty($updates)) { return false; } $query = "UPDATE tree_nodes SET ".implode(', ', $updates)." WHERE id = ?"; $paramTypes .= 'i'; // for id $paramValues[] = $id; $stmt = $this->conn->prepare($query); // Bind parameters dynamically $bindParams = array_merge([$paramTypes], $paramValues); $bindParams = $this->refValues($bindParams); call_user_func_array([$stmt, 'bind_param'], $bindParams); return $stmt->execute(); } /** * ช่วยในการ bind parameters แบบไดนามิก */ private function refValues($arr) { $refs = []; foreach ($arr as $key => $value) { $refs[$key] = &$arr[$key]; } return $refs; } /** * ลบโหนดและลูกหลานทั้งหมด * * @param int $id ID ของโหนดที่ต้องการลบ * @return bool สำเร็จหรือไม่ */ public function deleteNode($id) { // ดึงข้อมูลลูกหลานทั้งหมด $children = $this->getChildren($id); // ลบลูกหลานก่อน foreach ($children as $child) { $this->deleteNode($child['id']); } // ลบโหนดหลัก $stmt = $this->conn->prepare("DELETE FROM tree_nodes WHERE id = ?"); $stmt->bind_param("i", $id); return $stmt->execute(); } /** * ดึงข้อมูลลูกทั้งหมดของโหนด * * @param int $parentId ID ของโหนดพ่อ * @return array ข้อมูลโหนดลูกทั้งหมด */ public function getChildren($parentId) { $stmt = $this->conn->prepare("SELECT * FROM tree_nodes WHERE parent_id = ? ORDER BY node_order"); $stmt->bind_param("i", $parentId); $stmt->execute(); $result = $stmt->get_result(); $children = []; while ($row = $result->fetch_assoc()) { $children[] = $row; } return $children; } /** * ดึงข้อมูลโหนดตาม ID * * @param int $id ID ของโหนด * @return array|null ข้อมูลของโหนด หรือ null ถ้าไม่พบ */ public function getNode($id) { $stmt = $this->conn->prepare("SELECT * FROM tree_nodes WHERE id = ?"); $stmt->bind_param("i", $id); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { return $row; } return null; } /** * ดึงข้อมูลโหนดทั้งหมดเป็นโครงสร้างต้นไม้ * * @return array โครงสร้างต้นไม้ที่สมบูรณ์ */ public function getTreeStructure() { // ดึงโหนดระดับบนสุด $rootNodes = $this->getRootNodes(); // สร้างโครงสร้างต้นไม้ที่สมบูรณ์ foreach ($rootNodes as &$node) { $this->buildNodeTree($node); } return $rootNodes; } /** * สร้างโครงสร้างต้นไม้ของโหนด * * @param array &$node โหนดที่ต้องการสร้างโครงสร้าง */ private function buildNodeTree(&$node) { $children = $this->getChildren($node['id']); $node['children'] = []; foreach ($children as &$child) { $this->buildNodeTree($child); $node['children'][] = $child; } } /** * ดึงโหนดระดับบนสุดทั้งหมด * * @return array โหนดระดับบนสุดทั้งหมด */ public function getRootNodes() { $result = $this->conn->query("SELECT * FROM tree_nodes WHERE parent_id IS NULL ORDER BY node_order"); $rootNodes = []; while ($row = $result->fetch_assoc()) { $rootNodes[] = $row; } return $rootNodes; } /** * เลื่อนตำแหน่งโหนด * * @param int $id ID ของโหนดที่ต้องการเลื่อน * @param string $direction ทิศทางที่ต้องการเลื่อน ('up' หรือ 'down') * @return bool สำเร็จหรือไม่ */ public function moveNode($id, $direction) { $node = $this->getNode($id); if (!$node) { return false; } // หาโหนดพี่น้องทั้งหมด if ($node['parent_id'] === null) { $siblings = $this->getRootNodes(); } else { $siblings = $this->getChildren($node['parent_id']); } // หาตำแหน่งปัจจุบัน $currentPosition = -1; foreach ($siblings as $index => $sibling) { if ($sibling['id'] == $id) { $currentPosition = $index; break; } } if ($currentPosition === -1) { return false; } // คำนวณตำแหน่งใหม่ if ($direction === 'up' && $currentPosition > 0) { $targetPosition = $currentPosition - 1; } elseif ($direction === 'down' && $currentPosition < count($siblings) - 1) { $targetPosition = $currentPosition + 1; } else { return false; } // สลับลำดับ $targetNode = $siblings[$targetPosition]; // อัพเดตลำดับของโหนด $this->updateNode($id, ['node_order' => $targetNode['node_order']]); $this->updateNode($targetNode['id'], ['node_order' => $node['node_order']]); return true; } /** * ย้ายโหนดไปเป็นลูกของโหนดอื่น * * @param int $id ID ของโหนดที่ต้องการย้าย * @param int|null $newParentId ID ของโหนดพ่อใหม่ (null สำหรับย้ายเป็นโหนดระดับบนสุด) * @param int $newLevel ระดับใหม่ของโหนด * @return bool สำเร็จหรือไม่ */ public function moveNodeToParent($id, $newParentId, $newLevel) { $node = $this->getNode($id); if (!$node) { return false; } // คำนวณความแตกต่างของระดับ $levelDifference = $newLevel - $node['level']; // อัพเดตโหนด $nodeOrder = $this->getNextNodeOrder($newParentId); $this->updateNode($id, [ 'parent_id' => $newParentId, 'level' => $newLevel, 'node_order' => $nodeOrder ]); // อัพเดตระดับของลูกหลาน $this->updateChildrenLevels($id, $levelDifference); return true; } /** * อัพเดตระดับของลูกหลาน * * @param int $parentId ID ของโหนดพ่อ * @param int $levelChange การเปลี่ยนแปลงระดับ */ private function updateChildrenLevels($parentId, $levelChange) { $children = $this->getChildren($parentId); foreach ($children as $child) { $newLevel = $child['level'] + $levelChange; $this->updateNode($child['id'], ['level' => $newLevel]); // อัพเดตลูกหลานแบบเรียกซ้ำ $this->updateChildrenLevels($child['id'], $levelChange); } } /** * นำเข้าข้อมูลต้นไม้จาก JavaScript TreeManager * * @param array $treeData ข้อมูลต้นไม้ในรูปแบบของ JavaScript TreeManager * @return bool สำเร็จหรือไม่ */ public function importFromTreeManager($treeData) { // เริ่มต้น transaction $this->conn->begin_transaction(); try { // ลบข้อมูลเก่าทั้งหมด $this->conn->query("TRUNCATE TABLE tree_nodes"); // นำเข้าข้อมูลใหม่ foreach ($treeData as $rootNode) { $this->importNode($rootNode, null); } // ยืนยัน transaction $this->conn->commit(); return true; } catch (Exception $e) { // ยกเลิก transaction ถ้ามีข้อผิดพลาด $this->conn->rollback(); return false; } } /** * นำเข้าโหนดและลูกหลาน * * @param array $node ข้อมูลโหนด * @param int|null $parentId ID ของโหนดพ่อ * @return int ID ของโหนดที่นำเข้า */ private function importNode($node, $parentId) { // สร้างโหนดใหม่ $nodeId = $this->addNode( $node['name'], $node['level'], $parentId, isset($node['external_id']) ? $node['external_id'] : null, isset($node['data']) ? json_encode($node['data']) : null ); // นำเข้าลูกหลาน if (isset($node['children']) && is_array($node['children'])) { foreach ($node['children'] as $child) { $this->importNode($child, $nodeId); } } return $nodeId; } /** * ส่งออกข้อมูลต้นไม้เป็นรูปแบบของ JavaScript TreeManager * * @return array ข้อมูลต้นไม้ในรูปแบบของ JavaScript TreeManager */ public function exportToTreeManager() { $treeData = $this->getTreeStructure(); // แปลงรูปแบบให้ตรงกับ TreeManager $this->formatNodesForTreeManager($treeData); return $treeData; } /** * แปลงรูปแบบโหนดให้ตรงกับ TreeManager * * @param array &$nodes รายการโหนดที่ต้องการแปลง */ private function formatNodesForTreeManager(&$nodes) { foreach ($nodes as &$node) { // แปลง id, parent_id เป็นรูปแบบของ TreeManager $node['id'] = (int) $node['id']; if ($node['parent_id'] !== null) { $node['parentId'] = (int) $node['parent_id']; } unset($node['parent_id']); // แปลง node_order เป็น order $node['order'] = (int) $node['node_order']; unset($node['node_order']); // แปลง level เป็น level $node['level'] = (int) $node['level']; // แปลงข้อมูล JSON เป็น object ถ้ามี if (!empty($node['data'])) { $node['data'] = json_decode($node['data'], true); } // แปลงลูกหลาน if (isset($node['children']) && is_array($node['children'])) { $this->formatNodesForTreeManager($node['children']); } } } /** * ดึงข้อมูลโหนดตาม external_id * * @param string $externalId External ID ที่ต้องการค้นหา * @return array|null ข้อมูลของโหนด หรือ null ถ้าไม่พบ */ public function getNodeByExternalId($externalId) { $stmt = $this->conn->prepare("SELECT * FROM tree_nodes WHERE external_id = ?"); $stmt->bind_param("s", $externalId); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { return $row; } return null; } /** * ค้นหาโหนดตามชื่อ * * @param string $searchTerm คำค้นหา * @return array ผลลัพธ์การค้นหา */ public function searchNodes($searchTerm) { $searchTerm = "%$searchTerm%"; $stmt = $this->conn->prepare("SELECT * FROM tree_nodes WHERE name LIKE ? ORDER BY level, node_order"); $stmt->bind_param("s", $searchTerm); $stmt->execute(); $result = $stmt->get_result(); $nodes = []; while ($row = $result->fetch_assoc()) { $nodes[] = $row; } return $nodes; } }