0.00
56 читателей, 29 топиков

Дополнительные поля WebLoginPE и modUsers

Cниппет WebLoginPE, как известно его пользователям, позволяет использовать при работе с пользователями дополнительные поля. Для работы с этими полями сниппет создает отдельную таблицу (по умолчанию — web_user_attributes_extended), где и хранит значения полей; при этом каждому полю соответствует отдельный столбец в указанной таблице.

Вполне может так быть, что кто-то, как zloyxrom , решит отказаться от WebLoginPE и переделать работу с пользователями, используя класс modUsers из MODxAPI. И сразу же столкнется с проблемой — modUsers знать ничего не знает о дополнительных полях WebLoginPE.

Но решается проблема очень просто, по сути копипастом. Создадим в assets/lib/MODxAPI файл modUsersExt.php c классом modUsersExt:
<?php
require_once('modUsers.php');

class modUsersExt extends modUsers
{

    protected $default_field = array(
        'user' => array(
            'username' => null,
            'password' => null,
            'cachepwd' => null
        ),
        'attribute' => array(
            'fullname' => null,
            'role' => null,
            'email' => null,
            'phone' => null,
            'mobilephone' => null,
            'blocked' => null,
            'blockeduntil' => null,
            'blockedafter' => null,
            'logincount' => null,
            'lastlogin' => null,
            'thislogin' => null,
            'failedlogincount' => null,
            'sessionid' => null,
            'dob' => null,
            'gender' => null,
            'country' => null,
            'city' => null,
            'state' => null,
            'zip' => null,
            'fax' => null,
            'photo' => null,
            'comment' => null
        ),
        'extended' => array(

        ),
        'hidden' => array(
            'internalKey'
        )
    );

    public $extendedTable = 'web_user_attributes_extended';

    public function issetField($key)
    {
        return (array_key_exists($key, $this->default_field['user']) ||
                array_key_exists($key, $this->default_field['attribute']) ||
                array_key_exists($key, $this->default_field['extended']) ||
                in_array($key, $this->default_field['hidden'])
        );
    }

    /**
     * @param $id
     * @return $this
     */
    public function edit($id)
    {
        $id = is_scalar($id) ? trim($id) : '';
        if ($this->getID() != $id) {
            $this->close();
            $this->newDoc = false;

            if (!$find = $this->findUser($id)) {
                $this->id = null;
            }else {
                $result = $this->query("
                    SELECT * from {$this->makeTable('web_user_attributes')} as attribute
                    LEFT JOIN {$this->makeTable('web_users')} as user ON user.id=attribute.internalKey
                    LEFT JOIN {$this->makeTable($this->extendedTable)} as extended ON user.id=extended.internalKey
                    WHERE BINARY {$find}='{$this->escape($id)}'
                ");
                $this->field = $this->modx->db->getRow($result);

                $this->id = empty($this->field['internalKey']) ? null : $this->get('internalKey');
                unset($this->field['id']);
                unset($this->field['internalKey']);
            }
        }
        return $this;
    }

    /**
     * @param null $fire_events
     * @param bool $clearCache
     * @return bool|int|null|void
     */
    public function save($fire_events = null, $clearCache = false)
    {
        if ($this->get('email') == '' || $this->get('username') == '' || $this->get('password') == '') {
            $this->log['EmptyPKField'] = 'Email, username or password is empty <pre>' . print_r($this->toArray(), true) . '</pre>';
            return false;
        }

        if (!$this->checkUnique('web_users', 'username')) {
            $this->log['UniqueUsername'] = 'username not unique <pre>' . print_r($this->get('username'), true) . '</pre>';
            return false;
        }

        if (!$this->checkUnique('web_user_attributes', 'email', 'internalKey')) {
            $this->log['UniqueEmail'] = 'Email not unique <pre>' . print_r($this->get('email'), true) . '</pre>';
            return false;
        }

        /*$this->invokeEvent('OnBeforeDocFormSave',array (
            "mode" => $this->newDoc ? "new" : "upd",
            "id" => $this->id ? $this->id : ''
        ),$fire_events);*/

        $fld = $this->toArray();
        foreach ($this->default_field['user'] as $key => $value) {
            $tmp = $this->get($key);
            if ($this->newDoc && ( !is_int($tmp) && $tmp=='')) {
                if($tmp == $value){
                    //take default value from global config
                }
                $this->field[$key] = $value;
            }
            $this->Uset($key, 'user');
            unset($fld[$key]);
        }
        if (!empty($this->set['user'])) {
            if ($this->newDoc) {
                $SQL = "INSERT into {$this->makeTable('web_users')} SET " . implode(', ', $this->set['user']);
            } else {
                $SQL = "UPDATE {$this->makeTable('web_users')} SET " . implode(', ', $this->set['user']) . " WHERE id = " . $this->id;
            }
            $this->query($SQL);
        }

        if ($this->newDoc) {
            $this->id = $this->modx->db->getInsertId();
        }

        foreach ($this->default_field['attribute'] as $key => $value) {
            $tmp = $this->get($key);
            if ($this->newDoc && ( !is_int($tmp) && $tmp=='')) {
                if($tmp == $value){
                    //take default value from global config
                }
                $this->field[$key] = $value;
            }
            $this->Uset($key, 'attribute');
            unset($fld[$key]);
        }
        if (!empty($this->set['attribute'])) {
            if ($this->newDoc) {
                $this->set('internalKey', $this->id)->Uset('internalKey', 'attribute');
                $SQL = "INSERT into {$this->makeTable('web_user_attributes')} SET " . implode(', ', $this->set['attribute']);
            } else {
                $SQL = "UPDATE {$this->makeTable('web_user_attributes')} SET " . implode(', ', $this->set['attribute']) . " WHERE  internalKey = " . $this->getID();
            }
            $this->query($SQL);
        }

        foreach ($this->default_field['extended'] as $key => $value) {
            $tmp = $this->get($key);
            if ($this->newDoc && ( !is_int($tmp) && $tmp=='')) {
                if($tmp == $value){
                    //take default value from global config
                }
                $this->field[$key] = $value;
            }
            $this->Uset($key, 'extended');
            unset($fld[$key]);
        }
        if (!empty($this->set['extended'])) {
            if ($this->newDoc) {
                $this->set('internalKey', $this->id)->Uset('internalKey', 'extended');
                $SQL = "INSERT into {$this->makeTable($this->extendedTable)} SET " . implode(', ', $this->set['extended']);
            } else {
                $SQL = "UPDATE {$this->makeTable($this->extendedTable)} SET " . implode(', ', $this->set['extended']) . " WHERE  internalKey = " . $this->getID();
            }
            $this->query($SQL);
        }

        foreach ($fld as $key => $value) {
            if ($value == '') continue;
            $result = $this->query("SELECT `setting_value` FROM {$this->makeTable('web_user_settings')} WHERE `webuser` = '{$this->id}' AND `setting_name` = '{$key}'");
            if ($this->modx->db->getRecordCount($result) > 0) {
                $this->query("UPDATE {$this->makeTable('web_user_settings')} SET `setting_value` = '{$value}' WHERE `webuser` = '{$this->id}' AND `setting_name` = '{$key}';");
            } else {
                $this->query("INSERT into {$this->makeTable('web_user_settings')} SET `webuser` = {$this->id},`setting_name` = '{$key}',`setting_value` = '{$value}';");
            }
        }
        if (!$this->newDoc && $this->givenPassword) {
            $this->invokeEvent('OnWebChangePassword',array(
                'userObj'       => $this,
                'userid'        => $this->id,
			    'user'	        => $this->toArray(),
			    'userpassword'	=> $this->givenPassword,
                'internalKey'   => $this->id,
                'username'      => $this->get('username')
            ),$fire_events);
        }
        $this->invokeEvent('OnWebSaveUser',array (
            'userObj'   => $this,
            'mode'      => $this->newDoc ? "new" : "upd",
            'id'        => $this->id,
            'user'      => $this->toArray()
        ),$fire_events);

        if ($clearCache) {
            $this->clearCache($fire_events);
        }
        return $this->id;
    }

    public function delete($ids, $fire_events = null)
    {
        if ($this->edit($ids)) {
            $flag = $this->query("
          DELETE user,attribute,extended FROM {$this->makeTable('web_user_attributes')} as attribute
            LEFT JOIN {$this->makeTable('web_users')} as user ON user.id=attribute.internalKey
            LEFT JOIN {$this->makeTable($this->extendedTable)} as extended ON user.id=extended.internalKey
            WHERE attribute.internalKey='{$this->escape($this->getID())}'");
            $this->query("DELETE FROM {$this->makeTable('web_user_settings')} WHERE webuser='{$this->getID()}'");
            $this->query("DELETE FROM {$this->makeTable('web_groups')} WHERE webuser='{$this->getID()}'");
            $this->invokeEvent('OnWebDeleteUser', array(
                'userObj'       => $this,
                'userid'        => $this->getID(),
                'internalKey'   => $this->getID(),
                'username'      => $this->get('username'),
                'timestamp'     => time()
            ), $fire_events);
        } else {
            $flag = false;
        }
        $this->close();
        return $flag;
    }
}


Как можно понять из кода, в этом классе меняются основные методы класса modUsers с учетом существования дополнительной таблицы атрибутов. Чтобы этим классом пользоваться достаточно заполнить массив extended в свойстве default_field по аналогии с уже имеющимися там массивами. Ну и предварительно создать таблицу, например:

CREATE TABLE `modx_web_user_attributes_extended` (
`id` int(10) NOT NULL auto_increment,
`internalKey` int(10) NOT NULL,
`firstField` TEXT NOT NULL default '',
`secondField` int(10) NOT NULL default 0,
`thirdField` datetime NOT NULL,
PRIMARY KEY  (`id`),
KEY `userid` (`internalKey`)
) ENGINE=MyISAM


При более внимательном рассмотрении класса modUsersExt можно заметить новое свойство extendedTable c именем таблицы дополнительных атрибутов. Это позволяет хранить дополнительные поля в разных таблицах, как WebLoginPE, а также еще немного упростить работу с дополнительными полями — например для вышеприведенной таблицы достаточно просто расширить класс modUsersExt:

<?php
require_once('modUsersExt.php');

class customUser extends modUsersExt
{
    public $extendedTable = 'web_user_attributes_extended';

    public function __construct(DocumentParser $modx, $debug = false) {
        parent::__construct($modx, $debug);
        $this->default_field['extended'] = array(
            'firstField'=>'',
            'secondField'=>0,
            'thirdField'=>0,
        );
    }
}


Если пользоваться FormLister, то для последнего примера нужно добавить при вызове параметры:

&model=`\customUser`
&modelPath=`assets/lib/MODxAPI/customUser.php`


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

Использование prepare-сниппетов в FormLister

На написание топика сподвигло достаточно содержательное обсуждение компонента с paic , по итогам которого я решил прояснить некоторые моменты, связанные с практическим использованием FormLister.

Читать дальше →

Тестирование отправки почты

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

Читать дальше →

Документация DocLister

Пришла пора исправить положение с документацией к DocLister'у — блог автора закрылся, посылать читать документацию стало некуда. Что-то, конечно, осталось в кэше гугла и закромах Wayback Machine, но удобство пользования там никакое.

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

Если есть какие-то замечания по документации, то пишите в комментариях, а еще лучше отправляйте правки сразу в docs (:

P.S. Делать документацию даже к своим дополнениям — дело унылое, что говорить про не свои, так что не откажусь от пива — R189925059228 ((:

Спасибо за поддержку: Agel_Nash , Fess .

[EVO] SimpleGallery: расширение функционала, часть 3

В develop-сборке Evo Custom v1.1b-d7.1 появилось новое событие OnParseProperties, которое сразу вызвало у меня интерес.

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

Есть сайт, где реализовано две галереи с помощью SimpleGallery — в одной картинки с образцами продукции, а во второй хранятся фотографии сотрудников с их биографиями (по смыслу это не совсем галерея, просто удобно их хранить так, а не в документах). Логично было бы вкладки с галереями подписать по-разному, но в SimpleGallery название вкладки (в данном случае — «Галерея») задается в параметрах плагина, который может быть только один. Выход из положения — плагин на событие OnParseProperties:


$e = &$modx->event;
if ($e->name == "OnParseProperties") {
    if ($element == "SimpleGallery") {
        if (isset($args['template']) && $args['template']==6) {
            $out = array ();
	    //задаем новое значение параметра tabName
            $out["tabName"] = "Сотрудники";
	    $e->_output = $out;
        }
    }
}


Теперь для страницы «Список сотрудников» с шаблоном 6 вкладка будет называться «Сотрудники», а во всех остальных случаях — «Галерея». Можно проверять и $args['id'] — id страницы, на которой вызывается плагин.

Немного подумав, я решил сделать возможность подменять в onParseProperties не только параметры плагина, но и свойства соответствущего ему класса. Таким образом появляется возможность использовать произвольные списки загружаемых js- и css-файлов, что в сочетании с параметром controller позволяет (в теории) менять интерфейс и логику работы плагина для разных страниц. Например, для разных типов галерей можно использовать разные наборы дополнительных полей.

Аналогично можно изменить работу и других моих плагинов, тем более, что они все делаются из общей заготовки. Например в SimpleFiles теперь появляется возможность создавать разные типы файловых архивов (например, в одном документе храним только zip, в другом только doc и xls), меняя с помощью события OnParseProperties список разрешенных для загрузки файлов.

[EVO] SimpleGallery: расширение функционала, часть 2

В этой статье речь пойдет о том, как изменить интерфейс SimpleGallery под конкретную задачу. При этом файлы плагина останутся без изменений — а значит, работа не пропадет после обновления SimpleGallery. Дальше много текста и говнокода.

Читать дальше →

[EVO] SimpleGallery: расширение функционала, часть 1

Среди достоинств SimpleGallery есть возможность изменять логику работы, не опасаясь потерять эти изменения с очередным обновлением (ну, почти не опасаясь).
Возможности даже три — можно писать плагины, можно расширять классы, можно загружать свои js-скрипты.
В этой статье опишу события для плагинов, а в следующий раз покажу пример более радикального вмешательства с изменением не только логики работы, но и интерфейса.

Читать дальше →

[EVO] Переползаем с Ditto на Doclister, часть 2

В прошлой статье Grinyaha рассказал о простых примерах использования DocLister — по сути, как вместо Ditto вызвать DocLister. Я покажу пример тоже для новичков, но чуть сложнее — для понимания необходимо минимальное знание PHP.

Читать дальше →