Вышел Yii 1.1.8

Вышел Yii 1.1.8. По словам разработчиков внесено более 80 улучшений, изменений и фиксов багов. Из интересного:

    • Использование классов в правилах роутинга в urlManager’е
    • Улучшены автозагрузчик
    • Отправка сообщений в лог в реальном времени
    • Работаем со счётчиками в БД через AR
      Сомнительная фишка добавленная в AR, которая даже вызвала некоторые споры.
    • В clips теперь можно использовать параметры

Анонс можно прочитать на русском здесь.

Plesk заменяем Horde на Roundcube

Приобрел себе панель управления хостингом Plesk 10.2.0. Пока все устраивает, кроме встроенного почтового веб-клиента Horde. Показался он мне морально устаревшим и перегруженным функционалом. Не долго выбирая из возможных клиентов остановился на Roundcube. Мне не удалось найти внятной информации на родном сайте plesk’а как к нему прикрутить другой веб-клиент, так же как и на сайте самого roundcube. Конечно не отрицаю, что может плохо искал)) По сети информацию можно найти обрывками. Вообщем от слов к делу.
Входные данные:

  • Debian 6
  • Plesk 10.2.0
  • У вас есть root доступ к серверу по ssh
  • И помните что вы все делаете на свой страх и риск

Начнем с настройки Roundcube.
1. Заходим в Plesk под администратором.
2. Переходим Управление Серверов — Инструменты и утилиты — Серверы базы данных
3. Кликаем на иконку Вход на этот сервер через DB Webamin. Откроется phpMyAdmin.
4. Создаем БД для Roundcube, я назвал roundcube
5. Идем на сайт http://roundcube.net/ и скачиваем последнию версию. На данный момент это 0.5.3
6. Распаковываем архив, у меня это папка roudcubemail
7. Находим roudcubemail/SQL в ней открываем mysql.initial.sql и выполняем увиденный SQL в нашей БД roundcube.
8. В phpMyAdmin кликаем на иконку домика. Дальше вкладка Привелегии и Добавить нового пользователя. Заполните поля Имя пользователя: username Хост: localhost Пароль: pass Подтверждение: pass. Отметьте все привилегии. Эти данные потом надо будет указать в конфиге roundcube.
9. Создаем файл roudcubemail/config/db.inc.php с следующим содержимым:

<?php
/*
 +-----------------------------------------------------------------------+
 | Configuration file for database access                                |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
*/
$rcmail_config = array();

$rcmail_config['db_dsnw'] = 'mysql://username:pass@localhost/roundcube';
$rcmail_config['db_dsnr'] = '';

$rcmail_config['db_max_length'] = 512000;
$rcmail_config['db_persistent'] = FALSE;

$rcmail_config['db_table_users'] = 'users';
$rcmail_config['db_table_identities'] = 'identities';
$rcmail_config['db_table_contacts'] = 'contacts';
$rcmail_config['db_table_session'] = 'session';
$rcmail_config['db_table_cache'] = 'cache';
$rcmail_config['db_table_messages'] = 'messages';

$rcmail_config['db_sequence_users'] = 'user_ids';
$rcmail_config['db_sequence_identities'] = 'identity_ids';
$rcmail_config['db_sequence_contacts'] = 'contact_ids';
$rcmail_config['db_sequence_cache'] = 'cache_ids';
$rcmail_config['db_sequence_messages'] = 'message_ids';

В $rcmail_config[‘db_dsnw’] указываем свои данные.
10. Создаем файл roundcubemain/config/main.inc.php

<?php
/*
 +-----------------------------------------------------------------------+
 | Configuration file for database access                                |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2009, Roundcube Dev. - Switzerland                 |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 +-----------------------------------------------------------------------+
*/
$rcmail_config = array();

$tmp = strtolower($_SERVER['HTTP_HOST']);
$tmp = str_replace('www.', '', $tmp);
$tmp = str_replace('webmail.', '', $tmp);
$rcmail_config['real_host_name'] = $tmp;
$rcmail_config['real_mail_host'] = 'mail.' . $tmp;
unset($tmp);

$rcmail_config['default_host'] = $rcmail_config['real_mail_host'];
$rcmail_config['default_port'] = 143;

$rcmail_config['username_domain'] = ''; // $rcmail_config['real_host_name'];
$rcmail_config['mail_domain'] = ''; // $rcmail_config['real_host_name'];

$rcmail_config['smtp_server'] = $rcmail_config['real_mail_host'];
$rcmail_config['smtp_port'] = 25;
$rcmail_config['smtp_user'] = '%u';
$rcmail_config['smtp_pass'] = '%p';
$rcmail_config['smtp_auth_type'] = '';
$rcmail_config['smtp_helo_host'] = '';
$rcmail_config['smtp_log'] = TRUE;

$rcmail_config['imap_auth_type'] = null;
$rcmail_config['imap_root']      = null;
$rcmail_config['imap_delimiter'] = null;

$rcmail_config['include_host_config'] = FALSE;
$rcmail_config['sendmail_delay'] = 30;

$rcmail_config['debug_level'] = 1;
$rcmail_config['log_dir'] = 'logs/';
$rcmail_config['log_date_format'] = 'd-M-Y H:i:s O';
$rcmail_config['log_driver'] = 'file';
$rcmail_config['log_logins'] = FALSE;
$rcmail_config['syslog_id'] = 'roundcube';
$rcmail_config['syslog_facility'] = LOG_USER;

$rcmail_config['temp_dir'] = 'temp/';
$rcmail_config['plugins_dir'] = 'plugins/';
$rcmail_config['plugins'] = array(
    'emoticons',
    'markasjunk'
);

$rcmail_config['enable_caching'] = TRUE;
$rcmail_config['message_cache_lifetime'] = '14d';
$rcmail_config['list_cols'] = array('subject', 'from', 'date', 'size', 'flag', 'attachment');
$rcmail_config['skin_include_php'] = FALSE;

$rcmail_config['auto_create_user'] = TRUE;
$rcmail_config['des_key'] = '9IDJVL8%nf6%RTgOwpyfkuDv';
$rcmail_config['double_auth'] = FALSE;
$rcmail_config['ip_check'] = FALSE;
$rcmail_config['keep_alive'] = 60;
$rcmail_config['min_keep_alive'] = 60;
$rcmail_config['session_lifetime'] = 10;
$rcmail_config['session_domain'] = '.' . $rcmail_config['real_host_name'];
$rcmail_config['virtuser_file'] = '';
$rcmail_config['virtuser_query'] = '';

$rcmail_config['language'] = null;
$rcmail_config['date_short'] = 'D H:i';
$rcmail_config['date_long'] = 'd.m.Y H:i';
$rcmail_config['date_today'] = 'H:i';
$rcmail_config['useragent'] = 'RoundCube Webmail/0.3-beta';
$rcmail_config['quota_zero_as_unlimited'] = TRUE;
$rcmail_config['create_default_folders'] = TRUE;
$rcmail_config['check_all_folders'] = FALSE;
$rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
$rcmail_config['protect_default_folders'] = TRUE;
$rcmail_config['drafts_mbox'] = 'Drafts';
$rcmail_config['junk_mbox'] = 'Junk';
$rcmail_config['sent_mbox'] = 'Sent';
$rcmail_config['trash_mbox'] = 'Trash';
$rcmail_config['delete_always'] = FALSE;
$rcmail_config['default_charset'] = 'ISO-8859-1';
$rcmail_config['enable_spellcheck'] = TRUE;
$rcmail_config['spellcheck_engine'] = 'googlie';
$rcmail_config['spellcheck_languages'] = NULL;
$rcmail_config['spellcheck_uri'] = '';
$rcmail_config['address_book_type'] = 'sql';
$rcmail_config['autocomplete_addressbooks'] = array('sql');
$rcmail_config['ldap_public'] = array();
$rcmail_config['dont_override'] = array();

$rcmail_config['enable_installer'] = FALSE;
$rcmail_config['generic_message_footer'] = '';
$rcmail_config['http_received_header'] = FALSE;
$rcmail_config['http_received_header_encrypt'] = FALSE;

$rcmail_config['identities_level'] = 0;
$rcmail_config['mail_header_delimiter'] = NULL;
$rcmail_config['max_pagesize'] = 500;

$rcmail_config['mdn_requests'] = 0;
$rcmail_config['mime_magic'] = '/usr/share/misc/magic';
$rcmail_config['product_name'] = 'RoundCube WebMail';
$rcmail_config['skin'] = 'default';

$rcmail_config['draft_autosave'] = 300;
$rcmail_config['dst_active'] = (bool)date('I');
$rcmail_config['flag_for_deletion'] = FALSE;
$rcmail_config['focus_on_new_message'] = TRUE;
$rcmail_config['htmleditor'] = TRUE; // FALSE;
$rcmail_config['inline_images'] = TRUE;
$rcmail_config['logout_expunge'] = FALSE;
$rcmail_config['logout_purge'] = FALSE;
$rcmail_config['message_sort_col'] = 'date';
$rcmail_config['message_sort_order'] = 'DESC';
$rcmail_config['mime_param_folding'] = 0;
$rcmail_config['pagesize'] = 50;
$rcmail_config['prefer_html'] = TRUE;
$rcmail_config['prettydate'] = FALSE; // TRUE;
$rcmail_config['preview_pane'] = TRUE; // FALSE;
$rcmail_config['read_when_deleted'] = TRUE;
$rcmail_config['show_images'] = 2; // 0;
$rcmail_config['skip_deleted'] = FALSE;
$rcmail_config['timezone'] = 'auto';
?>

11. Теперь необходимо закачать содержимое папки roudcubemail на сервер в папку /usr/share/psa-horde. Перед этим сохраните папку psa-horde или переименуйте в _psa-horde чтобы можно было откатится если что-то пойдет не так.
12. Если все правильно было сделано, то теперь webmail.yourdomain.prefix будет открываться roundcube.

Но если у вас появилась ошибка, как у меня:

Fatal error: ini_set/set_include_path does not work.

То необходимо еще потанцевать с бубном)) Для того чтобы избавится от этой ошибки, необходимо найти на сервере /etc/apache2/conf.d/zz***httpd.conf там будут строкa /opt/psa/admin/conf/generated/*****_horde.include
В данном файле необходимо изменить значения php_admin_value include_path.

php_admin_value include_path "/usr/share/psa-horde/program:/usr/share/psa-horde/program/lib:
/usr/share/psa-horde/program/include:/usr/share/psa-horde:/usr/share/psa-horde/lib:
/usr/share/psa-pear:."

Ну и последний штрих идем в /usr/share/psa-horde/program/include/iniset.php

/* Комментируем этот код
if (set_include_path($include_path) === false) {
    die("Fatal error: ini_set/set_include_path does not work.");
}
*/
...
// убираем первый слыш
require_once 'program/include/main.inc';
require_once 'program/include/rcube_shared.inc';

Надеюсь это кому-нить поможет))

CListView пример использования

Недавно в конференции были вопросы по CListView. Поэтому решил написать небольшой пример использования данного класса. Он предназначен для вывода сущностей(в частности моделей) в виде списка(упорядоченных блоков). Допустим у нас товары и их надо вывести в категории, по 12штук на странице с возможность сортировки по названию и цене товара. Код моделей опущу, т.к. он прост и не принципиален.
Контроллер отвечающий за вывод категории, ни чего сложно. Принимает параметром $alias — псевдоним категории. Производит простые манипуляции, получает данные от модели и отдает их в отображение. Все остальные значимые пометки сделал в коде.
CategoriesController.php

public function actionView($alias) {
    $category = Category::model()->getByAlias($alias);

    if(!$category)
        throw new CHttpException(404, 'Такой страницы не существует.');

    // заполняем заголовок страницы, хлебные крошки,
    // устанавливаем различные значения для СЕО и т.п.
    ...
    // формируем критерий для выборки товаров
    $criteria = new CDbCriteria();
    $criteria->condition = 'category_id = :categoryId';
    $criteria->params = array(':categoryId'=>$category->id);
    // создаем экземпляр CSort для сортировки в CGridView,
    // можно так же описать простым массивом
    $sort = new CSort();
    // имя $_GET параметра для сортировки,
    // по умолчанию ModelName_sort
    $sort->sortVar = 'sort';
    // сортировка по умолчанию 
    $sort->defaultOrder = 'good.final_price ASC';
    // включает поддержку мультисортировки, 
    // т.е. можно отсортировать сразу и по названию и по цене
    $sort->multiSort = true;
    // здесь описываем аттрибуты, по которым будет сортировка
    // ключ может быть произвольный, это будет $_GET параметр
    $sort->attributes = array(
                    'title'=>array(
                        'label'=>'названию',
                        'asc'=>'good.title ASC',
                        'desc'=>'good.title DESC',
                        'default'=>'desc',
                    ),
                    'price'=>array(
                        'asc'=>'good.final_price ASC',
                        'desc'=>'good.final_price DESC',
                        'default'=>'desc',
                        'label'=>'цене',
                    ),
                );
    $dataProvider = new CActiveDataProvider(Good::model()->active()->with('category'), 
            array(
                'criteria'=>$criteria,
                'sort'=>$sort,
                'pagination'=>array(
                    'pageSize'=>12,
                ),
            )
    );
    $this->render('view', array('model'=>$category, 'dataProvider'=>$dataProvider));
}

Представление view.php:

<?php $this->widget('zii.widgets.CListView', array(
    'dataProvider'=>$dataProvider,
    'itemView'=>'_good', // представление для одной записи
    'ajaxUpdate'=>false, // отключаем ajax поведение
    'emptyText'=>'В данной категории нет товаров.',
    'summaryText'=>"{start}&mdash;{end} из {count}",
    'template'=>'{summary} {sorter} {items} <hr> {pager}',
    'sorterHeader'=>'Сортировать по:',
    // ключи, которые были описаны $sort->attributes
    // если не описывать $sort->attributes, можно использовать атрибуты модели
    // настройки CSort перекрывают настройки sortableAttributes
    'sortableAttributes'=>array('title', 'price'),
    'pager'=>array(
        'class'=>'CLinkPager',
        'header'=>false,
        'cssFile'=>'/css/pager.css', // устанавливаем свой .css файл
        'htmlOptions'=>array('class'=>'pager'),
    ),
)); ?>

Представление _good.php не буду приводить, т.к. в нем нет ни чего сложно. В нем доступен объект $data. Вот и весь пример использования.

Алиасы моделей в Yii

При работе с моделями, yii сам формирует алиасы для таблиц. Для обычных запросов это t0, t1, t2, t3 и т.п. В отношениях алиас для связанной таблицы — это название отношения. Но для меня это не очень удобно, т.к иногда взглянув на сложный запрос в профайлере сложно понять что к чему. Поэтому я устанавливаю для всех моделей алиас равный названию таблицы. Казалось бы просто в методе init() или __construct() нужно вызвать setTableAlias() и передать ему $this->tableName(), но почему у меня так не удалось сделать, я не стал с этим разбиратся и устанавливаю алиас таблицы через defaultScope() моей базовой модели.

class BaseModel extends CActiveRecord { 
    ...
    public function defaultScope() {
        return array(
            'alias' => $this->tableName(),
        );
    }
    ...
}

Самое главное помнить, что если вы захотите использовать в какой-то модели defaultScope(), то надо смержить с базовым чтобы не утратить данное поведение, CMap::mergeArray() в помощь)

Относительные пути при работе с кроном

При разработке консольных приложений, которые будут выполняться по крону, есть свои особенности. Одна из таких — это пути. Можно сделать рабочие консольное приложение, протестировать его, но попробовав запустить его через крон поймать ошибок связанных с использованием относительных путей при подключение внешних файлов и т.п. Для того чтобы такого не произошло в «индексном» файле стоит указать:

chdir(dirname(__FILE__));

Эмулятор компьютера с linux на JavaScript

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

Релиз ExtJS4

26 апреля 2011 года вышел в свет ExtJS4. Библиотека стала еще мощнее.

  • улучшено ядро
  • добавлено более 40 новых примеров
  • расширено руководство по использованию
  • более 4000 новый модульных тестов
  • новый пакет для построения диаграмм
  • улучшенный грид
  • «тематизация»

Вообщем для тех кто пользуется это приятная новость.

Скидки в JetBrains 50%

Посколько сам использую PhpStorm для разработки, решил сообщить о такой хорошей новости от разработчиков этой IDE. Ребята из JetBrains хотят всех обрадовать 50% скидками в честь пасхи. С 20 по 30 ареля будут действовать 50% скидка на следующие продукты:

  • PyCharm — Python & Django IDE (50% OFF)
  • IntelliJ IDEA — the smartest code-centric Java IDE (40% OFF)
  • PhpStorm — the lightweight and smart PHP IDE (50% OFF)
  • RubyMine — Ruby and Rails IDE (30% OFF)
  • WebStorm — the best JavaScript and HTML editor (50% OFF)

Спешите, акция только до 30апреля.

События в Yii

На днях решил освоить события в Yii. После мозгового штурма с знакомым, родилась некая схема работы.
Рассмотрим на примере, после успешной авторизации пользователя необходимо обновить поле даты последнего посещения(User->last_visit).
Для этого в класс идентификации необходимо объявить событие onAuthenticate:
PUserIdentity.php

    public function onAuthenticate(CEvent $event) {
        $this->raiseEvent('onAuthenticate', $event);
    }

Затем после успешной аутентификации пользователя необходимо вызвать событие:
PUserIdentity.php

    /**
     * Аутентификация пользователя
     * @return int
     */
    public function authenticate() {
        $user = User::model()->find('LOWER(email) = ?', array(strtolower($this->email)));

        if($user === null) {
            $this->errorCode = self::ERROR_EMAIL_INVALID;
        } else if(PPassword::encrypt($this->password, $user->salt) !== $user->password) {
            $this->errorCode = self::ERROR_PASSWORD_INVALID;
        } else {
            $this->_id = $user->id;
            $this->username = $user->username;
            $this->errorCode = self::ERROR_NONE;
            // вызываем событие
            $this->onAuthenticate(new CEvent($user));
        }

        return !$this->errorCode;
    }

Теперь самое интересное, все обработчики централизовано хранятся в массивеconfig/handlers.php:


return array(
    'onAuthenticate'=>array(
        array('User', 'updateLastVisit'),
    ),
);

Метод updateLastVisit в модели User в качестве параметра должен принимать экземпляр класса CEvent. Описание данного метода я не стану приводит.
Для того чтобы зарегистрировать обработчики событий в классе, напишем статический метод, который будет это делать и вызывать его при инициализации класса. У меня он расположен в хелпере.

    /**
     * Навещивает обработчики событий
     * @static
     * @param CComponent $object
     * @return object
     */
    public static function attacheHandlers(CComponent &$object) {
        $config = PConfig::getEventsConfig();
        foreach($config as $event=>$handlers) {
            if(!method_exists(get_class($object), $event))
                continue;

            foreach($handlers as $handler)
                $object->$event = $handler;
        }
    }

Теперь в нужном классе его нужно вызвать при инициализации:
PUserIdentity.php

    public function __construct($email, $password) {
        $this->email = $email;
        $this->password = $password;
        // PUtils хелпер где распологается метод
        PUtils::attacheHandlers($this);
    }

Вышел Yii 1.1.7

Вышел в свет Yii 1.1.7. По словам разработчиков внесено более 90 улучшений, изменений и фиксов багов. На мой взгляд самое вкусное это кэширование запросов, пример:

$posts = Post::model()->cache(1000)->findAll();

Так же реализована интересная вещь в CUrlManager, теперь в правилах валидации можно указаьть типа запроса: GET, POST, PUT, пример:

'rules'=>array(
    '<controller:w>/<id:d+>' => array('<controller>/view', 'verb'=>'GET'),
    '<controller:w>/<id:d+>' => array('<controller>/update', 'verb'=>'PUT, POST'),
),

Официальный анонс можно прочитать здесь.