Закрыть
Скидки в Webasyst 25–29 ноября 2021

Леонид Вакуленко Webasyst


Леонид Вакуленко

  • Угу. Если бы не привязка к куче сторонних (и просто модифицированных) тем дизайна, то зоопарк с валютами, конвертацией и округлением был бы не таким пугающим.

    Параметр frontend был и раньше. Он с самого начала влиял на генерацию ссылок, которая у товара зависит от настройки роутинга для витрины.

    в ответ на Цена товара в валюте витрины из бекэнда

  • Коллекция действительно вернёт продукт в том виде, в котором он предназначен для витрины (сгенерятся ссылки в нужном формате, вызовутся плагины по событию frontend_products, применится округление). Но это не всегда включает в себя изменение валюты и перевод цены в валюту фронтенда. Конвертируйте цены уже вне коллекции, используя `shop_currency()` или `shop_currency_html()`. Как это делается во всех темах дизайна, например.

    Коллекция подменяет валюту и цены товаров только если для текущей валюты фронтенда включено округление цен. Если не включено, товары вернутся как есть.

    Если интересно, то вот небольшая историческая справка. Исторически сложилось, что товары в шаблоны тем передаются без конвертации валют. На дворе было сказочное время, класс коллекции ничего не знал про валюты, а темы дизайна использовали `shop_currency_html()` прямо на месте (как впрочем и сейчас используют). Жили они долго и счастливо, пока поверх этого не стало нужно добавить округление цен. Чтобы не надо было менять никакие темы дизайна, округлять надо было в коллекции. И округлять так хитро, чтобы существующие в теме дизайна вызовы `shop_currency` ничего не заметили и не испортили. Пришлось научить коллекцию подменять валюту товара на валюту фронтенда и переводить все цены. Тогда темы можно было не менять. Это объясняет, почему коллекция иногда подменяет валюты товаров.

    в ответ на Цена товара в валюте витрины из бекэнда

  • Гитхаб всё помнит :)

    С theme.xml вроде не должно быть проблем. Изменения в DTD'шке влияют на все темы сразу и не привязаны к версии магазина.

    в ответ на Вопрос версионности (ц) плагинов

  • Это проблема не только сторонних разработчиков, но и плагинов самого вебасиста. И план вебасиста для наших плагинов такой: все существующие плагины, которые были опубликованы для версии 6, продолжат работать на обеих версиях магазина, используя один и тот же исходный код плагина. Обратная совместимость это вполне позволяет делать. Никаких серьёзных проблем пока не возникло.

    Если старому плагину для новых фич нужен функционал, который есть только в 7, плагин обязан аккуратно проверять, на какой версии он установлен, и не использовать новые функции, если их нет. Соответственно, на 6 версии магазина этих новых фич плагина не будет.

    Сложно представить, что один из старых плагинов нам захочется кардинально переписать для 7, так что на 6 он совсем перестанет работать. Если такое случится, будет одно из двух. Либо появится новый плагин, с новым id'шником, который будет опубликован только для 7 версии. Либо (что наименее вероятно) будет объявлено, что поддержка этого плагина для 6 версии больше не оказывается и обновления не выпускаются. Как это однажды было, например, со старыми версиями магазина Pro и Premium, ещё до фреймворка и Shop-Script 5.

    в ответ на Вопрос версионности (ц) плагинов

  • Леонид Вакуленко Леонид Вакуленко Webasyst 3 февраля 2016 08:48 #

    "Флок исправно работает" в данном контексте значит следующее: когда один процесс держит лок, другой процесс не может получить тот же самый лок. То есть в том, что "флок исправно работает" как раз только и можно убедиться, если он вернул false. Что тут и написано.

    Чанк 10 секунд на 2.5 - это, наверное, и правда многовато. Видимо, расчёт был "тише едешь, дальше будешь".

    * * *

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

    У вас не бегает второй параллельный процесс.

    Идея с LongActionController'ом вообще такая. JS из браузера раз в 2-3 секунды дёргает этот контроллер. Постоянно дёргает, независимо от того, есть ли уже другой висящий XHR на этот контроллер или нет. Первый такой запрос становится Runner'ом и виснет на 30+ секунд, ничего не возвращая в JS. А остальные понимают, что они Messenger'ы и сразу возвращают в JS инфу о работающем Runner'е. Вот первый такой Messenger, который отвалился по flock'у и понял, что он не Runner, и должен создать файл flock_ok.

    У вас этого не происходит по какой-то причине. Либо потому что JS не пытается отправлять запросы. Либо потому что браузер блокирует второй запрос и держит его на паузе, пока первый не закончился. Либо потому что сервер не обрабатывает больше одного запроса за раз.

    в ответ на waLongActionController flock

  • Леонид Вакуленко Леонид Вакуленко Webasyst 3 февраля 2016 08:07 #
    //
    // Файл создаётся функцией touch(), строка 706. Создавать его в своём коде не обязательно.
    // Но, наверное, можно, если вы точно уверены в том, что делаете.
    //
    
    if (!touch($this->_files['flock_ok'])) {
        $this->runnerFatalWarning('Unable to create file: '.$this->_files['flock_ok']);
    }
    
    // * * *
    
    //
    // Вычисление времени ожидания базируется на $this->_data['avg_time'], которое в свою очередь -
    // на $this->_data['total_time']. Там не учитывается время, потраченное на получение lock'а, только
    // время непосредственного исполнения step() + _save(), когда все lock'и уже получены.
    //
    
    //
    // Если подробнее, то вот. Я опустил некоторые строки кода в надежде, что логика будет понятнее.
    //
    
    // Это цикл внутри execute() в районе строки 306.
    
    $this->_heartbeat = microtime(true);           // засекли время первый раз
    while ($continue && !$is_done) {
        $this->_data['total_steps']++;
        $continue = $this->step();
        $continue = $this->_save() && $continue;   // добавили total_time и перезаписали _heartbeat, см. метод _save() в районе строки 590.
        $is_done = $this->isDone();
    }
    
    //
    // Проверка lock'ов и получение статуса Runner происходит до этого цикла.
    // Время, потраченное на lock, никогда не попадёт между _heartbeat'ами,
    // никогда не попадёт внутрь total_time, и поэтому не учтётся в avg_time.
    //

    в ответ на waLongActionController flock

  • Леонид Вакуленко Леонид Вакуленко Webasyst 3 февраля 2016 07:01 #

    flock_ok создаётся, если функция flock() работает нормально. На некоторых файловых системах flock() не работает. Windows или не windows тут ни причём: у меня и на windows всё нормально.

    Сделано это для того, чтобы контроллер работал даже когда flock()'у доверять нельзя. Медленно, не торопясь, но надёжно работал.

    * * *

    "Среднее время выполнения скрипта" с геометрической прогрессией расти не будет. Засекается среднее время выполнения метода step(), то есть "всё время работы" делить на "сколько раз мы запускали step()".

    * * *

    flock_ok конечно не создаётся по умолчанию, потому что мы ничего не знаем о файловой системе сервера в общем случае. Если у вас свой сервер на своей установке, ну переопределите у себя метод _mainLock() и используйте любой механизм блокировки, хоть flock, хоть Redis, хоть memcache.

    в ответ на waLongActionController flock

  • Первый пункт сложный.

    Принадлежность товаров к витринам и тегов к товарам - два раза многие-к-многим и в общем случае довольно динамична. Пытаться поддерживать где-то в БД постоянно обновляемое состояние связи тегов с витринами - неблагодарное дело.

    С другой стороны, вычислять теги конкретной витрины на лету - дорого. Большая нагрузка на сайт, тормозить будет.

    Мне кажется, целесообразно пойти следующим путём. По крону раз в несколько часов получать для каждой витрины облако тегов. Честным, тяжёлым способом: получить список типов товаров одной витрины; сделать запрос в БД с джойнами тип товара->товар->тэги. Полученное облако тегов сохранить в файл в кеше. При генерации фронтэнда брать данные из кеша.

    Этот способ не требует модификации исходников магазина и добавления колонок в БД. Всё вполне реализуемо в рамках скромного плагина.

    в ответ на Баги и недоделки Shop-Script 6

  • Создайте категорию-фильтр, настройте её как надо, кроме условий поиска. Название, URL, описание, meta всякие.

    Потом выполните вот такой запрос в БД:

    UPDATE shop_category SET conditions='badge=new' WHERE id=100500

    заменив 100500 на id вашей категории. Категория станет фильтровать по пометке "новый", но редактировать её через бэкэнд будет уже нельзя. То есть можно, но фильтр тогда сломается.

    в ответ на Категория по фильтру "New"

  • Думаю, что было дело в кеше автолоада. Когда добавляете новые классы, очищайте кеш в инталлере (или удаляйте подкаталоги wa-cache), иначе в не-дебаг режиме фреймворк эти новые классы не увидит.

    в ответ на Не запускается waCliController на сервере.

  • Ну, пока что можно только сказать, что нечто в приложении Магазин кидает waException('...', 404). Например, такое может быть, если ваш контроллер пытается прочитать данные несуществующего контакта.

    Включите на сервере режим разработчика и попробуйте запустить CLI ещё раз. Сообщение об ошибке должно стать более полезным.

    в ответ на Не запускается waCliController на сервере.

  • Часть? Пф! Да он весь так живёт. Утром приходим на работу, а всё сломалось. Кто ломал? Никто. Само сломалось.

    в ответ на Как удалить зеленый бейджик?

  • Чёрт его знает. Пока никак. Со временем произойдёт одно из двух: либо бэйджик пропадёт у всех, у кого нет опубликованных плагинов, либо где-то в настройках профиля появится возможность его убрать. Либо и то, и другое...

    в ответ на Как удалить зеленый бейджик?

  • Разговор о "подмене цены продукта" напомнил мне о том, как я сам делал буквально это. Подмену цены продукта во фронтэнде, чтобы ничего не сломалось ни в корзине, ни при создании заказа, ни в темах дизайна. Да ещё и так, чтобы существующие темы дизайна (и по возможности плагины) ничего не заметили. Знаете, что это было? Округление цен, класс shopRounding. Это сработало и пошло в продакшн. Обратите внимание на shopRounding и где он используется. Может, что-нибудь из этого и получится.

    Сама безумная идея, собственно, состоит в том, чтобы вкорячить подмену цены в shopRounding::roundProducts(), ::roundSkus() и соседние методы.

    Можно ли это сделать без модификации кода приложения? На отдельной установке конкретного клиента можно. Надо добиться того, чтобы в автолоад попал ваш shopRounding, а не стандартный. Начните с внимательного всматривания в waAppConfig::getClasses() и поймёте, куда какой файлик нужно поместить.

    Излишне упоминать, что в магазин плагинов это вряд ли пройдёт. И вообще, не говорите Саше Музыченко, что это я вам предложил :)

    Что касается добавления такой штуки в стандартную коробочную версию. Помню, что обсуждалось много раз. Возникают трудно решабельные вопросы. Добавление множественных цен улучшает жизнь маленькой части клиентов, но усложняет жизнь большой части клиентов. Как сделать малой кровью, мы не придумали. Отложили в долгий ящик, где-то рядом с наценкой от способа оплаты и продажей дробного количества товара.

    в ответ на Магазин - несколько цен

  • Никаких особенных настроек не нужно. Наследование тем - это способ сказать "если тебе нужен шаблон X и его нету в текущей теме дизайна, возьми его в родительской". Это способ избежать дублирования кода.

    Если не знаете с чего начать, скопируйте тему Блога Default 2, унаследуйте копию от вашей собственной темы Сайта и посмотрите, что получится. Должно получиться что-то рабочее, от чего можно начать плясать.

    Остальные многабукаф - это я пытаюсь подробно объяснить, какие именно шаблоны нужны и где они живут на примере темы Дефолт 2.

    Так вот. Приложение Блог рассчитывает, что в теме дизайна должен быть определённый набор обязательных шаблонов. Будут они в самой теме или в родительской, Блогу всё равно.

    Вот эти файлы:

    index.html
    page.html
    signup.html
    forgotpassword.html
    login.html
    error.html
    my.profile.html
    my.nav.html
    stream.html
    post.html

    Последние два специфичны для Блога, и их в темах Сайта нету. Остальные - которые в темах Сайта есть - можно унаследовать, если это удобно.

    Теоретически можно себе представить тему дизайна Блога, которая состоит только из этих 10 шаблонов. Но обычно разработчику темы удобно вынести часть HTML в отдельные файлы и подключать их с помощью конструкции {include}. Набор таких шаблонов отличается в разных темах дизайна. Например, в Default 2.х используются такие подшаблоны (список разбит по месту использования):

    post.html
    -> comments.html -> comment.html
    -> post.album.html
    
    stream.html
    -> stream_search.html
    -> stream_posts.html
    
    index.html // в приложении Сайт
    -> head.html
    -> header.html
    -> main.html
    -> footer.html

    в ответ на Как "адаптировать" собственную тему дизайна для использования в качестве родительской

  • Вы правы. Спасибо, исправим.

    в ответ на Объединение контактов - не переносится Дата рождения

  • Если в плагине после того, как сделали что-то с категоряими, то вот такой метод:

    $model = new shopCategoryModel();
    $model->repair();

    Если задача разово починить табличку, то можно воспользоваться такой ссылкой:

    /webasyst/shop/?module=repair&action=categories

    в ответ на shop_category left_key и right_key. Подскажите самый легкий способ выставить их.

  • Мне кажется, это хороший повод рассказать, как архитектурно работает waLongActionController. Более-менее то же самое написано и в комментах к этому классу.

    Есть PHP контроллер и есть JS в браузере. Взаимодействуют они так.

    • [JS] запускает процесс [PHP]. [PHP] немедленно возвращает идентификатор processId нового процесса и не делает никакой реальной работы. Это должно произойти мгновенно и без задержек.
    • После того, как [JS] получил processId, задача [JS] - постоянно напоминать о себе и пинать [PHP], возобновляя работу процесса. Скажем, раз в 30 секунд. Ответ на пинок может прийти или не прийти. Потеря некоторых ответов не имеет особенного значения, если хотя бы иногда информация возвращается в браузер корректно. Главная инфа, которую отслеживает [JS] в ответе сервера - это закончен ли процесс или ещё продолжается. Пока не закончен, продолжаем пинать. Когда закончен, радостно уведомляем юзера, что всё готово. В качестве второстепенной инфы можно получать состояние прогресс-бара или что угодно ещё.
    • Итак, [PHP] раз в 30 секунд получает запрос со стороны [JS]. Логика при получении этого запроса следующая. Вызванный контроллер может стать либо Runner'ом, либо Messenger'ом с таким расчётом, чтобы Runner всегда мог быть только один.
      • Если [PHP] видит, что Runner есть и работает, то он становится Messenger'ом и немедленно возвращает информацию в [JS] о текущем состоянии процесса. Никакой работы Messenger не выполняет. Такие ответы приходят мгновенно и обновляют прогресс-бар в браузере.
      • Если [PHP] видит, что активного Runner'а нет (например, потому что тот сдох, превысив max_execution_time), контроллер сам становится Runner'ом и начинает выполнять работу. В браузер такой вызов контроллера ничего не отправит до самой своей героической смерти. Скорее всего, совсем ничего полезного не отправит.
    • Такая схема работы продолжается, пока вся работа не будет выполнена. С того момента, когда работа заканчивается, все вызовы [PHP] считаются Messenger'ами и возвращают в [JS] инфу о завершившемся процессе. Таких вызовов может быть много, если [JS] логике это по какой-то причине удобно. Завершившийся процесс будет храниться до тех пор, пока [JS] отдельным независимым способом не сообщит в [PHP] о том, что данные процесса больше не нужны. Например, [JS] может дожидаться, пока юзер скачает результат экспорта, и только после этого разрешит [PHP] их удалить.


    * * *

    Так вот. Описанный в первом посте "в самом начале скрипт", который работает 10 минут - это первый Runner, которому настройки PHP+nginx разрешили действовать 10 минут и более. Проблем он не создаёт: даже если он сдохнет и не отправит ничего в браузер (как собственно и должен), очень быстро запустится новый Runner и продолжит работу.

    Найденная ошибка, про которую говорил Владислав, специфична для конкретного импорта CSV, а не для waLongActionController'а в целом. На последнем шаге описанной выше схемы [JS] при определённых обстоятельствах не получал инфы о том, что процесс завершился. Это исправится в очередной версии плагина.

    в ответ на Импорт большого количества товара

  • Или про lib/config/install.php

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

    в ответ на Создание приложения->База данных, файл DB.php

  • Да, капча без GD не работает. Для меня это сюрприз, честно говоря. Серьёзных причин для этого я не вижу. Запишу в хотелки.

    В качестве workaround: если включить обе библиотеки, то для всего остального будет предпочитаться ImageMagick. GD, по сути, будет использоваться только для капчи.

    в ответ на Капча не отображается

  • Это хорошо! Не обижайтесь на меня, параноика. Сами понимаете: новый аккаунт, первое сообщение на форуме публикует ссылку, начиная топик холиварного характера. Email не гуглится. Тут у всякого модератора сразу нехорошие подозрения начнут появляться...

    в ответ на Какая CMS лучше всего подходит для средних интернет-магазинов?

  • Мда, я тоже не нашёл документацию.

    Идея простая. Плагин может подписаться только на события своего родного приложения. А приложение может подписаться на события любого приложения (в том числе свои, если вдруг удобно). Чтобы подписаться, надо создать файл lib/handlers/<app>.<hook>.handler.php с классом yourapp<App><Hook>Handler extends waEventHandler.

    Примеров сколько угодно. В любом приложении есть lib/handlers: Контакты, Блог, Фото, Магазин...

    в ответ на Как сделать, чтобы приложение запускалось при открытии любой страницы

  • wa-config - это не ядро. Ничего не слетит при обновлении. При установке приложения (или, наверное, при нажатии юзером на кнопку "включить") я могу себе представить автоматическое добавление чего надо куда надо. А самая большая опасность в том, что два разных приложения захотят использовать такую технику, и случится что-то плохое.

    Но это, конечно, необычно глубокий уровень чёрной магии, тут спорить не стану.

    Альтернатива (без чёрной магии и без хэлперов в шаблонах) - это подписаться на много хуков, отдельно для каждого имеющегося приложения.

    в ответ на Как сделать, чтобы приложение запускалось при открытии любой страницы

  • Чтобы прям везде, включая бэкэнд... Не очень просто. На хук не подпишешься.

    Вот такой способ есть. В файле:

    wa-config/SystemConfig.class.php

    Переопределить public function init() и там после вызова parent::init() вставить нужный вам код. Минус в том, что в этот момент фреймворка ещё, по сути, нет. Инициализировать приложение не получится, даже wa() не сработает. Подключаться к БД придётся через отдельное соединение:

    $db_config = $this->getDatabase();
    $m = new waModel($db_config['default']);

    Так можно делать что-то простое, типа дописать строчку в файл или в БД. А парсить и по-человечески сортировать данные в приложении уже после (по крону, например).

    Если надо прям кровь-из-носу инициализировать приложение, могу придумать для этого адовые костыли. Но лучше так не делать. Тем более что запускать лишнее приложение при каждой загрузке страницы может оказаться не очень мудро с точки зрения производительности.

    в ответ на Как сделать, чтобы приложение запускалось при открытии любой страницы

  • Схема сохранения заказа в редакторе бэкэнда такая.

    Открывается форма редактирования заказа. lib\actions\order\shopOrderEdit.action.php + templates\actions\order\OrderEdit.html
    Плагины могут использовать хук backend_order_edit, чтобы добавить свой HTML/JS в эту форму.

    Эта форма отправляет AJAX'ом данные для сохранения сюда: lib\actions\order\shopOrderSave.controller.php
    Этот контроллер формирует массив с данными и отправляет его в действие воркфлоу: lib\workflow\shopWorkflowEditAction.class.php
    Там сохраняется новое состояние заказа в БД и после этого кидает событие order_action.edit

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

    Исхитриться можно. Предлагаю следующий план. По хуку backend_order_edit добавить JS, который поменяет атрибут action формы сохранения. Чтобы данные отправлялись не на встроенный контроллер, а на ваш, который живёт в плагине. Ваш контроллер унаследовать от основного, но перед вызовом parent::execute() запомнить данные заказа. После возвращения из parent::execute() проверить, что $this->errors пустой (т.е. сохранение произошло), и если так, то добавить в историю заказа то, что предварительно запомнили.

    в ответ на История изменений заказа

  • Можно. Но в общем случае это плохая идея, потому что открывает возможность для атак через фронтэнд на бэкэнд-контроллеры. Не делайте так. Разве что на время разработки и отладки.

    Вот такое правило роутинга приложения делает то, что вы просите:

    '<module>/<action>/' => array(),

    в ответ на Frontend роутинг приложения

  • Предлагаю унаследовать ваш shopFrontendCategory2Action от стандартного shopFrontendCategoryAction и запускать методы родителя по возможности. Это сведёт к минимуму шанс, что в будущем что-то сломается. Но гарантию я вам не дам, конечно))

    Между SS5 и SS6 в этом плане отличий нет. Сработает везде.

    в ответ на Переопределение маршрутизации

  • Вижу проблему. Роутинг сам по себе не сможет определить, где заканчивается урл категории, а где начинается инфа о фильтрах. И то, и другое для него просто набор слов со слешами.

    Решение я вижу такое. Скопировать lib/actions/frontend/shopFrontendCategory.action.php в свой отдельный контроллер и прокинуть правило 'category/<category_url>/' на этот новый контроллер. Внутри нового контроллера делать разбор category_url на реальный урл категории и инфу о фильтрах. Ну и добавить любой другой логики, которая вам нужна.

    Между урлом категории и урлом фильтров можно добавить любое слово-вставку, чтобы помочь роутингу: /category/<category_url>/filters/<customs>/
    Это упростит разбор урла внутри вашего нового контроллера.

    И есть тонкость. Чтобы не сломалась герерация урлов на категории, в роутинге должно остаться оригинальное правило 'category/<category_url>/' => 'frontend/category'. Но работать должно новое - для этого оно должно быть выше в списке.

    Подвожу итог. В файле routing.php перед строчкой 'category/<category_url>/' => 'frontend/category', добавьте одно из двух:

    // Без вставки. Разбирайте category_url внутри контроллера.
    // (Вопросик нужен, чтобы в массиве не было двух одинаковых ключей.)
    'category/<category_url>/?' => 'frontend/category2',

    // Или со вставкой. Получайте кусок про фильтры с помощью waRequest::param('customs')
    'category/<category_url>/filters/<customs>/' => 'frontend/category2',

    И делайте что угодно в своём новом экшне shopFrontendCategory2Action.

    в ответ на Переопределение маршрутизации

  • А что именно должно происходить в зависимости от customs? Расскажите поподробнее.

    В простом случае может быть достаточно изменить правило разбора для frontend/category, плагином реагировать на хук frontend_category, доставать дополнительный параметр через waRequest::param('customs') и делать то, что нужно. В сложном случае надо заменить правило frontend/category (или добавить копию), чтобы вело на ваш новый самописный контроллер. Там уже можно делать что угодно.

    в ответ на Переопределение маршрутизации