воскресенье, 5 декабря 2010 г.

Socket-соединения в Веб-приложениях


источник

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

Хочу немного расказать о сокетах своими словами. Сокет — это что-то вроде радио-приемника, который появляется на сервере после соединения с ним клиента, для обратной связи с клиентским приложением. Как только в этот приемник попадают (записываются) данные, он сразу отправляет их своему приложению-слушателю. Приложение, услышав (прочитав) данные, выполняет какие-либо действия на их основе. Это может быть простое сообщение, которое следует напечатать в чате, или команда которую нужно выполнить итд. Если не углубляться в подробности работы TCP/IP, то примерно так все и происходит.

Ну а теперь давайте вернемся к нашим бара… сокетам. Итак, что нам нужно? Нам нужно сделать так, чтобы как только один соединенный с сервером клиент что-то отправит на сервер, это сразу же было отправлено всем другим соединенным в данный момент с сервером клиентам. Или другая ситуация. Допустим при определенных обстоятельствах послать с сервера сообщение всем соединенным с ним клиентам, и чтобы они его мгновенно (!) получили. Как этого добиться? Я раскажу ниже.

Итак, определим что нам нужно для этого:
  • Один php-файл для сервера.
  • Один html-файл для клиента.
  • Библиотека jQuery.
  • И папка sockets для хранения сокетов.

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

Клиент (index.html):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"«http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd»>

<html xmlns=«http://www.w3.org/1999/xhtml»>
  
    <head>
        <title>Socket AppClient</title>
        <meta http-equiv=«content-type»content=«text/html; charset=utf-8» />
        <meta name=«author» content=«Melnaron»/>
        <link rel=«stylesheet» type=«text/css»href=«css/style.css» />
        <style type=«text/css»>
          
            #log {
               border: 1px solid #999999;
               height: 250px;
               overflow: auto;
               margin: 10px 0;
               padding: 10px;
            }
          
        </style>
        <script type=«text/javascript»src=«lib/jquery.js»></script>
        <script type=«text/javascript»>
          
           $(document).ready(function() {
              
                /*
                * Настройка AJAX
                */
               $.ajaxSetup({url: 'server.php', type: 'post', dataType: 'json'});
              
                /*
                * События кнопок и поля ввода
                */
               $('#btnConnect').click(user.Connect);
               $('#btnDisconnect').click(user.Disconnect);
               $('#btnSend').click(user.Send);
               $('#input')
                   .keydown(function(e){ if (e.keyCode == 13) { user.Send();return false; } })
                   .keypress(function(e){ if (e.keyCode == 13) { return false; }})
                   .keyup(function(e){ if (e.keyCode == 13) { return false; } })
                ;
              
            });
          
        </script>
        <script type=«text/javascript»>
          
            /*
             * Лог
             */
            var log = {
              
               print: function(s) {
                  $('#log').append('<div>'+s+'</div>').get(0).scrollTop += 100;
                }
              
            };
          
            /*
             * Действияприсылаемые с сервера
             */
            var actions = {
              
               Connect: function(params) {
                   log.print('Connected.');
                   log.print('Sock: '+params.sock);
                   user.sock = params.sock;
                   user.conn = true;
                   user.Read();
                },
              
               Disconnect: function(params) {
                   log.print('Disconnected.');
                },
              
               Print: function(params) {
                   log.print(params.message);
                }
              
            };
          
            /*
             * Пользователь(клиент)
             */
            var user = {
              
               sock: null,
              
               conn: false,
              
               busy: false,
              
               read: null,
              
                /*
                * Эта функция обрабатывает приходящие с сервера действия и выполняет их.
                */
               onSuccess: function(data) {
                   if (typeof data.actions == 'object') {
                       for (var i = 0; i <data.actions.length; i++) {
                           if (typeofactions[data.actions[i].action] == 'function') {
                              actions[data.actions[i].action](data.actions[i].params);
                           }
                       }
                   }
                },
              
                /*
                * Эта функция выполняется по завершении ajax-запроса.
                */
               onComplete: function(xhr) {
                   if (xhr.status == 404) {
                       actions.Disconnect();
                   }
                   user.busy = false;
                },
              
                /*
                * Эта функция выполняется по завершении запроса-слушания.
                * При удачном завершении запроса (==200) моментальное возобновлениепрослушивания соккета.
                * При неудачном (!=200) возобновление через 5 секунд.
                */
               onCompleteRead: function(xhr) {
                   if (xhr.status == 200) {
                       user.Read();
                   } else {
                       setTimeout(user.Read, 5000);
                   }
                },
              
                /*
                * Действие.
                * Соединение с сервером.
                */
               Connect: function() {
                   if (user.conn == false && user.busy == false) {
                       log.print('Connecting...');
                       user.busy = true;
                       $.ajax({
                           data: 'action=Connect',
                           success:user.onSuccess,
                           complete:user.onComplete
                       });
                   }
                },
              
                /*
                * Действие.
                * Отсоединение от сервера.
                */
               Disconnect: function() {
                   if (user.conn && user.busy == false &&user.read) {
                       log.print('Disconnecting...');
                       user.busy = true;
                       $.ajax({
                           data:'action=Disconnect&sock='+user.sock,
                           success:user.onSuccess,
                           complete:user.onComplete
                       });
                       user.sock = null;
                       user.conn = false;
                       user.read.abort();
                   }
                },
              
                /*
                * Действие.
                * Отправка данных на сервер.
                */
               Send: function() {
                   if (user.conn) {
                       var data = $.trim($('#input').val());
                       if (!data) {
                           return;
                       }
                       $.ajax({
                           data:'action=Send&sock='+user.sock+'&data='+data,
                           success:user.onSuccess,
                           complete:user.onComplete
                       });
                       $('#input').val('');
                   } else {
                       log.print('Please connect.');
                   }
                },
              
                /*
                * Действие.
                * Прослушивание соккета.
                */
               Read: function() {
                   if (user.conn) {
                       user.read = $.ajax({
                           data:'action=Read&sock='+user.sock,
                           success:user.onSuccess,
                           complete:user.onCompleteRead
                       });
                   }
                }
              
            };
          
        </script>
    </head>
  
    <body>
        <input id=«btnConnect» type=«button»value=«Connect» />
        <input id=«btnDisconnect» type=«button»value=«Disconnect» />
        <div id=«log»></div>
        <input id=«input» type=«text» />
        <input id=«btnSend» type=«button»value=«Send» />
    </body>
  
</html>



Сервер (server.php):

<?php

class Server {
  
    /*
     * Стек действий для отправки клиенту в текущем запросе.
     */
    static private $actions;
  
    /*
     * Основная функция сервера.
     * Запускает на выполнение переданное клиентом действие.
     */
    static function Run() {
        foreach (glob('sockets/*') as $sock) {
            if (filesize($sock)> 2048) {
               unlink($sock);
            }
        }
        $action = 'action'.$_POST['action'];
        if (is_callable('self::'.$action)) {
            self::$action();
            self::Send();
        }
    }
  
    /*
     * Эта функция пишет действие в сокеты.
     * Если передан параметр $self, то исключает указанный в этом параметре сокет.
     */
    static function AddToSock($action, $params = '', $self =null) {
        foreach (glob('sockets/*') as $sock) {
            if ($self &&strpos($sock, $self) !== false) {
               continue;
            }
            $f = fopen($sock,'a+b') or die('socket not found');
            flock($f, LOCK_EX);
            fwrite($f, '{action:"'.$action.'", params: {'.$params.'}}'."\r\n");
            fclose($f);
        }
    }
  
    /*
     * Эта функция добавляет действие в стек для отправки в текущем запросе.
     */
    static function AddToSend($action, $params = '') {
        self::$actions[] = '{action:"'.$action.'", params: {'.$params.'}}';
    }
  
    /*
     * Отправка стека действий на выполнение клиенту.
     */
    static function Send() {
        if (self::$actions) {
            exit('{actions:['.implode(', ', self::$actions).']}');
        }
    }
  
    /*
     * Действие.
     * Соединение с сервером.
     * Создает сокет и отправляет его идентификатор клиенту.
     */
    static function actionConnect() {
        $sock = md5(microtime().rand(1, 1000));
        fclose(fopen('sockets/'.$sock, 'a+b'));
        self::AddToSock('Print', 'message: «Clientconnected.»', $sock);
        self::AddToSend('Connect', 'sock:"'.$sock.'"');
    }
  
    /*
     * Действие.
     * Отсоединение от сервера.
     * Удаляет сокет.
     */
    static function actionDisconnect() {
        $sock = $_POST['sock'];
        unlink('sockets/'.$sock);
        self::AddToSock('Print', 'message: «Client disconnected.»');
        self::AddToSend('Disconnect');
    }
  
    /*
     * Действие.
     * Отправляет введенные данные всем клиентам.
     */
    static function actionSend() {
        $sock = $_POST['sock'];
        $data =htmlspecialchars(trim($_POST['data']), ENT_QUOTES);
        if (strlen($data)) {
           self::AddToSock('Print', 'message: "'.$data.'"', $sock);
           self::AddToSend('Print', 'message: "'.$data.'"');
        }
    }
  
    /*
     * Действие.
     * Слушает сокет до момента когда в нем появятся данные или же до истечения таймаута.
     */
    static function actionRead() {
        $sock = $_POST['sock'];
        $time = time();
        while ((time() — $time) < 30) {
            if ($data =file_get_contents('sockets/'.$sock)) {
                $f =fopen('sockets/'.$sock, 'r+b') or die('socket not found');
               flock($f, LOCK_EX);
               ftruncate($f, 0);
               fwrite($f, '');
               fclose($f);
               $data = trim($data, "\r\n");
               foreach (explode("\r\n", $data) as $action) {
                   self::$actions[] = $action;
                }
               self::Send();
            }
            usleep(250);
        }
    }
  
}

if (@$_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
  
    header('content-type: text/plain; charset=utf-8');
  
    Server::Run();
  
}

?>



Для того чтобы увидеть это чудо в действии вам следует открыть приведенную ниже ссылку в 2х или более окнах браузера (или вообще в разных браузерах).

Итак, работающий пример находится тут.

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

Соединив с сервером первое окно-приложение вы увидите идентификатор своего сокета отправленный с сервера. Соединив второе окно, в первом вы тут же увидите сообщение о соединении клиента. А теперь попробуйте наблюдая за первым окном что-то отправить из второго. Я надеюсь интернет у вас не очень медленный/загруженный, и вы увидите как быстро сообщение отобразится в обоих окнах — почти мгновенно.

Итак, это простой пример. В коде конечно можно еще покопаться. Для старта я думаю хватит.

Исходники полностью работающего примера можно скачать тут.

среда, 24 ноября 2010 г.

Программы, которые я использую в убунту

Захотелось оставить заметку для себя (вдруг буду переустанавливать ос) список программ, которые я дополнительно устанавливаю к стандартной сборке ubuntu.. может кому будет полезно:

Оргайнайзер:
  • был какой-то, который мне нравился, но сейчас нашёл getting things gnome! (gtg)
Интернет:
  • google chrome ( почему-то меня он больше привлекает, чем Mozilla)
  • eiskaltdc++ (многопоточный dc++, замена linuxdcpp)
  • gftp (клиент для работы с FTP)
  • qutim (симпатичный клиент для ICQ)
  • gnochm (читать CHM файлы в gnome)
  • MAF (Плагин для FireFox читать MHT файлы)
  • flashplugin-nonfree (Flash плеер)
Программирование:
  • geany (удобный редактор с подсветкой синтаксиса и вываливающимися подсказками)
Графика:
  • gpick (подбор цветов)
  • inkscape (удобный векторный редактор с хорошей документацией)
Виртуальная машина:
  • Virtualbox ( Использую для интеграции рабочего стола windows xp с некоторыми приложениями)
Файловые менеджеры:
  • gnome-commander (аналог Total Commander)
  • mc (аналог Norton Commander)
Игры:
  • Neverball
  • Armagetron
Кейлоггер:
  •  gxneur
    Список буду пополнять

    воскресенье, 21 ноября 2010 г.

    Разговор про безопасность

    Один мой знакомый утверждает, что joomla является небезопасной CMS лишь потому, что она имеет открытый исходный код и она бесплатна. "В крайнем случае, если не писать самому CMS, нужно брать какую-нибудь платную".. Тогда давайте и линукс объявим небезопасной ОС. Я не утверждаю, что joomla самая безопасная система управления контентом, особенно если на неё напихать различных расширений от сторонних разработчиков, но это не аргументы, господа. А самописные CMS обычно имеют ещё большие минусы в безопасности, чем моя джумлушка =). Медленная - это да.. =( Будем оптимизировать...

    Автоматическое изменение прав папок и файлов на 755 и 644 из консоли (SSH)

    Для безопасной работы joomla(да и вообще всех веб-приложений) необходимо выставить для папок права доступа 755 и для файлов 644. Как это сделать быстро?) Давным-давно я делал это вручную, писал специальные программы. Чуть позже я монтировал ftp в папку и потом менял рекурсивно права доступа(это было долго). Но, чтобы вы не повторяли моих ошибок, метод прост). Как-то я узнал слово SSH. А теперь, начав работать с joomla, решил поделиться с вами. SSH есть практически на любом хостинге. В панели управления можно соединиться с помощью java апплёта. Из linux вы соединяетесь так:
    ssh username@hosting.ru . Он спросит пароль и пропустит дальше. Если владеете основным перечнем комманд linux, то можете управлять сайтом прямо оттуда =). И вот мы вошли.
    набираем 
    Для папок: find /path/to_dir -type d -exec chmod 755 {} \;
    Для файлов: find /path/to_dir -type f -exec chmod 644 {} \; 

    /path/to_dir - это путь к вашей папке. У меня он был ~/public_html

    Информация для новичка:
    Если подсоединившись, вы не знаете, что делать, то вам достаточно узнать о коммандах cd и ls. cd позволяет переходить в другой каталог, ls - отображает все файлы и папки в данном каталоге. Т.е. если вы подключились к хостингу, введите    cd ~  Это переведёт вас в вашу домашнюю папку. Далее введите ls вы увидите список папок. Обычно есть папки public_html или www(обычно это просто ссылка на public_html). Теперь вы можете ввести cd public_html и окажетесь в нужной папке. Теперь можно упростить комманды и ввести для папок только find -type d -exec chmod 755{} \; а для файлов find -type f -exec chmod 644 {} \; 
    Немного подождав, всё вдруг станет безопасным. Кратенькая добавочка к рецепту от joomla docs

    • Если Joomla нужны будут права для записи/перезаписи, то она скажет об этом сама, подсветив нужные ей папки и файлы КРАСНЫМ цветом с сообщением Недоступно для записи.
    Для работы Joomla (полностью настроенного сайта, когда нужные расширения уже установлены) только у нижеуказанных папок могут быть CHMOD 777:
    • administrator/backups/ - chmod 777 иногда требуется для различных компонентов, делающих бэкап базы Joomla.
    • images/
    • images/banners/ - chmod 777, если записываете сюда баннеры с помощью Joomla
    • images/stories/ -
    • Каталог кэша cache/ - папка для записи кэша
    • Каталог сессий - определено в настройках PHP - ДОЛЖЕН БЫТЬ ДОСТУПЕН ДЛЯ ЗАПИСИ
    Защитите папку administrator паролем. В большинстве хостинговых панелей это делается за несколько секунд, а нервы здорово сбережет.

    воскресенье, 31 октября 2010 г.

    Jdoc создание шаблонов

    JDoc закинул без перевода, чтобы просто валялось

    jdoc statements

    jdoc statements are included in every Joomla template and indicate where the output from other parts of Joomla or its extensions should be positioned in the overall web page. A typical jdoc statement looks like this: <jdoc:include type="component" /> See the jdoc statements article for more information.

    Contents

     [hide]

    jdoc:include

    The <jdoc:include /> statement is a Joomla! template's method of displaying content specific to the page being viewed. There are various <jdoc:include />statements, each returning a different part of a Joomla! page.

    The type attribute

    The type attribute specifies the type of content to be rendered in place of the <jdoc:include /> element. For example, the <jdoc:include type="head" />statement uses the type attribute head (type="head"). (Note: Jdoc expressions require double quotes around attributes, and won't work with single quotes.)

    Component

    <jdoc:include type="component" />
    
    This element should only appear once in the <body> element of the Template to render the main content of the page with respect to the current page being viewed.

    Head

    <jdoc:include type="head" />
    
    This element should only appear once in the <head> element of the Template to render the content of the style, script and meta elements associated with the current page.

    Installation

    <jdoc:include type="installation" />
    
    This element is only used within the Joomla! Installer template and of no particular use in a Front-end or Back-end template. It's somewhat the equivalent to the 'component' type, rendering the main content of an installation step.

    Message

    <jdoc:include type="message" />
    
    This element should only appear once in the <body> element of the Template to render system and error messages that occurred in the request.
    CSS styles for system messages can be found in templates\system\css\system.css

    Module

    <jdoc:include type="module" name="breadcrumbs" title="Breadcrumbs" />
    <jdoc:include type="module" name="mainmenu" title="Main Menu" />
    
    This element renders a single module given by the name and title attributes: name should match module type (mod_breadcrumbs and mod_menu in the examples above) while title should be the module name of the desired module. The module in question must be published and accessible by the current user in order to become visible. Additional attributes can be provided to control the layout and appearance of the module, if supported.

    Modules

    Modules are rendered on a page using one of the following code examples. The modules are separated into different areas of a template using template positions set in the templatedetails.xml file. Using the jdoc:include's name="[template position name]" attribute, the various modules in their respective positions can be called, rendered, and styled separately. Additional attributes can be provided to control the layout and appearance of modules, if supported.
    Below are some examples of module statements with module positions used frequently by Joomla! theme developers.
    <jdoc:include type="modules" name="debug" />
    <jdoc:include type="modules" name="icon" />
    <jdoc:include type="modules" name="left" style="rounded" />
    <jdoc:include type="modules" name="left" style="xhtml" />
    <jdoc:include type="modules" name="right" style="xhtml" />
    <jdoc:include type="modules" name="status"  />
    <jdoc:include type="modules" name="syndicate" />
    <jdoc:include type="modules" name="title" />
    <jdoc:include type="modules" name="toolbar" />
    <jdoc:include type="modules" name="top" />
    <jdoc:include type="modules" name="top" style="xhtml" />
    <jdoc:include type="modules" name="user1" style="xhtml" />
    <jdoc:include type="modules" name="user2" style="xhtml" />
    <jdoc:include type="modules" name="user3" />
    <jdoc:include type="modules" name="user4" />
    
    Note: The name="user3" module position is normally (by default) used for the top menu.

    The style attribute

    The optional style="" attribute is available for the module and modules types of <jdoc:include /> statements. The attribute value refers to the chrome style used to wrap the output generated by the Module. If no style is provided, a value of "none" is used by default.
    Template designers may add additional chrome names as described in Applying custom module chrome.

    Создаём сайт под Joomla=) Фотогалерея Phoca Gallery

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

    Вообщем, необходимо сперва поставить задачу. Допустим у меня есть на сайте eventlist, переведённый. Первое, что меня заинтересовало, после того, как я поиграл с этим интересным компонентом, это как же добавить фотоотчёты. Ну, для начала необходим хороший компонент для хранения фоток. Идём на extensions.joomla.org. Пойду пока попразную хэллуин))
    .......................................................................
    утро.. болит голова)) ща напишу))....
    .......................................................................
    Нужен компонент, который будет хранить множество фотоальбомов. Я остановился на Phoca Gallery. На их сайте и вправду множество файлов, помогающих настроить этот компонент. Есть даже файл с русификацией. Надеюсь этот компонент подойдёт. Скачиваем сам компонент, его плагин для работы с JComments, и русификацию. Идём в панель администратора-расширения-установить-устанавливаем компонент(файлы русификации просто компируем в папки administrator/language/ru-RU и /language/ru-RU). Полазив по меню, обнаружил, что можно создавать категории и подкатегории.. Это очень здорово, ведь именно такой компонент и нужен был мне, сейчас попробуйте создать категорию и в неё напихать фоток... Пока всё, что мне не понравилось - это то, что у меня не загружается несколько фотографий сразу... Надеюсь вы поиграли с компонентом и разобрались как с ним управляться... Теперь перейдём к интересному. Я хочу, чтобы на странице каждого клуба (в eventlist) появлялось несколько фоток, которые сохранила для меня Phoca Gallery для этого клуба. Думаю для этого нужно использовать какой-нибудь плагин. (При вводе в редакторе некой строки будут выводиться картинки). Такой плагин я нашёл на их же сайте. Phoca Gallery Plugin Docs - тут хорошо описано как использовать этот плагин, хоть и на английском. Т.е. для того, чтобы отобразить 5 фотографий на странице клуба(или события) мне нужно для начала включить этот плагин в менеджере плагинов, затем использовать его в описании клуба таким образом: {phocagallery view=category|categoryid=1|limitcount=5}
    Не правда ли просто? Джумла рулит)) Поигравшись с параметром detail, пришел к выводу, что самые красивые эффекты это 3 и 6, т.е.


    {phocagallery view=category|categoryid=1|limitcount=5|displaydownload=0|displayname=0|detail=3}
    {phocagallery view=category|categoryid=1|limitcount=5|displaydownload=0|displayname=0|detail=6}


    Однако, если использовать detail=0, то в быстром просмотре можно увидеть не только 5 фоток, представленных на странице, а все фотографии, входящие в категорию. Мне лучше использовать значение 0, а красоту я наведу с помощью тем.. 

    суббота, 30 октября 2010 г.

    Создаем сайт под Joomla=). Переводим компонент

    Смысл изучения мной джумлы заключался в том, чтобы создавать сайты быстрее. Хотел изучить как можно больше информации в блоге, потом забацать этот сайтик, но уже надо начинать делать, поэтому будем учиться по ходу дела.. Это не профессионально, но зато экстрим, детка))..

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

    Пока с дизайном ещё не разобрались, приступим непосредственно к созданию ПО.
    Для начала, я думаю необходимо поискать готовые расширения (а вдруг повезёт и ничего писать не понадобится) Для тех кто не в курсе - самый большой источник расширений для joomla http://extensions.joomla.org/


    Первым самым полезным для меня компонентом оказался EventList . установив его, я понял, что основная архитектура полностью проработана именно в нём. Мне необходимо лишь перевести его на русский язык, да и изменить стиль(может несколько представлений добавить). Я залез в первый попавшийся шаблон этого компонента и решил узнать, как же выводят текст в joomla. Интересно, что выводят его примерно так:
    echo JText::_( 'CITY' ).':';


    Я полез по документации joomla, ничего не понял, полез в исходники. Из-за возможности отладки кода в функции достаточно много. Смысл такой. JText вызывает метод из JLanguage, который загружает ini файлы с языками, которые находятся в папке language/ru-Ru (Для русского языка). Т.е., если мы вызываем в нашем компоненте echo JText::_('CITY');, а в ini файле с нужным именем прописано (в нашем случае ru-RU.com_eventlist.ini) есть строчка CITY=Город, то вместо City нам выведет город. Если же оно не нашло нужной строки, то выведет City (заглавная буква только первая). Вот так-то всё просто =)). Не знаю насколько быстро работает такой метод, но он очень удобный. Займусь переводом, после чего выложу все файлы.


    По окончании пользовательской части, обнаружилось, что при просмотре деталей мероприятия в подзаголовке Клуб в графе информации о клубе всё равно пишется Venue. Дело было в файле components/com_eventlist/views/details/tmpl/default.php на строке 149 (к моменту вашего прочтения номер может измениться, или ошибка может быть исправлена):


    <dt class="venue"><?php echo $this->elsettings->locationname.':'; ?></dt> 


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


    <dt class="venue"><?php echo JText::_($this->elsettings->locationname).':'; ?></dt>


    Допустим, вы как и я переводите какой-либо компонент.. тогда придётся переводить и его администраторскую часть.. всё тоже самое.. всё также, только в папке administrator/language создаём опять же файл ru-RU.com_eventlist


    Перевод сделал, можете посмотреть на результат. Перевод не полный, но вполне исчерпывающий.. Чёрт, нельзя в блоге файлы публиковать. Публикую в 
    Google Documents, придётся авторизоваться.. Успехов с переводами ;)

    понедельник, 25 октября 2010 г.

    Создадим самый простой компонент. Часть 4. Заключительная

    Вот мы и подошли к концу создания самого простого компонента). Неожиданно, но всё это дело растянулось на 4 части.. хм.. Могло бы и на 5, если быть честным =)) Ну что же, мы имеем компонент, который выводит строки из БД. Осталось сделать только, чтобы эти самые строки мы могли добавлять из пользовательского интерфейса. Не будем делать админскую часть, возможность удаления, редактирования. Сроки жмут =)) Начнём

    Первым делом мы должны создать шаблон формы.
    /components/com_counter/views/counter/tmpl/default_form.php

    <? defined('_JEXEC') or die('доступ запрещен'); ?>
    <form method="post" action="index.php?option=com_counter&task=add">
    ADD; <input type="text" name="new_comment" />
    <input type="submit"  />
    </form>
    Простенький шаблон. Обычная форма. ссылка index.php?option=com_counter&task=add означает, что мы передаём нашему компоненту данные с заданием add...

    Теперь в зависимости от того, авторизован пользователь или нет мы должны показать эту форму или наоборот
    Изменим немного представление
    /components/com_counter/views/counter/view.html.php

    <?php
    defined('_JEXEC') or die();
    jimport( 'joomla.application.component.view');
    class CounterViewCounter extends JView
    {
    function display ($tpl = null)
    {
    $user = &JFactory::getUser(); //получаем данные текущего пользователя
    $model = &$this->getModel();
    $rows = $model->getComments();
    foreach($rows as $list)
    {
    $this->assignRef('username', $model->getUserName($list->user_id));
    $this->assignRef('list', $list);
    parent::display($tpl);
    }
    if(!$user->guest) parent::display('form'); // если пользователь не гость, то показать шаблон form
    }
    }
    ?>

    В представление добавилось всего две строчки. Следует заметить, что когда мы передаём методу parent::display('xxx'); какое-то название, то joomla будет искать шаблон в папке tml(которая находится в той же папке, что и представление), но будет искать не xxx.php, а default_xxx.php

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

    Чтобы компонент и вправду был завершен, нам нужно в контроллере реализовать функцию add
    /component/com_counter/controller.php


    <?
    defined('_JEXEC') or die();
    jimport('joomla.application.component.controller');

    class CounterController extends JController
    {
    function __construct()
    {
    parent::__construct();
    $this->registerTask('add','add');
    $this->registerTask('show','show');
    }
    function add()
    {
    $user = &JFactory::getUser();
    if(!$user->guest)
    {
    JTable::addIncludePath(JPATH_ADMINISTRATOR.DS.'components'.DS.'com_counter'.DS.'tables');
    $row = &JTable::getInstance('counter', 'Table');

    $row->user_id = $user->id;
    $row->content = JRequest::getVar('new_comment');
    if (!$row->store()) {
     JError::raiseError(500, $row->getError());
    }
    } else echo "Forbidden";
    $this->show();
    }
    function show()
    {
    parent::display();
    }
    function display()
    {
    parent::display();
    }
    }
    ?>

    Рассмотрим метод add(). Для начала мы проверяем авторизован ли пользователь. Если авторизован, то позволяем ему добавить комментарий. Методы сохранения данных в таблицу могут быть различны, но хотелось испробовать недавно выученный JTable. в шаге 2 мы создавали класс нашей таблицы, который расширял JTable. в этом шаге я решил его использовать. Все известные переменные мы заполнили инфой. Переменные, которые остались установлены в null JTable игнорирует в запросах вставки. Итак, использовать класс для сохранения информации мне понравилось =) Шаги закончены, осталось жуткое послевкусие писанины и ощущение того, что везде всё написано бездарно. Не огорчайся читатель, просто не обращай внимания =)

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

    Создадим самый простой компонент. Часть 4. Заключительная

    Создадим самый простой компонент. Часть 3. JDatabase, JUser

    Вообщем, разобравшись, как joomla работает с данными, я понял, что через обычные запросы. Да-да ничего мудреного тут нет. Ну, может это и к лучшему =). У нас есть класс JDatabase . Надеюсь вы ознакомились с ним по ссылке. Теперь можно переходить к реализации.

    у нас немного изменится модель
    /component/com_counter/models/counter.php

    <?
    defined('_JEXEC') or die('доступ запрещен');

    class CounterModelCounter extends JModel
    {
    function getComments( $options=array() )
    {
    $database = &$this->getDBO();
    $database->setQuery("SELECT * FROM #__counter");
    $list = $database->loadObjectList();
    return $list;
    }
    }
    ?>

    Тут всё ясно. модель реализует функцию, которая получает ссылку на базу данных, потом шлёт запрос, получает ответ и возвращает его представлению. Интересно, что сейчас я пришел к выводу, что, благодаря модели MVC, joomla не нуждается в заумных классах для работы с БД. Можно больше сказать, что класс JTable во многих случаях избыточен, однако я думаю с его помощью получается информация о пользователях и других полезных данных сайта. ну.. посмотрим

    Представление и шаблон пришлось тоже изменить.

    Представление
    /components/com_counter/views/counter/view.html.php

    <?php
    defined('_JEXEC') or die();
    jimport( 'joomla.application.component.view');
    class CounterViewCounter extends JView
    {
    function display ($tpl = null)
    {
    $model = &$this->getModel();
    $rows = $model->getComments();
    foreach($rows as $list)
    {
    $this->assignRef('list', $list);
    parent::display($tpl);
    }
    }
    }
    ?>

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

    Шаблон

    /components/com_counter/views/counter/tmpl/default.php
    <? echo "<b>".$this->list->user_id."</b>:".$this->list->content; ?><br />

    Вот такой простой шаблон. Отображает всего 2 переменные. Теперь встаёт вопрос.. Кто хочет видеть в комментах id пользователя? мы хотим узнать его имя по идентификатору. Конечно, у меня в базе всего один пользователь) но это не проблема для такого заядлого программера как я))).. зарегистрирую ещё одного)

    Заметил, что в базе данных пользователь Admin у меня с идентификатором 62, второй пользователь имеет идентификатор 63. Меняю в соответствии с ними записи в БД (чтобы user_id совпадало с этими идентификаторами)

    Теперь нам надо подумать как узнать имя пользователя по идентификатору. за эти моменты отвечают классы JUser и JFactory. Нам нужно будет добавить 1 функцию в нашу модель:

    Модель
    /component/com_counter/models/counter.php
    <?
    defined('_JEXEC') or die('доступ запрещен');
    jimport( 'joomla.database.table.user' );

    class CounterModelCounter extends JModel
    {
    function getUserName($id)
    {
    $user = &JFactory::getUser($id);
    return $user->username;
    }
    function getComments( $options=array() )
    {
    $database = &$this->getDBO();
    $database->setQuery("SELECT * FROM #__counter");
    $list = $database->loadObjectList();
    return $list;
    }
    }
    ?>

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

    сразу же это вносит мельчайшие изменения в шаблон и представление

    Представление
    /components/com_counter/views/counter/view.html.php
    <?php 
    defined('_JEXEC') or die();
    jimport( 'joomla.application.component.view');  
    class CounterViewCounter extends JView 
    { 
    function display ($tpl = null) 
    { 
    $model = &$this->getModel();
    $rows = $model->getComments();
    foreach($rows as $list)
    {
    $this->assignRef('username', $model->getUserName($list->user_id));
    $this->assignRef('list', $list);
    parent::display($tpl);
    }
    } 
    } 
    ?>


    Шаблон
    /components/com_counter/views/counter/tmpl/default.php
    <? echo "<b>".$this->username."</b>:".$this->list->content; ?><br />

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