Максим Кузнецов

Максим Кузнецов

Был в сети 04 августа 2021, 21:51
Заказы принимаю
Чаще всего используется аутентификация через Office. Во-первых, потому что ajax, csrf-токены, возможность авторизации по телефону (с смс-подтверждением). Плюс, сопутствующий функционал редактирования профиля, восстановления пароля, авторизации через соцсети а так же легкая расширяемость любых функций через ООП.

Если писать все самому с нуля, то делается примерно так:
— создаете требуемую html-форму
— при submit-e отправляете с помощью ajax-a данные на ваш action.php, который уже будет взаимодействовать с бэк-логикой
— обрабатываете данные (валидация, допустимая длина символов и тд)
— в зависимости от результата возвращаете ответ и выводите через js
— если все ОК, то регистрируете/авторизовываете/редактируете через те же процессоры

Резюмируя:
— если хотите научиться как сейчас модно-молодежно, то приобретите Office и ковыряйтесь в нем.
— если хотите быстро-удобно-безопасно, то тот же вариант
— если хотите бесплатно или поностальгировать о давно-ушедших 00-ых, то Login или какой-нибудь велосипед из той же оперы
Если речь все еще о radio-кнопке, то примерно так:

//Допустим, мы хотим добавить кнопку для родного поля ресурса parent, указав ему псевдоним category

//'filters' => '
//	resource|parent:default
//',
//'aliases' => '
//	resource|parent==category
//'

//Код чанка будет следующим:
<label for="mse2_resource|parent_all" class="">
	<input type="radio" name="category" id="mse2_resource|parent_all" value=""{$.get.category | length == 0 ? 'checked' : ''} /> 
	<span>Сбросить фильтр category</span> 
</label>

Расшифровка:
— суффикс _all нужен исключительно для наглядности, вы можете заменить содержимое for и id на любое другое уникальное для фильтров данной категории значение.
— в name передается название псевдонима фильтра. Если псевдоним отсутствует, указывается значение из filters (resource|parent)
— в value ничего не указывается, он остается пустым (чтобы при нажатии функция mFilter2 сбросила значение требуемого фильтра)
— обработка гет-параметра ($.get.category) нужна для проставления checked-состояния при первой загрузке страницы
Если вы используете checkbox + старый mFilter2, я бы посоветовал сделать так:
1. Подключаете любую js-библиотеку, взаимодействующую с get-параметрами или написать свои функции для их получения и изменения. Например, такие:

function getURLParam(name) {
	return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
}


function changeUrlParam(key, value, url) {
	if (!url) {
		url = window.location.href;
	}

	var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"), hash;

	if (re.test(url)) {
		if (typeof value !== 'undefined' && value !== null) {
			url = url.replace(re, '$1' + key + "=" + value + '$2$3');
		}
		else {
			hash = url.split('#');
			url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '');
			if (typeof hash[1] !== 'undefined' && hash[1] !== null)  {
				url += '#' + hash[1];
			}
		}
	}
	else {
		if (typeof value !== 'undefined' && value !== null) {
			var separator = url.indexOf('?') !== -1 ? '&' : '?';
			hash = url.split('#');
			url = hash[0] + separator + key + '=' + value;
			if (typeof hash[1] !== 'undefined' && hash[1] !== null) {
				url += '#' + hash[1];
			}
		}
	}
	
	if (key && (value == '' || value === null)) {
		//Присмотреться за работой - может стоит заменить на более обширную регулярку ([^&;]+?)(&|#|;|$)
		var regular = new RegExp('[?|&]' + key + '=');
		
		url = url.replace(regular, "");
	}
	
	window.history.replaceState('', '', url);
}

2. Добавляете в нужном месте кнопку сброса:
<a onclick="changeUrlParam('category', ''); mSearch2.submit();">Сбросить поле 'category'</a>
В UserFiles есть встроенный cropper для таких задач.

В рамках ms2gallery из коробки ничего похожего нет, поэтому или править исходники, дописывая нужный обработчик, или сторонний компонент, который будет выцеплять уже загруженные к ресурсу изображения и обрабатывать их с последующим сохранением в отдельную директорию (т.е. писать все с нуля).
Если не вдаваться в сильное извращение, я бы поступил так:

Шаг 1.
Создается мультиселект-tv-поле (tv1) для страниц услуг, в котором выводятся все категории (в формате pagetitle==id)

Шаг 2.
Все страницы услуг создаются в едином разделе (во «всех услугах» или каком-то скрытом, например)

Шаг 3.
Создается тв-поле «id подходящих услуг» для категорий с услугами (tv2).
Это нужно для того, чтобы отображения списка подходящих услуг в категории происходила быстрее (не тратя время на where IN для каждой услуги).

Шаг 4.
Вешется плагин на создание/изменение страниц услуги, при котором в tv2 всех выбранных категории из tv1 переносился id сохраненной услуги.

Шаг 5.
Теперь нужно позаботимся о том, чтобы страницы услуг были доступны внутри любой из подходящих категорий. При помощи
кастомной маршрутизации на событие OnPageNotFound вешаем подходящий вам паттерн.
Предположим, что все услуги имеют следующий вид ссылок:
/services/(category_name)/(service_name)

Тогда логика будет примерно такая:
— срабатывает событие OnPageNotFound
— превращаем запрошенную ссылку в массив
— если длина массива == 3, а первый элемент в нем == 'services', то работаем дальше
— ищем страницу вида /services/(category_name)
— если находим, то ищем услугу с alias == (service_name)
— если находим и её, то из объекта найденной категории получаем значение tv2 и ищем в нем id услуги
— если есть совпадение, то делаем sendForward, иначе отпускаем плагин на 404.

Шаг 5.
Выводим список подходящих услуг в категории по принципу &resources=`[[*tv2]]`. В чанке вызова формируем ссылку на услуги примерно таким образом:
{$_modx->resource.parent | url}/{$alias}

$_modx->resource.parent | url лучше сформировать вне вызова сниппета и передать готовый результат уже в него, чтобы не плодить кучу одинаковых запросов

Хлебные кроши.
Тут все просто, если у нас сгенерирована виртуальная страница через sendForward, то в плагине передаем в неё какой-либо свой плейсхолдер. Дальше от него в шаблоне формируются if-условия. В случае с хлебными крошками можно просто выводить их до «родителя» и дописывать текущую страницу в последнем пункте.

Карта сайта и СЕО
Тут у вас есть, условно, 2 важных момента:

1. Канонический url, во избежании дублей (link rel=«canonical»).
Можно указать или родной url ресурса (там, где они физически хранятся), или создать еще одно tv-поля для страниц услуги «основная категория» и формировать адрес в зависимости от неё.
В случае, если был выбран второй вариант, доступность услуг по «физическому» адресу нужно перекрыть (опять же, в шаблоне услуг можно проверять наличие плейсхолдера из плагина, и при его отсутствии создавать редирект).

2. Карта сайта.
Если с каноническим url был выбран вариант «физического адреса», то с картой не нужно проводить дополнительных манипуляций. В противном случае, физические адреса из вызова pdoSitemap-a нужно исключить и дополнительно сгенерировать виртуальные ссылки. (для минимализации времени генерации результата я бы разбил sitemap на 2 раздела (основные страницы и страницы услуг).
Да, к слову, чтобы не плодить сущности, правильнее сделать так:
{if $_modx->resource.id == 3}
	{'pdoMenu' | snippet : [
		'parents' => $_modx->resource.id,
		'level' => 1,
		'tplOuter' => '@INLINE  <div style="padding: 15px; text-align: center!Important;">{$wrapper}</div>',
		'tpl' => '@INLINE   <h2 style="padding: 15px; margin:0; display: inline-block;" class="text_bold text_size_h2">
			<a href="{$link}">{$menutitle}</a>
		</h2>{$wrapper}'
	]}
{/if}

{$_modx->resource.content}
Способ 1:

1. Создаешь страницу «Дневники» (/diary/), в которой будут храниться дневники всех пользователей. Лучше сделать страницу
коллекцией, чтобы админка не захламлялась.

2. Создаешь плагин, который при регистрации пользователя нужной группы создает раздел с тикетами в странице из шага 1. Код плагина будет примерно таким.

3. Для шаблона разделов с дневниками и страниц с записями (тикеты) закрываешь просмотр всем, кроме администратора и автора:
{if $_modx->isAuthenticated($_modx->context.key) == 0}
	Для просмотра публикации необходимо авторизоваться
{else}
	{if ($_modx->user.id | ismember : 'Administrator') || ($_modx->user.id == $_modx->resource.createdon)}
		Контент
	{else}
		{$_modx->sendRedirect($_modx->makeUrl(1), ['responseCode' => 'HTTP/1.1 403 Forbidden'])}
	{/if}
{/if}

4. Закрываем страницы от индексации, sitemap-a и прочее СЕО.

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

Минусы: плодятся множество страниц. При большом объеме пользователей и их хорошей активности, объем БД и нагрузка на процессоры, связанные с работой ресурсов MODX-a (очистка кэша, генерация дерева и тд) будет выше, чем от самостоятельной таблицы.


Способ 2:

1. Создаешь раздел тикетов «Дневники» (/diary/). Все тикеты всех пользователей добавляются в данный раздел.

2. В шаблоне из пункта 1 совершаешь проверку пользователей по такому принципу:
{if $_modx->isAuthenticated($_modx->context.key) == 0}
	Для просмотра публикации необходимо авторизоваться
{else}
	{if ($_modx->user.id | ismember : 'Administrator')}
		{var $where = ''}
	{else}
		{var $where = ' "createdby": ' ~ $_modx->user.id}
	{/if}

	Дальше передаем {$where} в сниппет, выводящий список тикетов, чтобы показывать лишь нужные данные.
{/if}

3. Аналогично закрываем страницу тикета, sitemap, СЕО и тд и тп.

Плюсы: меньше разделов тикетов и глубина вложенности страниц, чем в пункте 1.

Минусы: не так удобно смотреть записи из админки.


Способ 3:

1-N. Написать свою таблицу с нужными полями, свою маршрутизацию запросов и вообще все с нуля.

Плюсы: самое быстрая итоговая скорость, минимально отягощающая работу самого MODX-a.

Минусы: реализация займет гораздо больше времени, а требования к навыкам намного выше.
Все и работает потому, что в событии сохранения ресурса массив $resource есть, а в заказе — нет.

По поводу msGetOrder — честно говоря, без понятия. Насколько я вижу, сниппет msGetOrder, из коробки вызывается только при отправлении письма менеджеру или пользователю.

Что же до вашего участка кода… Не совсем улавливаю, для чего вы изменяете коробочный сниппет для вашей задачи. Скорее всего, её можно решить изящнее, например с помощью того же fenom-a.
UPD2: Вдогонку, раз изменили шаг 4, нужно подправить и шаг 5:

$(document).ready(function(){
	$('.bimbo-real').each(function(){
		if ($(this).val().split("||") instanceof Array) {
			//В зависимости от указанного в админке разделителя, заместо || может быть запятая
			$('.bimbo-input[data-real="' + $(this).attr("name") + '"]').val($(this).val().split("||"));
		}
		else {
			$('.bimbo-input[data-real="' + $(this).attr("name") + '"]').val($(this).val());
		}
	});
});
UPD: Упс, забыл добавить преобразование массива значений в пригодный вид для мультиселектора админке:

//измененный шаг 4
$(document).on('change', '.bimbo-input', function(e) {
	var real_form = $('input[name="' + $(this).attr("data-real") + '"]');

	if ($(this).val() instanceof Array) {
		real_form.val('');

		$.each($(this).val(), function(key, value) {
			if (real_form.val() == "") {
				real_form.val(value);
			}
			else {
				//Если разделитель отличается от дефолтного - указать актуальный
				real_form.val(real_form.val() + '||' + value);
			}
		});
	}
	else {
		real_form.val($(this).val());
	}
});