<?php
//
// Copyright (C) 2025 Next Generation CMS (http://ngcms.org/)
// Name: categories.php
// Description: Менеджер категорий
// Author: NGCMS Development Team
//
// Protect against hack attempts
if (!defined('NGCMS')) {
    exit('HAL');
}
// Load library
@include_once root . 'actions/categories.rpc.php';
$lang = LoadLang('categories', 'admin');
// Safe wrapper for legacy print_msg: fallback to msg()+redirect if function doesn't exist
if (!function_exists('safe_print_msg')) {
    function safe_print_msg($type, $title, $text, $target)
    {
        // If legacy helper exists – use it
        if (function_exists('print_msg')) {
            return print_msg($type, $title, $text, $target);
        }
        // Map type to msg levels
        $level = ($type == 'error') ? 'error' : (($type == 'success' || $type == 'update') ? 'info' : 'warning');
        // Try to redirect to provided target
        $url = null;
        if (is_array($target)) {
            $keys = array_keys($target);
            if (count($keys)) {
                $url = $keys[0];
            }
        } else {
            $url = $target;
        }
        if ($url) {
            // Save message to session for display after redirect
            if (!isset($_SESSION)) {
                @session_start();
            }
            $_SESSION['flash_msg'] = ['type' => $level, 'text' => $text];
            global $PHP_SELF;
            // Normalize url: allow full or relative (?mod=...)
            if ((strpos($url, 'http://') === 0) || (strpos($url, 'https://') === 0) || (strpos($url, 'admin.php') === 0)) {
                header('Location: ' . $url);
            } else {
                $base = $PHP_SELF ?: 'admin.php';
                header('Location: ' . $base . $url);
            }
            exit;
        }
        // No redirect - display message immediately
        msg(['type' => $level, 'text' => $text]);
        return '';
    }
}
function listSubdirs($dir)
{
    $list = [];
    if ($h = @opendir($dir)) {
        while (($fn = readdir($h)) !== false) {
            if (($fn != '.') && ($fn != '..') && is_dir($dir . '/' . $fn)) {
                array_push($list, $fn);
            }
        }
        closedir($h);
    }
    return $list;
}
// ////////////////////////////////////////////////////////////////////////////
// Processing functions :: form for adding category
// ///////////////////////////////////////////////////////////////////////////
function admCategoryAddForm()
{
    global $mysql, $twig, $mod, $PHP_SELF, $config, $lang, $AFILTERS, $breadcrumb;
    // Check for permissions
    if (!checkPermission(['plugin' => '#admin', 'item' => 'categories'], null, 'modify')) {
        msg(['type' => 'error', 'text' => $lang['perm.denied']]);
        return safe_print_msg('warning', $lang['category'], $lang['perm.denied'], '?mod=categories');
    }
    $tpl_list = '<option value="">* ' . $lang['cat_tpldefault'] . " *</option>\n";
    $selectedTpl = isset($_REQUEST['tpl']) ? $_REQUEST['tpl'] : '';
    foreach (listSubdirs(tpl_site . 'ncustom/') as $k) {
        $tpl_list .= '<option value="' . secure_html($k) . '"' . (($selectedTpl == $k) ? ' selected="selected"' : '') . '>' . secure_html($k) . "</option>\n";
    }
    $templateMode = '';
    $selTM = isset($_REQUEST['template_mode']) ? intval($_REQUEST['template_mode']) : 0;
    foreach (['0', '1', '2'] as $k => $v) {
        $templateMode .= '<option value="' . $k . '"' . (($k == $selTM) ? ' selected="selected"' : '') . '>' . $lang['template_mode.' . $v] . '</option>';
    }
    // Breadcrumb helper может отсутствовать в некоторых скинах; используем, только если определён
    if (function_exists('breadcrumb')) {
        $breadcrumb = breadcrumb(
            '<i class="fa fa-plus btn-position"></i><span class="text-semibold">' . $lang['addnew'] . '</span>',
            array(
                '?mod=categories' => '<i class="fa fa-folder-open btn-position"></i>' . $lang['categories_title'],
                '' . $lang['addnew'] . ''
            )
        );
    } else {
        $breadcrumb = '';
    }
    $tVars = [
        'php_self'      => $PHP_SELF,
        'parent'        => makeCategoryList(['name' => 'parent', 'doempty' => 1, 'resync' => ($_REQUEST['action'] ? 1 : 0)]),
        'orderlist'     => OrderList(''),
        'token'         => genUToken('admin.categories'),
        'tpl_list'      => $tpl_list,
        'template_mode' => $templateMode,
        'flags'         => [
            'haveMeta' => $config['meta'] ? 1 : 0,
        ],
    ];
    if (is_array($AFILTERS['categories'])) {
        foreach ($AFILTERS['categories'] as $k => $v) {
            $v->addCategoryForm($tVars);
        }
    }
    // Рендерим шаблон текущего скина админки
    $xt = $twig->loadTemplate('skins/' . $config['admin_skin'] . '/tpl/categories/add.tpl');
    return $xt->render($tVars);
}
// ////////////////////////////////////////////////////////////////////////////
// Processing functions :: add new category
// ///////////////////////////////////////////////////////////////////////////
function admCategoryAdd()
{
    global $mysql, $lang, $mod, $parse, $config, $AFILTERS;
    $SQL = [];
    $SQL['name'] = secure_html(trim($_REQUEST['name']));
    $SQL['info'] = $_REQUEST['info'];
    $SQL['alt'] = trim($_REQUEST['alt']);
    $SQL['parent'] = intval($_REQUEST['parent']);
    $SQL['icon'] = $_REQUEST['icon'];
    $SQL['alt_url'] = $_REQUEST['alt_url'];
    $SQL['orderby'] = $_REQUEST['orderby'];
    $SQL['tpl'] = $_REQUEST['tpl'];
    $SQL['cat_show'] = intval($_REQUEST['cat_show']);
    $SQL['number'] = intval($_REQUEST['number']);
    $SQL['flags'] = intval($_REQUEST['cat_show']) ? '1' : '0';
    $SQL['flags'] .= (string) (abs(intval($_REQUEST['show_link']) <= 2) ? abs(intval($_REQUEST['show_link'])) : '0');
    $SQL['flags'] .= (string) (abs(intval($_REQUEST['template_mode']) <= 2) ? abs(intval($_REQUEST['template_mode'])) : '0');
    $category = intval($_REQUEST['category']);
    // Check for permissions
    if (!checkPermission(['plugin' => '#admin', 'item' => 'categories'], null, 'modify')) {
        msg(['type' => 'error', 'text' => $lang['perm.denied']]);
        return safe_print_msg('error', $lang['category'], '' . $lang['perm.denied'] . '', '?mod=categories');
    }
    // Check for security token
    if ((!isset($_REQUEST['token'])) || ($_REQUEST['token'] != genUToken('admin.categories'))) {
        msg(['type' => 'error', 'text' => $lang['error.security.token'], 'info' => $lang['error.security.token#desc']]);
        return safe_print_msg('error', $lang['category'], '' . $lang['error.security.token'] . '<br>' . $lang['error.security.token#desc'] . '', '?mod=categories');
    }
    if (!$SQL['name']) {
        msg(['type' => 'error', 'text' => $lang['msge_name'], 'info' => $lang['msgi_name']]);
        return safe_print_msg('error', $lang['category'], '' . $lang['msge_name'] . '<br>' . $lang['msgi_name'] . '', '?mod=categories');
    }
    // IF alt name is set:
    if ($SQL['alt'] != '') {
        // - check for allowed chars
        if (!$parse->nameCheck($SQL['alt'])) {
            // ERROR
            msg(['type' => 'error', 'text' => $lang['category.err.wrongalt'], 'info' => $lang['category.err.wrongalt#desc']]);
            return safe_print_msg('error', $lang['category'], '' . $lang['category.err.wrongalt'] . '<br>' . $lang['category.err.wrongalt#desc'] . '', '?mod=categories');
        }
        // - check for duplicate alt name
        if (is_array($mysql->record('select * from ' . prefix . '_category where lower(alt) = ' . db_squote($SQL['alt'])))) {
            msg(['type' => 'error', 'text' => $lang['category.err.dupalt'], 'info' => $lang['category.err.dupalt#desc']]);
            return safe_print_msg('error', $lang['category'], '' . $lang['category.err.dupalt'] . '<br>' . $lang['category.err.dupalt#desc'] . '', '?mod=categories');
        }
    } else {
        // alt name was not set, generate new alt name in automatic mode
        $SQL['alt'] = strtolower($parse->translit($SQL['name']));
        $i = '';
        while (is_array($mysql->record('select id from ' . prefix . '_category where alt = ' . db_squote($SQL['alt'] . $i) . ' limit 1'))) {
            $i++;
        }
        $SQL['alt'] = $SQL['alt'] . $i;
    }
    if ($config['meta']) {
        $SQL['description'] = secure_html(trim($_REQUEST['description']));
        $SQL['keywords'] = secure_html(trim($_REQUEST['keywords']));
    }
    // Prepare tvars for plugin filters
    $tvars = [];
    $pluginNoError = 1;
    if (is_array($AFILTERS['categories'])) {
        foreach ($AFILTERS['categories'] as $k => $v) {
            if (!($pluginNoError = $v->addCategory($tvars, $SQL))) {
                msg(['type' => 'error', 'text' => str_replace('%plugin%', $k, $lang['msge_pluginlock'])]);
                break;
            }
        }
    }
    if (!$pluginNoError) {
        return 0;
    }
    $SQLout = [];
    foreach ($SQL as $k => $v) {
        $SQLout[$k] = db_squote($v);
    }
    cacheStoreFile('LoadCategories.dat', '');
    // Add new record into SQL table
    $mysql->query('insert into ' . prefix . '_category (' . implode(', ', array_keys($SQLout)) . ') values (' . implode(', ', array_values($SQLout)) . ')');
    // Get the ID of newly inserted category
    $newCatId = $mysql->lastid();
    if (!$newCatId) {
        // Fallback to LAST_INSERT_ID() if lastid() doesn't work
        $rowID = $mysql->record('select LAST_INSERT_ID() as id');
        $newCatId = $rowID['id'] ?? 0;
    }
    $fmanager = new file_managment();
    $imanager = new image_managment();
    // Check if new image was attached
    if (isset($_FILES) && isset($_FILES['image']) && is_array($_FILES['image']) && isset($_FILES['image']['error']) && ($_FILES['image']['error'] == 0)) {
        // new file is uploaded
        $up = $fmanager->file_upload(['dsn' => true, 'linked_ds' => 2, 'linked_id' => $newCatId, 'type' => 'image', 'http_var' => 'image', 'http_varnum' => 0]);
        //print "OUT: <pre>".var_export($up, true)."</pre>";
        if (is_array($up)) {
            // Image is uploaded. Let's update image params
            //print "<pre>UPLOADED. ret:".var_export($up, true)."</pre>";
            $img_width = 0;
            $img_height = 0;
            $img_preview = 0;
            $img_pwidth = 0;
            $img_pheight = 0;
            if (is_array($sz = $imanager->get_size($config['attach_dir'] . $up[2] . '/' . $up[1]))) {
                //print "<pre>IMG SIZE. ret:".var_export($sz, true)."</pre>";
                $img_width = $sz[1];
                $img_height = $sz[2];
                $tsz = intval($config['thumb_size']);
                if (($tsz < 10) || ($tsz > 1000)) {
                    $tsz = 150;
                }
                $thumb = $imanager->create_thumb($config['attach_dir'] . $up[2], $up[1], $tsz, $tsz, $config['thumb_quality']);
                if ($thumb) {
                    $img_preview = 1;
                    $img_pwidth = $thumb[0];
                    $img_pheight = $thumb[1];
                    //print "<pre>THUMB CREATED. ret:".var_export($thumb, true)."</pre>";
                }
            }
            // Update table 'images'
            $mysql->query('update ' . prefix . '_images set width=' . db_squote($img_width) . ', height=' . db_squote($img_height) . ', preview=' . db_squote($img_preview) . ', p_width=' . db_squote($img_pwidth) . ', p_height=' . db_squote($img_pheight) . ' where id = ' . db_squote($up[0]));
            // Update table 'categories'
            $mysql->query('update ' . prefix . '_category set image_id = ' . db_squote($up[0]) . ' where id = ' . db_squote($newCatId));
        }
    }
    // Reorder categories to recalculate poslevel
    admCategoryReorder();
    // Reload categories to update global arrays
    if (function_exists('ngLoadCategories')) {
        ngLoadCategories();
    }
    // Report about adding new category
    // Use the ID we already obtained
    $catid = $newCatId;
    if (!$catid || $catid == 0) {
        msg(['type' => 'error', 'text' => 'Failed to get new category ID', 'info' => 'newCatId=' . var_export($newCatId, true)]);
        return safe_print_msg('error', $lang['category'], 'Failed to get new category ID', '?mod=categories');
    }
    return safe_print_msg('success', $lang['category'], $lang['msgo_added'], array('?mod=categories&action=edit&catid=' . $catid . '' => $lang['edit'], '?mod=categories' => $lang['back']));
}
// ////////////////////////////////////////////////////////////////////////////
// Processing functions :: form for editing category
// ///////////////////////////////////////////////////////////////////////////
//
function admCategoryEditForm()
{
    global $mysql, $lang, $mod, $config, $twig, $AFILTERS, $PHP_SELF, $breadcrumb;
    // Check for permissions
    $permModify = checkPermission(['plugin' => '#admin', 'item' => 'categories'], null, 'modify');
    $permDetails = checkPermission(['plugin' => '#admin', 'item' => 'categories'], null, 'details');
    if (!$permModify && !$permDetails) {
        msg(['type' => 'error', 'text' => $lang['perm.denied']]);
        return safe_print_msg('warning', $lang['category'], $lang['perm.denied'], '?mod=categories');
    }
    $catid = intval($_REQUEST['catid'] ?? 0);
    if ($catid === 0) {
        msg(['type' => 'error', 'text' => $lang['msge_id'], 'info' => 'catid is missing or zero']);
        return safe_print_msg('warning', $lang['category'], $lang['msge_id'], '?mod=categories');
    }
    if (!is_array($row = $mysql->record('select nc.*, ni.id as icon_id, ni.name as icon_name, ni.storage as icon_storage, ni.folder as icon_folder, ni.preview as icon_preview, ni.width as icon_width, ni.height as icon_height, ni.p_width as icon_pwidth, ni.p_height as icon_pheight from `' . prefix . '_category` as nc left join `' . prefix . '_images` ni on nc.image_id = ni.id where nc.id = ' . db_squote($catid) . ' order by nc.posorder asc'))) {
        $errorInfo = sprintf($lang['msgi_id'], $PHP_SELF . '?mod=categories') . '<br>catid=' . $catid . ', REQUEST=' . var_export($_REQUEST['catid'] ?? 'missing', true);
        msg(['type' => 'error', 'text' => $lang['msge_id'], 'info' => $errorInfo]);
        return safe_print_msg('warning', $lang['category'], $lang['msge_id'] . '<br>' . $errorInfo, '?mod=categories');
    }
    $tpl_list = '<option value="">* ' . $lang['cat_tpldefault'] . " *</option>\n";
    foreach (listSubdirs(tpl_site . 'ncustom/') as $k) {
        $tpl_list .= '<option value="' . secure_html($k) . '"' . (($row['tpl'] == $k) ? ' selected="selected"' : '') . '>' . secure_html($k) . "</option>\n";
    }
    $showLink = '';
    foreach (['always', 'ifnews', 'never'] as $k => $v) {
        $showLink .= '<option value="' . $k . '"' . (($k == intval(substr($row['flags'], 1, 1))) ? ' selected="selected"' : '') . '>' . $lang['link.' . $v] . '</option>';
    }
    $templateMode = '';
    foreach (['0', '1', '2'] as $k => $v) {
        $templateMode .= '<option value="' . $k . '"' . (($k == intval(substr($row['flags'], 2, 1))) ? ' selected="selected"' : '') . '>' . $lang['template_mode.' . $v] . '</option>';
    }
    $link = (checkLinkAvailable('news', 'by.category') ?
        generateLink('news', 'by.category', ['category' => $row['alt'], 'catid' => $row['id']], [], false, true) :
        generateLink('core', 'plugin', ['plugin' => 'news', 'handler' => 'by.category'], ['category' => $row['alt'], 'catid' => $row['id']], false, true));
    if (function_exists('breadcrumb')) {
        $breadcrumb = breadcrumb(
            '<i class="fa fa-pencil btn-position"></i><span class="text-semibold">' . $lang['category'] . ' ' . $row['name'] . '</span>',
            array(
                '?mod=categories' => '<i class="fa fa-folder-open btn-position"></i>' . $lang['categories_title'],
                '' . $row['name'] . ''
            )
        );
    }
    $tVars = [
        'php_self'      => $PHP_SELF,
        'parent'        => makeCategoryList(['name' => 'parent', 'selected' => $row['parent'], 'skip' => $row['id'], 'doempty' => 1]),
        'catid'         => $row['id'],
        'name'          => $row['name'],
        'alt'           => secure_html($row['alt']),
        'alt_url'       => secure_html($row['alt_url']),
        'orderlist'     => OrderList($row['orderby'], true),
        'description'   => $row['description'],
        'keywords'      => $row['keywords'],
        'icon'          => secure_html($row['icon']),
        'tpl_value'     => secure_html($row['tpl']),
        'number'        => $row['number'],
        'show_link'     => $showLink,
        'link'          => $link,
        'template_mode' => $templateMode,
        'tpl_list'      => $tpl_list,
        'info'          => secure_html($row['info']),
        'token'         => genUToken('admin.categories'),
        'flags'         => [
            'haveMeta'   => $config['meta'] ? 1 : 0,
            'canModify'  => $permModify ? 1 : 0,
            'showInMenu' => (substr($row['flags'], 0, 1)) ? 1 : 0,
            'haveAttach' => $row['icon_id'],
        ],
    ];
    if ($row['icon_id']) {
        $tVars['attach_url'] = $config['attach_url'] . '/' . $row['icon_folder'] . '/' . ($row['icon_preview'] ? 'thumb/' : '') . $row['icon_name'];
    }
    if (is_array($AFILTERS['categories'])) {
        foreach ($AFILTERS['categories'] as $k => $v) {
            $v->editCategoryForm($catid, $row, $tVars);
        }
    }
    $xt = $twig->loadTemplate('skins/' . $config['admin_skin'] . '/tpl/categories/edit.tpl');
    return $xt->render($tVars);
}
// ////////////////////////////////////////////////////////////////////////////
// Processing functions :: edit category
// ///////////////////////////////////////////////////////////////////////////
//
function admCategoryEdit()
{
    global $mysql, $lang, $config, $parse, $catz, $catmap, $AFILTERS;
    //print "<pre>POST DATA:\n".var_export($_POST, true)."\n\nFILES: ".var_export($_FILES, true)."</pre>";
    $SQL = [];
    $SQL['name'] = secure_html($_REQUEST['name']);
    $SQL['info'] = $_REQUEST['info'];
    $SQL['alt'] = trim($_REQUEST['alt']);
    $SQL['parent'] = intval($_REQUEST['parent']);
    $SQL['icon'] = $_REQUEST['icon'];
    $SQL['alt_url'] = $_REQUEST['alt_url'];
    $SQL['orderby'] = $_REQUEST['orderby'];
    $SQL['tpl'] = $_REQUEST['tpl'];
    $SQL['number'] = intval($_REQUEST['number']);
    $SQL['cat_show'] = intval($_REQUEST['cat_show']);
    $SQL['flags'] = intval($_REQUEST['cat_show']) ? '1' : '0';
    $SQL['flags'] .= (string) (abs(intval($_REQUEST['show_link']) <= 2) ? abs(intval($_REQUEST['show_link'])) : '0');
    $SQL['flags'] .= (string) (abs(intval($_REQUEST['template_mode']) <= 2) ? abs(intval($_REQUEST['template_mode'])) : '0');
    $catid = intval($_REQUEST['catid'] ?? 0);
    // Check for permissions
    if (!checkPermission(['plugin' => '#admin', 'item' => 'categories'], null, 'modify')) {
        msg(['type' => 'error', 'text' => $lang['perm.denied']]);
        return safe_print_msg('warning', $lang['category'], $lang['perm.denied'], '?mod=categories');
    }
    // Check if catid is valid
    if ($catid === 0) {
        msg(['type' => 'error', 'text' => $lang['msge_id'], 'info' => 'catid is missing or zero in edit form']);
        return safe_print_msg('error', $lang['category'], $lang['msge_id'], '?mod=categories');
    }
    // Check for security token
    if ((!isset($_REQUEST['token'])) || ($_REQUEST['token'] != genUToken('admin.categories'))) {
        msg(['type' => 'error', 'text' => $lang['error.security.token'], 'info' => $lang['error.security.token#desc']]);
        return safe_print_msg('error', $lang['category'], '' . $lang['error.security.token'] . '<br>' . $lang['error.security.token#desc'] . '', '?mod=categories');
    }
    if (!$SQL['name'] || !$catid || (!is_array($SQLold = $catz[$catmap[$catid]]))) {
        msg(['type' => 'error', 'text' => $lang['msge_name'], 'info' => $lang['msgi_name']]);
        return safe_print_msg('error', $lang['category'], '' . $lang['msge_name'] . '<br>' . $lang['msgi_name'] . '', '?mod=categories');
    }
    if (!$catid || (!is_array($SQLold = $catz[$catmap[$catid]]))) {
        msg(['type' => 'error', 'text' => $lang['msge_id'], 'info' => $lang['msgi_id']]);
        return safe_print_msg('error', $lang['category'], '' . $lang['msge_id'] . '<br>' . $lang['msgi_id'] . '', '?mod=categories');
    }
    // Check alt name in case it was changed
    if ($SQL['alt'] != $catid) {
        // - check for allowed chars
        if (!$parse->nameCheck($SQL['alt'])) {
            // ERROR
            msg(['type' => 'error', 'text' => $lang['category.err.wrongalt'], 'info' => $lang['category.err.wrongalt#desc']]);
            return safe_print_msg('error', $lang['category'], '' . $lang['category.err.wrongalt'] . '<br>' . $lang['category.err.wrongalt#desc'] . '', '?mod=categories');
        }
        // - check for duplicate alt name
        if (is_array($mysql->record('select * from ' . prefix . '_category where (id <> ' . db_squote($catid) . ') and (lower(alt) = ' . db_squote($SQL['alt']) . ')'))) {
            msg(['type' => 'error', 'text' => $lang['category.err.dupalt'], 'info' => $lang['category.err.dupalt#desc']]);
            return safe_print_msg('error', $lang['category'], '' . $lang['category.err.dupalt'] . '<br>' . $lang['category.err.dupalt#desc'] . '', '?mod=categories');
        }
    }
    if ($config['meta']) {
        $SQL['description'] = secure_html(trim($_REQUEST['description']));
        $SQL['keywords'] = secure_html(trim($_REQUEST['keywords']));
    }
    // Prepare tvars for plugin filters
    $tvars = [];
    $fmanager = new file_managment();
    $imanager = new image_managment();
    // Check is existent image should be deleted
    if (isset($_POST['image_del']) && $_POST['image_del'] && ($SQLold['image_id'])) {
        $fmanager->file_delete(['type' => 'image', 'id' => $SQLold['image_id']]);
        $SQL['image_id'] = 0;
    }
    // Check if new image was attached
    if (isset($_FILES) && (!$SQL['image_id']) && isset($_FILES['image']) && is_array($_FILES['image']) && isset($_FILES['image']['error']) && ($_FILES['image']['error'] == 0)) {
        // new file is uploaded
        $up = $fmanager->file_upload(['dsn' => true, 'linked_ds' => 2, 'linked_id' => $catid, 'type' => 'image', 'http_var' => 'image', 'http_varnum' => 0]);
        //print "OUT: <pre>".var_export($up, true)."</pre>";
        if (is_array($up)) {
            // Image is uploaded. Let's update image params
            //print "<pre>UPLOADED. ret:".var_export($up, true)."</pre>";
            $img_width = 0;
            $img_height = 0;
            $img_preview = 0;
            $img_pwidth = 0;
            $img_pheight = 0;
            if (is_array($sz = $imanager->get_size($config['attach_dir'] . $up[2] . '/' . $up[1]))) {
                //print "<pre>IMG SIZE. ret:".var_export($sz, true)."</pre>";
                $img_width = $sz[1];
                $img_height = $sz[2];
                $tsz = intval($config['thumb_size']);
                if (($tsz < 10) || ($tsz > 1000)) {
                    $tsz = 150;
                }
                $thumb = $imanager->create_thumb($config['attach_dir'] . $up[2], $up[1], $tsz, $tsz, $config['thumb_quality']);
                if ($thumb) {
                    $img_preview = 1;
                    $img_pwidth = $thumb[0];
                    $img_pheight = $thumb[1];
                    //print "<pre>THUMB CREATED. ret:".var_export($thumb, true)."</pre>";
                }
            }
            // Update SQL records
            $mysql->query('update ' . prefix . '_images set width=' . db_squote($img_width) . ', height=' . db_squote($img_height) . ', preview=' . db_squote($img_preview) . ', p_width=' . db_squote($img_pwidth) . ', p_height=' . db_squote($img_pheight) . ' where id = ' . db_squote($up[0]));
            $SQL['image_id'] = $up[0];
        }
    }
    $pluginNoError = 1;
    if (is_array($AFILTERS['categories'])) {
        foreach ($AFILTERS['categories'] as $k => $v) {
            if (!($pluginNoError = $v->editCategory($catid, $SQLold, $SQL, $tvars))) {
                msg(['type' => 'error', 'text' => str_replace('%plugin%', $k, $lang['msge_pluginlock'])]);
                break;
            }
        }
    }
    if (!$pluginNoError) {
        return 0;
    }
    $SQLout = [];
    foreach ($SQL as $var => $val) {
        $SQLout[] = '`' . $var . '` = ' . db_squote($val);
    }
    cacheStoreFile('LoadCategories.dat', '');
    $mysql->query('update ' . prefix . '_category set ' . implode(', ', $SQLout) . ' where id=' . db_squote($catid));
    // Reorder categories to recalculate poslevel
    admCategoryReorder();
    // Reload categories to update global arrays
    if (function_exists('ngLoadCategories')) {
        ngLoadCategories();
    }
    return safe_print_msg('update', $lang['category'], $lang['msgo_saved'], array('?mod=categories&action=edit&catid=' . $catid . '' => 'Редактировать', '?mod=categories' => 'Вернуться назад'));
}
// ////////////////////////////////////////////////////////////////////////////
// MAIN ACTION
// ///////////////////////////////////////////////////////////////////////////
//
// Check for flash message from previous redirect
if (!isset($_SESSION)) {
    @session_start();
}
if (isset($_SESSION['flash_msg']) && is_array($_SESSION['flash_msg'])) {
    msg($_SESSION['flash_msg']);
    unset($_SESSION['flash_msg']);
}
if ($action == 'edit') {
    $main_admin = admCategoryEditForm();
} elseif ($action == 'add') {
    $main_admin = admCategoryAddForm();
} else {
    $dosort = 1;
    switch ($action) {
        case 'doadd':
            admCategoryAdd();
            break;
        case 'remove':
            // В некоторых сборках category_remove() может отсутствовать; используем fallback через RPC-обработчик
            if (function_exists('category_remove')) {
                category_remove();
            } else {
                // Пытаемся удалить через RPC-обработчик
                $rid = intval($_REQUEST['catid'] ?? $_REQUEST['id'] ?? 0);
                if ($rid) {
                    $result = admCategoriesRPCmodify([
                        'mode'  => 'del',
                        'id'    => $rid,
                        'token' => genUToken('admin.categories'),
                    ]);
                    if (is_array($result) && !empty($result['status'])) {
                        msg(['type' => 'info', 'text' => $lang['msgo_deleted'] ?? 'Deleted']);
                    } else {
                        msg(['type' => 'error', 'text' => ($result['errorText'] ?? 'Delete failed')]);
                    }
                    $main_admin = admCategoryList(1);
                }
            }
            break;
        case 'doedit':
            admCategoryEdit();
            break;
        default:
            $dosort = 0;
            $main_admin = admCategoryList();
    }
    if ($dosort) {
        admCategoryReorder();
    }
}
