В сообществе периодически возникают вопросы типа «как мне вывести документы только второго/третьего/четвертого уровня». Данный лайфхак поможет решить эту задачу очень легко и просто. И использовать для этого мы будем известный приём по расширению модели ресурсов.

1. Добавляем в таблицу ресурсов (modx_site_content) поле level

Тип: TINYINT
По-умолчанию: NULL
Атрибуты: UNSIGNED

2. Создаём плагин на события OnMODXInit, OnDocFormSave и OnResourceSort

<?php
switch ($modx->event->name) {
    case 'OnMODXInit':
        // Загружаем поле в модель ресурса
        $modx->loadClass('modResource');
        $modx->map['modResource']['fields']['level'] = null;
        $modx->map['modResource']['fieldMeta']['level'] = array(
            'dbtype' => 'tinyint',
            'precision' => 3,
            'attributes' => 'unsigned',
            'phptype' => 'integer',
            'null' => true,
        );
        // Если вы используете библиотеку modHelpers, то весь код можно заменить одной строчкой
        // load_model('modResource', function($model){$model->tinyint('level, true)->null();});
        break;
    case 'OnDocFormSave':
        if ($resource->parent == 0) {
            $level = 1;
        } else {
            $level = $resource->Parent->level + 1;
        }
        // Сохраняем уровень ресурса
        if (!empty($level) {
            $resource->set('level', $level);
            $resource->save();
        }
        break;
    case 'OnResourceSort':
        foreach ($modifiedNodes as $resource) {
            if ($resource->parent == 0) {
                $level = 1;
            } else {
                $level = $resource->Parent->level + 1;
            }
            $resource->set('level', $level);
            $resource->save();
        }
        break;
}

Если база пустая (обычно 1 документ) или документов не много, то можно просто их пересохранить. Сначала документы первого уровня. Потом дочерние. Плагин проставит соответствующие уровни. Также плагин отработает при переносе ресурса в другого родителя. А вот функционал для переноса контейнеров с ресурсами придётся дописывать самостоятельно. Это непростая задача и может подвесить сайт при большом количестве дочерних элементов.

Если у вас база документов приличная, то придётся выполнить дополнительную операцию. Проще всего создать временный ресурс и в нём вызывать сниппет для обновления ресурсов. Код сниппета приведён ниже. Отмечу, что в нём используется библиотека modHelpers. У кого этот компонент не установлен, измените код сниппета самостоятельно.

// Шаг 1. Устанавливаем уровень для ресурсов 1-го уровня.
return resources()->where(['parent'=>0])->set(['level'=>1]);
// Шаг 2. Комментируем шаг 1.
$parentLevel = 1; // Начинаем с первого уровня и последовательно увеличиваем на 1
$level = $parentLevel + 1;
$count = 0;
$resources = resources()->where(['level'=>$parentLevel])->toArray();
if (empty($resources)) return 'Ресурсы указанного уровня не найдены!';
foreach($resources as $resource) {
    $res = resources()->where(['parent'=>$resource['id']])->set(['level'=>$level]);
    $count += $res;
}
return "Обновлены {$count} ресурсов {$level}-го уровня.";

Сниппет нужно выполнять последовательно для каждого уровня. Сначала выполняем как есть. После того, как ресурсы первого уровня обновлены, закомментируйте или удалите первый шаг. Дальше первая итерация установит второй уровень всем ресурсам, у которых родители имеют первый уровень. Затем увеличьте значение переменной $parentLevel на единицу. И так далее, пока не получите сообщение «Ресурсы указанного уровня не найдены!»

Опять же, если у вас записей не многие тысячи, то второй шаг можно выполнить в цикле

// Шаг 2.
$maxLevel = 5; // Укажите максимальный уровень
for($parentLevel=1; $parentLevel <= $maxLevel; $parentLevel++) {
    $level = $parentLevel + 1;
    $count = 0;
    $resources = resources()->where(['level'=>$parentLevel])->toArray();
    if (empty($resources)) return 'Ресурсы указанного уровня не найдены!';
    foreach($resources as $resource) {
        $res = resources()->where(['parent'=>$resource['id']])->set(['level'=>$level]);
        $count += $res;
    }
    $output .= "Обновлены {$count} ресурсов {$level}-го уровня." . "<br>";
}
return $output;

Теперь вы можете в запросах указывать нужный уровень документов, используя для ограничения поле level.

У меня записей всего несколько сотен и 4 уровня. Данная операция заняла секунду. Ну и совет напоследок — всегда перед любой операцией с БД делайте резервную копию.

19 января 2017, 09:48   634     0

Комментарии ()

    Вы должны авторизоваться, чтобы оставлять комментарии.

    Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.