<?php

use idoit\Component\FeatureManager\FeatureManager;
use idoit\Component\Helper\Ip;

/**
 * AJAX
 *
 * @package     i-doit
 * @subpackage  General
 * @author      Leonard Fischer <lfischer@i-doit.org>
 * @version     1.0
 * @copyright   synetics GmbH
 * @license     http://www.i-doit.com/license
 * @since       0.9.9-8
 */
class isys_ajax_handler_ip_addresses extends isys_ajax_handler
{
    /**
     * @var $m_event_manager isys_event_manager
     */
    private $m_event_manager;

    /**
     * Init method, which gets called from the framework.
     *
     * @author  Leonard Fischer <lfischer@i-doit.org>
     */
    public function init()
    {
        // We set the header information because we don't accept anything than JSON.
        header('Content-Type: application/json');

        if (!empty($_GET['method'])) {
            $this->m_event_manager = isys_event_manager::getInstance();

            switch ($_GET['method']) {
                case 'ping':
                    echo isys_format_json::encode($this->ping_ip(isys_format_json::decode($_POST['ip']), $_POST['net_obj']));
                    break;

                case 'r_nslookup':
                    echo isys_format_json::encode($this->reverse_nslookup(isys_format_json::decode($_POST['catg_ip_id']), $_POST['net_obj']));
                    break;

                case 'nslookup':
                    echo isys_format_json::encode($this->nslookup(isys_format_json::decode($_POST['catg_ip_id']), $_POST['net_obj']));
                    break;

                case 'update-ip-address':
                    echo isys_format_json::encode($this->update_ip_address($_POST['catg_ip_id'], $_POST['new_ip'], $_POST['net_obj_id']));
                    break;

                case 'update-hostname':
                    echo isys_format_json::encode($this->update_hostname($_POST['catg_ip_id'], $_POST['new_hostname'], $_POST['net_obj_id']));
                    break;

                case 'c':
                    echo isys_format_json::encode($this->connect_v4());
                    break;

                case 'd':
                    echo isys_format_json::encode($this->disconnect_v4());
                    break;

                case 'dv6':
                    echo isys_format_json::encode($this->disconnect_v6());
                    break;

                case 'show_ip_list':
                    // @see ID-10476 Re-set the content-type, because in this case we'll receive HTML.
                    header_remove('Content-Type: application/json');
                    header('Content-Type: text/html');
                    $l_net_info = $this->get_all_net_information();

                    if (!is_array($l_net_info['data']['hosts']) || count($l_net_info['data']['hosts']) === 0) {
                        // This will do the trick!
                        $l_net_info['data']['hosts'] = '{}';
                    }

                    if (!is_array($l_net_info['data']['non_addressed_hosts']) || count($l_net_info['data']['non_addressed_hosts']) === 0) {
                        // This will do the trick!
                        $l_net_info['data']['non_addressed_hosts'] = '{}';
                    }

                    $template = isys_application::instance()->container->get('template');
                    $database = isys_application::instance()->container->get('database');

                    $ipNetUI = new isys_cmdb_ui_category_s_net_ip_addresses($template);

                    $template
                        ->assign('has_edit_right', false)
                        ->assign('has_execute_right', isys_auth_cmdb::instance()->has_rights_in_obj_and_category(isys_auth::EXECUTE, $_POST['net_object'], 'C__CATS__NET_IP_ADDRESSES'))
                        ->assign('legend_scroller', 900)
                        ->assign('zones', $l_net_info['data']['zones'])
                        ->assign('address_range_from', $l_net_info['data']['address_range_from'])
                        ->assign('address_range_to', $l_net_info['data']['address_range_to'])
                        ->assign('address_default_gateway', $l_net_info['data']['address_default_gateway'])
                        ->assign('net_address', $l_net_info['data']['net_address'])
                        ->assign('net_subnet_mask', $l_net_info['data']['net_subnet_mask'])
                        ->assign('net_cidr_suffix', $l_net_info['data']['net_cidr_suffix'])
                        ->assign('obj_id', $_POST['net_object'])
                        ->assign('is_global_net', ($_POST['net_object'] == defined_or_default('C__OBJ__NET_GLOBAL_IPV4') || $_POST['net_object'] == defined_or_default('C__OBJ__NET_GLOBAL_IPV6')));

                    if ($_POST['net_type'] == defined_or_default('C__CATS_NET_TYPE__IPV4')) {
                        $l_ping_method = isys_tenantsettings::get('cmdb.ip-list.ping-method', 'nmap');

                        $ipNetUI->process_ipv4(isys_cmdb_dao_category_s_net_ip_addresses::instance($database), (int)$_POST['net_object']);

                        $template
                            ->assign('ping_available', !!system_which($l_ping_method))
                            ->assign('ping_method', strtoupper($l_ping_method))
                            ->assign('nslookup_available', !!system_which('nslookup'))
                            ->assign('net_cidr_suffix', $l_net_info['data']['net_cidr_suffix'])
                            ->display($this->m_smarty_dir . 'templates/content/bottom/content/cats__net_ipv4_addresses.tpl');
                    } elseif ($_POST['net_type'] == defined_or_default('C__CATS_NET_TYPE__IPV6')) {
                        $ipNetUI->process_ipv6(isys_cmdb_dao_category_s_net_ip_addresses::instance($database), (int)$_POST['net_object']);
                        $template->display($this->m_smarty_dir . 'templates/content/bottom/content/cats__net_ipv6_addresses.tpl');
                    }

                    break;
            }

            $this->_die();
        }
    }

    /**
     * Method for connecting a new object to a layer3 net.
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.org>
     */
    public function connect_v4()
    {
        $l_ip_dao = isys_cmdb_dao_category_g_ip::instance($this->m_database_component);
        $l_net_dao = isys_cmdb_dao_category_s_net::instance($this->m_database_component);
        $l_zone_dao = isys_cmdb_dao_category_s_net_zone::instance($this->m_database_component);

        $l_zone = $l_zone_dao->get_zone_by_ip($_POST['ip'], $_POST[C__CMDB__GET__OBJECT]);

        // For the "primary" field. We set this to 1 if it's the first entry for this connected object.
        $l_primary = 1;
        if (count($l_ip_dao->get_data(null, $_POST[C__CMDB__GET__OBJECT . '2']))) {
            $l_primary = 0;
        }

        // For retrieving the DNS server and domain, we first need the net ID.
        $l_cats_net_id = $l_net_dao->get_data(null, $_POST[C__CMDB__GET__OBJECT])
            ->get_row_value('isys_cats_net_list__id');

        // Here we fetch the assigned DNS server from the layer3 net.
        $l_dns_server = $l_net_dao->get_assigned_dns_server($l_cats_net_id);

        // And here the assigned DNS domain.
        $l_dns_domain = [];
        $l_dns_domain_res = $l_net_dao->get_assigned_dns_domain(null, $l_cats_net_id);
        while ($l_dns_domain_row = $l_dns_domain_res->get_row()) {
            $l_dns_domain[] = $l_dns_domain_row['isys_net_dns_domain__id'];
        }

        // ID of the values from "isys_ip_assignment" - static, dhcp, dhcp-reserved.
        $l_assign = null;
        $l_ip_addresses_dao = new isys_cmdb_dao_category_s_net_ip_addresses($this->m_database_component);
        $l_ip_assignment = $l_ip_addresses_dao->get_ip_assignment_by_ip($_POST['ip'], $_POST[C__CMDB__GET__OBJECT]);

        // If our IP-address is empty, we can assume we've got a unnumbered entry.
        if (empty($_POST['ip'])) {
            $_POST['ip'] = '';
            $l_ip_assignment = defined_or_default('C__CATP__IP__ASSIGN__UNNUMBERED');
        }

        $l_ip_dao->create($_POST[C__CMDB__GET__OBJECT . '2'],    // The layer3 object ID.
            '', $l_ip_assignment, $_POST['ip'], $l_primary, null, $l_dns_server,                        // DNS server
            $l_dns_domain,                        // DNS domain
            1,                                    // We set the new connection "active".
            defined_or_default('C__CATS_NET_TYPE__IPV4'),                // IPv4 from isys_net_type, currently we don't .
            $_POST[C__CMDB__GET__OBJECT],        // The ID of the object, we want to connect to.
            '', C__RECORD_STATUS__NORMAL, null, null, null, null, $l_zone);

        $this->m_event_manager->triggerCMDBEvent(
            'C__LOGBOOK_EVENT__CATEGORY_CHANGED',
            $l_ip_dao->get_last_query(),
            $_POST[C__CMDB__GET__OBJECT],
            $l_ip_dao->get_objTypeID($_POST[C__CMDB__GET__OBJECT]),
            isys_application::instance()->container->get('language')
                ->get('LC__CMDB__CATS__NET_IP_ADDRESSES'),
            null,
            sprintf(isys_application::instance()->container->get('language')
                ->get('LC__CMDB__CATS__NET_IP_ADDRESSES__IP_ASSIGNED'), $_POST['ip'], $l_ip_dao->get_obj_name_by_id_as_string($_POST['objID2']))
        );

        $l_new_res = $l_net_dao->get_assigned_hosts($_POST[C__CMDB__GET__OBJECT]);

        while ($l_low = $l_new_res->get_row()) {
            $l_dns_domains = [];
            $l_dns_res = $l_ip_dao->get_assigned_dns_domain(null, $l_low['isys_catg_ip_list__id']);

            if (count($l_dns_res)) {
                while ($l_dns_row = $l_dns_res->get_row()) {
                    $l_dns_domains[] = $l_dns_row['isys_net_dns_domain__title'];
                }
            }

            if ($l_low['isys_cats_net_ip_addresses_list__title'] == '0.0.0.0' || $l_low['isys_cats_net_ip_addresses_list__title'] == '' ||
                $l_low['isys_cats_net_ip_addresses_list__title'] == 'D.H.C.P') {
                // As in the DAO - We only set the ID-key because we need a JSON Object and no array.
                $l_return['not_addressed_hosts']['id-' . $l_low['isys_catg_ip_list__id']] = [
                    'catg_ip_id'      => $l_low['isys_catg_ip_list__id'],
                    'list_id'         => $l_low['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                    'isys_obj__id'    => $l_low['isys_obj__id'],
                    'isys_obj__title' => $l_low['isys_obj__title'] . ' (' . isys_application::instance()->container->get('language')
                            ->get($l_low['isys_obj_type__title']) . ')',
                    'isys_obj__type'  => $l_low['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
                    'assignment__id'  => $l_low['isys_catg_ip_list__isys_ip_assignment__id'],
                    'hostname'        => $l_low['isys_catg_ip_list__hostname'],
                    'domain'          => $l_low['isys_catg_ip_list__domain'],
                    'domains'         => $l_dns_domains,
                    'zone'            => $l_low['isys_catg_ip_list__isys_obj__id__zone'] ?: null
                ];
            } else {
                $l_return['hosts'][$l_low['isys_cats_net_ip_addresses_list__title']][] = [
                    'catg_ip_id'      => $l_low['isys_catg_ip_list__id'],
                    'list_id'         => $l_low['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                    'isys_obj__id'    => $l_low['isys_obj__id'],
                    'isys_obj__title' => $l_low['isys_obj__title'] . ' (' . isys_application::instance()->container->get('language')
                            ->get($l_low['isys_obj_type__title']) . ')',
                    'isys_obj__type'  => $l_low['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
                    'assignment__id'  => $l_low['isys_catg_ip_list__isys_ip_assignment__id'],
                    'hostname'        => $l_low['isys_catg_ip_list__hostname'],
                    'domain'          => $l_low['isys_catg_ip_list__domain'],
                    'domains'         => $l_dns_domains,
                    'zone'            => $l_low['isys_catg_ip_list__isys_obj__id__zone'] ?: null
                ];
            }
        }

        $l_return['result'] = 'success';

        return $l_return;
    }

    /**
     * Method for disconnecting an object.
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.org>
     */
    public function disconnect_v4()
    {
        $l_return = [];

        $l_ip_list_dao = isys_cmdb_dao_category_s_net_ip_addresses::instance($this->m_database_component);
        $l_ip_dao = isys_cmdb_dao_category_g_ip::instance($this->m_database_component);
        $l_rel_dao = isys_cmdb_dao_category_g_relation::instance($this->m_database_component);

        $l_ip_list_row = $l_ip_list_dao->get_data($_POST[C__CMDB__GET__OBJECT])
            ->get_row();

        // We don't really "disconnect" the object, we just assign it to our GLOBAL layer3-net.
        $l_ip_list_dao->save(
            $l_ip_list_row['isys_cats_net_ip_addresses_list__id'],
            $l_ip_list_row['isys_cats_net_ip_addresses_list__title'],
            defined_or_default('C__OBJ__NET_GLOBAL_IPV4'),
            $l_ip_list_row['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
            C__RECORD_STATUS__NORMAL
        );

        // Update relation between client and net
        $l_rel_dao->handle_relation(
            $l_ip_list_row['isys_catg_ip_list__id'],
            "isys_catg_ip_list",
            defined_or_default('C__RELATION_TYPE__IP_ADDRESS'),
            $l_ip_list_row["isys_catg_ip_list__isys_catg_relation_list__id"],
            defined_or_default('C__OBJ__NET_GLOBAL_IPV4'),
            $l_ip_list_row['isys_catg_ip_list__isys_obj__id']
        );

        $this->m_event_manager->triggerCMDBEvent(
            'C__LOGBOOK_EVENT__CATEGORY_CHANGED',
            $l_ip_list_dao->get_last_query(),
            $_POST['objID2'],
            $l_ip_list_dao->get_objTypeID($_POST['objID2']),
            isys_application::instance()->container->get('language')
                ->get('LC__CMDB__CATS__NET_IP_ADDRESSES'),
            null,
            sprintf(
                isys_application::instance()->container->get('language')
                ->get('LC__CMDB__CATS__NET_IP_ADDRESSES__IP_RELEASED'),
                $l_ip_list_row['isys_cats_net_ip_addresses_list__title'],
                $l_ip_list_dao->get_obj_name_by_id_as_string($l_ip_list_row['isys_catg_ip_list__isys_obj__id'])
            )
        );

        $l_new_dao = new isys_cmdb_dao_category_s_net($this->m_database_component);
        $l_new_res = $l_new_dao->get_assigned_hosts($_POST[C__CMDB__GET__OBJECT . '2']);

        while ($l_low = $l_new_res->get_row()) {
            $l_dns_domains = [];
            $l_dns_res = $l_ip_dao->get_assigned_dns_domain(null, $l_low['isys_catg_ip_list__id']);

            if (count($l_dns_res)) {
                while ($l_dns_row = $l_dns_res->get_row()) {
                    $l_dns_domains[] = $l_dns_row['isys_net_dns_domain__title'];
                }
            }

            if ($l_low['isys_cats_net_ip_addresses_list__title'] == '0.0.0.0' || $l_low['isys_cats_net_ip_addresses_list__title'] == '' ||
                $l_low['isys_cats_net_ip_addresses_list__title'] == 'D.H.C.P') {
                // As in the DAO - We only set the ID-key because we need a JSON Object and no array.
                $l_return['not_addressed_hosts']['id-' . $l_low['isys_catg_ip_list__id']] = [
                    'catg_ip_id'      => $l_low['isys_catg_ip_list__id'],
                    'list_id'         => $l_low['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                    'isys_obj__id'    => $l_low['isys_obj__id'],
                    'isys_obj__title' => $l_low['isys_obj__title'] . ' (' . isys_application::instance()->container->get('language')
                            ->get($l_low['isys_obj_type__title']) . ')',
                    'isys_obj__type'  => $l_low['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
                    'assignment__id'  => $l_low['isys_catg_ip_list__isys_ip_assignment__id'],
                    'hostname'        => $l_low['isys_catg_ip_list__hostname'],
                    'domain'          => $l_low['isys_catg_ip_list__domain'],
                    'domains'         => $l_dns_domains
                ];
            } else {
                $l_return['hosts'][$l_low['isys_cats_net_ip_addresses_list__title']][] = [
                    'catg_ip_id'      => $l_low['isys_catg_ip_list__id'],
                    'list_id'         => $l_low['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                    'isys_obj__id'    => $l_low['isys_obj__id'],
                    'isys_obj__title' => $l_low['isys_obj__title'] . ' (' . isys_application::instance()->container->get('language')
                            ->get($l_low['isys_obj_type__title']) . ')',
                    'isys_obj__type'  => $l_low['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
                    'assignment__id'  => $l_low['isys_catg_ip_list__isys_ip_assignment__id'],
                    'hostname'        => $l_low['isys_catg_ip_list__hostname'],
                    'domain'          => $l_low['isys_catg_ip_list__domain'],
                    'domains'         => $l_dns_domains
                ];
            }
        }

        $l_return['result'] = 'success';

        return $l_return;
    }

    /**
     * Method for disconnecting an object from a IPv6 list.
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.org>
     */
    public function disconnect_v6()
    {
        $l_return = [];

        /**
         * Typehinting
         *
         * @var $l_ip_list_dao isys_cmdb_dao_category_s_net_ip_addresses
         * @var $l_rel_dao     isys_cmdb_dao_category_g_relation
         */
        $l_ip_list_dao = isys_cmdb_dao_category_s_net_ip_addresses::instance($this->m_database_component);
        $l_rel_dao = isys_cmdb_dao_category_g_relation::instance($this->m_database_component);
        $l_ip_list_row = $l_ip_list_dao->get_data($_POST[C__CMDB__GET__OBJECT])
            ->get_row();

        // We don't really "disconnect" the object, we just assign it to our GLOBAL layer3-net.
        $l_ip_list_dao->save(
            $l_ip_list_row['isys_cats_net_ip_addresses_list__id'],
            $l_ip_list_row['isys_cats_net_ip_addresses_list__title'],
            defined_or_default('C__OBJ__NET_GLOBAL_IPV6'),
            $l_ip_list_row['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
            C__RECORD_STATUS__NORMAL
        );

        // Update relation between client and net
        $l_rel_dao->handle_relation(
            $l_ip_list_row['isys_catg_ip_list__id'],
            "isys_catg_ip_list",
            defined_or_default('C__RELATION_TYPE__IP_ADDRESS'),
            $l_ip_list_row["isys_catg_ip_list__isys_catg_relation_list__id"],
            defined_or_default('C__OBJ__NET_GLOBAL_IPV6'),
            $l_ip_list_row['isys_catg_ip_list__isys_obj__id']
        );

        $this->m_event_manager->triggerCMDBEvent(
            'C__LOGBOOK_EVENT__CATEGORY_CHANGED',
            $l_ip_list_dao->get_last_query(),
            $_POST['objID2'],
            $l_ip_list_dao->get_objTypeID($_POST['objID2']),
            isys_application::instance()->container->get('language')
                ->get('LC__CMDB__CATS__NET_IP_ADDRESSES'),
            null,
            sprintf(
                isys_application::instance()->container->get('language')
                ->get('LC__CMDB__CATS__NET_IP_ADDRESSES__IP_RELEASED'),
                $l_ip_list_row['isys_cats_net_ip_addresses_list__title'],
                $l_ip_list_dao->get_obj_name_by_id_as_string($l_ip_list_row['isys_catg_ip_list__isys_obj__id'])
            )
        );

        $l_new_dao = new isys_cmdb_dao_category_s_net($this->m_database_component);
        $l_new_res = $l_new_dao->get_assigned_hosts($_POST[C__CMDB__GET__OBJECT . '2']);

        while ($l_low = $l_new_res->get_row()) {
            if (empty($l_low['isys_cats_net_ip_addresses_list__title'])) {
                // The key is just used, so that we get an JSON object, and no array.
                $l_return['not_addressed_hosts']['id-' . $l_low['isys_catg_ip_list__id']] = [
                    'list_id'         => $l_low['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                    'isys_obj__id'    => $l_low['isys_obj__id'],
                    'isys_obj__title' => $l_low['isys_obj__title'] . ' (' . isys_application::instance()->container->get('language')
                            ->get($l_low['isys_obj_type__title']) . ')',
                    'isys_obj__type'  => $l_low['isys_catg_ip_list__isys_ipv6_assignment__id']
                ];
            } else {
                $l_return['hosts'][Ip::validate_ipv6($l_low['isys_cats_net_ip_addresses_list__title'], true)][] = [
                    'list_id'         => $l_low['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                    'isys_obj__id'    => $l_low['isys_obj__id'],
                    'isys_obj__title' => $l_low['isys_obj__title'] . ' (' . isys_application::instance()->container->get('language')
                            ->get($l_low['isys_obj_type__title']) . ')',
                    'isys_obj__type'  => $l_low['isys_catg_ip_list__isys_ipv6_assignment__id']
                ];
            }
        }

        $l_return['result'] = 'success';

        return $l_return;
    }

    /**
     * Method for pinging a single IP or a IP range.
     *
     * @param   mixed   $p_ip
     * @param   integer $p_net_obj
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.com>
     */
    private function ping_ip($p_ip, $p_net_obj = null)
    {
        if (!FeatureManager::isFeatureActive('ip-ping')) {
            return [
                'success' => false,
                'data'    => [],
                'message' => isys_application::instance()->container->get('language')->get('LC__FEATUREMANAGEMENT__FEATURE_IS_NOT_ACTIVE')
            ];
        }

        /**
         * Prevent blocking session because of
         * long running command in shell.
         *
         * @see ID-6052
         */
        session_write_close();

        $l_return = [
            'success' => true,
            'data'    => [],
            'message' => null
        ];

        try {
            if (!is_array($p_ip)) {
                $l_first_ip = $p_ip;
                $l_last_ip = null;
                $l_ip_range = [$l_first_ip => false];
            } else {
                $l_first_ip = reset($p_ip);
                $l_last_ip = end($p_ip);

                for ($i = Ip::ip2long($l_first_ip);$i <= Ip::ip2long($l_last_ip);$i++) {
                    $l_ip_range[Ip::long2ip($i)] = false;
                }
            }

            switch ($l_ping_method = isys_tenantsettings::get('cmdb.ip-list.ping-method', 'nmap')) {
                default:
                case 'nmap':
                    $l_return['data'] = Ip::nmap_ping($l_first_ip, $l_last_ip) + $l_ip_range;
                    break;
                case 'fping':
                    $l_return['data'] = Ip::fping_ping($l_first_ip, $l_last_ip) + $l_ip_range;
                    break;
            }

            if ($p_net_obj !== null) {
                $l_cache = isys_caching::factory('catg_net_ip_addresses__' . $p_net_obj, isys_tenantsettings::get('cmdb.ip-list.cache-lifetime', isys_convert::DAY));

                // Refresh the "ping" cache.
                $l_cache->set('pings', array_merge($l_cache->get('pings', []), $l_return['data']))
                    ->save(true);
            }
        } catch (Exception $e) {
            $l_return['success'] = false;
            $l_return['message'] = $e->getMessage();
        }

        // Show success notification
        if ($l_return['success'] === true) {
            isys_notify::success(isys_application::instance()->container->get('language')->get('LC__CMDB__CATS__NET_IP_ADDRESSES__PING_EXECUTED_SUCCESSFULLY'));
        }

        return $l_return;
    }

    /**
     * Method for retrieving the correct IP address for the given host(s).
     *
     * @param   mixed   $p_catg_ip_id
     * @param   integer $p_net_obj
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.com>
     */
    private function nslookup($p_catg_ip_id, $p_net_obj = null)
    {
        if (!FeatureManager::isFeatureActive('ip-ping')) {
            return [
                'success' => false,
                'data'    => [],
                'message' => isys_application::instance()->container->get('language')->get('LC__FEATUREMANAGEMENT__FEATURE_IS_NOT_ACTIVE')
            ];
        }

        $l_return = [
            'success' => true,
            'data'    => [],
            'message' => null
        ];

        $l_dao = isys_cmdb_dao_category_g_ip::instance($this->m_database_component);
        $l_caching_ips = [];
        $l_net_dns_servers = [];

        if (!is_array($p_catg_ip_id)) {
            $p_catg_ip_id = [$p_catg_ip_id];
        }

        $p_catg_ip_id = array_unique(array_filter($p_catg_ip_id));

        if ($p_net_obj !== null) {
            $l_dns_res = $l_dao->get_ip_addresses_by_ids($l_dao->get_assigned_dns_server($p_net_obj));

            while ($l_dns_row = $l_dns_res->get_row()) {
                $l_net_dns_servers[] = $l_dns_row['isys_cats_net_ip_addresses_list__title'];
            }
        }

        foreach ($p_catg_ip_id as $l_catg_ip_id) {
            $l_dns_servers = $l_net_dns_servers;

            $l_iteration = [
                'success' => true,
                'data'    => null,
                'message' => null
            ];

            $l_ip_row = $l_dao->get_data($l_catg_ip_id)
                ->get_row();

            $l_hostname = $l_ip_row['isys_catg_ip_list__hostname'];
            $l_dns_domain = $l_ip_row['isys_catg_ip_list__domain'];

            if (!empty($l_dns_domain)) {
                $l_hostname .= '.' . $l_dns_domain;
            }

            $l_ip_res = $l_dao->get_ip_addresses_by_ids($l_dao->get_assigned_dns_server($l_catg_ip_id));

            while ($l_ip_address_row = $l_ip_res->get_row()) {
                $l_dns_servers[] = $l_ip_address_row['isys_cats_net_ip_addresses_list__title'];
            }

            try {
                $l_iteration['data'] = Ip::nslookup($l_hostname, $l_dns_servers);
            } catch (Exception $e) {
                $l_iteration['success'] = false;
                $l_iteration['message'] = $e->getMessage();
            }

            $l_caching_ips[$l_ip_row['isys_cats_net_ip_addresses_list__title']] = $l_iteration['data'];
            $l_return['data'][$l_ip_row['isys_cats_net_ip_addresses_list__title']] = $l_iteration;
        }

        if ($p_net_obj !== null) {
            $l_cache = isys_caching::factory('catg_net_ip_addresses__' . $p_net_obj, isys_tenantsettings::get('cmdb.ip-list.cache-lifetime', isys_convert::DAY));

            // Refresh the "ping" cache.
            $l_cache->set('nslookup_ips', array_merge($l_cache->get('nslookup_ips', []), $l_caching_ips))
                ->save(true);
        }

        return $l_return;
    }

    /**
     * Method for retrieving the correct hostname for the given host(s).
     *
     * @param   mixed   $p_catg_ip_id
     * @param   integer $p_net_obj
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.com>
     */
    private function reverse_nslookup($p_catg_ip_id, $p_net_obj = null)
    {
        if (!FeatureManager::isFeatureActive('ip-ping')) {
            return [
                'success' => false,
                'data'    => [],
                'message' => isys_application::instance()->container->get('language')->get('LC__FEATUREMANAGEMENT__FEATURE_IS_NOT_ACTIVE')
            ];
        }

        $l_return = [
            'success' => true,
            'data'    => [],
            'message' => null
        ];

        /* @var  isys_cmdb_dao_category_g_ip $l_dao */
        $l_dao = isys_cmdb_dao_category_g_ip::instance($this->m_database_component);
        $l_hostname_cache = [];

        if (!is_array($p_catg_ip_id)) {
            $p_catg_ip_id = [$p_catg_ip_id];
        }

        $p_catg_ip_id = array_unique(array_filter($p_catg_ip_id));

        foreach ($p_catg_ip_id as $l_catg_ip_id) {
            $l_iteration = [
                'success' => true,
                'data'    => null,
                'message' => null
            ];

            if (is_numeric($l_catg_ip_id)) {
                $l_ip_row = $l_dao->get_data($l_catg_ip_id)
                    ->get_row();
            } elseif (Ip::validate_ip($l_catg_ip_id)) {
                $l_ip_row['isys_cats_net_ip_addresses_list__title'] = $l_catg_ip_id;
            } else {
                continue;
            }

            try {
                $l_iteration['data'] = Ip::reverse_nslookup($l_ip_row['isys_cats_net_ip_addresses_list__title']);
            } catch (Exception $e) {
                $l_iteration['success'] = false;
                $l_iteration['message'] = $e->getMessage();
            }

            $l_hostname_cache[$l_ip_row['isys_cats_net_ip_addresses_list__title']] = $l_iteration['data'];

            $l_return['data'][$l_ip_row['isys_cats_net_ip_addresses_list__title']] = $l_iteration;
        }

        if ($p_net_obj !== null) {
            $l_cache = isys_caching::factory('catg_net_ip_addresses__' . $p_net_obj, isys_tenantsettings::get('cmdb.ip-list.cache-lifetime', isys_convert::DAY));

            // Refresh the "ping" cache.
            $l_cache->set('nslookup_hostnames', array_merge($l_cache->get('nslookup_hostnames', []), $l_hostname_cache))
                ->save(true);
        }

        return $l_return;
    }

    /**
     * Method for updating the given host address with a new IP.
     *
     * @param   integer $p_catg_ip_id
     * @param   string  $p_new_hostname
     * @param   integer $p_net_obj_id
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.com>
     */
    protected function update_hostname($p_catg_ip_id, $p_new_hostname, $p_net_obj_id = null)
    {
        $l_return = [
            'success' => true,
            'data'    => [],
            'message' => null
        ];

        $l_dao = new isys_cmdb_dao_category_g_ip($this->m_database_component);

        try {
            // Save the DNS separately.
            $l_dns = substr(strstr($p_new_hostname, '.'), 1);

            if (!empty($l_dns)) {
                $l_dao->attach_dns_domain_by_string($p_catg_ip_id, $l_dns);
            }

            $l_hostname = strstr($p_new_hostname, '.', true);

            $l_sql = 'UPDATE isys_catg_ip_list
				SET isys_catg_ip_list__hostname = ' . $l_dao->convert_sql_text($l_hostname) . '
				WHERE isys_catg_ip_list__id = ' . $l_dao->convert_sql_id($p_catg_ip_id) . ';';

            if (!empty($l_hostname) && $l_dao->update($l_sql) && $l_dao->apply_update()) {
                // Reload the ip list data.
                $l_data = $this->get_all_net_information($p_net_obj_id);

                $l_return['data']['hosts'] = $l_data['data']['hosts'];
            }
        } catch (Exception $e) {
            $l_return = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return $l_return;
    }

    /**
     * Method for updating the given host address with a new IP.
     *
     * @param   integer $p_catg_ip_id
     * @param   string  $p_new_ip
     * @param   integer $p_net_obj_id
     *
     * @return  array
     * @author  Leonard Fischer <lfischer@i-doit.com>
     */
    protected function update_ip_address($p_catg_ip_id, $p_new_ip, $p_net_obj_id = null)
    {
        $l_return = [
            'success' => true,
            'data'    => [],
            'message' => null
        ];

        $l_dao = new isys_cmdb_dao_category_g_ip($this->m_database_component);

        try {
            if ($l_dao->update_ip_address($p_catg_ip_id, $p_new_ip)) {
                // Reload the ip list data.
                $l_data = $this->get_all_net_information($p_net_obj_id);

                $l_return['data']['hosts'] = $l_data['data']['hosts'];
            }
        } catch (Exception $e) {
            $l_return = [
                'success' => false,
                'data'    => null,
                'message' => $e->getMessage()
            ];
        }

        return $l_return;
    }

    /**
     * @param null $p_net_obj_id
     *
     * @return array
     */
    private function get_all_net_information($p_net_obj_id = null)
    {
        $l_net_obj_id = $p_net_obj_id ?: $_POST['net_object'];
        $language = isys_application::instance()->container->get('language');

        $l_return = [];
        $l_dao_net = new isys_cmdb_dao_category_s_net($this->m_database_component);
        $l_hostaddress_dao = new isys_cmdb_dao_category_g_ip($this->m_database_component);
        $l_net_row = $l_dao_net->get_all_net_information_by_obj_id($l_net_obj_id);

        $l_address_default_gateway = 0;
        $l_address_ranges = [];

        // Get all Zones
        $l_zone_data = [];
        $l_zone_options_dao = isys_cmdb_dao_category_g_net_zone_options::instance(isys_application::instance()->database);

        $l_zone_res = $l_zone_options_dao->get_objects_by_catg_id(filter_defined_constants(['C__CATG__NET_ZONE', 'C__CATG__NET_ZONE_SCOPES', 'C__CATG__NET_ZONE_OPTIONS']));

        if ($l_zone_res->num_rows() > 0) {
            while ($l_zone_row = $l_zone_res->get_row()) {
                $l_zone_data[$l_zone_row['isys_obj__id']] = [
                    'id'    => $l_zone_row['isys_obj__id'],
                    'title' => $l_zone_row['isys_obj__title'],
                    'color' => $l_zone_options_dao->get_data(null, $l_zone_row['isys_obj__id'])
                        ->get_row_value('isys_catg_net_zone_options_list__color') ?: '#ffffff'
                ];
            }
        }

        // We also select all DHCP address-ranges, so that we can display them.
        $l_dhcp_dao = new isys_cmdb_dao_category_s_net_dhcp($this->m_database_component);
        $l_dhcp_res = $l_dhcp_dao->get_data(null, $l_net_obj_id, '', null, C__RECORD_STATUS__NORMAL);

        while ($l_dhcp_row = $l_dhcp_res->get_row()) {
            $l_address_ranges[] = [
                'from' => $l_dhcp_row['isys_cats_net_dhcp_list__range_from'],
                'to'   => $l_dhcp_row['isys_cats_net_dhcp_list__range_to'],
                'type' => $l_dhcp_row['isys_cats_net_dhcp_list__isys_net_dhcp_type__id']
            ];
        }

        $l_dhcp_ranges = $l_address_ranges;

        if (!empty($l_net_row['isys_cats_net_list__isys_catg_ip_list__id'])) {
            $l_address_default_gateway = isys_cmdb_dao_category_g_ip::instance($this->m_database_component)
                ->get_data($l_net_row['isys_cats_net_list__isys_catg_ip_list__id'])
                ->get_row_value('isys_cats_net_ip_addresses_list__title');
        }

        if ($l_net_row['isys_cats_net_list__isys_net_type__id'] == defined_or_default('C__CATS_NET_TYPE__IPV4')) {
            // ipv4
            $l_net_range = [
                'from'         => $l_net_row["isys_cats_net_list__address_range_from_long"],
                'from_address' => $l_net_row["isys_cats_net_list__address_range_from"],
                'to'           => $l_net_row["isys_cats_net_list__address_range_to_long"],
                'to_address'   => $l_net_row["isys_cats_net_list__address_range_to"],
            ];

            // Get the assigned hosts of our net.
            $l_hosts = [];
            $l_hosts_res = $l_dao_net->get_assigned_hosts(
                $l_net_row['isys_obj__id'],
                '',
                C__RECORD_STATUS__NORMAL,
                'ORDER BY isys_cats_net_ip_addresses_list__ip_address_long ASC;'
            );
            $l_address_conflict = false;

            while ($l_hosts_row = $l_hosts_res->get_row()) {
                $l_dns_domains = [];
                $l_dns_res = $l_hostaddress_dao->get_assigned_dns_domain(null, $l_hosts_row['isys_catg_ip_list__id']);

                if (count($l_dns_res)) {
                    while ($l_dns_row = $l_dns_res->get_row()) {
                        $l_dns_domains[] = $l_dns_row['isys_net_dns_domain__title'];
                    }
                }

                if (!in_array($l_hosts_row['isys_cats_net_ip_addresses_list__title'], [
                    '',
                    '0.0.0.0',
                    'D.H.C.P'
                ])) {
                    $l_hosts[$l_hosts_row['isys_cats_net_ip_addresses_list__title']][] = [
                        'catg_ip_id'      => $l_hosts_row['isys_catg_ip_list__id'],
                        'list_id'         => $l_hosts_row['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                        'isys_obj__id'    => $l_hosts_row['isys_obj__id'],
                        'isys_obj__title' => $l_hosts_row['isys_obj__title'] . ' (' . $language->get($l_hosts_row['isys_obj_type__title']) . ')',
                        'isys_obj__type'  => $l_hosts_row['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
                        'assignment__id'  => $l_hosts_row['isys_catg_ip_list__isys_ip_assignment__id'],
                        'hostname'        => $l_hosts_row['isys_catg_ip_list__hostname'],
                        'domain'          => $l_hosts_row['isys_catg_ip_list__domain'],
                        'domains'         => $l_dns_domains
                    ];
                }
            }

            // When the array is empty, we can't give an empty JSON array to the template because that will break the $H() object.
            if (count($l_hosts) <= 0) {
                // This will do the trick!
                $l_hosts = [];
            }
        } else {
            // ipv6

            $l_net_range = [
                'from_address' => $l_net_row["isys_cats_net_list__address_range_from"],
                'to_address'   => $l_net_row["isys_cats_net_list__address_range_to"]
            ];

            // Get the assigned hosts of our net.
            $l_hosts = [];
            $l_hosts_res = $l_dao_net->get_assigned_hosts($l_net_row['isys_obj__id']);

            if (is_object($l_hosts_res)) {
                while ($l_hosts_row = $l_hosts_res->get_row()) {
                    $l_dns_domains = [];
                    $l_dns_res = $l_hostaddress_dao->get_assigned_dns_domain(null, $l_hosts_row['isys_catg_ip_list__id']);

                    if (count($l_dns_res)) {
                        while ($l_dns_row = $l_dns_res->get_row()) {
                            $l_dns_domains[] = $l_dns_row['isys_net_dns_domain__title'];
                        }
                    }

                    // Maybe we should check for more than just "empty".
                    if (!empty($l_hosts_row['isys_cats_net_ip_addresses_list__title'])) {
                        $l_hosts[Ip::validate_ipv6($l_hosts_row['isys_cats_net_ip_addresses_list__title'], true)][] = [
                            'catg_ip_id'      => $l_hosts_row['isys_catg_ip_list__id'],
                            'list_id'         => $l_hosts_row['isys_catg_ip_list__isys_cats_net_ip_addresses_list__id'],
                            'isys_obj__id'    => $l_hosts_row['isys_obj__id'],
                            'isys_obj__title' => $l_hosts_row['isys_obj__title'] . ' (' . $language->get($l_hosts_row['isys_obj_type__title']) . ')',
                            'isys_obj__type'  => $l_hosts_row['isys_cats_net_ip_addresses_list__isys_ip_assignment__id'],
                            'assignment__id'  => $l_hosts_row['isys_catg_ip_list__isys_ipv6_assignment__id'],
                            'hostname'        => $l_hosts_row['isys_catg_ip_list__hostname'],
                            'domain'          => $l_hosts_row['isys_catg_ip_list__domain'],
                            'domains'         => $l_dns_domains
                        ];
                    }
                }
            }

            // When the array is empty, we can't give an empty JSON array to the template because that will break the $H() object.
            if (count($l_hosts) <= 0) {
                // This will do the trick!
                $l_hosts = [];
            }
        }

        $l_return['data'] = [
            'address_default_gateway' => $l_address_default_gateway,
            'net_address'             => $l_net_row['isys_cats_net_list__address'],
            'net_subnet_mask'         => $l_net_row['isys_cats_net_list__mask'],
            'net_cidr_suffix'         => $l_net_row['isys_cats_net_list__cidr_suffix'],
            'address_range_from'      => $l_net_range['from_address'],
            'address_range_to'        => $l_net_range['to_address'],
            'zones'                   => $l_zone_data,
            'hosts'                   => $l_hosts
        ];

        return $l_return;
    }
}
