Art-revolver или как не стоит делать сайты

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

Я не ставлю целью якобы обличить “дураков-заказчиков” и “дураков-менеджеров”, а себя выгородить как жертву обстоятельств, а просто пытаюсь поделиться опытом и рассказать, как не стоит делать сайты. Тем более сложные.

Сначала расскажу немного о себе, чтобы вы понимали обстановку на момент начала работы над проектом. Мне 23, программирую с переменным успехом с 15 лет, были долгие (несколько лет) перерывы в связи с отсутствием компьютера и/или должной мотивации. Поэтому в то время, когда некоторые мои ровесники на этом ресурсе вовсю пишут дополнения с применением Node.js, я едва научился делать действительно неплохие сайты. Работать с MODX Revo я начал осенью 2015 года, до этого год делал сайты на OpenCart, до этого год – на MODX Evo.

Собственно, именно с осени 2015 началась разработка этого проекта. Полгода он обсуждался заказчиком и директором веб-студии, в которой я работал. Заказчик – профессиональный фотограф с большим опытом и собственной большой студией, лауреат международных выставок. Он обнаружил, что на российском рынке отсутствует ресурс, похожий на всемирно известный YelloKorner и решил сделать его аналог.
YelloKorner – это огромный интернет-магазин с фотографиями, которые печатаются ограниченным тиражом на отличной бумаге и имеют уникальные идентификаторы. Почти тоже самое, что купить хорошую картину, в общем-то. Так вот. Несмотря на то, что проект обсуждался с сентября 2015 по январь 2016, я к моменту начала работы не получил вообще никакого технического задания. Только общие фразы: “интернет-магазин с двумя языками, опциями товара, от которых зависит цена, личный кабинет и платежная система”. Если убрать всю воду из того “ТЗ”, то получается, что это все. Бюджет на разработку, прошу не ржать в голос – десять в пятой степени рублей, из них только (два в шестой степени минус четыре) тысяч рублей – на оплату труда программиста. Техническое задание я придумал у себя в голове уже тогда, когда получил дизайн-макеты будущего сайта.

И оно получалось примерно таким:

Мультиязычный интернет-магазин по продаже фотографий с различными опциями – размер фото, наличие/отсутствие рамки, выбор бумаги. Каждая опция имеет свою цену, и при ее выборе общая цена товара меняется. При изменении размера фотографии и ее опций – рамка, тубус, бумага – фотография товара должна меняться на соответствующую заданным параметрам. На сайте должна быть функция фотобанка – продажа цифровых копий фотографий по заданной стоимостьи, с защитой от копирования и защитой от несанкционированного скачивания. На сайте должен быть реализован личный кабинет автора – человека, который выставляет свои фотографии на продажу. Он должен иметь возможность менять аватарку, фон в личном кабинете, заполнять информацию о себе, указывать ссылки в соц.сетях, а так же, добавлять товары. Цены на сайте должны быть реализованы в двух валютах – рубли и евро. При переключении языка цена товара меняется на соответствующую: русский – рубли, английский – евро, с конвертацией по актуальному курсу. На сайте должен быть реализован блог – раздел, где авторы делятся своим опытом и дискуссируют на различные темы. Блог, опять же, двуязычный. На сайте должна присутствовать поисковая система и фильтр товаров по характеристикам – цветовой тон фотографии, раздел, тип товара (живопись, фото, фотобанк). Оплата на сайте должна производиться с помощью систем PayPal для иностранных клиентов и Яндекс.Касса для российских. Все.

Ошибка номер 1. При создании более-менее нестандартного сайта требуйте полное техническое задание. Со всеми, со всеми подробностями! Если ни у кого нет на ТЗ времени – не беритесь за проект, пока ТЗ не будет. Или составьте сами посредством интервью с заказчиком. За отдельную плату, офкорс.

Окей. Так как я был работником веб-студии, оспаривать бюджет права не имел, ибо все равно, оплата сдельная, а не попроектная. Получаю свои пять умножить на шесть тысяч рублей в месяц и ок. Начал работу. Мне с самого начало казалось, что моих способностей на это не хватит, но договор есть, крутись-вертись, но делай. Да и по-хорошему, такое бы на каком-нибудь Laravel делать, ведь чуяло мое сердце, что все будет не так просто, особенно с фотобанком. Это же ведь, защиту нужно придумать, способ передачи фотокопии клиенту, и вообще и вообще. Но, наша веб-студия делала сайты только на MODX Revo, и никак иначе.

Итак, ход мыслей:
  • Мультиязычность – значит Babel.
  • Личный кабинет – Office. Но, он платный, а бюджет и так никакой. Однако, тогда еще лежал в репозитории UserProfile и Virtual Page от Володи. Берем.
  • Магазин – значит miniShop2.
  • Блог – Tickets.
  • Цены в валюте – CurrencyRate.
  • Поиск – mSearch2.
  • Платежные системы – и так все ясно.
  • Верстка – Bootstrap, ибо полюбил.
Далее самое интересное. В нашей веб-студии я в одном лице был и веб-программист, и верстальщик, студия небольшая и в месяц обслуживали порядка 10 проектов – два под ключ, остальное – работы по SEO + мелкие правки. Работа началась над этим проектом началась примерно в конце февраля, но дальше нескольких сверстанных страниц дело не двигалось до середины апреля, так как другие проекты отнимали все время, и за них платили полностью, а этот был на рассрочке. Максимум на Art-Revolver уделялся день в неделю в течение двух с половиной месяцев. В середине апреля босс говорит – “там заказчик волнуется, что Art-Revolver не готов, надо бы до конца месяца срочно все сделать”. Бросаю все проекты, занимаюсь две недели только им. На выходе получается:

  • Virtual Page и User Profile толком не хотят работать. То ли я криворукий, то ли что, но кабинет автора ну вообще не получался. Еле-еле сделал какое-то демо, где можно было менять аватарку и менять пароль с личной информацией.
  • Опции товара с помощью MIGX и кривонаписанного JS скрипта реализовал созданием скрытого поля options[], которое добавляло стоимость.
Таблица MIGX выглядела примерно так:
Размер 30*45 Базовая цена
Размер 40*60 Кастомная цена
Размер 120*180 Кастомная цена
Бумага Fuji 0 рублей
Бумага Epson 1000 рублей
Без рамки 0 рублей
Пенокартон 1000 рублей
Пенокартон + рамка 2000 рублей

  • Мультиязычность через разные контексты сделал. Все работает. Но вот проблема – некоторые вещи, такие как опции товара и какие-то другие тексты не переводились через Babel. О словарях я тогда знал немного.
  • Поиска еще не было, как не было и фильтра.
  • Фотографии к товарам не меняются при изменении опций, ибо я хз как это сделать вообще. Идей не было никаких.
  • Блог, понятно дело, получился одноязычным, ибо писать две статьи и иметь на одной версии русские комментарии, а на другой – английские это идиотизм.
Итог: конец апреля, а у нас едва живое демо. Заказчик недоволен, приезжает разговаривать и полностью обрисовывать картину проекта.

Техническое задание получается теперь таким:

Интернет-магазин по продаже фотографий с различными опциями – размер фото, наличие/отсутствие рамки, выбор бумаги. Каждая опция имеет свою цену, и при ее выборе общая цена товара меняется. Но! Все опции взаимосвязаны между собой определенным ценовым коэффициентом. То есть, если размер фотографии 30*45, то цена бумаги одна, цена рамки одна, то при размере фотографии 40*60 цена бумаги становится другой, цена рамки – другой, и все в том же духе. Мало того. Размеры фотографий тоже должны задаваться вручную, так как не всегда фотографии делаются именно в этих стандартных размерах, есть исключения. При этом, для определенного типа бумаги действительны одни размеры фотографии, для другого типа бумаги – другие, так как большой формат не печатают на Fuji, а маленький не печатают на Epson. А с холстом вообще все сложно – его нельзя натянуть на паспарту и пенокартон, только в рулоне или подрамнике. А для фотобумаги нельзя выбрать подрамник.

Получается крайне изощренная система опций. Если попытаться осмыслить всю эту систему с первого раза, то скорее всего, голова задымится. А ведь это еще закодить надо…

Логика работы фотобанка тоже пересмотрена заказчиком – товар не продается по одной цене, можно купить разные копии фото – в маленьком разрешении, в стандартном, и в HD.

Система с Babel тоже не выдерживает критики от заказчика, так как заполнять два ресурса для одного товара слишком утомительно, да и в блоге должны быть и русские комментарии, и зарубежные. Необходимо сделать так, чтобы язык менялся как-то иначе, Babel ну вообще не вариант.

Итак. После встречи оказывается, что нужно за десять в пятой степени рублей сделать убер-магазин с нереальным функционалом, когда разработчик (т.е. я) ничего сложнее магазина свадебных платьев никогда не делал. Я понимаю, что все, что я могу сделать – это суперкостыльный сайт, но ничего с этим поделать не могу, так как я единственный разработчик с кучей проектов на очереди. И что же мы делаем дальше?

Правильно! Еще на месяц забиваем на этот проект, ибо он на рассрочке, а деньги-то нужны. Тем временем в моей жизни происходят кое-какие изменения, и я вынужден покинуть место работы и уехать из города на пару месяцев. Я нахожу себе замену – на MODX.pro я оставлял даже соответствующее объявление, и уезжаю. Art-revolver остается на мне, так как новый разработчик, хоть вроде, даже поумнее меня, но ему сходу дают парсинг и перенос крупного сайта с самописной CMS на MODX + все старые проекты. Ему точно некогда, и так, задержки по всем фронтам. Договариваемся с боссом, что я за оставшиеся из бюджета пять умножить на шесть тысяч рублей доделаю этот сайт и мы больше ничего друг другу не должны. Сайт практически не работает, все нужно переделывать. То есть, по цене обычного небольшого магазина я должен за два месяца сделать суперпроект. Но деваться некуда, иных источников дохода я найти не могу, так как обстоятельства, беру и делаю.

Ошибка номер 2. Если дело пахнет керосином, а денюжка небольшая – бегите. Больше потеряете, чем заработаете. И это не только о деньгах. Еще есть нервы там, личное время…
Ошибка номер 3. Если вы понимаете, что не осилите проект, не сможете сделать его хорошо – лучше слиться. Репутация и отношения дороже. У меня забрали проект, почти ничего не объяснив и никто со мной теперь не общается из тех людей. Такие дела.


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

Далее по тексту наконец-то будут технические подробности реализации, УРА!

Так как я ранее много работал с OpenCart, то решил использовать их систему мультиязычности, в контексте работы с MODX. И такую систему я накостылил следующим образом.
В менюшке есть переключалка языка – rus/eng. Язык по-умолчанию – русский. Ссылка на переключалке имеет GET-параметр “lang”, который передает серверу команду на переключение словарей в плагине на событие OnHandleRequest и сохраняет текущую настройку в куках:

<?php
if($modx->context->key == "mgr"){return;} 
else
{
    $defaultLang = 'ru';
   if (empty($_COOKIE['lang'])){
       $siteLang = $defaultLang;
       setcookie('lang',$siteLang);
   } else {
       $siteLang = $_COOKIE['lang'];
       if (isset($_GET['lang'])){
           if ($siteLang != $_GET['lang']){
               $siteLang = $_GET['lang'];
               setcookie('lang',$siteLang);
           } 
       }
   }
    $modx->setOption('cultureKey', $siteLang);
    $modx->getService('lexicon','modLexicon');
    $modx->lexicon->load($siteLang.':art-revolver:default');
}
В админке сайта у каждого ресурса появляются TV-поля: content-en, pagetitle-en, longtitle-en. Теперь все данные заполняются в одном ресурсе, довольно удобно. Для менеджеров-то точно. Минус – все-таки это TV, занимает время на выборку + английский сайт не индексируется поисковыми системами (но это не точно).
На фронте замена всего происходит так.

Пункты меню, наименования опций, кнопки и прочая фиготень берут тексты из нужных записей словаря текущего языка:

<div class="col-md-10">
                    <span class="option-name">Fuji</span>
                     <p class="photo-description">[[!%art-revolver.fuji]]</p>
                </div>
Важно! Записи словаря должны вызываться некэшированными, иначе нифига не будет работать.
Названия ресурсов и их страниц обрабатывают сниппеты getPagetitle и getContent:

<a href="[[+link]]" [[+attributes]]>[[!getPagetitle?&lang=`[[!%art-revolver.sitelang?&namespace=`art-revolver`]]`&id=`[[+id]]`]] </a>
Код сниппета getPagetitle (для getContent тоже самое. Можно даже универсально сделать, но тогда как-то это в голову не пришло):

<?php
if ((isset($lang)) && (isset($id)) ){
  $page = $modx->getObject('modResource', $id);
    if ($lang == 'ru'){
        $pagetitle = $page->get('pagetitle');
    } else if ($lang == 'en') {
        $pagetitle = $page->getTVValue('longtitle-en');
        if ($pagetitle == ''){
            $pagetitle = $page->get('pagetitle');
        }
    } else {
        $pagetitle = $page->get('pagetitle').' / '.$page->getTVValue('longtitle-en');
    }
     return $pagetitle;
} else {
    return 'эта строка не может быть отображена';
}
Вот таким, не очень хитрым способом я запилил мультиязычность на сайте. До сих пор считаю, что данная система имеет право на существование, но я бы очень хотел узнать методы улучшения, и очень интересно, как мультиязычность работает на MODX.pro.

Система опций.
Бэкенд.


Толком кодить на JS я не умею, кастомизировать miniShop2 и админку MODX для меня задача непосильная. До сих пор боюсь. Решил продолжить делать все на MIGX, знатно расширив таблицу опций и сделав значения по-умолчанию, а если надо, то в карточке товара можно что-нибудь подправить. Расширил таблицу следующим образом – есть пять размеров фотографии – от обычной рамочной, до полотна на всю стену. Каждый размер имеет свои ценовые значения для каждой опции – бумага, рамка, тубус, стоимость печати. По идее, при выборе нужного чекбокса на фронтенде, JS-скрипт лезет через AJAX на сервер и тянет нужные цены для выбранного параметра, типа, вот так:

<?php
if ($_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest') {return;}
if (empty($_POST['action'])) {return;}

//На выходе всегда отдавались крокозябры, добавил header(), вроде помогло.
header('Content-Type: text/html; charset=utf-8');
$action = $_POST['action'];
$id = '';
$id = $_POST['id'];
//По полученным параметрам запускаем MIGX сниппет с параметром WHERE и тянем данные.
if ($product = $modx->getObject('msProduct', $id)) {	
   $output = $modx->runSnippet('getImageList',array(
   'tvname' => 'photo_format',
   'tpl' => 'photo_options-tpl',
   'where' => '{"MIGX_id:=":"'.$action.'"}'
    ));
} else {
    $output = '<h2>Невозможно загрузить страницу</h2>';
}
if (!empty($output)) {
	die($output);
}
Весь скрипт вырван из заметки Василия за 2013 год о работе AJAX, ибо тогда я реально мало что в этом понимал. Сейчас мои AJAX запросы выглядят прикольнее. Правда.

Сама таблица опций MIGX выглядит так:

[{"MIGX_id":"1","name":"[[!%art-revolver.photo-standart]]","description":"[[!%art-revolver.photo-standart-decription]]","size":"30*45","price":"4800","epson":"0","fuji":"0","canvas":"1600","nodecor":"0","mont1":"1120","mont2":"1760","printCanvas":"0","mont3":"800"},
{"MIGX_id":"2","name":"[[!%art-revolver.photo-optimal]]","description":"[[!%art-revolver.limited-edition]] 500 [[!%art-revolver.copies]]","size":"40*60","price":"8000","epson":"0","fuji":"0","canvas":"2640","nodecor":"0","mont1":"1280","mont2":"2800","printCanvas":"0","mont3":"960"},
{"MIGX_id":"3","name":"[[!%art-revolver.photo-real]]","description":"[[!%art-revolver.limited-edition]] 300 [[!%art-revolver.copies]]","size":"60*90","price":"12800","epson":"0","fuji":"0","canvas":"4000","nodecor":"0","mont1":"2400","mont2":"0","printCanvas":"0","mont3":"2000"},
{"MIGX_id":"4","name":"[[!%art-revolver.photo-exclusive]]","description":"[[!%art-revolver.limited-edition]] 200 [[!%art-revolver.copies]]","size":"100*150","price":"28000","epson":"0","fuji":"0","canvas":"10800","nodecor":"0","mont1":"6320","mont2":"0","printCanvas":"0","mont3":"2400"},
{"MIGX_id":"5","name":"[[!%art-revolver.photo-super]]","description":"[[!%art-revolver.limited-edition]] 100 [[!%art-revolver.copies]]","size":"120*180","price":"80000","epson":"0","fuji":"0","canvas":"16000","nodecor":"0","mont1":"9200","mont2":"0","printCanvas":"0","mont3":"4000"}]
Теги MODX используются для мультиязычности. Хитро. Хе-хе.

Фронтенд.

Боль моей души. Мой ночной кошмар. Мое незнание JavaScript…
Когда я начинал учиться веб-разработке, то у меня было две книги по HTML 4 (дело было в 2009 году), написанные русскими авторами, и оба они советовали забить на JS, ибо Flash это революция и он всех порвет. С тех пор и до 2016 года я упорно забивал на изучение JS (а препод по JS в универе только усилил ненависть к JS) и юзал только jQuery. Удобно же.

Ошибка номер 4. Выучи JS. Научись делать все классами/методами/объектами, и перестань быдлокодить на уровне if/else.

Собственно, за систему опций мне стыдно больше всего на этом сайте. Те, кто осиливает сейчас этот текст, и до сих пор не забил, может зайти на сайт и поковырять там код в карточке товара. Обещаю – скучно не будет. Когда я это писал, я уже понимал, что это полное д***мо. Но иначе не умел. Да и сейчас, я едва-едва начал что-то понимать в классах и методах, благо немного освоил Node.js, он дал мне неплохое представление о хорошем JS коде. Да и изучение исходников JS кода miniShop2 тоже помогает.
Когда я закончил эту систему опций, если не ошибаюсь, в июле, Володя выкатил новый, обновленный релиз msOptionsPrice2.
Интересно узнать, умеет ли этот компонент все то, что заложено в ТЗ в этом топике?

upd. Изменение фото в зависимости от опций
При выборе параметров необходимо изменять превью и фото товаров на соответствующие опциями. Совать 32 возможных варианта в админку — нереально, плюс неизвестно, как это выводить. Поэтому решение получилось примерно таким:
Есть две подложки — для маленьких форматов и больших — на столе и за диваном.
Есть само изображение товара. Есть опции в виде рамок и их цветов.
Путем переключения radio кнопок с помощью HTML+CSS я накладываю одну картинку на другую и CSS рисую рамку. Чтобы CSS был более-менее адекватным, была сделана система префиксов параметров:
размер фона — ориентация фото — размер фото, то есть, в боевом режиме:
класс .sofa-vertical-real означает показать фотографию за диваном в вертикальной ориентации в большом размере

Далее. Личный кабинет.

Так как к этому времени Володя уже убрал из магазина UserProfile, то ничего не оставалось, как купить Office. Запросил бюджет на компонент от заказчика, настроил по мануалу. А дальше самое интересное. По-идее, на сайте должно быть две группы пользователей – авторы и покупатели. Авторы – выставляют товары, делают посты, имеют аватарки и личные странички. Покупатели – только покупают и смотрят в личном кабинете статус заказа. Оказалось, что НИКТО вообще не знает, как должен выглядеть кабинет покупателя и что там должно быть. Мало того, оказалось, что НИКТО до конца не знает, как должен выглядеть кабинет автора, и что там должно быть. Нет, на макете был нарисован кабинет автора – личная страничка, аватарка, текст о себе, ссылки в соц.сетях, выставленные работы. Но нет кнопок – добавить товар, редактировать товар, выйти из личного кабинета, еще-что нибудь. На мои плечи, помимо реализации двух разных кабинетов, еще ложится разработка логики работы авторов и покупателей, так как никто кроме меня вообще не в курсе того, что это нужно. Иначе вообще же смысла в кабинете нет.

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

Итак, права пользователей и роутинг кабинетов. В админке создаем два контейнера ресурсов – пользователи и авторы. На всякий затер id страниц, мало ли что.



Далее создаем две группы пользователей – users и authors.

Роутинг осуществляется по заметке Василия, в плагине на событие OnPageNotFound, примерно так:

<?php
if ($modx->event->name != 'OnPageNotFound') {return false;}
$alias = $modx->context->getOption('request_param_alias', 'q');
if (!isset($_REQUEST[$alias])) {return false;}

$request = $_REQUEST[$alias];
$tmp = explode('/', $request);
// Ссылка подходит под заданный формат: user/username
if (($tmp[0] == 'user') || ($tmp[0]=='author')) {
	// Теперь очищаем имя пользователя от возможного расширения
	$name = str_replace('.html', '', $tmp[1]);

	// Здесь будем определять, какую страницу надо показывать
	// Дальше проверяем наличие запрошенного пользователя
	if ($user = $modx->getObject('modUser', array('username' => $name))) {
	    if ($user->isMember('Authors')){
	        $tmp[0] = 'author';
	        if ($tmp[2] == 'comments'){
	    	    $section = __; // id ресурса для подстраницы «Комментарии»
	        }
	    } else {
	        if ($tmp[2] == 'comments'){
	    	    $section = __; // id ресурса для подстраницы «Комментарии»
	        }
	    }
	
    	if (!$section = $modx->findResource($tmp[0] . '/')) {
    		return false;
    	}
    	
    	if ($tmp[1] != $name) { 
    		$modx->sendRedirect($tmp[0] . '/' . $name . '/');
    	}
		
		$modx->sendForward($section);
	}
}
Но тут появляется такая проблема. Дело в том, что переходить в личный кабинет автора могут как сами авторы, так и пользователи из группы Users, так и анонимы. А у автором же в кабинете загрузчик товаров (о нем позже), статистика продаж, манипуляции с товарами, редактирование информации, замена аватарок, фона кабинета. И авторы могут переходить в режим кабинета покупателя, так как по сути, имеют на это все право. Поэтому формируется задача – редиректить users и authors только на их шаблоны кабинетов + запрещать показывать непринадлежащие формы и чанки, но дать анонимам и юзерам смотреть страницы авторов. Решил я это таким вот сниппетом:

<?php
$alias = $modx->context->getOption('request_param_alias', 'q');
if (!isset($_REQUEST[$alias])) {return 'пустой запрос';}

$request = $_REQUEST[$alias];
$tmp = explode('/', $request);
$tmp[0] = strtolower($tmp[0]);
// Ссылка подходит под заданный формат: user/username
if (($tmp[0] == 'user') || ($tmp[0] == 'author') || ($tmp[0] == 'authors')) {
     $name = strtolower(str_replace('.html', '', $tmp[1]));
	// Здесь будем определять, какую страницу надо показывать
	// Дальше проверяем наличие запрошенного пользователя
	if ($user = $modx->getObject('modUser', array('username' => $name))) {
	    //получаем пользователя в сессии
	    $current_user = $modx->getUser();
	    $current_user = strtolower($current_user->get('username'));
	    //если запрошенный пользователь состоит в группе пользователей дать обычный шаблон, иначе - шаблон автора
	    if ($user->isMember('Users')){
	        $tpl = 'cabinet-tpl';
	    } else {
	        $tpl = 'author-cabinet-tpl';
	    }
	    //если имена пользователей сходятся, то запустить Офис, то есть пустить в личный кабинет
	    if ($name == $current_user){
	        $output = $modx->runSnippet('officeProfile',array(
               'tplProfile' => $tpl,
               'avatarParams' => '{"w":150,"h":150,"zc":1,"bg":"ffffff","f":"jpg"}'
            ));
            return $output;
	    } else {
	       //Иначе - отобразить страничку пользователя
	        $u_name = $user->get('username');
            $u_profile = $user->Profile->toArray();
            $user_prop = array('username' => $u_name, 'cabinet-menu' => 'off');
    		$u_profile = array_merge($u_profile,$user_prop);
    		$modx->setPlaceholders($u_profile);
            $output = $modx->getChunk($tpl, $u_profile);
    		return $output;
	    }
	} else if ( ($name == 'add') || ($name == 'comments') || ($name == 'add-socials') || ($name == 'history') || ($name == 'edit') || ($name == 'my-selling') ){
	    if ($user = $modx->getUser()){
	    if ($user->isMember('Users')){
	        $tpl = 'cabinet-tpl';
	    } else {
	        $tpl = 'author-cabinet-tpl';
	    }
	     $output = $modx->runSnippet('officeProfile',array(
               'tplProfile' => $tpl,
               'avatarParams' => '{"w":150,"h":150,"zc":1,"bg":"ffffff","f":"jpg"}'
            ));
        return $output;
	    }
	}
}
Было бы интересно более элегантное решение, хотя бы в теории.

Сам же шаблон страницы автора не так интересен. Картинка-фон к кабинету это адрес картинки, который хранится в extended поле юзера, его биография и ссылки на соц.сети – тоже extended. Как я реализовал загрузку кастомной картинки в кабинет пользователя – не помню. Не могу найти в исходниках (о бардаке в чанках и сниппетах — позже).

Внезапно оказывается, что заказчик хочет менять цвет текста-описания биографии. Естественно, учить HEX-кодам не вариант, HTML 5 поле color не все браузеры еще умеют, прилепил jQuery плагин, который показывает форму выбора цвета и сохраняет в extended поле HEX код. А при загрузке кабинета это прописывается прям в “style” атрибут тега с текстом. Костыль, но опять же. Задача внезапная, а увеличения бюджета тоже не планируется.

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



Ошибка номер 4. Никаких новых хотелок по ходу работы. Только после завершения оговоренных работ. Иначе разработка превратится в бесконечный цирк, где денег за доп.работу можно и не увидеть. Или увидеть не в полном объеме.


Загрузка товаров авторами

Это тоже задачка была не из простых, но к счастью, есть ms2Form. С горем-пополам настроил модуль, товары создаются. Правда, из-за того, что товары разные, и каждый тип имеет свои уникальные характеристики, пришлось сделать сначала выбор типа товара, потом AJAX’ом загружать сниппет и отдавать шаблон.




Во время настройки ms2Form произошел знатный прикол, который отнял у меня пару дней времени. Оказалось, что если один пользователь загружает фото товара к своему товару, но внезапно бросил это дело, и закрыл браузер, то если другой автор будет грузить свой товар и свои фото, то он будет видеть, что грузил другой автор. Но так как права на редактирования чужих товаров он не имеет, то и свой товар загрузить не может. Конфликт! Решилось ковырянием исходников ms2Form и раскомментированием последних строчек в сниппете ms2Form (да, нужные строчки были почему-то закомментированы).
В общем, с горем пополам, за две-три недели личные кабинеты пользователей двух групп и доступ к анонимам был реализован.

Переходим к самому прикольному – фотобанку

Идея реализовать полноценный фотобанк в стиле Fotolia на базе MODX, имея не самые хорошие знания платформы, как я уже говорил, с самого начала казалось мне плохой. Но, вызов принят. Арсенал костылей еще полон, полный вперед!

Итак, идея примерно такова.
Нужен раздел на сайте, продающий цифровые товары, то есть, ссылки на фотографии в различном разрешении. При этом, файлы нельзя скачать посторонним пользователям, доступ к ссылке имеют только купившие товар. В идеале, ссылка всегда должна хэшироваться и для каждого юзера быть уникальной. Фотографии хранятся в HD, и весят по 15-20 мегабайт. Хранить все это добро на shared-хостинге идиотизм. Но об этом, как всегда, никто не подумал. Поэтому я, вооружившись Swift, настроил облако Selectel и фотобанк хранится там. Хранение фотографий сначала реализовал с помощью FileAttach. Он даже ссылочки хэшировать умеет. Но, как оказалось, он работает только с локальным хранилищем, а с облаком не умеет. Ума допилить компонент у меня не хватило. Поэтому я обратил внимание на компонент от Володи – UserFiles. Подостовав Володю в тех.поддержке, я таки-настроил компонент и фотки загружаются в Selectel и хранятся там. Проблема только в том, что ссылки не хэшируются, и купивший фотографию человек, может спокойно расшарить ссылку всем. Но, думаю, до этого не дойдет.

Как же сделана выдача ссылок покупателю?

Подразумевается, что купить фотку в фотобанке можно только картой. Значит, у такого товара будет статус “ОПЛАЧЕН”. Выходит, задача такая – найти пользователя, который купил этот товар, проверить статус товара и вывести сниппет UserFiles со ссылкой на фотку. Создал сниппет getBuyingPhotos и поместил вызов сниппета на странице покупателя, сразу под стандартным кабинетом Office на ExtJs.

Код сниппета getBuyingPhotos:
<?php
$pdo = $modx->getService('pdoTools');
if (empty($tpl)){
    $tpl = 'UFfileattach.tpl';
}
if (empty($tplOuter)){
    $tplOuter = 'UFfileattachOuter.tpl';
}
//Если пользователь авторизован
if ($modx->user->isAuthenticated('web')) {
    $current_user = $modx->getUser();
    if (!isset($user_id)){
        $user_id = $current_user->get('id');    
    }
    //Создаем запрос
    $query = $modx->newQuery('modResource');
    //Проверяем необходимые параметры
    $parent == '' ? $parent = __ : $parent;
    $parent == '' ? $parent = __ : $template;
    //условие
    $query->where(array(
       'parent' => $parent,
       'template' => $template,
       'ms2o.status' => $status,
       'ms2o.user_id' => $user_id,
    ));
    //Запрос с JOIN нужных таблиц
    $query->select('modResource.id');
    $query->leftJoin('msOrderProduct', 'ms2op', 'modResource.id=ms2op.product_id');
    $query->leftJoin('msOrder', 'ms2o', 'ms2op.order_id=ms2o.id');
    $query->prepare();
    $query->stmt->execute();
    $result = $query->stmt->fetchAll(PDO::FETCH_ASSOC);
    foreach ($result as $res) {
        $links .= $pdo->runSnippet('!pdoResources',array(
        'parents' => __,
        'limit' => 10,
        'class' => 'UserFile',
        'loadModels' => 'UserFiles',
        'tpl' => $tpl,
         'leftJoin' => '{
    	"Thumb": {
    		"class": "UserFile",
    		"on": "Thumb.parent = UserFile.id"
    	}
        }',
        'where' => '{"UserFile.parent":"'.$res['id'].'","UserFile.name:!=":"modelrelease"}',
        'sortby' => 'rank',
        'sortdir' => 'ASC',
        'select'=>'{
    	"UserFile": "*",
    	"Thumb": "Thumb.url as thumb"
        }'
    ));
    }
   // return $links;
    if (!empty($links)){
        $output = $pdo->getChunk($tplOuter,array('links'=>$links));
    } else {
        $output = '';
    }
    return $output;
} else {
     $modx->sendUnauthorizedPage();
}
Таким вот образом я запилил что-то типа фотобанка. От вариаций покупки в разных разрешениях пришлось отказаться, так как слишком запарно, даже сам заказчик, кажется, забил на эту идею.

Бардак в коде

Проект делался (а точнее, делается) уже год.
За это время мои знания улучшались, но из-за спешки в реализации я не соблюдал никакой логики в хранении и именовании элементов, поэтому сейчас на сайте, чтобы понять, как что-то работает, нужно убить достаточно времени, ибо сразу отыскать все сниппеты и чанки задача не из легких.
Уже в сентябре я освоил Fenom и файловые элементы, когда большая часть была сделана, а переделывать такую махину было уже лень. Тем более, не забываем о оплате в пять умножить на шесть тысяч рублей. Тратить еще две недели на переписывание этого кошмара мне уже не моглось.

Ошибка номер 5. Следуй какой-то методике хранения элементов. Правильно называй чанки/сниппеты/плагины. Разложи все по папкам. По-хорошему – сделай все на файловых элементах Fenom, чтобы четко видеть местоложение всех элементов. Это реально удобно. Точнее, это нереально удобно.

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

P.S. Я все же получил еще доплату за проект в размере два в четвертой степени минус одна тысяча рублей. Итого, за 8 месяцев работы гонорар по проекту вышел в два в шестой степени плюс одиннадцать тысяч рублей. Стоит ли проект этот суммы, несмотря на костыли? Как считаете?

P.S.2. За последние полгода я сделал достаточно проектов, за которые мне ни разу не стыдно. Все сделано реально круто. Везде Fenom, грамотное наследование шаблонов, неплохо (а местами очень хорошо) написанные сниппеты, хорошая верстка, TV в работе с miniShop2 почти не использую, только плагины товаров, вовсю использую словари и системные параметры, ну и вообще, за исключением внезапных приступов тупости (можно видеть в моих предыдущих топиках), считаю, что могу сказать, что делаю хорошие сайты. Но с Art-revolver судьба не сложилась.
Дмитрий
31 марта 2017, 00:43
modx.pro
9
2 981
+19
Поблагодарить автора Отправить деньги

Комментарии: 38

Василий Наумкин
31 марта 2017, 04:21
+4
и очень интересно, как мультиязычность работает на MODX.pro.
Примерно так же, я это давно описывал.

Ну а вообще да, изрядная картина создания сайта на MODX из говна и палок за еду. Очень интересно, чем думает веб-студия, когда берёт такие проекты без ТЗ, сроков и выставляет смешные ценники. Жадность ли это, или банальная глупость?

Есть мнение, что для сдачи такого проекта заказчику, студия потратит больше, чем заработает.
    Дмитрий
    31 марта 2017, 11:13
    0
    Все в таком случае теряют больше, чем получают. Заказчик — год ждет результата. Разработчик — работает за еду, как вы верно подметили, а студия за этот год получает ужасно смешные деньги от этого проекта. Профита вообще никакого.
      Alexander V
      31 марта 2017, 15:29
      +2
      Такие проекты берут продажники или руководители, которые умеют только продавать. Плевать они хотели, как это будет делаться. Мыслят по-другому.
        mngatoff
        02 апреля 2017, 21:55
        +1
        я бы десять раз плюсанул, но можно только один)
          Alexander V
          02 апреля 2017, 22:03
          0
          Без этих ребят все равно никуда не деться. Есть торговцы, есть технари.
            mngatoff
            02 апреля 2017, 22:31
            0
            Есть нормальные торговцы, которые вникают в вопрос и советуются с исполнителем прежде чем его на что-то подписывать. Есть торговцы, которые просто «продать то не знаю что, главное побольше». Ему обороты, а тебе — сам разбирайся
              Alexander V
              02 апреля 2017, 22:45
              0
              Согласен. Но зачастую, это простые барыги. Рынок, знаете ли… диктует сои правила.
            Alexander V
            02 апреля 2017, 22:04
            0
            Так и живем)
        Евгений Webinmd
        31 марта 2017, 10:11
        +5
        Захватывающая история, но я бы на месте автора убрал хотя бы наименование сайта.
        Всё же заказчик не виноват что менеджер проектов не опирается на опыт работников.
        А так что я могу сказать, описаны советы от капитана:
        Без ТЗ получаем ХЗ
          Дмитрий
          31 марта 2017, 10:50
          0
          Немножко убрал название сайта. Теперь нужно погуглить, чтобы найти. В статье все-таки есть некоторый объем кода, и хотелось показать его в деле. А без кода и демонстрации это получаются и вправду, советы от капитана.
          Семён Лобачевский
          31 марта 2017, 11:34
          0
          Это какой-то страшный сон вёб разработчика…
            Виталий Серый
            31 марта 2017, 11:35
            +3
            Интересно прочиталось, спасибо.
            Чувствуется вся боль.
            Долгострой сам по себе морально сложен, а тут еще поддерживают стресс за счет «быстро-быстро, горим».
            Со временем, наверное, все приходят к тому, что лучше отказаться от чего-то подобного и потратить время на семью, себя или что-то поинтереснее в профессиональном плане.
              Николай Савин
              31 марта 2017, 11:56
              +2
              Спасибо за статью. Всегда интересно почитать чужой опыт взаимодействия с заказчиками и проект менеджерами, почитать о том, какой еще бред встречается, сравнить со своим опытом.
              Тоже штук 5 подобных проектов (по сложности брал), все слил в свое время, зато получил опыта и знаний немерено.
                Николай
                31 марта 2017, 23:14
                0
                По-хорошему – сделай все на файловых элементах Fenom
                А можете про этот пункт подробнее, пожалуйста?
                  Дмитрий
                  31 марта 2017, 23:29
                  4
                  +4
                  1. Вводная статья о том, что такое Fenom и зачем оно надо
                  2. Статья о том, как работать с файловыми элементами
                  Серьезно, после освоения этого инструмента делать сайты на MODX стало еще проще. И код стал куда красивее. Идиотские однообразные простыни шаблонов с одним отличием сменились на шаблоны с наследованием, все чанки и собственноручно написанные сниппеты/модификаторы хранятся в файлах, их можно использовать с Git, а это вообще просто потрясающе! Теперь никаких утерянных чанков, все всегда можно откатить и радоваться жизни. Прелесть. Если до сих пор никогда не использовали Fenom — потратьте пару дней и познайте дзен реально крутой разработки.
                    Николай
                    01 апреля 2017, 02:57
                    0
                    А по скорости есть какие-то преимущества, или вопрос только в удобстве?
                      Дмитрий
                      01 апреля 2017, 02:58
                      0
                      Ссылки почитайте. Там все написано.
                        Николай
                        01 апреля 2017, 10:10
                        0
                        Сравнения со скоростью загрузки из файла и из базы кажется не было. Если не ошибаюсь авторы MODx именно на это упирали, когда заносили все в базу.

                        Хотя, как я понял, проблема со скоростью и памятью если и может проявиться, то только при первой загрузке, дальше все попадает в кеш и работает также, как и в базовом подходе MODx.

                        Подход действительно великолепный, жаль, что MODx изначально не по этому пути шел.
                          Николай
                          01 апреля 2017, 10:13
                          0
                          Насколько сложно перевести все на Fenom?

                          Могут ли параллельно уживаться Fenom и обычный подход?

                          Не знаете, не планируется ли Modx на Fenom перевести?
                            Дмитрий
                            01 апреля 2017, 11:24
                            0
                            В ссылках написано всё. Всё. Кроме последнего вопроса. Но мне кажется, что нет, не планируется. Если весь мир до сих пор упорно использует getPage, Wayfinder, getResources, о чем может вообще речь идти?
                              Сергей Шлоков
                              01 апреля 2017, 11:50
                              +2
                              Не знаете, не планируется ли Modx на Fenom перевести?
                              А зачем? Кому нравится феном, ставит pdoTools, кому twig ставит twiggy, кому smarty — modxSmarty.
                          Владимир
                          01 апреля 2017, 08:47
                          +1
                          Из этого абзаца прямо следует, что вам бы не «за жизнь» (заказчики, потраченное время, нервы, эмоции) писать топик, а по теме «Fenom — потратьте пару дней и познайте дзен», «использовать с Git» ))) Напишите, как использовать с Git, как познать дзен. Будет интереснее чем терки за заказчиком (ИМХО).
                            Сергей Шлоков
                            01 апреля 2017, 10:19
                            +2
                            Думаю, вся статья состояла бы из нескольких слов «Феном позволяет вызывать элементы MODX из файлов, что позволяет контролировать версии элементов при использовании системы версионирования.»
                              Владимир
                              01 апреля 2017, 10:22
                              0
                              Это прям определение для справочника))) А статья может содержать примеры, сравнения, рассуждения, о «граблях» \фишках\кейсах, мало ли.
                                Сергей Шлоков
                                01 апреля 2017, 10:32
                                2
                                +2
                                Вот достаточно полная статья про файловые элементы. Что можно ещё добавить, да ещё на целую статью?

                                Тем более тебя интересовал Git
                                Напишите, как использовать с Git, как познать дзен. Будет интереснее чем терки за заказчиком (ИМХО).
                                А это уже к феному никакого отношения не имеет. ))
                                  Владимир
                                  01 апреля 2017, 10:38
                                  0
                                  Да я не о том. Я не просил лично для меня написать.
                                  Перевел на феном и файловые элементы уже не один свой сайт — полет нормальный. Просто по самой статье «Art-revolver или как не стоит делать сайты» предложит автору писать как эти сайты делать надо, применяя феном и т.п. со всеми тонкостями.
                                  Мне казалось именно это могло быть понятно из мой реплики автору топика.
                                    Владимир
                                    01 апреля 2017, 11:13
                                    0
                                    И, кстати, да, спасибо тебе и Василию за эти самые файловые элементы! Очень удобно.
                                      Сергей Шлоков
                                      01 апреля 2017, 11:44
                                      +2
                                      Да, помню. Начиналось всё вот так
                                      Функционал загрузки чанков из файлов там был давным-давно, распространение его на остальные элементы — дело времени и спроса. Которого, кстати говоря, не так-то и много.
                                      А закончилось так
                                      pdoTools за год серьёзно вырос, научился полноценно работать с файлами, что изменило лично мой подход к созданию сайтов.
                                      ))
                                      П.С. В спорах рождается истина.
                            Alexander V
                            02 апреля 2017, 22:06
                            0
                            В некоторых случаях, Fenom проигрывает в скорости. Это не панацея.
                              Павел Карелин
                              05 апреля 2017, 14:29
                              +1
                              Согласен, когда руки из жопы, тогда феном проигрывает в скорости. Ни кто и не говорит что это панацея, просто удобная и главное шустрая замена родному парсеру.
                          Леви Ким
                          01 апреля 2017, 20:52
                          0
                          а я думал время когда заказчики говорили микро веб студиям: «сделайте мне новый фэсбук, мы скоро сказочно разбогатеем» прошли эдак годах в 2010 :)
                            Павел Карелин
                            01 апреля 2017, 22:21
                            1
                            +3
                            Я тоже когда то думал что смогу сделать один любой сайт. Как же я ошибался. Все приходит с опытом.
                              Паша
                              05 апреля 2017, 17:31
                              +4
                              да. Гнать в шею такого работодателя. Который ставит сотрудников в столь ужасные условия…

                              Про цены ничего не понял. Для чего было писать эти ребусы со степенями… Лучшеб сразу написал суммы цифрами. И так текст длинный.
                                Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                                38