0) { $exists = db_fetch_cell_prepared('SELECT id FROM graph_tree_items WHERE parent = ? AND graph_tree_id = ? AND host_id = ?', [$pdata['leaf_id'], $tree_id, $data['host']]); if ($exists) { print 'tbranch:' . $exists; return; } } elseif ($data['graph'] > 0) { $exists = db_fetch_cell_prepared('SELECT id FROM graph_tree_items WHERE parent = ? AND graph_tree_id = ? AND local_graph_id = ?', [$pdata['leaf_id'], $tree_id, $data['graph']]); if ($exists) { print 'tbranch:' . $exists; return; } } elseif ($data['site'] > 0) { $exists = db_fetch_cell_prepared('SELECT id FROM graph_tree_items WHERE parent = ? AND graph_tree_id = ? AND site_id = ?', [$pdata['leaf_id'], $tree_id, $data['site']]); if ($exists) { print 'tbranch:' . $exists; return; } } else { $title = db_fetch_cell_prepared('SELECT title FROM graph_tree_items WHERE id = ?', [$data['leaf_id']]); } $save = []; $save['parent'] = $pdata['leaf_id']; $save['position'] = $new_position; $save['graph_tree_id'] = $tree_id; $save['local_graph_id'] = $data['graph']; $save['host_id'] = $data['host']; $save['site_id'] = $data['site']; $save['host_grouping_type'] = 1; $save['sort_children_type'] = TREE_ORDERING_INHERIT; $save['title'] = $title; $id = sql_save($save, 'graph_tree_items'); api_tree_sort_branch($id, $tree_id); /** * Save the last time a tree branch was created/updated * for Caching. */ set_config_option('time_last_change_branch', time()); header('Content-Type: application/json; charset=utf-8'); print json_encode(['id' => 'tbranch:' . $id]); } /** * Given a lock name, placed a timed lock on the database. * This function allows simulating transactions in an MyISAM database. * * @param string $lockname The name of the lock to acquire. * @param int $timeout The timeout period in seconds for acquiring the lock. Default is 10 seconds. * * @return bool Returns true if the lock is acquired, false otherwise. */ function api_tree_get_lock(string $lockname, int $timeout = 10) : bool { input_validate_input_number($timeout, 'timeout'); $lockname = sanitize_search_string($lockname); $count = 0; while ($count < 5) { if (register_process_start('tree_lock', $lockname, 0, $timeout)) { return true; } else { $count++; sleep(1); } } return false; } /** * Releases a lock on a tree. * This function allows simulating transactions in an MyISAM database. * * @param string $lockname The name of the lock to be released. * * @return void */ function api_tree_release_lock(string $lockname) : void { unregister_process('tree_lock', $lockname, 0); } /** * Given a tree, a destination leaf_id, order position, and title, create a branch/leaf * Data is not returned, it is printed to the screen. * * @param int $tree_id The ID of the tree where the node will be created. * @param mixed $node_id The ID of the node to be created. * @param int $position The position of the node within the tree. * @param string $title The title of the node. Defaults to 'New Branch' if not provided. * * @return bool */ function api_tree_create_node(int $tree_id, mixed $node_id, int $position, string $title = '') : bool { input_validate_input_number($tree_id, 'tree_id'); input_validate_input_number($position, 'position'); // Reject an out-of-range TreeID before the permission check: is_tree_allowed() // caches into $_SESSION and hits the database, so a 0/negative id should fail // as bad input rather than as an authorization result. if ($tree_id <= 0) { cacti_log("ERROR: Invalid TreeID: '$tree_id', Function create_node", false); return false; } if (!is_tree_allowed($tree_id)) { cacti_log("SECURITY: User is not permitted to modify TreeID:'$tree_id', Function create_node", false, 'AUTH'); return false; } if ($title == '') { $title = __('New Branch'); } $data = api_tree_parse_node_data($node_id); if ($data['leaf_id'] < 0) { cacti_log("ERROR: Invalid BranchID: '" . $data['leaf_id'] . "', Function create_node", false); return false; } $i = 0; $found = false; $orig = $title; while (true) { $title = $orig . ($found ? ' (' . $i . ')' : ''); $exists_id = api_tree_branch_exists($tree_id, $data['leaf_id'], $title); if ($exists_id == false) { break; } else { $found = true; $i++; } } // watch out for monkey business input_validate_input_number($data['leaf_id'], 'leaf_id'); $save = []; $save['parent'] = $data['leaf_id']; $save['position'] = $position; $save['graph_tree_id'] = $tree_id; $save['local_graph_id'] = 0; $save['host_id'] = 0; $save['site_id'] = 0; $save['host_grouping_type'] = 1; $save['sort_children_type'] = TREE_ORDERING_INHERIT; $save['title'] = $title; $id = sql_save($save, 'graph_tree_items'); api_tree_sort_branch($id, $tree_id); /** * Save the last time a tree branch was created/updated * for Caching. */ set_config_option('time_last_change_branch', time()); header('Content-Type: application/json; charset=utf-8'); print json_encode(['id' => 'tbranch:' . $id, 'text' => $title]); return true; } /** * Checks if a tree branch exists in the graph_tree_items table. * * @param int $tree_id The ID of the tree to check. * @param int $parent The parent ID of the branch to check. * @param string $title The title of the branch to check. * * @return mixed - The ID of the branch if it exists, or false if it does not. */ function api_tree_branch_exists(int $tree_id, int $parent, string $title) : mixed { $id = db_fetch_cell_prepared('SELECT id FROM graph_tree_items WHERE graph_tree_id = ? AND parent = ? AND title = ?', [$tree_id, $parent, $title]); return $id > 0 ? $id : false; } /** * Given a tree, parent branch, and a host_id, will check host on that branch * * @param int $tree_id The ID of the tree. * @param int $parent The ID of the parent item. * @param int $site_id The ID of the site. * * @return int|false The ID of the site if it exists, or false if it does not exist. */ function api_tree_site_exists(int $tree_id, int $parent, int $site_id) : int|false { $id = db_fetch_cell_prepared('SELECT id FROM graph_tree_items WHERE graph_tree_id = ? AND parent = ? AND site_id = ?', [$tree_id, $parent, $site_id]); return $id > 0 ? $id : false; } /** * Given a tree, parent branch, and a host_id, will check host on that branch * * @param int $tree_id The ID of the tree to check. * @param int $parent The ID of the parent under which to check for the host. * @param int $host_id The ID of the host to check for. * * @return int|false The ID of the host if it exists, or false if it does not. */ function api_tree_host_exists(int $tree_id, int $parent, int $host_id) : int|false { $id = db_fetch_cell_prepared('SELECT id FROM graph_tree_items WHERE graph_tree_id = ? AND parent = ? AND host_id = ?', [$tree_id, $parent, $host_id]); return $id > 0 ? $id : false; } /** * Given a tree, parent branch, and a local_graph_id, will check graph on that branch * * @param int $tree_id The ID of the tree to check. * @param int $parent The parent ID within the tree. * @param int $local_graph_id The local graph ID to check for. * * @return int|false The ID of the graph if it exists, or false if it does not. */ function api_tree_graph_exists(int $tree_id, int $parent, int $local_graph_id) : int|false { $id = db_fetch_cell_prepared('SELECT id FROM graph_tree_items WHERE graph_tree_id = ? AND parent = ? AND local_graph_id = ?', [$tree_id, $parent, $local_graph_id]); return $id > 0 ? $id : false; } /** * Given a tree and a branch/leaf, delete the node and it's content * * @param int $tree_id The ID of the tree from which the node will be deleted. * @param int|string $node_id The ID of the node to be deleted. * * @return void */ function api_tree_delete_node(int $tree_id, int|string $node_id) : void { input_validate_input_number($tree_id, 'tree_id'); // Basic Error Checking - reject an out-of-range TreeID before is_tree_allowed(), // which caches into $_SESSION and hits the database. if ($tree_id <= 0) { cacti_log("ERROR: Invalid TreeID: '$tree_id', Function delete_node", false); return; } if (!is_tree_allowed($tree_id)) { cacti_log("SECURITY: User is not permitted to modify TreeID:'$tree_id', Function delete_node", false, 'AUTH'); return; } if (empty($node_id)) { cacti_log("ERROR: Invalid NodeID: '$node_id', Function delete_node", false); return; } $data = api_tree_parse_node_data($node_id); if (isset($data['leaf_id']) && $data['leaf_id'] > 0) { if ($data['host'] == 0 && $data['graph'] == 0 && $data['site'] == 0) { api_tree_delete_node_content($tree_id, $data['leaf_id']); } db_execute_prepared('DELETE FROM graph_tree_items WHERE graph_tree_id = ? AND id = ?', [$tree_id, $data['leaf_id']]); /** * Save the last time a tree branch was created/updated * for Caching. */ set_config_option('time_last_change_branch', time()); } } /** * Given a tree and a branch/leaf, recursively remove all elements * * @param int $tree_id The ID of the tree from which the node content is to be deleted. * @param int $leaf_id The ID of the node whose content is to be deleted. * * @return void */ function api_tree_delete_node_content(int $tree_id, int $leaf_id) : void { $children = db_fetch_assoc_prepared('SELECT * FROM graph_tree_items WHERE graph_tree_id = ? AND parent = ?', [$tree_id, $leaf_id]); if (cacti_sizeof($children)) { foreach ($children as $child) { if ($child['host_id'] == 0 && $child['local_graph_id'] == 0) { api_tree_delete_node_content($tree_id, $child['id']); } db_execute_prepared('DELETE FROM graph_tree_items WHERE graph_tree_id = ? AND id = ?', [$tree_id, $child['id']]); /** * Save the last time a tree branch was created/updated * for Caching. */ set_config_option('time_last_change_branch', time()); } } } /** * Moves a node to a new parent and position within a tree. * * @param int $tree_id The ID of the tree. * @param int|string $node_id The ID of the node to move. * @param int|string $new_parent The ID of the new parent node or '#' for the root. * @param int $new_position The new position of the node within the new parent. * * @return void */ function api_tree_move_node(int $tree_id, int|string $node_id, int|string $new_parent, int $new_position) : void { input_validate_input_number($tree_id, 'tree_id'); input_validate_input_number($new_position, 'new_position'); $new_position++; // Basic Error Checking if (empty($tree_id) || $tree_id < 0) { cacti_log("ERROR: Invalid TreeID: '$tree_id', Function delete_node", false); return; } if (empty($node_id)) { cacti_log("ERROR: Invalid NodeID: '$node_id', Function move_node", false); return; } if (empty($new_parent)) { cacti_log("ERROR: Invalid Parent Node '$new_parent' for NodeID: '$node_id', Function move_node", false); return; } if ($new_parent == '#') { $pdata['leaf_id'] = 0; } else { $pdata = api_tree_parse_node_data($new_parent); } $data = api_tree_parse_node_data($node_id); if ($data['parent'] != $pdata['leaf_id']) { db_execute_prepared('UPDATE graph_tree_items SET parent = ?, position = ? WHERE id = ? AND graph_tree_id = ?', [$pdata['leaf_id'], $new_position, $data['leaf_id'], $tree_id]); $others = db_fetch_assoc_prepared('SELECT id FROM graph_tree_items WHERE parent = ? AND id != ? AND position >= ?', [$pdata['leaf_id'], $data['leaf_id'], $new_position]); $position = $new_position + 1; if (cacti_sizeof($others)) { foreach ($others as $other) { db_execute_prepared('UPDATE graph_tree_items SET position = ? WHERE id = ?', [$position, $other['id']]); $position++; } } api_tree_sort_branch($data['leaf_id'], $tree_id); } elseif (isset($data['leaf_id']) && $data['leaf_id'] > 0 && isset($pdata['leaf_id']) && $pdata['leaf_id'] >= 0) { db_execute_prepared('UPDATE graph_tree_items SET position = ? WHERE graph_tree_id = ? AND id = ?', [$new_position, $tree_id, $data['leaf_id']]); $others = db_fetch_assoc_prepared('SELECT id FROM graph_tree_items WHERE parent = ? AND id != ? AND position >= ?', [$pdata['leaf_id'], $data['leaf_id'], $new_position]); $position = $new_position + 1; if (cacti_sizeof($others)) { foreach ($others as $other) { db_execute_prepared('UPDATE graph_tree_items SET position = ? WHERE id = ?', [$position, $other['id']]); $position++; } } /** * Save the last time a tree branch was created/updated * for Caching. */ set_config_option('time_last_change_branch', time()); api_tree_sort_branch($data['leaf_id'], $tree_id); } else { cacti_log('Invalid Source Destination Branches, Function move_node', false); } return; } /** * Given the node information parse into a branch, parent, host, graph array * * @param string $variable The variable containing the node data to be parsed. If the variable is '#', it is treated as a special case. * * @return array An associative array containing the parsed node data */ function api_tree_parse_node_data(string $variable) : array { // Initialize some variables $leaf_id = 0; $graph_id = 0; $host_id = 0; $site_id = 0; if ($variable != '#') { // Process the 'id' variable $ndata = explode('_', $variable); if (cacti_sizeof($ndata)) { foreach ($ndata as $data) { [$type, $tid] = explode(':', $data); // watch out for monkey business input_validate_input_number($tid, 'tid'); switch ($type) { case 'tbranch': $leaf_id = $tid; break; case 'tgraph': $graph_id = $tid; break; case 'thost': $host_id = $tid; break; case 'tsite': $site_id = $tid; break; } } } } if ($leaf_id > 0) { $parent = db_fetch_cell_prepared('SELECT parent FROM graph_tree_items WHERE id = ?', [$leaf_id]); } else { $parent = '0'; } return ['leaf_id' => $leaf_id, 'graph' => $graph_id, 'host' => $host_id, 'site' => $site_id, 'parent' => $parent]; } /** * Renames a node in a tree. * * @param int $tree_id The ID of the tree. * @param string|null $node_id The ID of the node to rename. Defaults to an empty string. * @param string $title The new title for the node. Defaults to an empty string. * * @return void */ function api_tree_rename_node(int $tree_id, string|null $node_id = '', string $title = '') : void { input_validate_input_number($tree_id, 'tree_id'); // Basic Error Checking - reject an out-of-range TreeID before is_tree_allowed(), // which caches into $_SESSION and hits the database. if ($tree_id <= 0) { cacti_log("ERROR: Invalid TreeID: '" . $tree_id . "', Function rename_node", false); header('Content-Type: application/json; charset=utf-8'); print json_encode(['id' => $node_id, 'result' => false]); return; } if (!is_tree_allowed($tree_id)) { cacti_log("SECURITY: User is not permitted to modify TreeID:'$tree_id', Function rename_node", false, 'AUTH'); return; } if (empty($node_id)) { cacti_log("ERROR: Invalid NodeID: '" . $node_id . "', Function rename_node", false); header('Content-Type: application/json; charset=utf-8'); print json_encode(['id' => $node_id, 'result' => 'false']); return; } // Initialize some variables $leaf_id = 0; $graph_id = 0; $host_id = 0; $site_id = 0; // Process the 'id' variable $ndata = explode('_', $node_id); if (cacti_sizeof($ndata)) { foreach ($ndata as $data) { if (!str_contains($data, ':')) { cacti_log("ERROR: Invalid NodeID: '" . $node_id . "', Function rename_node", false); header('Content-Type: application/json; charset=utf-8'); print json_encode(['id' => $node_id, 'result' => 'false']); return; } [$type, $tid] = explode(':', $data); // watch out for monkey business input_validate_input_number($tid, 'tid'); switch ($type) { case 'tbranch': $leaf_id = $tid; break; case 'tsite': $site_id = $tid; break; case 'tgraph': $graph_id = $tid; break; case 'thost': $host_id = $tid; break; } } } if ($leaf_id > 0) { if ($host_id > 0 || $graph_id > 0 || $site_id > 0) { // Ignore. Need to customize context } else { db_execute_prepared('UPDATE graph_tree_items SET title = ? WHERE graph_tree_id = ? AND id = ?', [$title, $tree_id, $leaf_id]); } } header('Content-Type: application/json; charset=utf-8'); print json_encode(['id' => $node_id, 'result' => 'true']); } /** * Given the tree and the parent node information return tree elements * * @param mixed $tree_id The ID of the tree to retrieve. Can be null. * @param int $parent The parent node ID. Defaults to 0. If -1, it indicates the root node. * * @return void */ function api_tree_get_main(mixed $tree_id, int $parent = 0) : void { $is_root = false; if ($parent == -1) { $parent = 0; $is_root = true; if ($tree_id > 0) { $name = db_fetch_cell_prepared('SELECT name FROM graph_tree WHERE id = ?', [$tree_id]); print "