Modules/devices/devices edit inc php: различия между версиями

Материал из MajorDoMo инфо
(+ devices_edit.inc.php)
 
м (Elmax переименовал страницу Devices edit inc php в Modules/devices/devices edit inc php: Пропустил при создании путь Modules/devices/)
 
(нет различий)

Текущая версия от 12:31, 2 апреля 2024


ᐂ В корневой раздел ᐃ В директорию расположения файла

<?php

/**
 * @version 0.1 (wizard)
 */
// Проверяет, является ли текущий владелец панелью, и устанавливает флаг панели управления.
if ($this->owner->name == 'panel') {
    $out['CONTROLPANEL'] = 1;
}
// Определяет имя таблицы для устройств и получает запись устройства по ID или по связанному объекту.
$table_name = 'devices';
$rec = SQLSelectOne("SELECT * FROM $table_name WHERE ID='$id'");
if (!$id && gr('linked_object')) {
    $rec = SQLSelectOne("SELECT * FROM $table_name WHERE LINKED_OBJECT='" . DBSafe(gr('linked_object')) . "'");
}
// Устанавливает флаг отсутствия навигации, если владелец имеет флаг печати.
if ($this->owner->print == 1) {
    $out['NO_NAV'] = 1;
}

// Выполняет дополнительные действия, если связанный объект устройства не пуст.
if (isset($rec['LINKED_OBJECT']) && $rec['LINKED_OBJECT'] != '') {
    // Получаем запись объекта по названию связанного объекта
    $object_rec = SQLSelectOne("SELECT ID FROM objects WHERE TITLE='" . $rec['LINKED_OBJECT'] . "'");
    // Если ID объекта существует, выполняем дополнительные действия
    if ($object_rec['ID']) {
        // Получаем свойства объекта, связанные с модулями
        $properties = SQLSelect("SELECT pvalues.*, properties.TITLE as PROPERTY FROM pvalues LEFT JOIN properties ON properties.ID=pvalues.PROPERTY_ID WHERE pvalues.OBJECT_ID=" . $object_rec['ID'] . " AND pvalues.LINKED_MODULES!='' ORDER BY UPDATED DESC");
        // Если свойства найдены, выполняем дополнительные действия
        $total = count($properties);
        if ($total > 0) {
            for ($i = 0; $i < $total; $i++) {
                // Разделяем связанные модули по запятым
                $linked_modules = explode(',', $properties[$i]['LINKED_MODULES']);
                // Экранируем специальные символы в значении свойства
                $properties[$i]['VALUE'] = htmlspecialchars($properties[$i]['VALUE']);
                // Создаем массив связанных модулей
                $properties[$i]['LINKED_MODULES'] = array();
                foreach ($linked_modules as $module) {
                    // Добавляем каждый модуль в массив связанных модулей
                    $properties[$i]['LINKED_MODULES'][] = array('MODULE' => $module, 'PROPERTY' => $properties[$i]['PROPERTY'], 'OBJECT' => $rec['LINKED_OBJECT']);
                }
                // Добавляем свойства в выходной массив
                $out['LINKED_PROPERTIES'] = $properties;
            }
        }
    }
}

// Инициализирует массив для отображения методов и получает все методы для указанного типа устройства.
$show_methods = array();
if ($rec['TYPE'] != '') {
    $methods = $this->getAllMethods($rec['TYPE']);
    // Если методы представлены в виде массива, выполняем дополнительные действия
    if (is_array($methods)) {
        foreach ($methods as $k => $v) {
            // Если метод должен быть показан в конфигурации, добавляем его в массив для отображения
            if (isset($v['_CONFIG_SHOW']) && $v['_CONFIG_SHOW']) {
                $v['NAME'] = $k;
                $show_methods[] = $v;
            }
        }
    }
}
// Если есть методы для отображения, сортирует их и добавляет в выходной массив.
if (isset($show_methods[0])) {
    usort($show_methods, function ($a, $b) {
        if ($a['_CONFIG_SHOW'] == $b['_CONFIG_SHOW']) {
            return 0;
        }
        return ($a['_CONFIG_SHOW'] > $b['_CONFIG_SHOW']) ? -1 : 1;
    });
    $out['SHOW_METHODS'] = $show_methods;
}

// Добавляет сообщения об успехе или ошибке в выходной массив.
if (gr('ok_msg')) {
    $out['OK_MSG'] = gr('ok_msg');
}
if (gr('err_msg')) {
    $out['ERR_MSG'] = gr('err_msg');
}


// Выполняет дополнительные действия, если текущая вкладка - 'logic'.
if ($this->tab == 'logic') {


    // Получаем имя метода из запроса или устанавливаем его как 'logicAction', если не указано
    $method_name = gr('method');

    // Получаем объект по связанному объекту устройства
    $object = getObject($rec['LINKED_OBJECT']);

    // Получаем все методы родительского класса объекта
    $methods = $object->getParentMethods($object->class_id, '', 1);
    // Если есть методы, выполняем дополнительные действия
    $total = count($methods);
    for ($i = 0; $i < $total; $i++) {
        // Если есть описание метода, добавляем его к названию метода
        if ($methods[$i]['DESCRIPTION'] != '') {
            $methods[$i]['DESCRIPTION'] = $methods[$i]['TITLE'] . ' - ' . $methods[$i]['DESCRIPTION'];
        } else {
            $methods[$i]['DESCRIPTION'] = $methods[$i]['TITLE'];
        }
        // Если связанный объект устройства существует, выполняем дополнительные действия
        if (isset($object_rec['ID'])) {
            // Получаем метод объекта по названию метода и ID объекта
            $object_method = SQLSelectOne("SELECT * FROM methods WHERE TITLE='" . $methods[$i]['TITLE'] . "' AND OBJECT_ID=" . $object_rec['ID'] . " ORDER BY TITLE");
            // Если метод объекта существует, выполняем дополнительные действия
            if (isset($object_method['ID'])) {
                // Добавляем символ '(*)' к описанию метода
                $methods[$i]['DESCRIPTION'] .= ' (*)';
                // Если имя метода не установлено, устанавливаем его как название метода объекта
                if (!$method_name) {
                    $method_name = $object_method['TITLE'];
                }
            }
        }
    }

    // Если имя метода не установлено, устанавливаем его как 'logicAction'
    if (!$method_name) {
        $method_name = 'logicAction';
    }

    $out['METHOD'] = $method_name;
    // Добавляем методы в выходной массив
    $out['METHODS'] = $methods;

    // Получаем ID метода по имени метода, ID класса объекта и ID объекта
    $method_id = $object->getMethodByName($method_name, $object->class_id, $object->id);

    // Получаем запись метода по ID
    $method_rec = SQLSelectOne("SELECT * FROM methods WHERE ID=" . (int)$method_id);

    // Если ID объекта метода не совпадает с ID объекта устройства, создаем новую запись метода
    if ($method_rec['OBJECT_ID'] != $object->id) {
        $method_rec['CODE'] = '';
    }

    // Если определены настройки редактора кода, добавляем их в выходной массив
    if (defined('SETTINGS_CODEEDITOR_TURNONSETTINGS')) {
        $out['SETTINGS_CODEEDITOR_TURNONSETTINGS'] = SETTINGS_CODEEDITOR_TURNONSETTINGS;
        $out['SETTINGS_CODEEDITOR_UPTOLINE'] = SETTINGS_CODEEDITOR_UPTOLINE;
        $out['SETTINGS_CODEEDITOR_SHOWERROR'] = SETTINGS_CODEEDITOR_SHOWERROR;
    }

    // Если режим работы - 'update', выполняем дополнительные действия
    if ($this->mode == 'update') {

        if ($method_rec['OBJECT_ID'] != $object->id) {
            $method_rec = array();
            $method_rec['OBJECT_ID'] = $object->id;
            $method_rec['TITLE'] = $method_name;
            $method_rec['CALL_PARENT'] = 1;
            $method_rec['ID'] = SQLInsert('methods', $method_rec);
        }

        $code = gr('code');

        // Сохраняем старый код метода
        $old_code = $method_rec['CODE'];
        // Обновляем код метода новым значением
        $method_rec['CODE'] = $code;

        $ok = 1;
        // Если код метода не пуст, проверяем его на наличие ошибок
        if ($method_rec['CODE'] != '') {
            $errors = php_syntax_error($method_rec['CODE']);

            // Если есть ошибки, обрабатываем их и устанавливаем флаг ошибки
            if ($errors) {
                $out['ERR_LINE'] = preg_replace('/[^0-9]/', '', substr(stristr($errors, 'php on line '), 0, 18)) - 2;
                $out['ERR_CODE'] = 1;
                $errorStr = explode('Parse error: ', htmlspecialchars(strip_tags(nl2br($errors))));
                $errorStr = explode('Errors parsing', $errorStr[1]);
                $errorStr = explode(' in ', $errorStr[0]);
                //var_dump($errorStr);
                $out['ERRORS'] = $errorStr[0];
                $out['ERR_FULL'] = $errorStr[0] . ' ' . $errorStr[1];
                $out['ERR_OLD_CODE'] = $old_code;
                $ok = 0;
            }
        } else {
            if ($method_rec['ID']) {
                SQLExec("DELETE FROM methods WHERE ID=" . $method_rec['ID']);
            }
            $this->redirect("?id=" . $rec['ID'] . "&view_mode=" . $this->view_mode . "&tab=" . $this->tab . "&method=" . urlencode($method_rec['TITLE']));
        }
        // Если нет ошибок, обновляем запись метода в базе данных
        if ($ok) {
            SQLUpdate('methods', $method_rec);
            $out['OK'] = 1;
        } else {
            $out['ERR'] = 1;
        }
    }
    // Добавляем код метода в выходной массив
    if (isset($method_rec['CODE'])) {
        $out['CODE'] = htmlspecialchars($method_rec['CODE']);
    }
    // Добавляем ID объекта в выходной массив
    $out['OBJECT_ID'] = $method_rec['OBJECT_ID'];

    // Получаем ID родительского метода по имени метода, ID класса объекта и ID объекта
    $parent_method_id = $object->getMethodByName($method_name, $object->class_id, 0);
    // Если есть ID родительского метода, добавляем его в выходной массив
    if ($parent_method_id) {
        $out['METHOD_ID'] = $parent_method_id;
    } else {
        // Иначе добавляем ID текущего метода в выходной массив
        $out['METHOD_ID'] = $method_rec['ID'];
    }
}

// Если текущая вкладка - 'settings', выполняем дополнительные действия
if ($this->tab == 'settings') {
    // Получаем все свойства для типа устройства
    $properties = $this->getAllProperties($rec['TYPE']);
    //print_r($properties);exit; //debug
    // Если связанный объект устройства существует и свойства представлены в виде массива, выполняем дополнительные действия
    if ($rec['LINKED_OBJECT'] && is_array($properties)) {
        $res_properties = array();
        $onchanges = array();
        $apply_others = gr('apply_others');
        foreach ($properties as $k => $v) {
            // Если тип конфигурации свойства не пуст, выполняем дополнительные действия
            if (isset($v['_CONFIG_TYPE'])) {
                // Если режим работы - 'update', выполняем дополнительные действия
                if ($this->mode == 'update') {
                    global ${$k . '_value'};
                    // Если значение свойства установлено, выполняем дополнительные действия
                    if (isset(${$k . '_value'})) {
                        // Если значение свойства - массив, преобразуем его в строку
                        if (is_array(${$k . '_value'})) {
                            $value = implode(',', ${$k . '_value'});
                        } else {
                            // Иначе очищаем пробелы в начале и конце строки
                            $value = trim(${$k . '_value'});
                        }
                        // Устанавливаем глобальное значение свойства
                        setGlobal($rec['LINKED_OBJECT'] . '.' . $k, $value);
                        // Если есть другие устройства для применения, выполняем дополнительные действия
                        if (is_array($apply_others)) {
                            foreach ($apply_others as $other_dev) {
                                // Устанавливаем глобальное значение свойства для других устройств
                                setGlobal($other_dev . '.' . $k, $value);
                                // Если есть ограничения доступа для свойства, выполняем дополнительные действия
                                if ($v['_CONFIG_RESTRICTIONS'] && checkAccessDefined('prop_' . $k, $rec['ID'])) {
                                    $other_obj = getObject($other_dev);
                                    // Если объект существует и имеет ID устройства, копируем права доступа
                                    if (is_object($other_obj) && $other_obj->device_id) {
                                        checkAccessCopy('prop_' . $k, $rec['ID'], $other_obj->device_id);
                                    }
                                }
                            }
                        }
                    }
                    // Устанавливаем флаг успешного обновления
                    $out['OK'] = 1;
                    // Если есть метод, который должен быть вызван при изменении свойства, добавляем его в массив
                    if (isset($v['ONCHANGE']) && $v['ONCHANGE'] != '') {
                        $onchanges[$v['ONCHANGE']] = 1;
                    }
                }
                // Добавляем имя свойства в массив свойств
                $v['NAME'] = $k;
                // Если есть справочная информация для свойства, добавляем ее в массив свойств
                if (isset($v['_CONFIG_HELP'])) $v['CONFIG_HELP'] = $v['_CONFIG_HELP'];
                // Добавляем тип конфигурации в массив свойств
                $v['CONFIG_TYPE'] = $v['_CONFIG_TYPE'];
                // Получаем глобальное значение свойства
                $v['VALUE'] = getGlobal($rec['LINKED_OBJECT'] . '.' . $k);
                // Если тип конфигурации - 'select' или 'multi_select', выполняем дополнительные действия
                if ($v['CONFIG_TYPE'] == 'select' || $v['CONFIG_TYPE'] == 'multi_select') {
                    // Получаем выбранные опции из глобального значения свойства
                    $selected_options = explode(',', gg($rec['LINKED_OBJECT'] . '.' . $k));
                    // Разделяем опции по запятым
                    $tmp = explode(',', $v['_CONFIG_OPTIONS']);
                    $total = count($tmp);
                    for ($i = 0; $i < $total; $i++) {
                        // Разделяем каждую опцию на значение и название
                        $data_s = explode('=', trim($tmp[$i]));
                        $value = $data_s[0];
                        if (isset($data_s[1])) {
                            $title = $data_s[1];
                        } else {
                            $title = $value;
                        }
                        // Создаем массив опции
                        $option = array('VALUE' => $value, 'TITLE' => $title);
                        // Если значение опции выбрано, устанавливаем флаг выбранной опции
                        if (in_array($value, $selected_options)) $option['SELECTED'] = 1;
                        // Добавляем опцию в массив свойств
                        $v['OPTIONS'][] = $option;
                    }
                } elseif ($v['CONFIG_TYPE'] == 'style_image') {
                    // Если тип конфигурации - 'style_image', выполняем дополнительные действия
                    include_once(DIR_MODULES . 'scenes/scenes.class.php');
                    $scene_class = new scenes();
                    // Получаем все типы сцен
                    $styles = $scene_class->getAllTypes();
                    // Добавляем типы сцен в массив свойств
                    $v['FOLDERS'] = $styles;
                }

                // Если есть ограничения доступа для свойства и они определены, устанавливаем флаг установленных ограничений
                if (isset($v['_CONFIG_RESTRICTIONS']) && $v['_CONFIG_RESTRICTIONS'] && checkAccessDefined('prop_' . $v['NAME'], $rec['ID'])) {
                    $v['_CONFIG_RESTRICTIONS_SET'] = 1;
                }

                // Добавляем свойство в массив результатов
                $res_properties[] = $v;
            }
        }
        // Если режим работы - 'update', выполняем дополнительные действия
        if ($this->mode == 'update') {
            // Вызываем методы, которые должны быть вызваны при изменении свойства
            foreach ($onchanges as $k => $v) {
                callMethod($rec['LINKED_OBJECT'] . '.' . $k);
            }
            // Синхронизируем устройство с Homebridge
            $this->homebridgeSync($rec['ID'], 1);
        }
        //print_r($res_properties);exit; // debug
        // Добавляем свойства в выходной массив
        $out['PROPERTIES'] = $res_properties;
    }
    //print_r($res_properties);exit; // debug
    // Получаем все группы для типа устройства
    $groups = $this->getAllGroups($rec['TYPE']);

    // Инициализируем глобальную переменную для применения групп
    global $apply_groups;
    // Если режим работы - 'update', устанавливаем массив применения групп
    if ($this->mode == 'update') {
        if (!is_array($apply_groups)) {
            $apply_groups = array();
        }
    } else {
        $apply_groups = array();
    }

    // Получаем ID объекта
    $total = count($groups);

    $object_id = gg($rec['LINKED_OBJECT'] . '.object_id');

    // Если есть группы, выполняем дополнительные действия
    if ($total > 0) {
        for ($i = 0; $i < $total; $i++) {
            // Формируем название свойства группы
            $property_title = 'group' . $groups[$i]['SYS_NAME'];
            // Если режим работы - 'update', выполняем дополнительные действия
            if ($this->mode == 'update') {
                // Если группа выбрана, устанавливаем значение свойства группы
                if (in_array($groups[$i]['SYS_NAME'], $apply_groups)) {
                    sg($rec['LINKED_OBJECT'] . '.' . $property_title, 1);
                } elseif (gg($rec['LINKED_OBJECT'] . '.' . $property_title)) {
                    // Если группа не выбрана, сбрасываем значение свойства группы
                    sg($rec['LINKED_OBJECT'] . '.' . $property_title, 0);
                    // Получаем ID свойства группы
                    $property_id = current(SQLSelectOne("SELECT ID FROM properties WHERE OBJECT_ID=" . (int)$object_id . " AND TITLE='" . DBSafe($property_title) . "'"));
                    // Если ID свойства группы существует, удаляем свойство группы
                    if ($property_id) {
                        SQLExec("DELETE FROM pvalues WHERE PROPERTY_ID=" . $property_id . " AND OBJECT_ID=" . $object_id);
                        SQLExec("DELETE FROM properties WHERE ID=" . $property_id);
                    }
                    //echo $property_id;exit;
                }
            }
            // Если значение свойства группы установлено, устанавливаем флаг выбранной группы
            if (gg($rec['LINKED_OBJECT'] . '.' . $property_title)) {
                $groups[$i]['SELECTED'] = 1;
            }
        }
        // Добавляем группы в выходной массив
        $out['GROUPS'] = $groups;
    }
}

// Если текущая вкладка - 'interface', выполняем дополнительные действия
if ($this->tab == 'interface') {
    // Если режим работы - 'update', выполняем дополнительные действия
    if ($this->mode == 'update') {
        global $add_menu;
        global $add_menu_id;

        global $add_scene;
        global $add_scene_id;

        // Если не указана сцена, устанавливаем ID сцены в 0
        if (!$add_scene) {
            $add_scene_id = 0;
        }
        // Если не указан ID сцены, устанавливаем сцену в 0
        if (!$add_scene_id) {
            $add_scene = 0;
        }

        // Добавляем значения в выходной массив
        $out['ADD_MENU'] = $add_menu;
        $out['ADD_MENU_ID'] = $add_menu_id;
        $out['ADD_SCENE'] = $add_scene;
        $out['ADD_SCENE_ID'] = $add_scene_id;

        // Если указано добавление в меню, добавляем устройство в меню
        if ($out['ADD_MENU']) {
            $this->addDeviceToMenu($rec['ID'], $add_menu_id);
        }

        // Если указано добавление в сцену и ID сцены указан, добавляем устройство в сцену
        if ($out['ADD_SCENE'] && $out['ADD_SCENE_ID']) {
            $this->addDeviceToScene($rec['ID'], $add_scene_id);
        }

        // Устанавливаем флаг успешного обновления
        $out['OK'] = 1;
    }

    // Получаем все сцены
    $out['SCENES'] = SQLSelect("SELECT ID,TITLE FROM scenes ORDER BY TITLE");
    // Получаем все элементы меню
    $menu_items = SQLSelect("SELECT ID, TITLE FROM commands ORDER BY PARENT_ID,TITLE");
    // Инициализируем массив для элементов меню
    $res_items = array();
    // Получаем количество элементов меню
    $total = count($menu_items);
    for ($i = 0; $i < $total; $i++) {
        // Если у элемента меню есть подэлементы, добавляем его в массив
        $sub = SQLSelectOne("SELECT ID FROM commands WHERE PARENT_ID=" . $menu_items[$i]['ID']);
        if (isset($sub['ID'])) {
            $res_items[] = $menu_items[$i];
        }
    }
    // Добавляем элементы меню в выходной массив
    $out['MENU'] = $res_items;
}


// Если текущая вкладка пуста, выполняем дополнительные действия
if ($this->tab == '') {

    // Инициализируем массив для приоритетов
    for ($i = 1; $i < 100; $i++) {
        $out['PRIORITIES'][] = array('VALUE' => $i);
    }

    // Получаем переменные
    $prefix = gr('prefix');
    $out['PREFIX'] = $prefix;

    $source_table = gr('source_table');
    $out['SOURCE_TABLE'] = $source_table;

    $source_table_id = gr('source_table_id');
    $out['SOURCE_TABLE_ID'] = $source_table_id;

    $type = gr('type');
    $out['TYPE'] = $type;

    $linked_object = gr('linked_object');
    // Если связанный объект не пуст, выполняем дополнительные действия
    if ($linked_object != '') {
        // Если объект не существует, очищаем связанный объект
        if (!getObject($linked_object)) {
            $linked_object = '';
        }
    }
    // Добавляем связанный объект в выходной массив
    $out['LINKED_OBJECT'] = trim($linked_object);
    // Если связанный объект существует и ID устройства не установлен, выполняем дополнительные действия
    if ($out['LINKED_OBJECT'] && !$rec['ID']) {
        // Получаем запись устройства по связанному объекту
        $old_rec = SQLSelectOne("SELECT * FROM devices WHERE LINKED_OBJECT LIKE '" . DBSafe($out['LINKED_OBJECT']) . "'");
        // Если запись устройства существует, обновляем текущую запись устройства
        if ($old_rec['ID']) {
            $rec = $old_rec;
        }
    }
    // Получаем глобальную переменную для добавления заголовка
    global $add_title;
    // Если заголовок указан, добавляем его в выходной массив
    if ($add_title) {
        $out['TITLE'] = $add_title;
    }


    // Если указана таблица источника и ID устройства не установлен, выполняем дополнительные действия
    if ($out['SOURCE_TABLE'] && !$rec['ID']) {
        // Формируем запрос для выбора устройства        
        $qry_devices = 1;
        // Если тип устройства установлен, добавляем условие выборки по типу устройства
        if ($out['TYPE']) {
            $qry_devices .= " AND devices.TYPE='" . DBSafe($out['TYPE']) . "'";
        }
        // Выполняем запрос к базе данных для получения списка существующих устройств
        $existing_devices = SQLSelect("SELECT ID, TITLE FROM devices WHERE $qry_devices ORDER BY TITLE");
        // Если в результате запроса найдено хотя бы одно устройство, выполняем дополнительные действия
        if ($existing_devices[0]['ID']) {
            // Устанавливаем флаг наличия существующих устройств
            $out['SELECT_EXISTING'] = 1;
            // Добавляем список существующих устройств в выходной массив
            $out['EXISTING_DEVICES'] = $existing_devices;
        }
    }
}

// Если текущая вкладка - 'links', выполняем дополнительные действия
if ($this->tab == 'links') {
    // Включаем файл с логикой работы с ссылками устройств
    include_once(dirname(__FILE__) . '/devices_links.inc.php');
}

// Если текущая вкладка - 'schedule', выполняем дополнительные действия
if ($this->tab == 'schedule') {
    // Включаем файл с логикой работы с расписанием устройств
    include_once(dirname(__FILE__) . '/devices_schedule.inc.php');
}

// Если режим работы - 'update' и текущая вкладка пуста, выполняем дополнительные действия
if ($this->mode == 'update' && $this->tab == '') {
    $added = 0;
    $ok = 1;
    // Получаем название устройства из запроса и удаляем пробелы в начале и конце строки
    $rec['TITLE'] = gr('title', 'trim');
    // Если название устройства пустое, устанавливаем флаг ошибки
    if ($rec['TITLE'] == '') {
        $out['ERR_TITLE'] = 1;
        $ok = 0;
    }

    // Получаем альтернативные названия устройства из запроса и удаляем пробелы в начале и конце строки
    $rec['ALT_TITLES'] = gr('alt_titles', 'trim');

    // Получаем тип устройства из глобальной переменной
    $rec['TYPE'] = $type;
    // Если тип устройства пустой, устанавливаем флаг ошибки
    if ($rec['TYPE'] == '') {
        $out['ERR_TYPE'] = 1;
        $ok = 0;
    }

    // Получаем ID локации из глобальной переменной
    global $location_id;
    $rec['LOCATION_ID'] = (int)$location_id;

    // Если устройство отмечено как избранное, устанавливаем приоритет избранного
    if (gr('favorite', 'int')) {
        $rec['FAVORITE'] = gr('favorite_priority', 'int');
    } else {
        $rec['FAVORITE'] = 0;
    }

    // Устанавливаем флаг системного устройства
    $rec['SYSTEM_DEVICE'] = gr('system_device', 'int');
    // Устанавливаем флаг архивированного устройства
    $rec['ARCHIVED'] = gr('archived', 'int');


    // Получаем связанный объект устройства из глобальной переменной
    $rec['LINKED_OBJECT'] = $linked_object;
    // Если связанный объект устройства не пуст и ID устройства не установлен, выполняем дополнительные действия
    if ($rec['LINKED_OBJECT'] && !$rec['ID']) {
        // Проверяем, существует ли уже устройство с таким связанным объектом
        $other_device = SQLSelectOne("SELECT ID FROM devices WHERE LINKED_OBJECT LIKE '" . DBSafe($rec['LINKED_OBJECT']) . "'");
        // Если устройство с таким связанным объектом уже существует, устанавливаем флаг ошибки
        if ($other_device['ID']) {
            $out['ERR_LINKED_OBJECT'] = 1;
            $ok = 0;
        }
    }

    // Получаем флаг добавления нового объекта из глобальной переменной
    global $add_object;
    $out['ADD_OBJECT'] = $add_object;
    // Если флаг добавления нового объекта установлен, очищаем связанный объект устройства
    if ($add_object) {
        $rec['LINKED_OBJECT'] = '';
    }

    // Обновляем структуру устройств
    if ($ok) {

        $this->renderStructure();

        // Если устройство уже существует, обновляем его запись
        if ($rec['ID']) {
            SQLUpdate($table_name, $rec); // update
        } else {
            $new_rec = 1;
            // Если устройство добавляется, создаем новую запись устройства
            $rec['ID'] = SQLInsert($table_name, $rec); // adding new record
            $added = 1;
        }

        // Если устройство связано с локацией, выполняем дополнительные действия
        if ($rec['LOCATION_ID']) {
            // Получаем название объекта локации
            $location_title = getRoomObjectByLocation($rec['LOCATION_ID'], 1);
        }

        // Устанавливаем флаг успешного обновления
        $out['OK'] = 1;

        // Если тип устройства не пуст, выполняем дополнительные действия
        // Получаем детали типа устройства
        $type_details = $this->getTypeDetails($rec['TYPE']);
        // Если связанный объект устройства не пуст и флаг добавления нового объекта установлен, выполняем дополнительные действия
        if (!$rec['LINKED_OBJECT'] && $out['ADD_OBJECT']) {
            // Формируем новое название связанного объекта
            $prefix = $out['PREFIX'] . ucfirst($rec['TYPE']);
            $new_object_title = $prefix . $this->getNewObjectIndex($type_details['CLASS'], $prefix);
            // Добавляем новый объект с новым названием
            $object_id = addClassObject($type_details['CLASS'], $new_object_title, 'sdevice' . $rec['ID']);
            // Устанавливаем новое название связанного объекта устройства
            $rec['LINKED_OBJECT'] = $new_object_title;
            // Обновляем запись устройства с новым названием связанного объекта
            SQLUpdate('devices', $rec);
        }

        // Добавляем объект устройства с текущим названием
        $object_id = addClassObject($type_details['CLASS'], $rec['LINKED_OBJECT'], 'sdevice' . $rec['ID']);
        // Получаем ID класса объекта
        $class_id = current(SQLSelectOne("SELECT ID FROM classes WHERE TITLE LIKE '" . DBSafe($type_details['CLASS']) . "'"));


        // Если класс объекта изменился, выполняем дополнительные действия
        $object_rec = SQLSelectOne("SELECT * FROM objects WHERE ID=" . $object_id);
        // Устанавливаем описание объекта
        $object_rec['DESCRIPTION'] = $rec['TITLE'];
        // Устанавливаем ID локации объекта
        $object_rec['LOCATION_ID'] = $rec['LOCATION_ID'];
        $class_changed = 0;

        $class_2b_changed = 1;
        $tmp_class_id = $object_rec['CLASS_ID'];
        // Проверяем, изменился ли класс объекта
        while (isset($tmp_class_id)) {
            if ($tmp_class_id == $class_id) {
                $class_2b_changed = 0;
                break;
            }
            $tmp = SQLSelectOne("SELECT PARENT_ID FROM classes WHERE ID=" . (int)$tmp_class_id);
            $tmp_class_id = (int)$tmp['PARENT_ID'];
        }
        if ($class_2b_changed) {
            // Если класс объекта изменился, обновляем ID класса объекта
            $object_rec['CLASS_ID'] = $class_id;
            $class_changed = 1;
        }
        // Обновляем запись объекта в базе данных
        SQLUpdate('objects', $object_rec);
        // Если класс объекта изменился, вызываем функцию для обработки изменения класса
        if ($class_changed) {
            objectClassChanged($object_rec['ID']);
        }

        // Если устройство связано с локацией, выполняем дополнительные действия
        if ($location_title) {
            // Устанавливаем глобальное значение для связи устройства с локацией
            setGlobal($object_rec['TITLE'] . '.linkedRoom', $location_title);
        }

        // Если устройство добавлено и имеет свойства, устанавливаем значения свойств по умолчанию
        if ($added && is_array($type_details['PROPERTIES'])) {
            foreach ($type_details['PROPERTIES'] as $property => $details) {
                // Если для свойства определено значение по умолчанию, устанавливаем его
                if (isset($details['_CONFIG_DEFAULT'])) {
                    setGlobal($object_rec['TITLE'] . '.' . $property, $details['_CONFIG_DEFAULT']);
                }
            }
        }

        // Если устройство добавлено и имеет тип 'sensor_temp', устанавливаем значения свойств по умолчанию
        if ($added && $rec['TYPE'] == 'sensor_temp') {
            setGlobal($object_rec['TITLE'] . '.minValue', 16);
            setGlobal($object_rec['TITLE'] . '.maxValue', 25);
        }
        // Если устройство добавлено и имеет тип 'sensor_humidity', устанавливаем значения свойств по умолчанию
        if ($added && $rec['TYPE'] == 'sensor_humidity') {
            setGlobal($object_rec['TITLE'] . '.minValue', 30);
            setGlobal($object_rec['TITLE'] . '.maxValue', 60);
        }

        // Очищаем кэш данных
        clearCacheData();
        // Добавляем задачу в очередь для синхронизации устройств
        addToOperationsQueue('connect_sync_devices', 'required');

        // Если устройство связано с таблицей источника, выполняем дополнительные действия
        if ($out['SOURCE_TABLE'] && $out['SOURCE_TABLE_ID']) {
            // Добавляем устройство в таблицу источника
            $this->addDeviceToSourceTable($out['SOURCE_TABLE'], $out['SOURCE_TABLE_ID'], $rec['ID']);
        }

        // Синхронизируем устройство с Homebridge
        $this->homebridgeSync($rec['ID'], 1);

        // Если устройство добавлено, перенаправляем на вкладку настроек устройства
        if ($added) {
            $this->redirect("?view_mode=edit_devices&id=" . $rec['ID'] . "&tab=settings");
        }
    } else {
        $out['ERR'] = 1;
    }
}
if (is_array($rec)) {
    foreach ($rec as $k => $v) {
        // Проверяем, что значение не является массивом и не пустое
        if ($v && !is_array($v)) {
            // Экранируем специальные символы в значении свойства для предотвращения XSS атак
            $rec[$k] = htmlspecialchars($v);
        }
    }
}

if (!isset($rec['LOCATION_ID'])) {
    // Получаем ID локации из глобальной переменной
    $rec['LOCATION_ID'] = gr('location_id', 'int');
}


// Выполняем функцию для обработки хэша
outHash($rec, $out);


// Инициализируем массив для типов устройств
$types = array();
// Проходим по всем типам устройств
foreach ($this->device_types as $k => $v) {
    // Если у типа устройства есть название, добавляем его в массив типов
    if (isset($v['TITLE'])) {
        $types[] = array('NAME' => $k, 'TITLE' => $v['TITLE']);
    }
    // Если текущий тип устройства совпадает с типом устройства в записи и тип устройства не пуст, устанавливаем название типа устройства в выходной массив
    if (isset($rec['TYPE']) && $k == $rec['TYPE'] && $rec['TYPE'] != '') {
        $out['TYPE_TITLE'] = $v['TITLE'];
    }
}


if ($rec['LINKED_OBJECT']) {
    $processed = $this->processDevice($rec['ID']);
    $out['HTML'] = $processed['HTML'];
}

// Сортируем типы устройств по алфавиту
usort($types, function ($a, $b) {
    return strcmp($a['TITLE'], $b['TITLE']);
});
// Добавляем отсортированные типы устройств в выходной массив
$out['TYPES'] = $types;

// Получаем все локации
$out['LOCATIONS'] = SQLSelect("SELECT ID, TITLE FROM locations ORDER BY TITLE+0");


// Если устройство связано с локацией, выполняем дополнительные действия
if ($rec['LOCATION_ID']) {
    // Получаем запись локации по ID
    $location_rec = SQLSelectOne("SELECT ID,TITLE FROM locations WHERE ID=" . $rec['LOCATION_ID']);
    // Обрабатываем название локации и добавляем его в выходной массив
    $out['LOCATION_TITLE'] = processTitle($location_rec['TITLE']);
    // Получаем все устройства, связанные с текущей локацией
    $other_devices = SQLSelect("SELECT ID, TITLE, ARCHIVED FROM devices WHERE LOCATION_ID=" . (int)$rec['LOCATION_ID'] . " ORDER BY TITLE");
    // Добавляем список устройств в выходной массив
    $out['OTHER_DEVICES'] = $other_devices;
}

// Если тип устройства не пуст, выполняем дополнительные действия
if ($rec['TYPE']) {
    // Получаем все устройства, связанные с текущим типом устройства
    $other_devices_type = SQLSelect("SELECT ID, TITLE, ARCHIVED, LINKED_OBJECT FROM devices WHERE TYPE='" . $rec['TYPE'] . "' ORDER BY TITLE");
    // Добавляем список устройств в выходной массив
    $out['OTHER_DEVICES_TYPE'] = $other_devices_type;
}