Всего 125 352 комментария

Алексей
12 ноября 2025, 09:40
0
Спасибо большое за точную подсказку, в этой настройке удалил и заработало.… я его блок где-то закомментировал, возможно в /assets/js/scripts.js, думал не актуальный.

«delivery»:
{«transport»:"{core_path}components\/minishop2\/custom\/delivery\/msdeliveryhandlertransport.class.php",
Алексей
11 ноября 2025, 10:43
0
Когда пишу так запрос, все работает:

Акушер-гинеколог==24||COVID-19==37||Аллерголог-Иммунолог==38
В SQL как в первом, так и во втором случае все добавляется правильно «37||24»
Алексей
11 ноября 2025, 10:08
0
Добрый день, может кто поможет на MODX 2.8.8 ставлю ExtraFields все работает все классно, но если делаю тип поля «выпадающей список мультивыбор» из ресурсов, такое условие:

@SELECT `pagetitle`,`id` FROM `modx_site_content` WHERE `parent` = 23 ORDER BY `pagetitle` ASC
то фронтент всего сайта становится белый, в логах ошибок нет, в админке мультивыбор работает. Выявил что если отключаю системное событие OnMODXInit то сайт оживает. Подскажите в чем может быть проблема?
Konstantin
10 ноября 2025, 17:36
0
Заработало! да я по привычке в hook записал, а надо было так:
'filterresources' => [
        'snippet' => 'filterresources',
        'resultBlockSelector' => '#filter-results',
        'resultShowMethod'=>'insert',
    ],
Спасибо тебе большое!
Артур Шевченко
10 ноября 2025, 17:21
1
+1
Каким сниппетом обрабатывается данный пресет по-твоему?
'filter_resources' => [
        'hooks' => 'filter_resources',
        'resultBlockSelector' => '#filter-results',
        'resultShowMethod'=>'insert',
    ],
Konstantin
10 ноября 2025, 17:11
0
Спасибо Артур за ответ, удалил. Удалил также из сниппета всю логику вообще для теста, оставил только
return $SendIt->success('ок', ['html' => 'TEST']);
Ошибки ушли только после того как в названии сниппета убрал нижнее подчеркивание. Но данные пришедшие из сниппета не подставляются в указанный в пресете 'resultBlockSelector' => '#filter-results'. Что-то нужно еще прописать в js?
Артур Шевченко
10 ноября 2025, 15:29
+1
Логи сервера смотри. Но скорее всего путь к какому-то обработчику указан неверное в ms2_services
Артур Шевченко
10 ноября 2025, 15:28
0
$SendIt = $modx->getService('sendit', 'SendIt', MODX_CORE_PATH.'components/sendit/model/sendit/');
Удали
Alexandr
10 ноября 2025, 10:29
0
Спасибо. Вроде получилось, но не могу понять как передать дополнительные поля в CRM и почему-то время не правильное передает, +7 часов.
Prihod
09 ноября 2025, 23:05
0
да, только для импорта данные должны быть указаны в JSON формате
Артур Шевченко
09 ноября 2025, 19:55
0
Чего ж вы репозитории не делаете?
Евгений
09 ноября 2025, 16:46
0
Вот мой вариант. Тоже тут решил заняться вопросом избавления себя от jquery… :)
Сделал отдельный файл pdopage-no-jquery.js. Запихнул его в /assets/components/custom/
Комментарии решил оставить.
Получилось, вроде, неплохо, но кажется, что есть что ещё можно доработать

Вызов PdoPage
<div id="pdopage">
	<div class="section__pagelist">
		[[!pdoPage?
			&parents=`[[*id]]`
			&depth=`0`
			&tpl=`tpl_example`
			&sortby=`{"publishedon": "DESC"}`
			&limit=`6`
			&tplPageActive=`@INLINE <li class="active"><span class="page-link">[[+pageNo]]</span></li>`
			&ajax=`1`
			&ajaxElemRows=`#pdopage .section__pagelist`
			&ajaxElemMore=`#pdopage .btn-loadmore`
    		        &ajaxTplMore=`@INLINE <button class="btn btn-primary btn-sm btn-loadmore">Загрузить ещё</button>`
			&ajaxMode=`button`
			&frontend_js=`[[++assets_url]]/components/custom/pdopage-no-jquery.js`
		]]
	</div>
	[[-!+page.nav]]
</div>
Проверил во всех модах: дефолт (там, собственно, скрипт и не нужен), button, scroll

Сам js-код
const pdoPage = (function () {
  /*
  Параметры, которые прокидывает PdoPage в конфиг
  const basePdoToolsConfig = {
    wrapper: '#pdopage',
    rows: '#pdopage .rows',
    pagination: '#pdopage .pagination',
    link: '#pdopage .pagination a',
    more: '#pdopage .btn-more',
    moreTpl: '<button class='btn btn-primary btn-more'>Загрузить ещё</button>',
    mode: 'button',
    history: 0,
    pageVarKey: 'page',
    pageLimit: '6',
    assetsUrl: '/assets/components/pdotools/',
    connectorUrl: '/assets/components/pdotools/connector.php',
    pageId: 1,
    hash: 'string',
    scrollTop: true,
  };*/

  const additionalConfig = {
    scrollOffset: 150,
    moreBtnLoadingHTML: '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Загружаем...',
    rowsContainerLoadingClass: 'state--progress',
  };

  let keys = {};
  let configs = {};
  let isLoading = false;
  let isEndReached = false;

  function getHashParams() {
    const hash = window.location.hash.substring(1);
    return Object.fromEntries(new URLSearchParams(hash));
  }
  function setHashParams(params) {
    const hash = new URLSearchParams(params).toString();
    window.location.hash = hash;
  }
  function removeHashKey(key) {
    const params = getHashParams();
    delete params[key];
    setHashParams(params);
  }
  function addHashKey(key, value) {
    const params = getHashParams();
    params[key] = value;
    setHashParams(params);
  }

  function insertMoreButton(config) {
    const containerWrapper = document.querySelector(config.wrapper);
    const moreBtn = document.querySelector(config.more);

    if (!containerWrapper || moreBtn !== null) return;

    const moreTpl = config.moreTpl;
    containerWrapper.insertAdjacentHTML('beforeend', moreTpl);
  }

  function removeMoreButton(config) {
    const moreBtn = document.querySelector(config.more);

    if (moreBtn) moreBtn.remove();
  }

  function setLoadingState(config) {
    const moreBtn = document.querySelector(config.more);
    const containerRows = document.querySelector(config.rows);

    containerRows.classList.add(additionalConfig.rowsContainerLoadingClass);

    if (moreBtn) {
      if (!config.moreBtnText) {
        config.moreBtnText = moreBtn.textContent;
      }
      moreBtn.disabled = 'disabled';
      moreBtn.innerHTML = additionalConfig.moreBtnLoadingHTML;
    }
  }

  function removeLoadingState(config) {
    const moreBtn = document.querySelector(config.more);
    const containerRows = document.querySelector(config.rows);

    containerRows.classList.remove(additionalConfig.rowsContainerLoadingClass);

    if (moreBtn) {
      moreBtn.disabled = '';
      moreBtn.textContent = config.moreBtnText;
    }
  }

  function loadPage(url, config, append = false) {
    if (isLoading || isEndReached) return;

    isLoading = true;

    setLoadingState(config);

    fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
      .then((res) => {
        if (!res.ok) throw new Error(`Ошибка сети: ${res.status}`);

        return res.json();
      })
      .then((data) => {
        // data.output — HTML-контент
        // data.pagination — HTML пагинации
        // data.pages — всего страниц
        // data.page — текущая страница

        const containerRows = document.querySelector(config.rows);
        const containerPagination = document.querySelector(config.pagination);

        if (!containerRows) {
          throw new Error('Контейнер ajaxElemRows не найден в DOM');
        }

        if (append) {
          containerRows.insertAdjacentHTML('beforeend', data.output);
        } else {
          containerRows.innerHTML = data.output;
        }

        if (containerPagination) {
          containerPagination.innerHTML = data.pagination;
        }

        const currentPage = Number(data.page) || 1;
        const totalPages = Number(data.pages) || 1;

        // Проверка на конец данных
        if (currentPage >= totalPages || !data.output.trim()) {
          isEndReached = true;
          removeMoreButton(config);
          if (config.onEnd) config.onEnd();
        } else {
          isEndReached = false;
          if (config.mode === 'button') {
            removeLoadingState(config);
            insertMoreButton(config);
          }
        }

        if (config.callback && typeof config.callback === 'function') {
          config.callback();
        }
      })
      .catch((err) => {
        console.error(err);
      })
      .finally(() => {
        isLoading = false;
      });
  }

  function init(config) {
    if (!config) {
      console.error('pdoPage: конфигурация не передана');
      return;
    }

    configs[config.pageVarKey] = config;

    const pageKey = config.pageVarKey;
    const wrapperSelector = config.wrapper;
    const moreBtnSelector = config.more;
    const linkSelector = config.link;

    const params = new URLSearchParams(window.location.search);
    const pageParam = params.get(pageKey) || getHashParams()[pageKey] || '1';
    keys[pageKey] = Number(pageParam);

    switch (config.mode) {
      case 'default':
        document.addEventListener('click', (e) => {
          const target = e.target.closest(linkSelector);
          if (!target) return;
          e.preventDefault();

          const href = target.href;
          const regex = new RegExp(pageKey + '=(\\d+)');
          const match = href.match(regex);
          const page = match ? Number(match[1]) : 1;

          if (keys[pageKey] === page) return;

          keys[pageKey] = page;
          isEndReached = false;

          if (config.ajaxHistory) {
            if (page === 1) {
              removeHashKey(pageKey);
              history.pushState({}, '', href.split('?')[0]);
            } else {
              addHashKey(pageKey, page);
              history.pushState({}, '', href);
            }
          }

          loadPage(href, config, false);
        });
        break;

      case 'button':
        const container = document.querySelector(wrapperSelector);
        if (!container) {
          console.error(`pdoPage: не найден контейнер ${wrapperSelector}`);
          return;
        }

        insertMoreButton(config);

        container.addEventListener('click', (e) => {
          e.preventDefault();

          const target = e.target.closest(moreBtnSelector);
          if (!target) return;

          if (isLoading || isEndReached) return;

          const nextPage = keys[pageKey] + 1;
          const baseUrl =
            target.dataset.baseUrl || window.location.href.split('?')[0];
          const newUrl = new URL(baseUrl);
          newUrl.searchParams.set(pageKey, nextPage);

          keys[pageKey] = nextPage;

          if (config.ajaxHistory) {
            addHashKey(pageKey, nextPage);
            history.pushState({}, '', newUrl);
          }

          loadPage(newUrl.toString(), config, true);
        });
        break;

      case 'scroll':
        if (!config.scrollOffset) {
          config.scrollOffset = additionalConfig.scrollOffset;
        }

        window.addEventListener('scroll', () => {
          if (isLoading || isEndReached) return;

          const containerRows = document.querySelector(config.rows);
          if (!containerRows) return;

          // Получаем позицию bottom элемента со списком относительно документа
          const rect = containerRows.getBoundingClientRect();
          const scrollY =
            window.pageYOffset || document.documentElement.scrollTop;
          const elementBottom = rect.bottom + scrollY;

          // Текущая позиция прокрутки + высота окна
          const scrollPos = window.pageYOffset + window.innerHeight;

          // Сравниваем: если прокрутка достигла или прошла элемент + offset
          if (scrollPos >= elementBottom + config.scrollOffset) {
            const nextPage = keys[pageKey] + 1;
            const baseUrl = window.location.href.split('?')[0];
            const newUrl = new URL(baseUrl);
            newUrl.searchParams.set(pageKey, nextPage);

            keys[pageKey] = nextPage;

            if (config.ajaxHistory) {
              addHashKey(pageKey, nextPage);
              history.pushState({}, '', newUrl);
            }

            loadPage(newUrl.toString(), config, true);
          }
        });
        break;
    }

    if (config.history) {
      window.addEventListener('popstate', () => {
        const params = new URLSearchParams(window.location.search);
        const page = params.get(pageKey) || getHashParams()[pageKey] || '1';

        if (keys[pageKey] !== Number(page)) {
          keys[pageKey] = Number(page);
          const baseUrl = window.location.origin + window.location.pathname;
          const newUrl = baseUrl + '?' + pageKey + '=' + page;

          loadPage(newUrl, config, false);
        }
      });
    }
  }

  return {
    initialize: init
  };
})();
Nail
07 ноября 2025, 20:16
0
У меня была проблема с тем, что сделки не попадали в воронку. Хотя в списке контакт создавался. В итоге надо было просто поставить галочку в сист. настройках. Этот компонент работает на старом апи. Сейчас у амо по-другому устроено.
Alexandr
07 ноября 2025, 16:33
0
Не могу понять что я тогда делаю не так. У меня то 400 ошибка. То потом её вроде нету, но в crm не передаются данные из форм.
Nail
07 ноября 2025, 15:53
0
все еще работает. В октябре делал.
Наумов Алексей
07 ноября 2025, 15:22
0
Я слабо понял суть вопроса. Подозреваю что этот комментарий дублирует суть вопроса modx.pro/help/25398

Еще раз в этом случае — сниппет Login не работает с pdoTools и Fenom синтаксисом.
Евгений Лазарев
07 ноября 2025, 13:32
0
Нашел проблему еще одну. Я так понял нельзя вызывать сниппет с помощью fenom {'!YaSmartCaptcha' | snippet}. Поскольку при авторизации через Login если не проходил проверку то перезагрузится страница и весь fenom выйдет на странице необработанный в виде {'site_name' | option}
Alexey
06 ноября 2025, 19:58
0
Так $this->modx->getChunk() ничего не знает про феном.

$pdoTools = $this->modx->getService('pdoTools');
$pdoTools->getChunk();
Так должно работать
Сергей Шлоков
06 ноября 2025, 19:53
0
Есть системные события, которые позволяют пройти аутентификацию вручную.
Стоит проверить плагины.
Alexandr
06 ноября 2025, 16:45
0
В 2025 заводится? У меня не получилось отправлять данные из форм через formIt или ajaxForm.
Что я делаю не так?