Продолжая искать интересные решения по модификации кода 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
все.
результат на скриншоте.
Сами три файла в архиве.