Всего 125 905 комментариев

Олег Захаров
19 мая 2026, 04:04
0
Сделал новую версию с табами и возможностью запуска сразу для всех вариантов.
Сначала содержимое для технического ресурса откуда будет запускаться выполнение сниппета.
[[!versionCleanXTabs? &maxVersions=`2` &types=`chunk,resource,template,snippet,plugin,templatevar` &dryRun=`0` &optimize=`1`]]
Далее содержимое versionCleanXTabs:
— в описании Tabbed wrapper for versionCleanX cleanup results.
— в код:
<?php
/**
 * versionCleanXTabs
 *
 * Runs versionCleanX for several VersionX entity tables and shows results in tabs.
 *
 * PROPERTIES:
 * &types - comma-separated list: chunk,resource,template,snippet,plugin,templatevar
 * &maxVersions - integer max versions to keep per element
 * &dryRun - 1 to show what would be deleted without deleting rows
 * &optimize - 1 to run OPTIMIZE TABLE in versionCleanX
 */

$escape = function ($value) {
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
};

$toBool = function ($value) {
    return filter_var($value, FILTER_VALIDATE_BOOLEAN);
};

$typesRaw = (string)$modx->getOption('types', $scriptProperties, 'chunk,resource,template,snippet,plugin,templatevar');
$maxVersions = (int)$modx->getOption('maxVersions', $scriptProperties, 2);
$dryRun = $toBool($modx->getOption('dryRun', $scriptProperties, false));
$optimize = $toBool($modx->getOption('optimize', $scriptProperties, true));

$labels = [
    'chunk' => ['Чанки', 'versionx_chunk'],
    'resource' => ['Ресурсы', 'versionx_resource'],
    'template' => ['Шаблоны', 'versionx_template'],
    'snippet' => ['Сниппеты', 'versionx_snippet'],
    'plugin' => ['Плагины', 'versionx_plugin'],
    'templatevar' => ['TV-поля', 'versionx_templatevar'],
];

$types = [];
foreach (preg_split('/\s*,\s*/', $typesRaw, -1, PREG_SPLIT_NO_EMPTY) as $type) {
    $type = strtolower(trim($type));
    if (isset($labels[$type]) && !in_array($type, $types, true)) {
        $types[] = $type;
    }
}

if ($maxVersions < 1) {
    $maxVersions = 2;
}

if (empty($types)) {
    return '<p>versionCleanXTabs: не передан ни один поддерживаемый тип VersionX.</p>';
}

$uid = 'vcx-tabs-' . substr(md5(uniqid('', true)), 0, 10);

$css = <<<'VCX_STYLE'
<style>
.vcx-tool {
    max-width: 1180px;
    margin: 32px auto;
    padding: 0 18px 40px;
    color: #172033;
    font-family: Arial, sans-serif;
}
.vcx-tool h1 {
    margin: 0 0 12px;
    font-size: 34px;
    line-height: 1.15;
    font-weight: 600;
}
.vcx-tool__lead {
    max-width: 860px;
    margin: 0 0 22px;
    color: #536071;
    font-size: 16px;
    line-height: 1.55;
}
.vcx-tabs__nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 24px 0 0;
    border-bottom: 1px solid #d8dee8;
}
.vcx-tabs__button {
    appearance: none;
    border: 1px solid #d8dee8;
    border-bottom: 0;
    border-radius: 8px 8px 0 0;
    background: #f4f7fb;
    color: #1b2a41;
    padding: 11px 15px 10px;
    min-width: 132px;
    text-align: left;
    cursor: pointer;
}
.vcx-tabs__button small {
    display: block;
    margin-top: 2px;
    color: #6b7585;
    font-size: 11px;
}
.vcx-tabs__button.is-active {
    background: #fff;
    color: #00458f;
    border-color: #c5d1df;
}
.vcx-tabs__panel {
    display: none;
    padding: 24px 0 0;
}
.vcx-tabs__panel.is-active {
    display: block;
}
.vcx-result {
    overflow-x: auto;
}
.vcx-result h3 {
    margin-top: 0;
}
.vcx-result table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 14px;
}
.vcx-result th,
.vcx-result td {
    padding: 9px 10px;
    border-bottom: 1px solid #e5e9f0;
    text-align: left;
    vertical-align: top;
}
.vcx-result th {
    background: #f5f7fa;
    font-weight: 600;
}
.vcx-tool__note {
    margin-top: 18px;
    padding: 12px 14px;
    border-left: 4px solid #00458f;
    background: #f3f7fc;
    color: #3d4a5c;
}
@media (max-width: 680px) {
    .vcx-tool h1 {
        font-size: 27px;
    }
    .vcx-tabs__button {
        flex: 1 1 100%;
    }
}
</style>
VCX_STYLE;

$out = $css;
$out .= '<section class="vcx-tool" id="' . $escape($uid) . '">';
$out .= '<h1>VersionX cleanup</h1>';
$out .= '<p class="vcx-tool__lead">Служебная страница очистки промежуточных версий VersionX. При открытии страницы запускается очистка всех вкладок: остаются только ' . (int)$maxVersions . ' последние версии для выбранных сущностей.</p>';
if ($dryRun) {
    $out .= '<p class="vcx-tool__note"><strong>Dry run:</strong> записи не удаляются, показан только расчет.</p>';
}

$out .= '<div class="vcx-tabs">';
$out .= '<div class="vcx-tabs__nav" role="tablist" aria-label="VersionX cleanup tabs">';
foreach ($types as $index => $type) {
    $panelId = $uid . '-' . $type;
    $active = $index === 0 ? ' is-active' : '';
    $out .= '<button class="vcx-tabs__button' . $active . '" type="button" role="tab" aria-selected="' . ($index === 0 ? 'true' : 'false') . '" data-vcx-tab="' . $escape($panelId) . '">';
    $out .= '<span>' . $escape($labels[$type][0]) . '</span><small>_' . $escape($labels[$type][1]) . '</small>';
    $out .= '</button>';
}
$out .= '</div>';

foreach ($types as $index => $type) {
    $panelId = $uid . '-' . $type;
    $active = $index === 0 ? ' is-active' : '';
    $result = (string)$modx->runSnippet('versionCleanX', [
        'contentType' => $type,
        'maxVersions' => $maxVersions,
        'dryRun' => $dryRun ? 1 : 0,
        'optimize' => $optimize ? 1 : 0,
    ]);

    if ($result === '') {
        $result = '<p>Сниппет versionCleanX не вернул результат для типа ' . $escape($type) . '.</p>';
    }

    $out .= '<div class="vcx-tabs__panel' . $active . '" id="' . $escape($panelId) . '" role="tabpanel">';
    $out .= '<div class="vcx-result">' . $result . '</div>';
    $out .= '</div>';
}
$out .= '</div>';

$out .= <<<'VCX_SCRIPT'
<script>
(function () {
    var root = document.currentScript ? document.currentScript.closest('.vcx-tool') : null;
    if (!root) {
        return;
    }
    var buttons = root.querySelectorAll('[data-vcx-tab]');
    var panels = root.querySelectorAll('.vcx-tabs__panel');
    buttons.forEach(function (button) {
        button.addEventListener('click', function () {
            var targetId = button.getAttribute('data-vcx-tab');
            buttons.forEach(function (item) {
                var active = item === button;
                item.classList.toggle('is-active', active);
                item.setAttribute('aria-selected', active ? 'true' : 'false');
            });
            panels.forEach(function (panel) {
                panel.classList.toggle('is-active', panel.id === targetId);
            });
        });
    });
})();
</script>
VCX_SCRIPT;

$out .= '</section>';

return $out;
Далее содержимое сниппета versionCleanX (отличается от варианта выше):
<?php
ini_set('memory_limit', '256M');

/**
 * versionCleanX
 *
 * Cleans old VersionX rows while keeping the newest N versions per element.
 *
 * PROPERTIES:
 * &contentType - resource, chunk, plugin, snippet, template, or templatevar
 * &maxVersions - integer max versions to keep per element
 * &dryRun - 1 to show what would be deleted without deleting rows
 * &optimize - 1 to run OPTIMIZE TABLE after cleanup
 *
 * USAGE:
 * [[!versionCleanX? &contentType=`resource` &maxVersions=`10`]]
 */

$type = strtolower(trim((string)$modx->getOption('contentType', $scriptProperties, 'resource')));
$maxVersions = (int)$modx->getOption('maxVersions', $scriptProperties, 5);
$dryRun = (bool)$modx->getOption('dryRun', $scriptProperties, false);
$optimize = (bool)$modx->getOption('optimize', $scriptProperties, true);

$titleColumns = [
    'chunk' => 'name',
    'plugin' => 'name',
    'snippet' => 'name',
    'template' => 'templatename',
    'templatevar' => 'name',
    'resource' => 'title',
];

if (!array_key_exists($type, $titleColumns)) {
    $type = 'resource';
}

if ($maxVersions < 1) {
    return 'VersionX cleanup error: maxVersions must be greater than 0.';
}

$table = $modx->getOption('table_prefix') . 'versionx_' . $type;
$titleColumn = $titleColumns[$type];
$query = "SELECT version_id, content_id, {$titleColumn} AS page_title FROM `{$table}` ORDER BY content_id ASC, version_id DESC";

$stmt = $modx->query($query);
if (!is_object($stmt)) {
    return 'VersionX cleanup query error: ' . print_r($modx->errorInfo(), true);
}

$rowsHtml = '';
$total = 0;
$deleted = 0;
$currentContentId = null;
$currentTitle = '';
$currentTotal = 0;
$currentDeleted = 0;
$keptForCurrent = 0;

$flushRow = function () use (&$rowsHtml, &$currentContentId, &$currentTitle, &$currentTotal, &$currentDeleted) {
    if ($currentContentId === null) {
        return;
    }

    $rowsHtml .= '<tr><td>' . htmlspecialchars((string)$currentTitle, ENT_QUOTES, 'UTF-8') . '</td><td>' .
        (int)$currentTotal . '</td><td>' . (int)$currentDeleted . "</td></tr>\n";
};

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    $contentId = (int)$row['content_id'];

    if ($currentContentId !== $contentId) {
        $flushRow();
        $currentContentId = $contentId;
        $currentTitle = (string)$row['page_title'];
        $currentTotal = 0;
        $currentDeleted = 0;
        $keptForCurrent = 0;
    }

    $total++;
    $currentTotal++;

    if ($keptForCurrent < $maxVersions) {
        $keptForCurrent++;
        continue;
    }

    $versionId = (int)$row['version_id'];
    if (!$dryRun) {
        $delete = $modx->query("DELETE FROM `{$table}` WHERE version_id = {$versionId}");
        if (!is_object($delete)) {
            return 'VersionX cleanup delete error for ' . htmlspecialchars($currentTitle, ENT_QUOTES, 'UTF-8') .
                ': ' . print_r($modx->errorInfo(), true);
        }
    }

    $deleted++;
    $currentDeleted++;
}

$flushRow();

$optimizeMessage = '';
if (!$dryRun && $optimize) {
    $optimized = $modx->query("OPTIMIZE TABLE `{$table}`");
    if (!is_object($optimized)) {
        $optimizeMessage = '<p>Optimize error: ' . htmlspecialchars(print_r($modx->errorInfo(), true), ENT_QUOTES, 'UTF-8') . '</p>';
    }
}

return '<h3>VersionX Cleanup for ' . htmlspecialchars($table, ENT_QUOTES, 'UTF-8') . '</h3>' .
    ($dryRun ? '<p><strong>Dry run:</strong> rows were not deleted.</p>' : '') .
    '<p>Total records: <strong>' . (int)$total . '</strong>
Total deleted: <strong>' . (int)$deleted . '</strong></p>' .
    $optimizeMessage .
    '<table class="table table-striped"><thead><tr><th>Page name</th><th>Total found</th><th>Deleted</th></tr></thead><tbody>' .
    $rowsHtml .
    '</tbody></table>';
Итого имеем удобный вывод с вкладками.
<cut/>
Из статьи на моем сайте gowindo.ru/articles/modx/versioncleanxtabs-chistka-ot-ustarevshix-versij
Сергей Карпович
18 мая 2026, 17:28
0
github.com/VolgaIgor/editorjs-gallery — как будто то что нужно, основан на editor-js/image, имеет настройки галереи сетка или слайдер, как я понимаю
Андрей Шевяков
18 мая 2026, 14:54
0
Эту строчку:
ym('XXXXXX','reachGoal','TARGET_NAME')
Надо поменять на вашу из Яндекс метрики.

Для MODX 2 и miniShop2 все должно работать.

Не будет ли такого что пользователь будет обновлять по нескольку раз страницу успешного заказа и событие будет каждый раз засчитываться?
Нет не будет, скрипт отправит цель в Метрику, один раз, при создании заказа и все.
Максим
18 мая 2026, 13:45
0
Сделал в ручную. эту базу. При установке вообще не каких баз не создает
Павел Гвоздь
18 мая 2026, 13:00
0
который поддерживает 3 CLI интерфейса opencode, claude, codex. Это я к тому, что можно сделать адаптер под CLI, чтобы при старте сессии он читал стандартный системный Промт, в котором написано прочитать твой универсальный Промт
Вроде в aiAssist ровно так… ну разве, что кроме поддержки OpenCode CLI (пока не требуется).

А что касается агента в телеге:

Это Fcklaw – мой ответ OpenClaw. 🫠
Он тоже кстати, как и aiAssist, спавнит Claude Code CLI и Codex CLI напрямую, без API ключей.
Олег
18 мая 2026, 12:36
0
UPD. В чанк Scripts вставил, событие не работает и не фиксируется метрикой. И ещё момент, как сделать так чтобы был один заказ — одно событие? Не будет ли такого что пользователь будет обновлять по нескольку раз страницу успешного заказа и событие будет каждый раз засчитываться?
Максим
18 мая 2026, 10:05
0
Это при установки, в итоге не создает полностью все базы…
Иван Бочкарев
18 мая 2026, 10:02
0
Добрый день!

Это на какой версии проявляется?
Иван Бочкарев
18 мая 2026, 09:58
0
Привет!

Проверь в новой версии (выложил в магазин)
Максим
18 мая 2026, 09:52
0
Добрый день, выходит ошибка:
[MiniShop3] Migration execution failed: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'cc54143_pmk3.ms3_grid_fields' doesn't exist
[MiniShop3] Migration error: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'cc54143_pmk3.ms3_grid_fields' doesn't exist
[MiniShop3] Stack trace: #0 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php(462): PDOStatement->execute(Array) #1 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php(186): Phinx\Db\Adapter\PdoAdapter->bulkinsert(Object(Phinx\Db\Table\Table), Array) #2 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TimedOutputAdapter.php(103): Phinx\Db\Adapter\AdapterWrapper->bulkinsert(Object(Phinx\Db\Table\Table), Array) #3 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/AdapterWrapper.php(186): Phinx\Db\Adapter\TimedOutputAdapter->bulkinsert(Object(Phinx\Db\Table\Table), Array) #4 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/TablePrefixAdapter.php(373): Phinx\Db\Adapter\AdapterWrapper->bulkinsert(Object(Phinx\Db\Table\Table), Array) #5 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Db/Table.php(692): Phinx\Db\Adapter\TablePrefixAdapter->bulkinsert(Object(Phinx\Db\Table\Table), Array) #6 /home/c/cc54143/sitev3/core/components/minishop3/migrations/20251127000002_seed_customers_grid_config.php(171): Phinx\Db\Table->saveData() #7 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Migration/Manager/Environment.php(112): SeedCustomersGridConfig->up() #8 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php(413): Phinx\Migration\Manager\Environment->executeMigration(Object(SeedCustomersGridConfig), 'up', false) #9 /home/c/cc54143/sitev3/core/components/minishop3/vendor/robmorgan/phinx/src/Phinx/Migration/Manager.php(384): Phinx\Migration\Manager->executeMigration('production', Object(SeedCustomersGridConfig), 'up', false) #10 /home/c/cc54143/sitev3/core/packages/minishop3-1.10.1-beta1/MODX/Revolution/modCategory/3abf075ee3f2ca08e978d1b7fe3ff5b3.resolver_02_migrations.resolver(108): Phinx\Migration\Manager->migrate('production') #11 /home/c/cc54143/sitev3/core/vendor/xpdo/xpdo/src/xPDO/Transport/xPDOVehicle.php(216): include('/home/c/cc54143...') #12 /home/c/cc54143/sitev3/core/vendor/xpdo/xpdo/src/xPDO/Transport/xPDOObjectVehicle.php(218): xPDO\Transport\xPDOVehicle->resolve(Object(xPDO\Transport\xPDOTransport), Object(MODX\Revolution\mysql\modCategory), Array) #13 /home/c/cc54143/sitev3/core/vendor/xpdo/xpdo/src/xPDO/Transport/xPDOObjectVehicle.php(77): xPDO\Transport\xPDOObjectVehicle->_installObject(Object(xPDO\Transport\xPDOTransport), Array, Array, NULL, NULL) #14 /home/c/cc54143/sitev3/core/vendor/xpdo/xpdo/src/xPDO/Transport/xPDOTransport.php(263): xPDO\Transport\xPDOObjectVehicle->install(Object(xPDO\Transport\xPDOTransport), Array) #15 /home/c/cc54143/sitev3/core/src/Revolution/Transport/modTransportPackage.php(346): xPDO\Transport\xPDOTransport->install(Array) #16 /home/c/cc54143/sitev3/core/src/Revolution/Processors/Workspace/Packages/Install.php(73): MODX\Revolution\Transport\modTransportPackage->install(Array) #17 /home/c/cc54143/sitev3/core/src/Revolution/Processors/Processor.php(208): MODX\Revolution\Processors\Workspace\Packages\Install->process() #18 /home/c/cc54143/sitev3/core/src/Revolution/modX.php(1784): MODX\Revolution\Processors\Processor->run() #19 /home/c/cc54143/sitev3/core/src/Revolution/modConnectorResponse.php(151): MODX\Revolution\modX->runProcessor('Workspace/Packa...', Array, Array) #20 /home/c/cc54143/sitev3/core/src/Revolution/modConnectorRequest.php(89): MODX\Revolution\modConnectorResponse->outputContent(Array) #21 /home/c/cc54143/sitev3/core/src/Revolution/modConnectorRequest.php(77): MODX\Revolution\modConnectorRequest->prepareResponse(Array) #22 /home/c/cc54143/sitev3/public_html/kSo3uAxr/index.php(81): MODX\Revolution\modConnectorRequest->handleRequest() #23 {main}

как быть и что делать)))
Олег
18 мая 2026, 09:42
0
Спасибо, а куда нужно это вставить? В Чанк scripts?
Сергей Карпович
17 мая 2026, 22:19
0
Иван, подскажи пожалуйста по настройке пути загрузки файлов или медиа источника.
Проблема такая: если загружаю фото в редактор выбирая его с компа — то путь к изображению строится от источника файлов.
А если выбираю с браузера по кнопке Обзор, то путь прописывается абсолютный — от домена и перед именем файла двойной слешь.

Также, в системных настройках компонента не хватает натсройки mxeditorjs.file_upload_path
Если прикреплять блоки с файлами, то используется mxeditorjs.image_upload_path, что не совсем корректно для файлов.
Сергей Карпович
17 мая 2026, 21:36
0
В настройках компонент указал gallery, но в блоках она не появилось, полазил в исходниках компонента, там не нашел gallery