Василий Наумкин

Василий Наумкин

С нами с 08 декабря 2012; Место в рейтинге пользователей: #1
Василий Наумкин
23 января 2016, 10:55
2
+1
Отвечает «отец-основатель», который отказывается обучать javascript всех желающих в техподдержке дополнения.

Я действительно не понимаю, в чём может быть проблема вывести какие-то значения в чанке, а потом скопировать их через javascript.
Если это получается сделать с title и alt, что мешает это сделать с другими элементами? Очевидно, вы просто не знаете, как работать с jQuery.

В моём коде title и alt — это атрибуты тега img, поэтому и обращение к ним идёт через image[0].title и image[0].alt. Если рядом с картинкой будет элемент с классом, например, desc, то получить его можно так:
var image = $(this).find('img'); // здесь ищем картинку - это из оригинального кода

var desc = $(this).find('.desc'); // а рядом можно поискать элемент с классом desc
// После этого содержимое элемента desc можно вставить куда угодно. Например, в элемент .mainImageDesc 
var imageDesc = gallery.find('.mainImageDesc');
imageDesc.text(desc.text());
Это весь нужный javascript код. 80 lvl не требуется даже близко.

Чтобы всё работало, нужно не забыть вывести описание на страницу:
<div class="span2 col-md-2">
	<a href="[[+360x270:default=`[[+url]]`]]" class="thumbnail" data-image="[[+url]]">
		<img src="[[+120x90]]" alt="[[+alt]]" title="[[+name]]" width="120" height="90">
		<div class="desc" style="display:none;">[[+description]]</div>
	</a>
</div>
Я вывожу его в скрытом виде, чтобы не мешало.

А еще нужно предусмотреть блок для вывода текста в главной картинке
<div class="ms2Gallery">
	<a rel="fancybox" href="[[+url:default=`/assets/ms2gallery/minishop2/img/web/ms2_big.png`]]" target="_blank">
		<img src="[[+360x270:default=`/assets/components/ms2gallery/img/web/ms2_medium.png`]]" width="360" height="270" alt="[[+alt]]" title="[[+name]]" class="mainImage" />
	</a>
	<div class="mainImageDesc">[[+description]]</div>
	<div class="row">
		[[+rows]]
	</div>
</div>

Как видно, речь исключительно о работе с javascript, никаких особенностей, связанных с ms2Gallery, здесь нет.
Fi1osof
05 января 2016, 14:41
3
+6
Сорри за долгий ответ, отвлекся.

Но по моему, дело в том, что теги Fenom на странице отрабатывают после процессинга документа
Все верно. Из-за этого и была проблема. Дело в том, что $modx->regClientStartupScript() и подобные методы работают со свойствами самого $modx, а вот при сохранении кеша используются свойства самого ресурса. А так, как в отработанных уже после процессинга тегах выполняется типа $modx->regClientStartupScript() (который устанавливает свойства для $modx, но не устанавливает их для $modx->resource), то при генерации кеша документа этих скриптов в кеше просто нет. joxi.ru/4Ak3wb9tMX8nGA
Решение: пишем плагин на событие OnBeforeSaveWebPageCache, простейший вид:
$modx->resource->_jscripts = $modx->jscripts;
$modx->resource->_sjscripts = $modx->sjscripts;
$modx->resource->_loadedjscripts = $modx->loadedjscripts;
И тогда при генерации кеша документа будут сохранены все скрипты. joxi.ru/LmGVQx0uRJN1Xr
При чем это будет выполняться только при первом заходе на страницу. Когда документ уже закеширован будет, это не будет выполняться.

UPD: Может даже имеет смысл это в ядро запулить (то есть код кешманагера поправить), так как очень похоже на багу самого MODX-а. Какая-то глупость в двух отдельных сущностях хранить эти переменные и создавать/получать в разных местах на разных этапах.
Василий Наумкин
19 ноября 2015, 10:23
10
+7
My quick example:
<?php
$tplWrapper = '@INLINE <ul>{{+output}}</ul>';
$tplYear = '@INLINE <li>{{+year}}<sup>({{+count}})</sup><ul>{{+resources}}</ul></li>';
$tplMonth = '@INLINE <li>{{+month}}<sup>({{+count}})</sup><ul>{{+resources}}</ul></li>';
$tpl = '@INLINE <li><a href="{{+uri}}">{{+pagetitle}}</a></li>';

$pdo = $modx->getService('pdoFetch');

$resources = $pdo->getCollection(
	'modResource',
	array('published' => true, 'deleted' => false),
	array('parents' => 0, 'sortby' => 'createdon', 'sortdir' => 'DESC')
);
$tree = array();
foreach ($resources as $resource) {
	$year = date('Y', $resource['createdon']);
	$month = date('m', $resource['createdon']);
	$tree[$year][$month][] = $resource;
}

$output = '';
foreach ($tree as $year => $months) {
	$tmp1 = '';
	$count = 0;
	foreach ($months as $month => $resources) {
		$tmp2 = '';
		foreach ($resources as $resource) {
			$tmp2 .= $pdo->getChunk($tpl, $resource);
			$count++;
		}
		$tmp1 .=  $pdo->getChunk($tplMonth, array(
			'month' => $month,
			'count' => count($resources),
			'resources' => $tmp2,
		));
	}
	$output .=  $pdo->getChunk($tplYear, array(
		'year' => $year,
		'count' => $count,
		'resources' => $tmp1,
	));
}

return $pdo->getChunk($tplWrapper, array('output' => $output));

Result:
Василий Наумкин
27 августа 2015, 16:59
3
+2
В общем, этот конкретный недостаток — суть продолжение достоинств Fenom.

Все ajax запросы eventsCalendar2 отправляются на текущую страницу, где сниппет должен их словить, обработать, выдать ответ и прервать работу парсера. В общем, как написано в этой статье.

И всё отлично работает, за исключение того, что при компиляции шаблона Fenom он прописывает вывод всех данных не в переменную, которую покажет в конце, а выводит их сразу через echo.

Соотвественно, в ответ ajax запроса попадают все сниппеты, обработанные до eventsCalendar2 — и ты видишь их на странице. Это косяк старой архитектуры eventsCalendar2, ведь по уму нужно бы отправлять все запросы на отдельный коннектор или плагин, как в mFilter2 и miniShop2.

Но можно выйти из ситуации и сейчас:
1. Нужно вызывать eventsCalendar2 в шаблоне, перед всеми другими сниппетами, и сохранять его вывод в переменную.
2. А переменную уже выводить на странице. Но не в контенте, а тоже в шаблоне или в чанке, потому что ресурс обрабатывается перед шаблоном и переменная там еще будет пуста.

Тогда при обычной загрузке страницы сниппет отработает, данные сохранятся в переменную и выведутся в нужном месте страницы. А при ajax запросе сниппет отреагирует первым и выдаст чистый ответ, без других сниппетов.

Примерно так:
{set $calendar = $_modx->runSnippet('!eventsCalendar2', [
	...
])}

{$calendar}

Кстати говоря, можно так сохранять и вывод других сниппетов, чтобы вызывать их в одном месте, а показывать в другом.
Василий Наумкин
12 августа 2015, 09:22
4
0
Вот 2 правила для Nginx: первое пропускает превьюшки, а второе запрещает открывать в галерее всё кроме них
location ~* ^/assets/images/products/\d+/\d+x\d+/ {
	access_log		off;
	expires			10d;
	break;
}

location ~* ^/assets/images/products/\d+/ {
	deny			all;
}
Наверное, можно и как-то красивее написать, но я не придумал.

Теперь никто не откроет полноразмерные картинки, и ты можешь отдавать их через PHP покупателям.
Антон ХайЭксель
16 июля 2015, 02:10
3
+1
Просуммирую метод Володи, думаю может понадобится кому-то еще

1. естественно меняем класс обработчик фильтров. Идем в настройки системы и в настройках mSearch2 меняем параметр mse2_filters_handler_class на CustomFilter

2. теперь нам нужно создать сам класс. для этого создаем файл core/components/msearch2/custom/filters/custom.class.php с содержимым

<?php
class CustomFilter extends mse2FiltersHandler {

    public function getMsOptionMyValues(array $keys, array $ids) {
		$filters = array();
		$q = $this->modx->newQuery('msProductOption');
		$q->where(array('product_id:IN' => $ids, 'key:IN' => $keys));
		$q->select('`product_id`,`key`,`value`');
		$tstart = microtime(true);
		if ($q->prepare() && $q->stmt->execute()) {
			$this->modx->queryTime += microtime(true) - $tstart;
			$this->modx->executedQueries++;
			while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
				$value = trim($row['value']);
				if ($value == '') {continue;}
				$key = $row['key'];
				// Get ready for the special options in "key==value" format
				if (strpos($value, '==')) {
					list($key, $value) = explode('==', $value);
					$key = preg_replace('/\s+/', '_', $key);
				}
				// --
				if (isset($filters[$key][$value])) {
					$filters[$key][$value][] = $row['product_id'];
				}
				else {
					$filters[$key][$value] = array($row['product_id']);
				}
			}
		}
		else {
			$this->modx->log(modX::LOG_LEVEL_ERROR, "[mSearch2] Error on get filter params.\nQuery: ".$q->toSql()."\nResponse: ".print_r($q->stmt->errorInfo(),1));
		}
		return $filters;
	}

	public function buildDefaultMyFilter(array $values, $name = '') {
		if (count($values) < 2 && empty($this->config['showEmptyFilters'])) {
			return array();
		}
		$results = array();
		foreach ($values as $value => $ids) {
			$results[$value] = array(
				'title' => $value
				,'value' => $value
				,'type' => 'default'
				,'resources' => $ids
			);
		}
		ksort($results);
		return $results;
	}
	

    public function filterDefaultMy(array $requested, array $values, array $ids) {
		$matched = array();
		$tmp = array_flip($ids);
		foreach ($requested as $value) {
			if (isset($values[$value])) {
				$resources = $values[$value];
				foreach ($resources as $id) {
					if (isset($tmp[$id])) {
						$matched[] = $id;
					}
				}
			}
		}
		
	    $match = $matched;
		$matched = array();
		$count = count($requested);
		$count_values = array_count_values($match);
		foreach ($count_values as $id => $value) {
		    if ($value >= $count) {
		        $matched[] = $id;
		    }
		    else {
		        $matched[] = 0;
		    }
		}
		return $matched;
	}
	
}
осталось добавить в чанк вызова мфильтра строчку

&filters=`
		msoption|tags:DefaultMy'


вуаля — фильтр работает как нужно ;)
Василий Наумкин
23 июня 2015, 05:54
1
+1
На bash я такое написать не смогу, но сделал на Python:
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
import os
import re


def rename(file_path):
    global duplicate
    base_path = os.path.dirname(os.path.realpath(file_path))
    filename = os.path.basename(file_path)
    extension = os.path.splitext(filename)[1]

    new_filename = re.sub(r'[^0-9\_\-]', '', filename)
    new_filepath = base_path + '/' + new_filename + extension
    if new_filepath == file_path:
        print(new_filename + extension)
        return

    while os.path.isfile(new_filepath):
        tmp_filename = os.path.basename(new_filepath)
        tmp_basename = os.path.splitext(tmp_filename)[0]
        idx = 1
        match = re.search(r'.*?\-(\d+)$', tmp_basename)
        if match:
            idx = int(match.group(1)) + 1
            new_filename = re.sub(r'\-(\d+)$', '-' + str(idx), tmp_basename)
        else:
            new_filename += '-' + str(idx)
        new_filepath = base_path + '/' + new_filename + extension
    try:
        os.rename(file_path, new_filepath)
        print("{0} => {1}".format(filename, new_filename + extension))
    except Exception as e:
        print(e)


if __name__ == "__main__":
    if len(sys.argv) < 2:
        exit("Нужно указать имя файла или директории")
    path = os.path.realpath(sys.argv[1].strip())

    if os.path.isdir(path):
        files = os.listdir(path)
        for file in files:
            rename(path + '/' + file)
    elif os.path.isfile(path):
        rename(path)

Файл нужно сделать исполняемым, проверить наличие Python3 и путь к нему на сервере, а затем вызывать с указанием имени файла или путём к директории.

Скрипт переименует все файлы так, что останутся только символы 0-9- и _.

Если файл с новым именем уже есть в директории, то ему (имени) будет добавлена цифра на конце. Во время работы скрипта выводятся имена всех обработанных файлов.

Благодарность можно перечислять вот здесь.

Скрипт поставляется «как есть» — вы используете его на свой страх и риск. Претензии по работе скрипта не принимаются.
Василий Наумкин
23 марта 2015, 06:40
4
+1
Вот за что люблю SEO, так это за привычку утверждать без доказательств. Поэтому, сначала вопросы.

1. Я не смог найти способ проверить наличие дублей, вообще. Все статьи в интернете больше походят на набор бабушкиных сплетен «зайдите в гугл, поищите так, а потом на яндекс, поищите эдак — видите, у вас разные результаты, значит у вас там где-то дубли!».

2. Если на сайте есть страницы, которые Яндекс исключил из поиска, наверное — он должен где-то их показывать? Не вижу


3. Откуда вообще могут взяться дубли по url, если этих url нигде нет? Ссылки на сайте выводятся как надо, в sitemap.xml — тоже. Яндекс сам при индексировании будет убирать слэш у контейнеров, что ли?

Ну а теперь ответы.

Friendly urls — это команды движку открыть какую-то страницу не по номеру, а по имени. Имя отправляется на запрос в index.php и он уже думает, какую страницу отдать. Так что, Apache2 и Nginx здесь сразу не при чём, работать нужно с движком сайта.

Для примера я набросал простой плагин, который сравнивает запрошенный адрес с каноническим для страницы, и если они не совпадают — делает 301 редирект.
<?php
if ($modx->event->name != 'OnLoadWebDocument') {return;}

$uri = $modx->resource->get('uri');
$request = ltrim(urldecode($_SERVER['REQUEST_URI']), '/');
if (strpos($request, '?') !== false) {
	list($request, $params) = explode('?', $request);
}
else {
	$params = '';
}

if (!empty($request) && mb_strtolower($request, 'UTF-8') != mb_strtolower($uri, 'UTF-8')) {
	if (!empty($params)) {
		$uri .= '?' . $params;
	}
	$modx->sendRedirect($uri, array('responseCode' => 'HTTP/1.1 301 Moved Permanently'));
}
Конечно, его нужно внимательно тестировать, но общее направление работ понятно.
Василий Наумкин
04 января 2015, 13:16
4
0
1. Обнови mSearch2 и pdoTools до последних версий
2. Вызови mFilter2
[[!mFilter2?
	&parents=`0`
	&limit=`5`
	&element=`pdoResources`
	&ajaxMode=`button`
	&ajaxElemWrapper=`#mse2_mfilter`
	&ajaxElemRows=`#mse2_results`
	&ajaxElemPagination=`#mse2_pagination`
	&ajaxElemLink=`#mse2_pagination a`
	&ajaxElemMore=`#mse2_mfilter .btn-more`
]]
3. Допиши javascript для скрытия кнопки на странице, когда больше нечего показывать:
$(document).on('mse2_load', function(e, response) {
	var data = response.data;
	var total = data.total;
	var limit = mse2Config['limit'] || mse2Config['start_limit'];
	var page = pdoHash.get()['page'] || 1;
	
	var more = $('#mse2_mfilter .btn-more');
	if (page * limit >= total) {
		more.hide();
	}
	else if (more.is(':hidden')) {
		more.show();
	}
	//setMore();
});
4. По желанию можно добавить функцию setMore, которая будет писать сколько загрузится результатов на кнопке:
function setMore() {
	var btn = $('#mse2_mfilter .btn-more');
	var total = $(mSearch2.options['total']).text();
	var page = pdoHash.get()['page'] || 1;
	var limit = mse2Config['limit'] || mse2Config['start_limit'];
	
	var remains = total - (page * limit);
	if (remains > limit) {
		remains = limit;
	}
	
	var results = 'результатов';
	number = Math.abs(remains);
	number %= 100;
	if (number >= 5 && number <= 20) {
		results = 'результатов';
	}
	number %= 10;
	if (number == 1) {
		results = 'результат';
	}
	if (number >= 2 && number <= 4) {
		results = 'результата';
	}
	
	btn.text('Еще ' + remains + ' ' + results);
}

Вызов этой функции нужно раскомментировать в конце первой функции и добавить при загрузке страницы:
$(document).ready(function() {
	window.setTimeout(function() {
		setMore();
	}, 500);
});

Вот так у меня на тесте всё работает, даже пагинация кнопками. Вот вся тестовая страница.
Василий Наумкин
24 ноября 2014, 15:48
2
+1
А какое именно из всех изображений ресурса, первое? Вот тебе простейший сниппет:
<?php
$pdo = $modx->getService('pdoFetch');
$id = $modx->getOption('id', $scriptProperties, $modx->resource->id);
$rank = $modx->getOption('rank', $scriptProperties, 0);

$res = $pdo->getObject('msResourceFile',
	array('resource_id' => $id, 'rank' => $rank),
	array('loadModels' => 'ms2gallery','select' => 'url')
);
return $res ? $res['url'] : '';

Вызывать как-то так:
[[ms2GalleryImage?id=`[[*id]]`]]