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

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

С нами с 01 июля 2013; Место в рейтинге пользователей: #27
Максим Кузнецов
10 июля 2018, 20:25
0
C любой страницы, кроме главной, не проходит авторизация через соцсети.
Максим Кузнецов
02 июля 2018, 11:15
0
К сожалению, это взаимно..)

Не за что.
Максим Кузнецов
02 июля 2018, 10:47
0
Чаще всего используется аутентификация через Office. Во-первых, потому что ajax, csrf-токены, возможность авторизации по телефону (с смс-подтверждением). Плюс, сопутствующий функционал редактирования профиля, восстановления пароля, авторизации через соцсети а так же легкая расширяемость любых функций через ООП.

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

Резюмируя:
— если хотите научиться как сейчас модно-молодежно, то приобретите Office и ковыряйтесь в нем.
— если хотите быстро-удобно-безопасно, то тот же вариант
— если хотите бесплатно или поностальгировать о давно-ушедших 00-ых, то Login или какой-нибудь велосипед из той же оперы
Максим Кузнецов
28 июня 2018, 09:13
0
Если речь все еще о 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-состояния при первой загрузке страницы
Максим Кузнецов
28 июня 2018, 09:05
0
Если вы используете 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>
Максим Кузнецов
27 июня 2018, 22:03
+1
Указываем для нужного фильтра outer-чанк, в котором до плейсхолдера {$rows} дописываем:
<label for="mse2_resource|{$название поля}_all" class="">
	<input type="radio" name="{$название алиаса поля}" id="mse2_resource|{$название поля}_all" value=""{$.get.название_гет_параметра | length == 0 ? 'checked' : ''} /> Все
</label>

Суть: поскольку используется radio-инпут, достаточно передавать в value пустое значение, чтобы сбросить гет-параметр.

Пример реализации.
Максим Кузнецов
19 июня 2018, 19:37
0
> 1С сама выполняет чтение и запись в базу SQL на сайте, без сторонних внешних компонентов
> Демонстрация возможностей компонентов
яннп… :(
Максим Кузнецов
27 мая 2018, 11:34
0
Попробуй так:
{'!GetComparison' | snippet : ['list' => ($parent | resource : 'idcategory')]}

//или так:
{var $parent_idcategory = $parent | resource : 'idcategory'}

{'!GetComparison' | snippet : ['list' => $parent_idcategory]}
Максим Кузнецов
25 мая 2018, 16:59
0
В UserFiles есть встроенный cropper для таких задач.

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

Шаг 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 раздела (основные страницы и страницы услуг).
Максим Кузнецов
22 мая 2018, 21:23
0
Да, к слову, чтобы не плодить сущности, правильнее сделать так:
{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}
Максим Кузнецов
22 мая 2018, 21:21
+1
По поводу вашей задачи, я бы прибегнул к такому способу: если у вас прослеживается прямая зависимость от услуги к публикации/примеру, то можно, условно, создать 3 контекста (услуги, публикации, примеры).
При создании ресурсов определенных шаблонов (категория услуги и страница услуги) в первом контексте, на событие OnDocFormSave и $mode == 'new', создавать аналогичные ресурсы с тем же названием в двух остальных контекстах. Код плагина будет примерно похож на данный.

Таким образом, можно или в автоматическом режиме в том же плагине проставлять id связанных ресурсов в тв-поля «основного», или ограничиться полным соответствием поля pagetitle для сопоставимых рубрик, и выводить «сопутствующие» рубрики уже по нему.
Максим Кузнецов
22 мая 2018, 00:31
+1
В плагине выше допишите условие по такому принципу:
if ($delivery->get('id') == 'нужный_айди') {
	//обнуление стоимости
}
Максим Кузнецов
20 мая 2018, 11:44
1
+4
Способ 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.

Минусы: реализация займет гораздо больше времени, а требования к навыкам намного выше.
Максим Кузнецов
19 мая 2018, 21:55
0
Можно синхронизировать файлы Tickets с ms2Gallery из коробки.

После этого вы сможете загружать файлы к тикетам из админки.
Максим Кузнецов
18 мая 2018, 16:06
0
Все и работает потому, что в событии сохранения ресурса массив $resource есть, а в заказе — нет.

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

Что же до вашего участка кода… Не совсем улавливаю, для чего вы изменяете коробочный сниппет для вашей задачи. Скорее всего, её можно решить изящнее, например с помощью того же fenom-a.
Максим Кузнецов
18 мая 2018, 15:52
0
Попробуйте отправить запрос на коннтектор тикетов, ориентируясь на данный участок кода. Только не забудьте, что в процессе потребуется наличие нужных прав.
Максим Кузнецов
15 мая 2018, 19:47
+2
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());
		}
	});
});
Максим Кузнецов
15 мая 2018, 16:48
+2
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());
	}
});