Сниппет Sendex и формы подписки\\отписки

На прошлых занятиях мы закончили написание административного интерфейса нашего компонента и теперь переходим на фронтенд.

Нам нужено организовать возможно самостоятельной работы авторизованным пользователям с подписками. Определяем минимальный функционал:

  1. Вывод формы подписки на определенную рассылку - её мы укажем по id

  2. Если юзер уже подписан - тогда показываем форму отписки

  3. При том и другом действие происходит отправка писем с кодом, для подтверждения

  4. При переходе по коду, его ловит плагин и выполняет что нужно

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

Автоустановка пакетов

В официальных пакетах MODX используется поставка транспортных zip в составе своего пакета. То есть, качаете один Articles, а там внутри еще 5 пакетов не самых свежих версий. Причем, во время установки вас никто об этом не предупредит, а при деинсталяции тоже могут быть заморочки.

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

Если пользователь принципиально против - он может отказаться от установки, решать ему.

Итак, чтобы выполнять каакие-то произвольные действия с согласия пользователя мы используем специальный атрибут /_build/build.transport.php:


$builder->setPackageAttributes(array(
    'changelog' => file_get_contents($sources['docs'] . 'changelog.txt')
    ,'license' => file_get_contents($sources['docs'] . 'license.txt')
    ,'readme' => file_get_contents($sources['docs'] . 'readme.txt')
    // Вот он - опции установки!
    ,'setup-options' => array(
        // Указываем использовать ресолвер setup
        'source' => $sources['build'].'setup.options.php',
    ),
));

У вас этот блок закомментирован - нужно раскомментить.

Затем создаём файл-ресолвер /_build/resolvers/resolve.setup.php. Внутри обычная обвязка по определению действия установщика:


if ($object->xpdo) {
    /* @var modX $modx */
    $modx =& $object->xpdo;
    $success= false;
    switch ($options[xPDOTransport::PACKAGE_ACTION]) {
        case xPDOTransport::ACTION_INSTALL:
        case xPDOTransport::ACTION_UPGRADE:
            $success = true;
            break;
        case xPDOTransport::ACTION_UNINSTALL:
            $success = true;
            break;
    }
    return $success;
}

Вписываем установку pdoTools версии 1.8, обратите внимание - у нас тут массив, можно списать еще несколько дополнений, если нужно:


case xPDOTransport::ACTION_UPGRADE:
            /* Checking and installing required packages */
            $packages = array(
                'pdoTools' => array(
                    'version_major' => 1,
                    'version_minor:>=' =>  8,
                )
            );
            foreach ($packages as $package => $options) {
                $query = array('package_name' => $package);
                if (!empty($options)) {$query = array_merge($query, $options);}
                if (!$modx->getObject('transport.modTransportPackage', $query)) {
                    $modx->log(modX::LOG_LEVEL_INFO, 'Trying to install <b>'.$package.'</b>. Please wait...');

                    // Собственно закачка пакета специальной функцией
                    $response = installPackage($package);
                    if ($response['success']) {$level = modX::LOG_LEVEL_INFO;}
                    else {$level = modX::LOG_LEVEL_ERROR;}

                    $modx->log($level, $response['message']);
                }
            }
            $success = true;
            break;

Закачивает и устанавливает пакеты моя функция installPackage, о которой я писал в отдельной заметке.

Также еще нужно поправить ресолвер resolve.tables.php - там есть небольшой баг, который не даёт работать другим ресолверам после него:


// Меняем $object на $tmp
foreach ($objects as $tmp) {
    $manager->createObjectContainer($tmp);
//...

Вот коммит со всеми изменениями, нужными для автоустановки pdoTools.

Теперь можно синхронизировать все файлы с сервером и заново собрать пакет. Если у вас в конфиге включена опция автоустановки при сборке - в списке пакетов сайта должен появиться pdoTools.

Теперь вы знаете, как можно устанавливать любые дополнения из репозиториев MODX в своих компонентах.

Подготовка файлов

Теперь оформляем сниппет Sendex, который будет выводить наши формы отписки\подписки и проверять статус юзера.

Вообще-то, он у нас уже есть и даже установлен - там стандартное содержимое от modExtra. Нужно его переписать и добавить свои параметр сниппету.

Работаем с файлами:

  • /_build/data/transport.snippets.php - Здесь у нас указано, что нужно запаковывать сниппет Sendex, откуда его брать и какие добавлять параметры

  • /_build/properties/properties.sendex.php - Массив параметров, его мы будем изменять

  • /core/components/sendex/elements/snippets/snippet.sendex.php - Сам сниппет с выводом и обработкой форм.

Формы будут в чанках, так что нам понадобятся еще файлы

  • /_build/data/transport.chunks.php - Перечень чанков для установки

  • /core/components/sendex/elements/chunks - Здесь мы создадим новые чанки

Ну вроде подготовились, пишем сниппет.

Сниппет Sendex

Чтобы PhpStorm адекватно воспринимал pdoTools и его методы - нужно загрузить последнюю версию из репозитория, распаковать куда-то и добавить в проект.

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

Sendex/core/components/sendex/elements/snippets/snippet.sendex.php

Инициализируем классы Sendex и pdoTools:


<?php
/** @var array $scriptProperties */
/** @var Sendex $Sendex */
$Sendex = $modx->getService('sendex','Sendex',$modx->getOption('sendex_core_path',null,$modx->getOption('core_path').'components/sendex/').'model/sendex/',$scriptProperties);
/** @var pdoTools $pdoTools */
$pdoTools = $modx->getService('pdoTools');

if (!($Sendex instanceof Sendex) || !($pdoTools instanceof pdoTools)) return '';

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

А вот pdoTools, наоборот, загружается очень просто - об этом я писал здесь.

При загрузке класса Sendex выполняется его метод __construct() в файле /core/components/sendex/model/sendex/sendex.class.php, который загружает модель компонента и лексикон. Мы сразу можем их использовать.

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


if (!$modx->user->isAuthenticated($modx->context->key)) {
    return $modx->lexicon('sendex_err_auth_req');
}

Сохраняем, вызываем и проверяем.

Теперь нужно сделать авторизацию на сайте (HybridAuth, Login, Loginza - на выбор) или набросать авторизующий плагин для админов. Второе быстрее и проще, поэтому создаём в админке плагин autoWebAuth:

if ($modx->event->name == 'OnManagerPageInit' && !$modx->user->hasSessionContext('web')) {
    $modx->user->addSessionContext('web');
}

Отмечаем действие OnManagerPageInit, сохраняем, обновляем страницу и мы авторизованы на фронтенде, в контексте web - ошибка сниппета пропала.

Добавляем проверку указания id подписки для работы сниппет. Вот и первый параметр - id:

elseif (empty($id) || !$newsletter = $modx->getObject('sxNewsletter', $id)) {
    return $modx->lexicon('sendex_newsletter_err_ns');
}

Я обращаюсь к $id, а не к $scriptProperties['id'] потому, что все переменные этого массива доступны в сниппете благодаря функции extract(), которую MODX запускает при его старте.

Если вы там будуте делать, то всегда нужно проверять переменную на существование, ведь она может быть не прописана в параметрах сниппета, и тогда мы получим E_NOTICE уведомление, мол undefined.

Сохраняем сниппет, проверяем:

Указываем id существующей подписки в вызове сниппета и добавляем следующую проверку - на активность:

/** @var sxNewsletter $newsletter */
if (!$newsletter->active && empty($showInactive)) {
    return $modx->lexicon('sendex_newsletter_err_disabled');
}

Как видите, здесь появился очередной параметр showInactive. Он позволяет работать с неактивными подписками.

Проверяем, какую форму нам нужно вывести:


if ($newsletter->isSubscribed($modx->user->id)) {
    return !empty($tplUnsubscribeForm)
        ? $pdoTools->getChunk($tplUnsubscribeForm, $newsletter->toArray())
        : 'Parameter "tplUnsubscribeForm" is empty';
}
elseif (!$newsletter->isSubscribed($modx->user->id)) {
    return !empty($tplSubscribeForm)
        ? $pdoTools->getChunk($tplSubscribeForm, $newsletter->toArray())
        : 'Parameter "tplSubscribeForm" is empty';
}

Новые параметры с именами чанков и новый метод sxNewsletter::isSubscribed() для проверки статуса юзера по отношению к подписке - его нужно добавить. Лексиконы я здесь не использую, ибо чанки должны быть всегда заданы и это уже просто перестраховка для невнимательного админа.

Коммит обновлённого класса sxNewsletter.

Редактируем /core/components/sendex/model/sendex/sxnewsletter.class.php:

public function isSubscribed($user_id = 0, $email = '') {
    $q = $this->xpdo->newQuery('sxSubscriber', array('newsletter_id' => $this->get('id')));
    if (!empty($id)) {
        $q->where(array('user_id' => $user_id));
    }
    if (!empty($email)) {
        $q->where(array('email' => $email));
    }
    return (bool) $this->xpdo->getCount('sxSubscriber', $q);
}

Как видите, проверяем подписанность юзера по id или email или по обоим сразу. В ответ получаем true или false.

Прописываем имеющиеся параметры сниппету в /_build/properties/properties.sendex.php:

$tmp = array(
    // Цифровое поле для указания id рассылки
    'id' => array(
        'type' => 'numberfield',
        'value' => '',
    ),
    // Переключатель да\нет
    'showInactive' => array(
        'type' => 'combo-boolean',
        'value' => false,
    ),
    // Форма подписки с чанком по умолчанию
    'tplSubscribeForm' => array(
        'type' => 'textfield',
        'value' => 'tpl.Sendex.subscribe.form',
    ),
    // Форма отписки с чанком по умолчанию
    'tplUnsubscribeForm' => array(
        'type' => 'textfield',
        'value' => 'tpl.Sendex.unsubscribe.form',
    ),
);

Добавляем строки с описанием параметров в лексикон /core/components/sendex/lexicon/en/properties.inc.php и /core/components/sendex/lexicon/ru//core/components/sendex/lexicon/en/properties.inc.php.

После этого можно пересобрать пакет.

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

После пересборки и установки пакета, сниппет снова стал не статичным. Можно исправить это, как было.

Коммит сниппета с его параметрами.

Чанки подписки и отписки

Нужно добавить чанки подписки и отпискию Создаём два файла с говорящими названиями:

  • /core/components/sendex/elements/chunks/chunk.subscribe.form.tpl

  • /core/components/sendex/elements/chunks/chunk.unsubscribe.form.tpl

и добавляем их в установку пакета /_build/data/transport.chunks.php:


$tmp = array(
    'tpl.Sendex.subscribe.form' => array(
        'file' => 'subscribe.form',
        'description' => '',
    ),
    'tpl.Sendex.unsubscribe.form' => array(
        'file' => 'unsubscribe.form',
        'description' => '',
    ),
);

Мы создаём чанки, которые указаны по умолчанию в параметрах сниппета, поэтому пользователю не придётся их указывать каждый раз.

Пишем простейшие формы, нам главное указать там sx_action: subscribe или unsubscribe. У меня в итоге получилось вот так:

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

Да, советую выставить константу BUILD_CHUNK_UPDATE в true, чтобы чанки обновлялись при пересборке пакета. Напоминаю, что опции сборщика находятся в /_build/build.config.php.

Коммит с чанками.

Заключение

Вот мы и написали наш простенький сниппет Sendex, правда пока без логики подписки и отписки - это на следующем уроке.

Не забывайте добавлять записи в лексиконы - вот мои.

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

← Предыдущая заметка
Пишем интерфейс: таблица очереди писем
Следующая заметка →
Самостоятельная подписка и отписка пользователя
Комментарии (11)
alex.vakhitovAlex Vakhitov
06.12.2013 20:23

Эх, Василий, спасибо за уроки. Для себя понял что MODX в серьез больше заниматься не буду. (:

bezumkinВасилий Наумкин
06.12.2013 21:32

А чего так?

Неужели мои уроки повлияли?

alex.vakhitovAlex Vakhitov
07.12.2013 05:56

Да, благодаря урокам появилось более четкое понимание того сколько материала нужно изучить И по причине того что учить и наверстывать много нужно, оставлю MODX и вообще php на лучшие времена

ElectricaМихаил
07.12.2013 08:03

Было так же у меня. Кажется что ничего не знаю)

alex.vakhitovAlex Vakhitov
07.12.2013 08:10

У меня проблема в другом, я хорошо знаю Python и фреймверки на нем Django/Flask/Twisted. И это собственно то с чего и живу, так как 95% денег мне приносит именно это, плюс сейчас усиленно входит nodejs, появляются заказы и постоянные клиенты. Поэтому разумней увеличивать знания в этом, а не тратить силы на изучения php. Мне вообще нравится MODX как отличная система, но не складывается с ним постоянная и интересная работа

ElectricaМихаил
07.12.2013 09:58

Ну тогда да, верно делаешь. А я вот пока на php и надеюсь

bezumkinВасилий Наумкин
07.12.2013 10:00

Ровно по той же причине не слезаю с MODX и PHP =)

alex.vakhitovAlex Vakhitov
07.12.2013 10:28

Ну мне кажется это верный путь, становится мастером своего дела, а не знать везде по немногу ((:

bezumkinВасилий Наумкин
07.12.2013 10:56

При условии, что видишь перспективность дальнейшего развития.

Мастера турбопаскаля и бейсика сейчас вряд ли востребованы =)

alex.vakhitovAlex Vakhitov
07.12.2013 11:13

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

ElectricaМихаил
07.12.2013 18:12

Зато все равно средства были потраченны не зря, и не зря значит Василий делал эти уроки

bezumkin
Василий Наумкин
09.04.2024 01:45
Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. Во...
futuris
Futuris
04.04.2024 05:56
Я просто немного запутался. Когда в абзаце &quot;Vesp/Core&quot; ты пишешь про &quot;новый trait Fil...
bezumkin
Василий Наумкин
20.03.2024 18:21
Volledig!
Андрей
14.03.2024 10:47
Василий! Как всегда очень круто! Моё почтение!
russelgal
russel gal
09.03.2024 17:17
А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал ...
inetlover
Александр Наумов
27.01.2024 00:06
Василий, спасибо! Извини, тупанул.
bezumkin
Василий Наумкин
22.01.2024 04:43
Давай-давай!
bezumkin
Василий Наумкин
24.12.2023 11:26
Спасибо!
bezumkin
Василий Наумкин
27.11.2023 02:43
Ура!
bezumkin
Василий Наумкин
25.11.2023 08:30
Vesp тянет 2 зависимости: vesp-frontent для фронта и vesp-core для бэкенда. Их можно обновлять, но э...