Commits

Anonymous committed 67746e1

Add HTMLPurifier

Comments (0)

Files changed (360)

www/system/library/DoctrineX/Record.php

 <?php
 class DoctrineX_Record extends Doctrine_Record
 {
+    protected $user = null;
     protected $camelCaseToUnderscore = null;
 
     /**
      *
+     * @return Identity_User
+     */
+    public function getUser()
+    {
+        if (null === $this->user) {
+            $this->user = Zend_Registry::get('user');
+        }
+        return $this->user;
+    }
+
+    /**
+     *
      * @return Zend_Filter_Word_CamelCaseToUnderscore
      */
     public function getCamelCaseToUnderscore()

www/system/library/Neno/Translit.php

+<?php
+abstract class Neno_Translit
+{
+    protected static $_table = array(
+        'а' => 'a',
+        'б' => 'b',
+        'в' => 'v',
+        'г' => 'g',
+        'д' => 'd',
+        'е' => 'e',
+        'ё' => 'o',
+        'ж' => 'zh',
+        'з' => 'z',
+        'и' => 'i',
+        'й' => 'j',
+        'к' => 'k',
+        'л' => 'l',
+        'м' => 'm',
+        'н' => 'n',
+        'о' => 'o',
+        'п' => 'p',
+        'р' => 'r',
+        'с' => 's',
+        'т' => 't',
+        'у' => 'u',
+        'ф' => 'f',
+        'х' => 'h',
+        'ц' => 'c',
+        'ч' => 'ch',
+        'ш' => 'sh',
+        'щ' => 'sch',
+        'ъ' => '#',
+        'ы' => 'y',
+        'ь' => '\'',
+        'э' => 'je',
+        'ю' => 'yu',
+        'я' => 'ya',
+    );
+    protected static $_toTranslit = array();
+    protected static $_fromTranslit = array();
+
+    protected function __construct()
+    {}
+
+    protected static function _getToTranslit()
+    {
+        foreach (self::$_table as $key => $value) {
+            self::$_toTranslit[$key] = $value;
+            self::$_toTranslit[Utf8::ucfirst($key)] = ucfirst($value);
+        }
+        uasort(self::$_toTranslit, array('Neno_Translit', '_sort'));
+
+        return self::$_toTranslit;
+    }
+
+    protected static function _getFromTranslit()
+    {
+        self::$_fromTranslit = array_flip(self::_getToTranslit());
+        return self::$_fromTranslit;
+    }
+
+    protected static function _sort($a, $b)
+    {
+        $a = strlen($a);
+        $b = strlen($b);
+
+        if ($a === $b) {
+            return 0;
+        }
+        return ($a < $b) ? 1 : -1;
+    }
+
+    public static function toTranslit($string)
+    {
+        return strtr($string, self::_getToTranslit());
+    }
+
+    public static function fromTranslit($string)
+    {
+        return strtr($string, self::_getFromTranslit());
+    }
+}

www/system/library/Neno/Utils.php

                 stripslashes($value);
         return $value;
     }
+
+    public static function isNestedSetCall()
+    {
+        $nestedSetClasses = array(
+            'Doctrine_Tree_NestedSet',
+            'Doctrine_Node_NestedSet',
+        );
+
+        $backtrace = debug_backtrace(false);
+        foreach ($backtrace as $trace) {
+            if (isset($trace['class']) && in_array($trace['class'], $nestedSetClasses)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
 }

www/system/library/ZendY/Controller/Action.php

     {
         $this->flash(self::FLASH_ERROR, $message);
     }
-
-    protected function page()
-    {
-//        if (is_array($label) && Zend_Registry::isRegistered('Zend_Translate')) {
-//            $t = Zend_Registry::get('Zend_Translate');
-//            $label[0] = $t->translate($label[0]);
-//            $label = call_user_func_array('sprintf', $label);
-//        }
-
-
-    }
 }

www/system/library/ZendY/Escape/Object.php

         if (count($args) > 0) {
             $escapingMethod = $args[count($args) - 1];
             if (is_callable($escapingMethod)) {
-                array_pop($args);
+                $escapingMethod = array_pop($args);
             } else {
                 $escapingMethod = $this->escapingMethod;
             }

www/system/library/ZendY/Form/Doctrine.php

         $this->populate($this->model->toArray());
     }
 
+    public function getModel()
+    {
+        if (null === $this->model) {
+            $this->model = $this->service->createModel();
+            $this->model->fromArray($this->getValues());
+        }
+        return $this->model;
+    }
+
     public function create(Doctrine_Record $model = null)
     {
         if ($model) {
             $this->setModel($model);
         }
-        return $this->service->create($this->getValues(), $model);
+        return $this->service->create($this->getValues(), $this->model);
     }
 
     public function update(Doctrine_Record $model = null)
             $this->setModel($model);
         }
 
-        if ($this->model->exists()) {
+        if ($this->model && $this->model->exists()) {
             return $this->update();
         } else {
             return $this->create();

www/system/library/ZendY/View/Helper/Page.php

 
         $info = Zend_Navigation_Page::factory($info);
         if ($this->currentPage) {
-            $this->view->navigation()->findOneBy('label',
-                    $this->currentPage->getLabel())
-                    ->addPage($info);
+            $page = $this->view->navigation()->findOneBy('label',
+                    $this->currentPage->getLabel());
+        }
+
+        if (isset($page)) {
+            $page->addPage($info);
         } else {
             $this->view->navigation()->addPage($info);
         }

www/system/modules/blog/configs/doctrine/schema.yml

 
 Blog_Comment:
   actAs:
+    Timestampable: ~
     NestedSet: { hasManyRoots: true, rootColumnName: entry_id }
   columns:
     name: { type: string(255), notnull: true }
     email: { type: string(255), notnull: true }
     site: { type: string(255), notnull: true }
-    comment: { type: string, notnull: true }
+    text: { type: string, notnull: true }
+    text_html: { type: string, notnull: true }
+    entry_id: { type: integer, notnull: true }
     user_id: { type: integer, notnull: true }
 
 Blog_Entry:
     is_published: { type: boolean, notnull: true, default: 0 }
     user_id: { type: integer, notnull: true }
   relations:
-    EntryTags:
-      class: Blog_EntryTag
-      onDelete: CASCADE
-      local: id
-      foreign: entry_id
-    Tags:
-      class: Blog_Tag
-      local: entry_id
-      foreign: tag_id
-      refClass: Blog_EntryTag
-      foreignAlias: Entries
-    Comments:
-      class: Blog_Comment
-      onDelete: CASCADE
-      local: id
-      foreign: entry_id
-    Aliases:
-      class: Blog_EntryAlias
-      onDelete: CASCADE
-      local: id
-      foreign: entry_id
-    User:
-      class: Identity_User
-      local: user_id
-      foreign: id
+    EntryTags: { class: Blog_EntryTag, onDelete: CASCADE, local: id, foreign: entry_id }
+    Tags: { class: Blog_Tag, local: entry_id, foreign: tag_id, refClass: Blog_EntryTag, foreignAlias: Entries }
+    Comments: { class: Blog_Comment, onDelete: CASCADE, local: id, foreign: entry_id, foreignAlias: Entry, type: many }
+    Aliases: { class: Blog_EntryAlias, onDelete: CASCADE, local: id, foreign: entry_id }
+    User: { class: Identity_User, local: user_id, foreign: id }
 
 Blog_EntryAlias:
   columns:

www/system/modules/blog/configs/routes.yml

+blog_entry_show:
+  type: Zend_Controller_Router_Route_Regex
+  route: entries/([^/]+)
+  defaults:
+    module: blog
+    controller: entry
+    action: show
+  map: { 1: title_slug }
+  reverse: entries/%s
+
 index:
   route: ''
   defaults:
     controller: entry
     action: list
 
+blog_entry_manage:
+  route: entries/manage
+  defaults:
+    module: blog
+    controller: entry
+    action: manage
+
 blog_entry_new:
   route: entries/new
   defaults:
     module: blog
     controller: entry
-    action: add
+    action: new
 
 blog_entry_edit:
-  route: entries/edit
+  route: entries/edit/:title_slug
   defaults:
     module: blog
     controller: entry
     action: edit
 
 blog_entry_delete:
-  route: entries/delete
+  route: entries/delete/:title_slug
   defaults:
     module: blog
     controller: entry
     action: delete
 
-blog_entry_show:
-  type: Zend_Controller_Router_Route_Regex
-  route: entries/([^/]+)
+blog_entry_tag:
+  route: tag/:tag
   defaults:
     module: blog
     controller: entry
-    action: show
-  map: { 1: title_slug }
-  reverse: entries/%s
+    action: tag
+
+blog_comment_delete:
+  route: comments/delete
+  defaults:
+    module: blog
+    controller: comment
+    action: delete
+
+blog_comment_manage:
+  route: comments/manage
+  defaults:
+    module: blog
+    controller: comment
+    action: manage

www/system/modules/blog/controllers/CommentController.php

+<?php
+class Blog_CommentController extends ZendY_Controller_Action
+{
+    public function manageAction()
+    {
+        $this->view->page(__METHOD__);
+    }
+}

www/system/modules/blog/controllers/EntryController.php

     public function init()
     {
         $this->view->page($this->view->metas['title'], array('route' => 'index'));
+        $this->view->page('blog_entry_list', array('route' => 'blog_entry_list'));
     }
 
     public function listAction()
             $this->_forward('empty-list');
             return;
         }
-
-        $this->page(__METHOD__);
     }
 
     public function showAction()
         $this->view->entry = $entry = $service->get($this->_getParam('title_slug'));
         $this->_helper->isEmpty->notFound($entry);
 
+        $commentService = new Blog_Service_Comment();
+        $this->view->commentForm = $commentForm = $commentService->getForm();
+
+        if ($this->_request->isPost()) {
+            if ($commentForm->isValidAndPopulate($this->_request->getPost())) {
+                $model = $commentForm->getModel();
+                $model->entry_id = $entry->id;
+                $commentForm->save();
+
+                $this->reload();
+                return;
+            }
+        }
+
         $this->view->page($entry->getTitle());
     }
 
 
         $this->processForm($form);
 
-        $this->page(__METHOD__);
+        $this->view->page(__METHOD__);
     }
 
     public function editAction()
     {
+        $service = new Blog_Service_Entry();
         $entry = $service->get($this->_getParam('title_slug'));
         $this->_helper->isEmpty->notFound($entry);
 
-        $service = new Blog_Service_Entry();
         $this->view->form = $form = $service->getForm($entry);
 
         $this->processForm($form);
 
-        $this->page(__METHOD__);
+        $this->view->page(__METHOD__);
     }
 
     public function deleteAction()
         $service = new Blog_Service_Entry();
         $service->delete($this->_getParam('title_slug'));
 
-        $this->_helper->redirector->gotoRoute(array(), 'blog_entry_list', true);
+        $this->_helper->redirector->gotoRoute(array(), 'blog_entry_manage', true);
     }
 
     public function emptyListAction()
     {
-        $this->page(__METHOD__);
+        $this->view->page(__METHOD__);
+    }
+
+    public function manageAction()
+    {
+        $service = new Blog_Service_Entry();
+        $this->view->entries = $entries = $service->getPage($this->_getParam('page'));
+
+        if ($this->_helper->isEmpty($entries)) {
+            $this->_forward('empty-list');
+            return;
+        }
+
+        $this->view->page(__METHOD__);
     }
 
     public function processForm($form)

www/system/modules/blog/fixtures/comment.yml

+Blog_Comment:
+  comm:
+    name: Test name
+    text: Test text.

www/system/modules/blog/fixtures/entry.yml

 Blog_Entry:
   php_zf:
     title: PHP and Zend Framework
-    text: This is text.
-    Tags: [php, zf]
+    text: Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+    Tags: [ php, zf ]
+    User: admin
+    Comments: [ comm ]
+
+  php:
+    title: PHP
+    text: Sed ut perspiciatis, unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est, qui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
+    Tags: [ php ]
+    User: admin
+
+  zf:
+    title: Zend Framework
+    text: Quis autem vel eum iure reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur? At vero eos et accusamus et iusto odio dignissimos ducimus, qui blanditiis praesentium voluptatum deleniti atque corrupti, quos dolores et quas molestias excepturi sint, obcaecati cupiditate non provident, similique sunt in culpa, qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio, cumque nihil impedit, quo minus id, quod maxime placeat, facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
+    Tags: [ zf ]
     User: admin

www/system/modules/blog/fixtures/user.yml

 Identity_User:
   admin:
-    name: OLOLO
+    login: admin
+    password: qwerty
+    role_id: admin

www/system/modules/blog/i18n/en/entry/edit.php

+<?php
+return array(
+    'Blog_EntryController::editAction' => 'Edit entry',
+);

www/system/modules/blog/i18n/en/entry/manage.php

+<?php
+return array(
+    'Blog_EntryController::manageAction' => 'Manage entries',
+);

www/system/modules/blog/i18n/en/entryController.php

+<?php
+return array(
+    'blog_entry_list' => 'Blog entries',
+);

www/system/modules/blog/i18n/en/form/comment.php

+<?php
+return array(
+    'blog_comment_name' => 'Name',
+    'blog_comment_email' => 'Email',
+    'blog_comment_site' => 'Site',
+    'blog_comment_text' => 'Text',
+    'blog_comment_submit' => 'Submit',
+);

www/system/modules/blog/i18n/en/form/entry.php

+<?php
+return array(
+    'Blog_EntryController::newAction' => 'New entry',
+
+    'blog_entry_title' => 'Title',
+    'blog_entry_text' => 'Text',
+    'blog_entry_tags_list' => 'Tags',
+    'blog_entry_submit' => 'Save',
+);

www/system/modules/blog/library/Base/Comment.php

              'notnull' => true,
              'length' => '255',
              ));
-        $this->hasColumn('comment', 'string', null, array(
+        $this->hasColumn('text', 'string', null, array(
              'type' => 'string',
              'notnull' => true,
              ));
+        $this->hasColumn('text_html', 'string', null, array(
+             'type' => 'string',
+             'notnull' => true,
+             ));
+        $this->hasColumn('entry_id', 'integer', null, array(
+             'type' => 'integer',
+             'notnull' => true,
+             ));
         $this->hasColumn('user_id', 'integer', null, array(
              'type' => 'integer',
              'notnull' => true,
     public function setUp()
     {
         parent::setUp();
-        $this->hasMany('Blog_Entry', array(
+        $this->hasOne('Blog_Entry as Entry', array(
              'local' => 'entry_id',
              'foreign' => 'id'));
 
+        $timestampable0 = new Doctrine_Template_Timestampable();
         $nestedset0 = new Doctrine_Template_NestedSet(array(
              'hasManyRoots' => true,
              'rootColumnName' => 'entry_id',
              ));
+        $this->actAs($timestampable0);
         $this->actAs($nestedset0);
     }
 }

www/system/modules/blog/library/Base/Entry.php

              'local' => 'entry_id',
              'foreign' => 'tag_id'));
 
-        $this->hasOne('Blog_Comment as Comments', array(
+        $this->hasMany('Blog_Comment as Comments', array(
              'local' => 'id',
              'foreign' => 'entry_id',
              'onDelete' => 'CASCADE'));

www/system/modules/blog/library/Comment.php

 <?php
-
-
 class Blog_Comment extends Blog_Base_Comment
 {
+    public function construct()
+    {
+        $this->mapValue('parent_id');
+    }
 
+    public function preSave($event)
+    {
+        if (in_array('text', $this->_modified)) {
+            $filter = new Zend_Filter();
+            $filter
+                    ->addFilter(new Text_Filter_HtmlPurifier())
+                    ->addFilter(new Text_Filter_Markdown());
+            $this->text_html = $filter->filter($this->text);
+        }
+    }
+
+    public function postInsert($event)
+    {
+        if (!Neno_Utils::isNestedSetCall()) {
+            if ($this->parent_id) {
+                $parent = $this->getTable()->find($this->parent_id);
+                $this->getNode()->insertAsLastChildOf($parent);
+            } else {
+                if ($this->entry_id) {
+                    $parent = $this->getTable()->createQuery('c')
+                            ->andWhere('c.entry_id = ?', $this->entry_id)
+                            ->andWhere('c.level = 0')
+                            ->fetchOne();
+                    if ($parent) {
+                        $this->getNode()->insertAsLastChildOf($parent);
+                    } else {
+                        $this->getTable()->getTree()->createRoot($this);
+                    }
+                } else {
+                    $this->getTable()->getTree()->createRoot($this);
+                }
+            }
+        }
+    }
 }

www/system/modules/blog/library/Entry.php

 <?php
 class Blog_Entry extends Blog_Base_Entry
 {
+    public function construct()
+    {
+        $this->mapValue('tags_list');
+    }
+
     public function preSave($event)
     {
         if (in_array('text', $this->_modified)) {
             $this->text_html = Zend_Filter::filterStatic($this->text, 'markdown');
         }
+        $this->user_id = $this->getUser()->getId();
+    }
+
+    public function postSave($event)
+    {
+        $this->saveTags();
+
+        $comment = new Blog_Comment();
+        $comment->entry_id = $this->getId();
+        $comment->save();
+    }
+
+    public function getTagsList()
+    {
+        $tags = array();
+        foreach ($this->Tags as $tag) {
+            $tags[] = $tag->getName();
+        }
+        return implode(', ', $tags);
+    }
+
+    protected function saveTags()
+    {
+        if (empty($this->tags_list)) {
+            return;
+        }
+
+        $this->loadReference('Tags');
+        $this->unlink('Tags');
+
+        $tagTable = Doctrine_Core::getTable('Blog_Tag');
+
+        $tags = explode(',', $this->tags_list);
+        $links = array();
+        foreach ($tags as $tag) {
+            $record = $tagTable->findOneByName($tag, Doctrine_Core::HYDRATE_ARRAY);
+            if ($record) {
+                $links[] = $record['id'];
+            } else {
+                $record = new Blog_Tag();
+                $record->name = $tag;
+                $record->save();
+
+                $links[] = $record->id;
+            }
+        }
+        $this->link('Tags', $links);
+    }
+
+    public function toArray($deep = true, $prefixKey = false)
+    {
+        $arr = parent::toArray($deep, $prefixKey);
+        $arr['tags_list'] = $this->getTagsList();
+        return $arr;
     }
 }

www/system/modules/blog/library/Form/Comment.php

+<?php
+class Blog_Form_Comment extends ZendY_Form_Doctrine
+{
+    public function init()
+    {
+        parent::init();
+
+        $this->addElement('hidden', 'parent_id');
+
+        $this->addElement('text', 'name', array(
+            'attribs' => array(
+                'size' => 80,
+            ),
+            'required' => true,
+        ));
+
+        $this->addElement('text', 'email', array(
+            'attribs' => array(
+                'size' => 80,
+            ),
+        ));
+
+        $this->addElement('text', 'site', array(
+            'attribs' => array(
+                'size' => 80,
+            ),
+        ));
+
+        $this->addElement('textarea', 'text', array(
+            'attribs' => array(
+                'cols' => 80,
+                'rows' => 5,
+            ),
+            'required' => true,
+        ));
+
+        $this->addElement('submit', 'submit');
+    }
+}

www/system/modules/blog/library/Form/Entry.php

+<?php
+class Blog_Form_Entry extends ZendY_Form_Doctrine
+{
+    public function init()
+    {
+        parent::init();
+
+        $this->addElement('text', 'title', array(
+            'attribs' => array(
+                'size' => 80,
+            ),
+            'required' => true,
+        ));
+
+        $this->addElement('textarea', 'text', array(
+            'attribs' => array(
+                'cols' => 80,
+                'rows' => 20,
+            ),
+            'required' => true,
+        ));
+
+        $this->addElement('text', 'tags_list', array(
+            'attribs' => array(
+                'size' => 80,
+            ),
+            'required' => true,
+        ));
+
+        $this->addElement('submit', 'submit');
+    }
+}

www/system/modules/blog/library/Service/Comment.php

+<?php
+class Blog_Service_Comment extends Neno_Service_Doctrine
+{
+    
+}

www/system/modules/blog/library/Service/Entry.php

     public function get($title_slug)
     {
         return $this->createQuery('e')
+                ->leftJoin('e.Comments c')
                 ->addWhere('e.title_slug = ?', $title_slug)
+                ->addWhere('c.level <> 0')
                 ->fetchOne();
     }
 
                 ->setCurrentPageNumber($currentPage);
         return $entries;
     }
+
+    public function delete($title_slug)
+    {
+        return $this->createQuery('e')
+                ->delete()
+                ->where('e.title_slug = ?', $title_slug)
+                ->execute();
+    }
 }

www/system/modules/blog/library/Tag.php

 <?php
-
-
 class Blog_Tag extends Blog_Base_Tag
 {
-
 }

www/system/modules/blog/views/helpers/BlogTags.php

+<?php
+class Blog_View_Helper_BlogTags extends Zend_View_Helper_Abstract
+{
+    public function blogTags($tags, $separator = ' ')
+    {
+        $result = array();
+        foreach ($tags as $tag) {
+            $result[] = sprintf('<a href="%s">%s</a>',
+                    $this->view->url(array('tag' => $tag->getNameSlug()), 'blog_entry_tag', true),
+                    $tag->getName());
+        }
+        return implode($separator, $result);
+    }
+}

www/system/modules/blog/views/scripts/comment/create.phtml

+<?php
+    $this->jQuery()->enable();
+    $this->headScript()->appendFile('js/blog/Comment.js');
+?>
+
+<div class="b-comment" id="comment_0">
+    <p><a href="<?= $this->url(array('title_slug' => $this->entry->getTitleSlug()),
+            'blog_entry_show', true); ?>#comment_0" class="b-comment-answer"
+            onclick="return false;">Написать комментарий</a></p>
+</div>
+
+<div id="b-comment-commentForm"><?php echo $this->commentForm; ?></div>

www/system/modules/blog/views/scripts/comment/manage.phtml

+<?php if (count($this->comments) > 0): ?>
+    <?php foreach ($this->comments as $comment): ?>
+        <h3>
+            <?php echo $this->translate('COMMENT_ON'); ?>
+            <a href="<?php echo $this->url(array('title_slug' => $comment->getEntry()->getTitleSlug()),
+                    'blog_entry_show', true); ?>#comment_<?php echo $comment->getId(); ?>"
+                    ><?php echo $comment->getEntry()->getTitle(); ?></a>
+            <span class="normal">
+                <a href="<?= $this->url(array('id' => $comment->getId()),
+                        'blog_comment_delete', true); ?>">удалить</a>
+            </span>
+        </h3>
+        <strong>Имя</strong>: "<?php echo $comment->getName(); ?>",<br />
+        <strong>Email</strong>: "<?php echo $comment->getEmail(); ?>",<br />
+        <strong>Сайт</strong>: "<?php echo $comment->getSite(); ?>"<br />
+
+        <p><?php echo $this->unescape($comment->getTextHtml()); ?></p>
+    <?php endforeach; ?>
+<?php endif; ?>

www/system/modules/blog/views/scripts/contextMenu.phtml

+<div class="b-menu">
+    <ul>
+        <li><a href="<?php echo $this->url(array(), 'blog_entry_new');
+                ?>"><?php echo $this->translate('New entry'); ?></a></li>
+        <li><a href="<?php echo $this->url(array(), 'blog_entry_manage');
+                ?>"><?php echo $this->translate('Manage entries'); ?></a></li>
+        <li><a href="<?php echo $this->url(array(), 'blog_comment_manage');
+                ?>"><?php echo $this->translate('Manage comments'); ?></a></li>
+    </ul>
+</div>

www/system/modules/blog/views/scripts/entry/edit.phtml

+<?php echo $this->render('entry/new.phtml'); ?>

www/system/modules/blog/views/scripts/entry/list.phtml

         <a href="<?php echo $this->url(array('title_slug' => $entry->getTitleSlug()),
                 'blog_entry_show', true); ?>"><?php echo $entry->getTitle(); ?></a>
     </h2>
+
     <?php echo $entry->getTextHtml(); ?>
-<?php endforeach; ?>
+
+    <div>
+        <?php echo $this->blogTags($entry->getTags()); ?> |
+        <img src="img/blog/comment.png" /> <?php echo $entry->getCommentNumber(); ?>,
+        <?php echo $entry->getCreatedAt(); ?>
+    </div>
+<?php endforeach; ?>
+
+<?= $this->paginationControl($this->unescape($this->entries)); ?>

www/system/modules/blog/views/scripts/entry/manage.phtml

+<h1><?php echo $this->pageTitle(); ?></h1>
+
+<table class="b-table">
+    <?php foreach ($this->entries as $entry): ?>
+        <tr>
+            <td><a href="<?php echo $this->url(array('title_slug' => $entry->getTitleSlug()),
+                    'blog_entry_show', true); ?>"><?php echo $entry->getTitle(); ?></a></td>
+            <td>
+                <a href="<?php echo $this->url(array('title_slug' => $entry->getTitleSlug()),
+                        'blog_entry_edit', true); ?>"><?php echo $this->translate('edit'); ?></a>
+                <a href="<?php echo $this->url(array('title_slug' => $entry->getTitleSlug()),
+                        'blog_entry_delete', true); ?>"><?php echo $this->translate('delete'); ?></a>
+            </td>
+        </tr>
+    <?php endforeach; ?>
+</table>

www/system/modules/blog/views/scripts/entry/new.phtml

+<h1><?php echo $this->pageTitle(); ?></h1>
+
+<?php echo $this->form; ?>

www/system/modules/blog/views/scripts/entry/show.phtml

+<?php
+$this->highlight()
+        ->setBasePath('js/text/highlight')
+        ->setScriptPath('/highlight.pack.js')
+        ->setStylePath('/styles/vs.css')
+        ->inject();
+?>
+
 <h1><?= $this->pageTitle(); ?></h1>
 
-<?php echo $this->unescape($this->entry->getTextHtml()); ?>
+<?php echo $this->unescape($this->entry->getTextHtml()); ?>
+
+<div>
+    <?php echo $this->blogTags($this->entry->getTags()); ?> |
+    <img src="img/blog/comment.png" /> <?php echo $this->entry->getCommentNumber(); ?>,
+    <?php echo $this->entry->getCreatedAt(); ?>
+</div>
+
+<h3>
+    <img src="img/blog/comment.png" alt="<?php echo $this->translate('Comment');
+            ?>" title="<?php echo $this->translate('Comment'); ?>" />
+    <?php echo $this->translate('Comments'); ?> (<?php echo $this->entry->getCommentNumber(); ?>)
+</h3>
+
+<?php echo $this->render('comment/create.phtml'); ?>
+
+<?php foreach($this->entry->getComments() as $comment): ?>
+    <div class="b-comment" style="margin-left: <?php echo ($comment->getLevel() - 1) * 30;
+            ?>px" id="comment_<?php echo $comment->getId(); ?>">
+        <div class="b-comment-heading">
+            <strong><?php echo $comment->getName(); ?></strong>
+            <?php if ($comment->getEmail()): ?>
+                <?php echo $this->mailto($comment->getEmail(), '@'); ?>
+            <?php endif; ?>
+            <?php if ($comment->getSite()): ?>
+                <a href="<?php echo $comment->getSite(); ?>">www</a>
+            <?php endif; ?>
+            <small>
+                <a href="<?php echo $this->url(array('titleSlug' => $this->entry->getTitleSlug()),
+                        'blog_entry_show', true); ?>#comment_<?=
+                        $comment->getId(); ?>">#<?php echo $comment->getId(); ?></a>
+
+                <?php echo $comment->getCreatedAt(); ?>
+            </small>
+            <?php if ($this->user()->isAllowed('Blog_Comment', 'delete')): ?>
+                <a href="<?php echo $this->url(array('id' => $comment->getId()),
+                        'blog_comment_delete', true); ?>">удалить</a>
+            <?php endif; ?>
+        </div>
+        <div class="b-comment-text"><?php echo $this->unescape($comment->getTextHtml()); ?></div>
+        <div class="b-comment-extra">
+            <a href="<?php echo $this->url(array('title_slug' => $this->entry->getTitleSlug()),
+                    'blog_entry_show', true); ?>#comment_<?php echo $comment->getId(); ?>
+                    " class="b-comment-answer" onclick="return false;"><small>Ответить</small></a>
+        </div>
+    </div>
+<?php endforeach; ?>

www/system/modules/blog/views/scripts/layout.phtml

 <?php echo $this->headTitle()->setSeparator(' <- '); ?>
 <base href="<?php echo $this->serverUrl() . $this->baseUrl(); ?>/" />
 <?php
-$this->headLink()
-        ->prependStylesheet('css/blog/screen.css');
+    $this->headLink()
+            ->prependStylesheet('css/blog/screen.css');
 ?>
 <?php echo $this->bundleLink($this->headLink()); ?>
 <?php echo $this->headLink(); ?>
     </div>
 
     <div class="b-navigation">
-        <ul>
-            <li><a href="">One</a></li>
-            <li><a href="">Two</a></li>
-            <li><a href="">Three</a></li>
-            <li><a href="">Four</a></li>
-        </ul>
+        <?php echo $this->render('navigation.phtml'); ?>
     </div>
 
     <div class="b-column l-content b-content">
     </div>
 
     <div class="b-column l-sidebar">
-        right
+        <?php echo $this->render('contextMenu.phtml'); ?>
     </div>
 
     <div class="l-copyright b-copyright">

www/system/modules/blog/views/scripts/navigation.phtml

+<?php
+    $this->navigation()->addPage(array(
+        'label' => $this->metas['title'],
+        'route' => 'index',
+        'ns' => 'b-navigation'
+    ));
+    $this->navigation()->addPage(array(
+        'label' => 'blog_entry_list',
+        'route' => 'blog_entry_list',
+        'ns' => 'b-navigation'
+    ));
+    if ($this->user()->isLogged()) {
+        $this->navigation()->addPage(array(
+            'label' => 'Logout',
+            'route' => 'logout',
+            'ns' => 'b-navigation'
+        ));
+    }
+?>
+
+<?php
+    echo $this->navigation()->menu(
+            new Zend_Navigation($this->navigation()->findAllBy('ns', 'b-navigation')))
+            ->setMinDepth(0)->setMaxDepth(1);
+?>

www/system/modules/blog/web/css/screen.css

-@IMPORT url('/css/reset.css');
+@IMPORT url('/css/blog/reset.css');
 
 body {
     width: 950px;
     margin: 0.8em 0;
 }
 
+.b-menu ul {
+    margin: 1.5em 0;
+}
+
 .l-sidebar {
     float: left;
     width: 210px;
 }
 .b-copyright {
     padding: 20px 0;
+}
+
+label.required {
+    font-weight: bold;
+}
+.zend_form_checkbox p.description {
+    display: inline;
+}
+.errors {
+    color: #f00;
+}
+
+.b-table td {
+    padding: 4px;
 }

www/system/modules/blog/web/js/Comment.js

+(function($) {
+
+var showForm = function(id) {
+    var $commentForm = $('#b-comment-commentForm');
+    var $found = $('#comment_' + id);
+
+    var marginLeft = parseInt($found.css('margin-left'), 10) || 0;
+    marginLeft = (0 === id) ? 0 : (marginLeft + 30);
+    $commentForm.css('margin-left', marginLeft + 'px');
+
+    $commentForm.insertAfter($found);
+    $commentForm.toggle();
+}
+
+$(function() {
+    var parent = parseInt($('#parent_id').val());
+
+    $('#b-comment-commentForm').hide();
+    if (!isNaN(parent)) {
+        showForm(parent);
+    }
+
+    $('.b-comment-answer').click(function() {
+        var found = /#comment_(\d+)/.exec($(this).attr('href'));
+
+        var parent = parseInt(found[1]);
+        $('#parent_id').val(parent);
+        showForm(parent);
+    });
+});
+
+})(jQuery);

www/system/modules/identity/controllers/UserController.php

 <?php
 class Identity_UserController extends ZendY_Controller_Action
 {
+    public function init()
+    {
+        $this->view->page($this->view->metas['title'], array('route' => 'index'));
+    }
+
     public function loginAction()
     {
         $user = Zend_Registry::get('user');
             }
         }
 
-        $this->page(__METHOD__);
+        $this->view->page(__METHOD__);
     }
 
     public function logoutAction()

www/system/modules/system/controllers/ErrorController.php

                 }
         }
 
-        $this->page(__METHOD__);
+        $this->view->page(__METHOD__);
     }
 
     public function accessDeniedAction()
     {
         $this->getResponse()->setHttpResponseCode(403);
 
-        $this->page(__METHOD__);
+        $this->view->page(__METHOD__);
     }
 
     public function notFoundAction()
     {
         $this->getResponse()->setHttpResponseCode(404);
 
-        $this->page(__METHOD__);
+        $this->view->page(__METHOD__);
     }
 }

www/system/modules/text/Bootstrap.php

         $baseDir = dirname(__FILE__);
         set_include_path(get_include_path()
                 . PATH_SEPARATOR . $baseDir . '/library');
+        define('HTMLPURIFIER_PREFIX', $baseDir . '/library');
 
         Zend_Filter::addDefaultNamespaces('Text_Filter');
     }

www/system/modules/text/library/HTMLPurifier.php

+<?php
+
+/*! @mainpage
+ *
+ * HTML Purifier is an HTML filter that will take an arbitrary snippet of
+ * HTML and rigorously test, validate and filter it into a version that
+ * is safe for output onto webpages. It achieves this by:
+ *
+ *  -# Lexing (parsing into tokens) the document,
+ *  -# Executing various strategies on the tokens:
+ *      -# Removing all elements not in the whitelist,
+ *      -# Making the tokens well-formed,
+ *      -# Fixing the nesting of the nodes, and
+ *      -# Validating attributes of the nodes; and
+ *  -# Generating HTML from the purified tokens.
+ *
+ * However, most users will only need to interface with the HTMLPurifier
+ * and HTMLPurifier_Config.
+ */
+
+/*
+    HTML Purifier 4.0.0 - Standards Compliant HTML Filtering
+    Copyright (C) 2006-2008 Edward Z. Yang
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * Facade that coordinates HTML Purifier's subsystems in order to purify HTML.
+ *
+ * @note There are several points in which configuration can be specified
+ *       for HTML Purifier.  The precedence of these (from lowest to
+ *       highest) is as follows:
+ *          -# Instance: new HTMLPurifier($config)
+ *          -# Invocation: purify($html, $config)
+ *       These configurations are entirely independent of each other and
+ *       are *not* merged (this behavior may change in the future).
+ *
+ * @todo We need an easier way to inject strategies using the configuration
+ *       object.
+ */
+class HTMLPurifier
+{
+
+    /** Version of HTML Purifier */
+    public $version = '4.0.0';
+
+    /** Constant with version of HTML Purifier */
+    const VERSION = '4.0.0';
+
+    /** Global configuration object */
+    public $config;
+
+    /** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */
+    private $filters = array();
+
+    /** Single instance of HTML Purifier */
+    private static $instance;
+
+    protected $strategy, $generator;
+
+    /**
+     * Resultant HTMLPurifier_Context of last run purification. Is an array
+     * of contexts if the last called method was purifyArray().
+     */
+    public $context;
+
+    /**
+     * Initializes the purifier.
+     * @param $config Optional HTMLPurifier_Config object for all instances of
+     *                the purifier, if omitted, a default configuration is
+     *                supplied (which can be overridden on a per-use basis).
+     *                The parameter can also be any type that
+     *                HTMLPurifier_Config::create() supports.
+     */
+    public function __construct($config = null) {
+
+        $this->config = HTMLPurifier_Config::create($config);
+
+        $this->strategy     = new HTMLPurifier_Strategy_Core();
+
+    }
+
+    /**
+     * Adds a filter to process the output. First come first serve
+     * @param $filter HTMLPurifier_Filter object
+     */
+    public function addFilter($filter) {
+        trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING);
+        $this->filters[] = $filter;
+    }
+
+    /**
+     * Filters an HTML snippet/document to be XSS-free and standards-compliant.
+     *
+     * @param $html String of HTML to purify
+     * @param $config HTMLPurifier_Config object for this operation, if omitted,
+     *                defaults to the config object specified during this
+     *                object's construction. The parameter can also be any type
+     *                that HTMLPurifier_Config::create() supports.
+     * @return Purified HTML
+     */
+    public function purify($html, $config = null) {
+
+        // :TODO: make the config merge in, instead of replace
+        $config = $config ? HTMLPurifier_Config::create($config) : $this->config;
+
+        // implementation is partially environment dependant, partially
+        // configuration dependant
+        $lexer = HTMLPurifier_Lexer::create($config);
+
+        $context = new HTMLPurifier_Context();
+
+        // setup HTML generator
+        $this->generator = new HTMLPurifier_Generator($config, $context);
+        $context->register('Generator', $this->generator);
+
+        // set up global context variables
+        if ($config->get('Core.CollectErrors')) {
+            // may get moved out if other facilities use it
+            $language_factory = HTMLPurifier_LanguageFactory::instance();
+            $language = $language_factory->create($config, $context);
+            $context->register('Locale', $language);
+
+            $error_collector = new HTMLPurifier_ErrorCollector($context);
+            $context->register('ErrorCollector', $error_collector);
+        }
+
+        // setup id_accumulator context, necessary due to the fact that
+        // AttrValidator can be called from many places
+        $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
+        $context->register('IDAccumulator', $id_accumulator);
+
+        $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);
+
+        // setup filters
+        $filter_flags = $config->getBatch('Filter');
+        $custom_filters = $filter_flags['Custom'];
+        unset($filter_flags['Custom']);
+        $filters = array();
+        foreach ($filter_flags as $filter => $flag) {
+            if (!$flag) continue;
+            if (strpos($filter, '.') !== false) continue;
+            $class = "HTMLPurifier_Filter_$filter";
+            $filters[] = new $class;
+        }
+        foreach ($custom_filters as $filter) {
+            // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat
+            $filters[] = $filter;
+        }
+        $filters = array_merge($filters, $this->filters);
+        // maybe prepare(), but later
+
+        for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
+            $html = $filters[$i]->preFilter($html, $config, $context);
+        }
+
+        // purified HTML
+        $html =
+            $this->generator->generateFromTokens(
+                // list of tokens
+                $this->strategy->execute(
+                    // list of un-purified tokens
+                    $lexer->tokenizeHTML(
+                        // un-purified HTML
+                        $html, $config, $context
+                    ),
+                    $config, $context
+                )
+            );
+
+        for ($i = $filter_size - 1; $i >= 0; $i--) {
+            $html = $filters[$i]->postFilter($html, $config, $context);
+        }
+
+        $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
+        $this->context =& $context;
+        return $html;
+    }
+
+    /**
+     * Filters an array of HTML snippets
+     * @param $config Optional HTMLPurifier_Config object for this operation.
+     *                See HTMLPurifier::purify() for more details.
+     * @return Array of purified HTML
+     */
+    public function purifyArray($array_of_html, $config = null) {
+        $context_array = array();
+        foreach ($array_of_html as $key => $html) {
+            $array_of_html[$key] = $this->purify($html, $config);
+            $context_array[$key] = $this->context;
+        }
+        $this->context = $context_array;
+        return $array_of_html;
+    }
+
+    /**
+     * Singleton for enforcing just one HTML Purifier in your system
+     * @param $prototype Optional prototype HTMLPurifier instance to
+     *                   overload singleton with, or HTMLPurifier_Config