Категории форума

Статьи. Управление. Поиск по фразе, Фильтр по категории

Как в админке быстро искать нужные статьи по выбору категории, используя Select2 или по фразе через строку поиска

webitproff
webitproff • 07.07.2025 20:16 #334

Продолжая искать интересные решения по модификации кода Cotonti там, где разработчики не реализовали удобные элементы интерфейса, было интересно сделать фильтр по категориям в статьях и быстрый поиск по слову или фразу в строке поиска, в админке.

Все эти лайфхаки придется выполнять повторно при обновлении движка на своем сайте.

Все выполнялось на последней версии движка с репозитория, по состоянию на 07.07.2025
Вам понадобится мой шаблон админки, точнее тема админ панели управления сайтом с репозитория, которая уже содержит подключенную библиотеку Select2.

И так, кому не терпится, - скриншот прикреплен, того, что из этого вышло.
Предполагается, что скин "cotcp" уже установлен. (как установить “CotCP” и другие вопросы в этом разделе)

 

1. Открывае свой файл page.functions.php

/modules/page/inc/page.functions.php

и после всего кода, в самый конец добавляем код

/**
* Renders structure dropdown for cot_page_selectbox_structure_select2 with Select2(https://select2.org/) support and indented subcategories
*
* @param string $extension Extension code
* @param string $check Selected value
* @param string $name Dropdown name
* @param string $subcat Show only subcats of selected category
* @param bool $hidePrivate Hide private categories
* @param bool $isModule TRUE for modules, FALSE for plugins
* @param bool $addEmpty Allow empty choice
* @param mixed $attrs Additional attributes as an associative array or a string
* @param string $customRC Custom resource string name
* @return string
*/
function cot_page_selectbox_structure_select2(
   $extension,
   $check,
   $name,
   $subcat = '',
   $hidePrivate = true,
   $isModule = true,
   $addEmpty = false,
   $attrs = '',
   $customRC = ''
) {
   $categories = is_array(Cot::$structure[$extension]) ? Cot::$structure[$extension] : [];

   $options = [];

   foreach ($categories as $code => $category) {
       $display = ($hidePrivate && $isModule) ? cot_auth($extension, $code, 'W') : true;

       if ($display && !empty($subcat) && isset(Cot::$structure[$extension][$subcat])) {
           $mtch = Cot::$structure[$extension][$subcat]['path'] . '.';
           $mtchlen = mb_strlen($mtch);
           $display = (mb_substr($category['path'], 0, $mtchlen) == $mtch || $code === $subcat);
       }

       if ((!$isModule || cot_auth($extension, $code, 'R')) && $code !== 'all' && $display) {
           $depth = substr_count($category['path'], '.');
           $selected = ($code === $check) ? ' selected' : '';
           $attrs_str = is_array($attrs) ? cot_rc_attr_string($attrs) : $attrs;

           $options[] = '<option value="' . htmlspecialchars($code) . '" data-depth="' . $depth . '"' . $selected . ' ' . $attrs_str . '>' .
                        htmlspecialchars($category['title']) . '</option>';
       }
   }

   if ($addEmpty) {
       array_unshift($options, '<option value="">---</option>');
   }

   /* === Hook === */
   foreach (cot_getextplugins('selectBox.structure') as $pl) {
       include $pl;
   }
   /* ===== */

   return '<select name="' . htmlspecialchars($name) . '" class="form-select">' . implode("\n", $options) . '</select>';
}

/**
* Select page cat for search form. Используется с Select2 (https://select2.org/)
*
* @global array $structure
* @param string $check Selected category code
* @param string $name Name of the select input
* @param string $subcat Parent category code for filtering subcategories
* @param bool $hideprivate Hide private categories
* @return string
*/
function cot_page_selectcat_select2($check, $name, $subcat = '', $hideprivate = true)
{
   // Доступ к глобальной структуре категорий
   global $structure;

   // Проверяем, что массив категорий существует, иначе инициализируем пустым
   $structure['page'] = is_array($structure['page']) ? $structure['page'] : [];

   // Переменная для накопления всех option'ов
   $options = '';

   // Перебираем все категории в разделе 'page'
   foreach ($structure['page'] as $i => $x) {
       // Проверяем, разрешён ли просмотр категории (если нужно скрывать приватные)
       $display = $hideprivate ? cot_auth('page', $i, 'R') : true;

       // Если нужно фильтровать подкатегории, проверяем, входит ли текущая категория в фильтр
       if ($display && !empty($subcat) && isset($structure['page'][$subcat])) {
           // Формируем строку пути родительской категории с точкой на конце
           $mtch = $structure['page'][$subcat]['path'] . ".";
           // Длина этого пути
           $mtchlen = mb_strlen($mtch);
           // Проверяем, что путь текущей категории начинается с пути родителя или совпадает с ним
           $display = (mb_substr($x['path'], 0, $mtchlen) == $mtch || $i === $subcat);
       }

       // Если есть права на чтение категории, она не "all" и подходит по фильтру
       if (cot_auth('page', $i, 'R') && $i !== 'all' && $display) {
           // Считаем глубину категории — количество точек в пути
           $depth = substr_count($x['path'], '.');

           // Определяем, выбрана ли эта категория в данный момент
           $selected = ($i == $check) ? ' selected' : '';

           // Формируем тег option с value, data-depth и текстом
           $options .= '<option value="' . htmlspecialchars($i) . '" data-depth="' . $depth . '"' . $selected . '>' .
                       htmlspecialchars($x['title']) . '</option>';
       }
   }

   // Возвращаем полный select с классом Bootstrap
   return '<select name="' . htmlspecialchars($name) . '" class="form-select">' . $options . '</select>';
}


/**
* Select page cat for search form
* 
* @global array $structure
* @param type $check
* @param type $name
* @param type $subcat
* @param type $hideprivate
* @return string
*/
function cot_page_selectcat($check, $name, $subcat = '', $hideprivate = true)
{
    global $structure;

    $structure['page'] = (is_array($structure['page'])) ? $structure['page'] : array();

    $result_array = array();
    foreach ($structure['page'] as $i => $x)
    {
        $display = ($hideprivate) ? cot_auth('page', $i, 'R') : true;
        if ($display && !empty($subcat) && isset($structure['page'][$subcat]))
        {
            $mtch = $structure['page'][$subcat]['path'].".";
            $mtchlen = mb_strlen($mtch);
            $display = (mb_substr($x['path'], 0, $mtchlen) == $mtch || $i === $subcat);
        }

        if (cot_auth('page', $i, 'R') && $i != 'all' && $display) {
            $result_array[$i] = $x['tpath'];
        }
    }

   return cot_selectbox($check, $name, array_keys($result_array), array_values($result_array), true);
}

 

пояснение по назначению функций:
function cot_page_selectcat_select2 - выбор категории в списках через select2
function cot_page_selectcat - обычный выбор категории (на случай, если select2 не используется)
function cot_page_selectbox_structure_select2 - не используется, это подготовка для выбора категорий через select2 при создании и редактировании статей

 

2. Открываем page.admin.php

/modules/page/page.admin.php

или добавляете строки из файла page.admin.php, который в архиве, с заменой полностью, или строку за строкой, сравнивая исходный и модифицированный файл при помощи программы ExamDiff 

Файл page.admin.php - как его модифицировать "по-строчно"?

1. находим код (~ line 56)

$filter = empty($filter) ? 'valqueue' : $filter;

 

меняем на

$filter = empty($filter) ? 'all' : $filter; // лучше "все" по умолчанию. забываешь выбрать потом недоумеваешь

 

2. сразу после массива (~ lines 57-63):

$filter_type = [
    'all' => Cot::$L['All'],
    'valqueue' => Cot::$L['adm_valqueue'],
    'validated' => Cot::$L['adm_validated'],
    'expired' => Cot::$L['adm_expired'],
    'drafts' => Cot::$L['page_drafts'],
];

 

добавляем две строки:

$sq = cot_import('sq', 'G', 'TXT'); // Добавлено: импорт строки поиска
$c = cot_import('c', 'G', 'ALP'); // Добавлено: импорт категории для фильтрации

 


3.  находим код (~ lines 72-74)

if ($filter != 'valqueue') {
   $urlParams['filter'] = $filter;
}

 

и сразу за ними добавляем код:

if (!empty($sq)) { // Добавлено: добавление строки поиска в параметры URL
   $urlParams['sq'] = $sq;
}
if (!empty($c)) { // Добавлено: добавление категории в параметры URL
   $urlParams['c'] = $c;
}

 

4.  находим код (~ lines 98-101)

$catsub = cot_structure_children('page', '');
if (count($catsub) < count(Cot::$structure['page'])) {
    $sqlwhere .= " AND page_cat IN ('" . implode("','", $catsub) . "')";
}

 

меняем на

$catsub = cot_structure_children('page', $c); // Изменено: использование $c для фильтрации по категории
if (count($catsub) < count(Cot::$structure['page'])) {
    $sqlwhere .= " AND page_cat IN ('" . implode("','", $catsub) . "')";
}

if (!empty($sq)) { // Добавлено: логика фильтрации по строке поиска
   $words = explode(' ', $sq);
   $sqlsearch = '%' . implode('%', $words) . '%';
   $sqlwhere .= " AND (page_title LIKE '" . Cot::$db->prep($sqlsearch) . "' OR page_text LIKE '" . Cot::$db->prep($sqlsearch) . "')";
}

 

5. находим код (~ line 423)

'ADMIN_PAGE_ON_PAGE' => $ii,

 

и сразу после неё добавляем код:

'SEARCH_ACTION_URL' => cot_url('admin', 'm=page', '', true), // Добавлено: URL для формы поиска
'SEARCH_SQ' => cot_inputbox('text', 'sq', $sq, 'class="schstring"'), // Добавлено: строка поиска
'SEARCH_CAT_SELECT2' => cot_page_selectcat_select2($c, 'c'), // Добавлено: выбор категории через select2
'SEARCH_CAT' => cot_page_selectcat($c, 'c'), // Добавлено: обычный выбор категории (на случай, если select2 не используется)
'CATTITLE' => (!empty($c)) ? ' / ' . htmlspecialchars(Cot::$structure['page'][$c]['title']) : '' // Добавлено: заголовок категории

 

то есть весь массив должен иметь вид:

$t->assign([
    'ADMIN_PAGE_URL_CONFIG' => cot_url('admin', 'm=config&n=edit&o=module&p=page'),
    'ADMIN_PAGE_URL_ADD' => cot_url('page', 'm=add'),
    'ADMIN_PAGE_URL_EXTRAFIELDS' => cot_url('admin', 'm=extrafields&n=' . $db_pages),
    'ADMIN_PAGE_URL_STRUCTURE' => cot_url('admin', 'm=structure&n=page'),
    'ADMIN_PAGE_FORM_URL' => cot_url('admin', $common_params.'&a=update_checked&d=' . $durl),
    'ADMIN_PAGE_ORDER' => cot_selectbox($sorttype, 'sorttype', array_keys($sort_type), array_values($sort_type), false),
    'ADMIN_PAGE_WAY' => cot_selectbox($sortway, 'sortway', array_keys($sort_way), array_values($sort_way), false),
    'ADMIN_PAGE_FILTER' => cot_selectbox($filter, 'filter', array_keys($filter_type), array_values($filter_type), false),
    'ADMIN_PAGE_TOTALDBPAGES' => $totaldbpages,
   'ADMIN_PAGE_ON_PAGE' => $ii,
    'SEARCH_ACTION_URL' => cot_url('admin', 'm=page', '', true), // Добавлено: URL для формы поиска
    'SEARCH_SQ' => cot_inputbox('text', 'sq', $sq, 'class="schstring"'), // Добавлено: строка поиска
    'SEARCH_CAT_SELECT2' => cot_page_selectcat_select2($c, 'c'), // Добавлено: выбор категории через select2
    'SEARCH_CAT' => cot_page_selectcat($c, 'c'), // Добавлено: обычный выбор категории (на случай, если select2 не используется)
    'CATTITLE' => (!empty($c)) ? ' / ' . htmlspecialchars(Cot::$structure['page'][$c]['title']) : '' // Добавлено: заголовок категории
]);

 

Модификация page.admin.php завершена.
Напоминаю, сразу перед этим "докидуем" функции в /modules/page/inc/page.functions.php иначе насыпите на экран ошибок "фаталити".
Затем правим page.admin.php
а потом перезаливаем шаблон page.admin.tpl.
Разумеется бэкап сайта делать стоит перед любыми правками.

 

3. Шаблон админки, он прикреплен в архиве, 

его можно перезалить в папку /themes/admin/cotcp/modules/page/page.admin.tpl

все.

результат на скриншоте.
 

Сами три файла в архиве.

07_07_2025_19-50_page_admin_select2_ready_2.zip

Отредактировано: webitproff (07.07.2025 22:34, 1 год назад)
2/2/0

🗿

🧙‍♂️ Радуйся, странниче добрый и честный! Не понапрасну привела тя стезя в земли сии. Зде бо мужи мудры и искусники дела писменнаго и численнаго, что куют словеса и плетут узоры кода, тайны разгадывают и знанием делятся, летописи множа и ремесло цифровое творя.
⬅️ Аще налево пойдеши — обрящеши грамоты древния, писания старинныя и свитки учения. ⬆️ Аще прямо — слово мудрое и совет от мужей разумеющих, что в деле кодописном искусны, получиши. ➡️ Аще направо — сам в писцы и летописцы внидеши, и в ремесло цифровое, где строки и знаки в дело живое обращаются, слово свое в память веков вложиши.
📝 Не медли же — нареки имя свое, и вниди в чин наш, и стань с нами в братстве, где код есть магия, а строки — заклятия живыя, числом и буквою слово едины суть.
🏰 Не тайно есть собрание сие, и не инициации в нем, но многое сокрыто от пришлых и ступалого, понеже не всякому открывается знание, но токмо ищущим и пребывающим в деле сем.