chengwl преди 5 години
родител
ревизия
987d33d1d5
променени са 100 файла, в които са добавени 7683 реда и са изтрити 0 реда
  1. 1 0
      .gitignore
  2. 14 0
      app/code/Magefan/Blog/Api/CategoryManagementInterface.php
  3. 59 0
      app/code/Magefan/Blog/Api/ManagementInterface.php
  4. 24 0
      app/code/Magefan/Blog/Api/PostManagementInterface.php
  5. 52 0
      app/code/Magefan/Blog/App/Action/Action.php
  6. 29 0
      app/code/Magefan/Blog/Block/Adminhtml/Category.php
  7. 114 0
      app/code/Magefan/Blog/Block/Adminhtml/Category/Edit.php
  8. 31 0
      app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Form.php
  9. 154 0
      app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tab/Content.php
  10. 257 0
      app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tab/Main.php
  11. 123 0
      app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tab/Meta.php
  12. 26 0
      app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tabs.php
  13. 39 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/BackButton.php
  14. 32 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/CreateButton.php
  15. 44 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/DeleteButton.php
  16. 41 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/DuplicateButton.php
  17. 54 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/GenericButton.php
  18. 41 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/PreviewButton.php
  19. 29 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/ResetButton.php
  20. 34 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/SaveAndContinueButton.php
  21. 32 0
      app/code/Magefan/Blog/Block/Adminhtml/Edit/SaveButton.php
  22. 44 0
      app/code/Magefan/Blog/Block/Adminhtml/Grid/Column/Statuses.php
  23. 66 0
      app/code/Magefan/Blog/Block/Adminhtml/Import/Wordpress.php
  24. 207 0
      app/code/Magefan/Blog/Block/Adminhtml/Import/Wordpress/Form.php
  25. 29 0
      app/code/Magefan/Blog/Block/Adminhtml/Post.php
  26. 113 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit.php
  27. 38 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Form.php
  28. 155 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/Content.php
  29. 272 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/Main.php
  30. 122 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/Meta.php
  31. 318 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/RelatedPosts.php
  32. 372 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/RelatedProducts.php
  33. 48 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tabs.php
  34. 169 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Helper/Form/Gallery.php
  35. 142 0
      app/code/Magefan/Blog/Block/Adminhtml/Post/Helper/Form/Gallery/Content.php
  36. 52 0
      app/code/Magefan/Blog/Block/Adminhtml/System/Config/Form/Info.php
  37. 26 0
      app/code/Magefan/Blog/Block/Amp/Ldjson/Post.php
  38. 50 0
      app/code/Magefan/Blog/Block/Amp/Ldjson/PostList.php
  39. 70 0
      app/code/Magefan/Blog/Block/Amp/Og/Post.php
  40. 85 0
      app/code/Magefan/Blog/Block/Archive/PostList.php
  41. 63 0
      app/code/Magefan/Blog/Block/Author/PostList.php
  42. 84 0
      app/code/Magefan/Blog/Block/Catalog/Product/RelatedPosts.php
  43. 82 0
      app/code/Magefan/Blog/Block/Category/Info.php
  44. 106 0
      app/code/Magefan/Blog/Block/Category/View.php
  45. 50 0
      app/code/Magefan/Blog/Block/Index.php
  46. 70 0
      app/code/Magefan/Blog/Block/Link.php
  47. 149 0
      app/code/Magefan/Blog/Block/Post/AbstractPost.php
  48. 59 0
      app/code/Magefan/Blog/Block/Post/Info.php
  49. 151 0
      app/code/Magefan/Blog/Block/Post/PostList.php
  50. 120 0
      app/code/Magefan/Blog/Block/Post/PostList/AbstractList.php
  51. 17 0
      app/code/Magefan/Blog/Block/Post/PostList/Item.php
  52. 131 0
      app/code/Magefan/Blog/Block/Post/PostList/Toolbar.php
  53. 95 0
      app/code/Magefan/Blog/Block/Post/PostList/Toolbar/Pager.php
  54. 110 0
      app/code/Magefan/Blog/Block/Post/View.php
  55. 124 0
      app/code/Magefan/Blog/Block/Post/View/Comments.php
  56. 19 0
      app/code/Magefan/Blog/Block/Post/View/Gallery.php
  57. 149 0
      app/code/Magefan/Blog/Block/Post/View/NextPrev.php
  58. 84 0
      app/code/Magefan/Blog/Block/Post/View/Opengraph.php
  59. 73 0
      app/code/Magefan/Blog/Block/Post/View/RelatedPosts.php
  60. 138 0
      app/code/Magefan/Blog/Block/Post/View/RelatedProducts.php
  61. 115 0
      app/code/Magefan/Blog/Block/Post/View/Richsnippets.php
  62. 54 0
      app/code/Magefan/Blog/Block/Rss/Feed.php
  63. 64 0
      app/code/Magefan/Blog/Block/Search/PostList.php
  64. 56 0
      app/code/Magefan/Blog/Block/Sidebar.php
  65. 104 0
      app/code/Magefan/Blog/Block/Sidebar/Archive.php
  66. 89 0
      app/code/Magefan/Blog/Block/Sidebar/Categories.php
  67. 46 0
      app/code/Magefan/Blog/Block/Sidebar/Recent.php
  68. 38 0
      app/code/Magefan/Blog/Block/Sidebar/Rss.php
  69. 62 0
      app/code/Magefan/Blog/Block/Sidebar/Search.php
  70. 137 0
      app/code/Magefan/Blog/Block/Sidebar/TagClaud.php
  71. 45 0
      app/code/Magefan/Blog/Block/Sidebar/Widget.php
  72. 53 0
      app/code/Magefan/Blog/Block/Social/AddThis.php
  73. 63 0
      app/code/Magefan/Blog/Block/Tag/PostList.php
  74. 134 0
      app/code/Magefan/Blog/Block/Widget/Recent.php
  75. 461 0
      app/code/Magefan/Blog/Controller/Adminhtml/Actions.php
  76. 45 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category.php
  77. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/Delete.php
  78. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/Duplicate.php
  79. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/Edit.php
  80. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/Grid.php
  81. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/Index.php
  82. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/MassStatus.php
  83. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/NewAction.php
  84. 59 0
      app/code/Magefan/Blog/Controller/Adminhtml/Category/Save.php
  85. 34 0
      app/code/Magefan/Blog/Controller/Adminhtml/Import/Aw.php
  86. 39 0
      app/code/Magefan/Blog/Controller/Adminhtml/Import/Index.php
  87. 83 0
      app/code/Magefan/Blog/Controller/Adminhtml/Import/Run.php
  88. 46 0
      app/code/Magefan/Blog/Controller/Adminhtml/Import/Wordpress.php
  89. 46 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post.php
  90. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/Delete.php
  91. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/Duplicate.php
  92. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/Edit.php
  93. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/Grid.php
  94. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/Index.php
  95. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/MassStatus.php
  96. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/NewAction.php
  97. 40 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/Preview.php
  98. 33 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/RelatedPosts.php
  99. 17 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/RelatedPostsGrid.php
  100. 0 0
      app/code/Magefan/Blog/Controller/Adminhtml/Post/RelatedProducts.php

+ 1 - 0
.gitignore

@@ -40,6 +40,7 @@ atlassian*
 /pub/media/catalog/*
 !/pub/media/catalog/.htaccess
 /pub/media/customer/*
+/pub/media/reviewimages/*
 !/pub/media/customer/.htaccess
 /pub/media/downloadable/*
 !/pub/media/downloadable/.htaccess

+ 14 - 0
app/code/Magefan/Blog/Api/CategoryManagementInterface.php

@@ -0,0 +1,14 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Api;
+
+interface CategoryManagementInterface extends ManagementInterface
+{
+
+}

+ 59 - 0
app/code/Magefan/Blog/Api/ManagementInterface.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Api;
+
+interface ManagementInterface
+{
+    /**
+     * Create new item.
+     *
+     * @api
+     * @param string $data.
+     * @return string.
+     */
+    public function create($data);
+
+    /**
+     * Update item by id.
+     *
+     * @api
+     * @param int $id.
+     * @param string $data.
+     * @return string.
+     */
+    public function update($id, $data);
+
+    /**
+     * Remove item by id.
+     *
+     * @api
+     * @param int $id.
+     * @return bool.
+     */
+    public function delete($id);
+
+    /**
+     * Get item by id.
+     *
+     * @api
+     * @param int $id.
+     * @return bool.
+     */
+    public function get($id);
+
+    /**
+     * Get item by id and store id, only if item published
+     *
+     * @api
+     * @param int $id
+     * @param  int $storeId
+     * @return bool.
+     */
+    public function view($id, $storeId);
+}

+ 24 - 0
app/code/Magefan/Blog/Api/PostManagementInterface.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Api;
+
+interface PostManagementInterface extends ManagementInterface
+{
+	/**
+     * Retrieve list of post by page type, term, store, etc
+     *
+     * @param  string $type
+     * @param  string $term
+     * @param  int $storeId
+     * @param  int $page
+     * @param  int $limit
+     * @return bool
+     */
+    public function getList($type, $term, $storeId, $page, $limit);
+}

+ 52 - 0
app/code/Magefan/Blog/App/Action/Action.php

@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © 2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\App\Action;
+
+/**
+ * Blog frontend action controller
+ */
+abstract class Action extends \Magento\Framework\App\Action\Action
+{
+    /**
+     * Retrieve true if blog extension is enabled.
+     *
+     * @return bool
+     */
+    protected function moduleEnabled()
+    {
+        return (bool) $this->getConfigValue(
+            \Magefan\Blog\Helper\Config::XML_PATH_EXTENSION_ENABLED,
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve store config value
+     *
+     * @return string | null | bool
+     */
+    protected function getConfigValue($path)
+    {
+        $config = $this->_objectManager->get('\Magento\Framework\App\Config\ScopeConfigInterface');
+        return $config->getValue(
+            $path,
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Throw control to cms_index_noroute action.
+     *
+     * @return void
+     */
+    protected function _forwardNoroute()
+    {
+        $this->_forward('index', 'noroute', 'cms');
+    }
+
+}

+ 29 - 0
app/code/Magefan/Blog/Block/Adminhtml/Category.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml;
+
+/**
+ * Admin blog category
+ */
+class Category extends \Magento\Backend\Block\Widget\Grid\Container
+{
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        $this->_controller = 'adminhtml';
+        $this->_blockGroup = 'Magefan_Blog';
+        $this->_headerText = __('Category');
+        $this->_addButtonLabel = __('Add New Category');
+        parent::_construct();
+    }
+}

+ 114 - 0
app/code/Magefan/Blog/Block/Adminhtml/Category/Edit.php

@@ -0,0 +1,114 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Category;
+
+/**
+ * Admin blog category
+ */
+class Edit extends \Magento\Backend\Block\Widget\Form\Container
+{
+    /**
+     * Core registry
+     *
+     * @var \Magento\Framework\Registry
+     */
+    protected $_coreRegistry = null;
+
+    /**
+     * @param \Magento\Backend\Block\Widget\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Widget\Context $context,
+        \Magento\Framework\Registry $registry,
+        array $data = []
+    ) {
+        $this->_coreRegistry = $registry;
+        parent::__construct($context, $data);
+    }
+
+    /**
+     * Initialize cms page edit block
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        $this->_objectId = 'id';
+        $this->_blockGroup = 'Magefan_Blog';
+        $this->_controller = 'adminhtml_category';
+
+        parent::_construct();
+
+        if ($this->_isAllowedAction('Magefan_Blog::category')) {
+            $this->buttonList->add(
+                'saveandcontinue',
+                [
+                    'label' => __('Save and Continue Edit'),
+                    'class' => 'save',
+                    'data_attribute' => [
+                        'mage-init' => [
+                            'button' => ['event' => 'saveAndContinueEdit', 'target' => '#edit_form'],
+                        ],
+                    ]
+                ],
+                -100
+            );
+        } else {
+            $this->buttonList->remove('save');
+        }
+
+        if (!$this->_isAllowedAction('Magefan_Blog::category')) {
+            $this->buttonList->remove('delete');
+        }
+    }
+
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+
+    /**
+     * Getter of url for "Save and Continue" button
+     * tab_id will be replaced by desired by JS later
+     *
+     * @return string
+     */
+    protected function _getSaveAndContinueUrl()
+    {
+        return $this->getUrl('*/*/save', ['_current' => true, 'back' => 'edit', 'active_tab' => '{{tab_id}}']);
+    }
+
+    /**
+     * Prepare layout
+     *
+     * @return \Magento\Framework\View\Element\AbstractBlock
+     */
+    protected function _prepareLayout()
+    {
+        $this->_formScripts[] = "
+            function toggleEditor() {
+                if (tinyMCE.getInstanceById('category_content') == null) {
+                    tinyMCE.execCommand('mceAddControl', false, 'category_content');
+                } else {
+                    tinyMCE.execCommand('mceRemoveControl', false, 'category_content');
+                }
+            };
+        ";
+        return parent::_prepareLayout();
+    }
+}

+ 31 - 0
app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Form.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Category\Edit;
+
+/**
+ * Admin blog category edit form form
+ */
+class Form extends \Magento\Backend\Block\Widget\Form\Generic
+{
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create(
+            ['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
+        );
+        $form->setUseContainer(true);
+        $this->setForm($form);
+        return parent::_prepareForm();
+    }
+}

+ 154 - 0
app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tab/Content.php

@@ -0,0 +1,154 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Category\Edit\Tab;
+
+/**
+ * Admin blog category edit form content tab
+ */
+class Content extends \Magento\Backend\Block\Widget\Form\Generic implements
+    \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * @var \Magento\Cms\Model\Wysiwyg\Config
+     */
+    protected $_wysiwygConfig;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param \Magento\Framework\Data\FormFactory $formFactory
+     * @param \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Framework\Registry $registry,
+        \Magento\Framework\Data\FormFactory $formFactory,
+        \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig,
+        array $data = []
+    ) {
+        $this->_wysiwygConfig = $wysiwygConfig;
+        parent::__construct($context, $registry, $formFactory, $data);
+    }
+
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        /** @var $model \Magefan\Blog\Model\Category */
+        $model = $this->_coreRegistry->registry('current_model');
+
+        /*
+         * Checking if user have permissions to save information
+         */
+        $isElementDisabled = !$this->_isAllowedAction('Magefan_Blog::category');
+
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create();
+
+        $form->setHtmlIdPrefix('category_');
+
+        $fieldset = $form->addFieldset(
+            'content_fieldset',
+            ['legend' => __('Content'), 'class' => 'fieldset-wide']
+        );
+
+        $wysiwygConfig = $this->_wysiwygConfig->getConfig(['tab_id' => $this->getTabId()]);
+
+        $fieldset->addField(
+            'content_heading',
+            'text',
+            [
+                'name' => 'content_heading',
+                'label' => __('Content Heading'),
+                'title' => __('Content Heading'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $contentField = $fieldset->addField(
+            'content',
+            'editor',
+            [
+                'name' => 'content',
+                'style' => 'height:36em;',
+                'disabled' => $isElementDisabled,
+                'config' => $wysiwygConfig
+            ]
+        );
+
+        // Setting custom renderer for content field to remove label column
+        $renderer = $this->getLayout()->createBlock(
+            'Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element'
+        )->setTemplate(
+            'Magento_Cms::page/edit/form/renderer/content.phtml'
+        );
+        $contentField->setRenderer($renderer);
+
+        $this->_eventManager->dispatch('magefan_blog_category_edit_tab_content_prepare_form', ['form' => $form]);
+        $form->setValues($model->getData());
+        $this->setForm($form);
+
+        return parent::_prepareForm();
+    }
+
+    /**
+     * Prepare label for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabLabel()
+    {
+        return __('Content');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Content');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+}

+ 257 - 0
app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tab/Main.php

@@ -0,0 +1,257 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Category\Edit\Tab;
+
+/**
+ * Admin blog category edit form main tab
+ */
+class Main extends \Magento\Backend\Block\Widget\Form\Generic implements
+    \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * @var \Magento\Store\Model\System\Store
+     */
+    protected $_systemStore;
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Category\Collection
+     */
+    protected $_categoryCollection;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param \Magento\Framework\Data\FormFactory $formFactory
+     * @param \Magento\Store\Model\System\Store $systemStore
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Framework\Registry $registry,
+        \Magento\Framework\Data\FormFactory $formFactory,
+        \Magento\Store\Model\System\Store $systemStore,
+        \Magefan\Blog\Model\ResourceModel\Category\Collection $categoryCollection,
+        array $data = []
+    ) {
+        $this->_systemStore = $systemStore;
+        $this->_categoryCollection = $categoryCollection;
+        parent::__construct($context, $registry, $formFactory, $data);
+    }
+
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        /* @var $model \Magefan\Blog\Model\Category */
+        $model = $this->_coreRegistry->registry('current_model');
+
+        /*
+         * Checking if user have permissions to save information
+         */
+        $isElementDisabled = !$this->_isAllowedAction('Magefan_Blog::category');
+
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create();
+
+        $form->setHtmlIdPrefix('category_');
+
+        $fieldset = $form->addFieldset('base_fieldset', ['legend' => __('Category Information')]);
+
+        if ($model->getId()) {
+            $fieldset->addField('category_id', 'hidden', ['name' => 'id']);
+        }
+
+        $fieldset->addField(
+            'title',
+            'text',
+            [
+                'name' => 'title',
+                'label' => __('Category Title'),
+                'title' => __('Category Title'),
+                'required' => true,
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $fieldset->addField(
+            'identifier',
+            'text',
+            [
+                'name' => 'identifier',
+                'label' => __('URL Key'),
+                'title' => __('URL Key'),
+                'class' => 'validate-identifier',
+                'note' => __('Relative to Web Site Base URL'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $fieldset->addField(
+            'is_active',
+            'select',
+            [
+                'label' => __('Status'),
+                'title' => __('Category Status'),
+                'name' => 'is_active',
+                'required' => true,
+                'options' => $model->getAvailableStatuses(),
+                'disabled' => $isElementDisabled
+            ]
+        );
+        if (!$model->getId()) {
+            $model->setData('is_active', $isElementDisabled ? '0' : '1');
+        }
+
+        /**
+         * Check is single store mode
+         */
+        if (!$this->_storeManager->isSingleStoreMode()) {
+            $field = $fieldset->addField(
+                'store_id',
+                'multiselect',
+                [
+                    'name' => 'stores[]',
+                    'label' => __('Store View'),
+                    'title' => __('Store View'),
+                    'required' => true,
+                    'values' => $this->_systemStore->getStoreValuesForForm(false, true),
+                    'disabled' => $isElementDisabled
+                ]
+            );
+            $renderer = $this->getLayout()->createBlock(
+                'Magento\Backend\Block\Store\Switcher\Form\Renderer\Fieldset\Element'
+            );
+            $field->setRenderer($renderer);
+        } else {
+            $fieldset->addField(
+                'store_id',
+                'hidden',
+                ['name' => 'stores[]', 'value' => $this->_storeManager->getStore(true)->getId()]
+            );
+            $model->setStoreId($this->_storeManager->getStore(true)->getId());
+        }
+
+        $categories[] = ['label' => __('Please select'), 'value' => 0];
+        $collection = $this->_categoryCollection
+            ->addFieldToFilter('category_id', array('neq' => $model->getId()))
+            ->setOrder('position')
+            ->getTreeOrderedArray();
+
+        foreach($collection as $item) {
+            if (!$model->isChild($item)) {
+                $categories[] = array(
+                    'label' => $this->_getSpaces($item->getLevel()).' '.$item->getTitle() . ($item->getIsActive() ? '' : ' ('.__('Disabled').')' ),
+                    'value' => ($item->getPath() ? $item->getPath().'/' : '') . $item->getId() ,
+                );
+            }
+        }
+
+        if (count($categories)) {
+            $field = $fieldset->addField(
+                'path',
+                'select',
+                [
+                    'name' => 'path',
+                    'label' => __('Parent Category'),
+                    'title' => __('Parent Category'),
+                    'values' => $categories,
+                    'disabled' => $isElementDisabled,
+                    'style' => 'width:100%',
+                ]
+            );
+        }
+
+        $fieldset->addField(
+            'position',
+            'text',
+            [
+                'name' => 'position',
+                'label' => __('Position'),
+                'title' => __('Position'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $this->_eventManager->dispatch('magefan_blog_category_edit_tab_main_prepare_form', ['form' => $form]);
+
+        $form->setValues($model->getData());
+        $this->setForm($form);
+
+        return parent::_prepareForm();
+    }
+
+    /**
+     * Generate spaces
+     * @param  int $n
+     * @return string
+     */
+    protected function _getSpaces($n)
+    {
+        $s = '';
+        for($i = 0; $i < $n; $i++) {
+            $s .= '--- ';
+        }
+
+        return $s;
+    }
+
+    /**
+     * Prepare label for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabLabel()
+    {
+        return __('Category Information');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Category Information');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+}

+ 123 - 0
app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tab/Meta.php

@@ -0,0 +1,123 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Category\Edit\Tab;
+
+/**
+ * Admin blog category edit form meta data tab
+ */
+class Meta extends \Magento\Backend\Block\Widget\Form\Generic implements
+    \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        /* @var $model \Magefan\Blog\Model\Category */
+        $model = $this->_coreRegistry->registry('current_model');
+
+        /*
+         * Checking if user have permissions to save information
+         */
+        $isElementDisabled = !$this->_isAllowedAction('Magefan_Blog::category');
+
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create();
+
+        $form->setHtmlIdPrefix('category_');
+
+        $fieldset = $form->addFieldset(
+            'meta_fieldset',
+            ['legend' => __('Meta Data'), 'class' => 'fieldset-wide']
+        );
+
+        $fieldset->addField(
+            'meta_keywords',
+            'textarea',
+            [
+                'name' => 'meta_keywords',
+                'label' => __('Keywords'),
+                'title' => __('Meta Keywords'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $fieldset->addField(
+            'meta_description',
+            'textarea',
+            [
+                'name' => 'meta_description',
+                'label' => __('Description'),
+                'title' => __('Meta Description'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $this->_eventManager->dispatch('magefan_blog_category_edit_tab_meta_prepare_form', ['form' => $form]);
+
+        $form->setValues($model->getData());
+
+        $this->setForm($form);
+
+        return parent::_prepareForm();
+    }
+
+    /**
+     * Prepare label for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabLabel()
+    {
+        return __('Meta Data');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Meta Data');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+}

+ 26 - 0
app/code/Magefan/Blog/Block/Adminhtml/Category/Edit/Tabs.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Category\Edit;
+
+/**
+ * Admin blog category edit form tabs
+ */
+class Tabs extends \Magento\Backend\Block\Widget\Tabs
+{
+    /**
+     * @return void
+     */
+    protected function _construct()
+    {
+        parent::_construct();
+        $this->setId('category_tabs');
+        $this->setDestElementId('edit_form');
+        $this->setTitle(__('Category Information'));
+    }
+}

+ 39 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/BackButton.php

@@ -0,0 +1,39 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Class BackButton
+ */
+class BackButton extends GenericButton implements ButtonProviderInterface
+{
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        return [
+            'label' => __('Back'),
+            'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()),
+            'class' => 'back',
+            'sort_order' => 10
+        ];
+    }
+
+    /**
+     * Get URL for back (reset) button
+     *
+     * @return string
+     */
+    public function getBackUrl()
+    {
+        return $this->getUrl('*/*/');
+    }
+}

+ 32 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/CreateButton.php

@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Button "Create" in "New Category" slide-out panel of a post page
+ */
+class CreateButton extends GenericButton implements ButtonProviderInterface
+{
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        return [
+            'label' => __('Create'),
+            'class' => 'save primary',
+            'data_attribute' => [
+                'mage-init' => ['button' => ['event' => 'save']],
+                'form-role' => 'save',
+            ],
+            'sort_order' => 10
+        ];
+    }
+}

+ 44 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/DeleteButton.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Class DeleteButton
+ */
+class DeleteButton extends GenericButton implements ButtonProviderInterface
+{
+
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        $data = [];
+        if ($this->getPostId()) {
+            $data = [
+                'label' => __('Delete'),
+                'class' => 'delete',
+                'on_click' => 'deleteConfirm(\'' . __(
+                    'Are you sure you want to do this?'
+                ) . '\', \'' . $this->getDeleteUrl() . '\')',
+                'sort_order' => 20,
+            ];
+        }
+        return $data;
+    }
+
+    /**
+     * @return string
+     */
+    public function getDeleteUrl()
+    {
+        return $this->getUrl('*/*/delete', ['id' => $this->getPostId()]);
+    }
+}

+ 41 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/DuplicateButton.php

@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Class DuplicateButton
+ */
+class DuplicateButton extends GenericButton implements ButtonProviderInterface
+{
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        $data = [];
+        if ($this->getPostId()) {
+            $data = [
+                'label' => __('Duplicate'),
+                'class' => 'duplicate',
+                'on_click' => 'window.location=\'' . $this->getDuplicateUrl() . '\'',
+                'sort_order' => 40,
+            ];
+        }
+        return $data;
+    }
+
+    /**
+     * @return string
+     */
+    public function getDuplicateUrl()
+    {
+        return $this->getUrl('*/*/duplicate', ['id' => $this->getPostId()]);
+    }
+}

+ 54 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/GenericButton.php

@@ -0,0 +1,54 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Backend\Block\Widget\Context;
+use Magento\Framework\Exception\NoSuchEntityException;
+
+/**
+ * Class GenericButton
+ */
+class GenericButton
+{
+    /**
+     * @var Context
+     */
+    protected $context;
+
+    /**
+     * @param Context $context
+     * @param BlockRepositoryInterface $blockRepository
+     */
+    public function __construct(
+        Context $context
+    ) {
+        $this->context = $context;
+    }
+
+    /**
+     * Return CMS block ID
+     *
+     * @return int|null
+     */
+    public function getPostId()
+    {
+        return $this->context->getRequest()->getParam('id');
+    }
+
+    /**
+     * Generate url by route and parameters
+     *
+     * @param   string $route
+     * @param   array $params
+     * @return  string
+     */
+    public function getUrl($route = '', $params = [])
+    {
+        return $this->context->getUrlBuilder()->getUrl($route, $params);
+    }
+}

+ 41 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/PreviewButton.php

@@ -0,0 +1,41 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Class PreviewButton
+ */
+class PreviewButton extends GenericButton implements ButtonProviderInterface
+{
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        $data = [];
+        if ($this->getPostId()) {
+            $data = [
+                'label' => __('Preview'),
+                'class' => 'preview',
+                'on_click' => 'window.open(\'' . $this->getPreviewUrl() . '\');',
+                'sort_order' => 35,
+            ];
+        }
+        return $data;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPreviewUrl()
+    {
+        return $this->getUrl('*/*/preview', ['id' => $this->getPostId()]);
+    }
+}

+ 29 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/ResetButton.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Class ResetButton
+ */
+class ResetButton implements ButtonProviderInterface
+{
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        return [
+            'label' => __('Reset'),
+            'class' => 'reset',
+            'on_click' => 'location.reload();',
+            'sort_order' => 30
+        ];
+    }
+}

+ 34 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/SaveAndContinueButton.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Class SaveAndContinueButton
+ */
+class SaveAndContinueButton extends GenericButton implements ButtonProviderInterface
+{
+
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        return [
+            'label' => __('Save and Continue Edit'),
+            'class' => 'save',
+            'data_attribute' => [
+                'mage-init' => [
+                    'button' => ['event' => 'saveAndContinueEdit'],
+                ],
+            ],
+            'sort_order' => 80,
+        ];
+    }
+}

+ 32 - 0
app/code/Magefan/Blog/Block/Adminhtml/Edit/SaveButton.php

@@ -0,0 +1,32 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Adminhtml\Edit;
+
+use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;
+
+/**
+ * Class SaveButton
+ */
+class SaveButton extends GenericButton implements ButtonProviderInterface
+{
+    /**
+     * @return array
+     */
+    public function getButtonData()
+    {
+        return [
+            'label' => __('Save'),
+            'class' => 'save primary',
+            'data_attribute' => [
+                'mage-init' => ['button' => ['event' => 'save']],
+                'form-role' => 'save',
+            ],
+            'sort_order' => 90,
+        ];
+    }
+}

+ 44 - 0
app/code/Magefan/Blog/Block/Adminhtml/Grid/Column/Statuses.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Grid\Column;
+
+/**
+ * Admin blog grid statuses 
+ */
+class Statuses extends \Magento\Backend\Block\Widget\Grid\Column
+{
+    /**
+     * Add to column decorated status
+     *
+     * @return array
+     */
+    public function getFrameCallback()
+    {
+        return [$this, 'decorateStatus'];
+    }
+
+    /**
+     * Decorate status column values
+     *
+     * @param string $value
+     * @param  \Magento\Framework\Model\AbstractModel $row
+     * @param \Magento\Backend\Block\Widget\Grid\Column $column
+     * @param bool $isExport
+     * @return string
+     */
+    public function decorateStatus($value, $row, $column, $isExport)
+    {
+        if ($row->getIsActive() || $row->getStatus()) {
+            $cell = '<span class="grid-severity-notice"><span>' . $value . '</span></span>';
+        } else {
+            $cell = '<span class="grid-severity-critical"><span>' . $value . '</span></span>';
+        }
+        return $cell;
+    }
+}

+ 66 - 0
app/code/Magefan/Blog/Block/Adminhtml/Import/Wordpress.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Import;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Wordpress import block
+ */
+class Wordpress extends \Magento\Backend\Block\Widget\Form\Container
+{
+
+    /**
+     * Initialize wordpress import block
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        $this->_objectId = 'id';
+        $this->_blockGroup = 'Magefan_Blog';
+        $this->_controller = 'adminhtml_import';
+        $this->_mode = 'wordpress';
+
+        parent::_construct();
+
+        if (!$this->_isAllowedAction('Magefan_Blog::import')) {
+            $this->buttonList->remove('save');
+        } else {
+          $this->updateButton(
+              'save', 'label', __('Start Import')
+          );
+        }
+
+        $this->buttonList->remove('delete');
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+
+    /**
+     * Get form save URL
+     *
+     * @see getFormActionUrl()
+     * @return string
+     */
+    public function getSaveUrl()
+    {
+        return $this->getUrl('*/*/run', ['_current' => true]);
+    }
+
+}

+ 207 - 0
app/code/Magefan/Blog/Block/Adminhtml/Import/Wordpress/Form.php

@@ -0,0 +1,207 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Import\Wordpress;
+
+/**
+ * Wordpress import form block
+ */
+class Form extends \Magento\Backend\Block\Widget\Form\Generic
+{
+    /**
+     * @var \Magento\Store\Model\System\Store
+     */
+    protected $_systemStore;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param \Magento\Framework\Data\FormFactory $formFactory
+     * @param \Magento\Store\Model\System\Store $systemStore
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Framework\Registry $registry,
+        \Magento\Framework\Data\FormFactory $formFactory,
+        \Magento\Store\Model\System\Store $systemStore,
+        array $data = []
+    ) {
+        $this->_systemStore = $systemStore;
+        parent::__construct($context, $registry, $formFactory, $data);
+    }
+
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        //Magento\Backend\Model\Session
+        $form = $this->_formFactory->create(
+            ['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
+        );
+        $form->setUseContainer(true);
+
+        $data = $this->_coreRegistry->registry('import_config')->getData();
+
+        /*
+         * Checking if user have permissions to save information
+         */
+        if ($this->_authorization->isAllowed('Magefan_Blog::import')) {
+            $isElementDisabled = false;
+        } else {
+            $isElementDisabled = true;
+        }
+        $isElementDisabled = false;
+
+
+        $form->setHtmlIdPrefix('import_');
+
+        $fieldset = $form->addFieldset('base_fieldset', ['legend' => '']);
+
+        $fieldset->addField(
+            'type',
+            'hidden',
+            [
+                'name' => 'type',
+                'required' => true,
+                'disabled' => $isElementDisabled,
+            ]
+        );
+
+        $fieldset->addField(
+            'notice',
+            'label',
+            [
+                'label' => __('NOTICE'),
+                'name' => 'prefix',
+                'after_element_html' => 'When the import is completed successfully, please copy image files from WordPress <strong style="color:#bd1616;">wp-content/uploads</strong> directory to Magento <strong style="color:#105610;">pub/media/magefan_blog</strong> directory.',
+            ]
+        );
+
+        $fieldset->addField(
+            'dbname',
+            'text',
+            [
+                'name' => 'dbname',
+                'label' => __('Database Name'),
+                'title' => __('Database Name'),
+                'required' => true,
+                'disabled' => $isElementDisabled,
+                'after_element_html' => '<small>The name of the database you run in WP.</small>',
+            ]
+        );
+
+        $fieldset->addField(
+            'uname',
+            'text',
+            [
+                'label' => __('User Name'),
+                'title' => __('User Name'),
+                'name' => 'uname',
+                'required' => true,
+                'disabled' => $isElementDisabled,
+                'after_element_html' => '<small>Your WP MySQL username.</small>',
+            ]
+        );
+
+        $fieldset->addField(
+            'pwd',
+            'text',
+            [
+                'label' => __('Password'),
+                'title' => __('Password'),
+                'name' => 'pwd',
+                'required' => true,
+                'disabled' => $isElementDisabled,
+                'after_element_html' => '<small>…and your WP MySQL password.</small>',
+            ]
+        );
+
+        $fieldset->addField(
+            'dbhost',
+            'text',
+            [
+                'label' => __('Database Host'),
+                'title' => __('Database Host'),
+                'name' => 'dbhost',
+                'required' => true,
+                'disabled' => $isElementDisabled,
+                'after_element_html' => '<small>…and your WP MySQL host.</small>',
+            ]
+        );
+
+        $fieldset->addField(
+            'prefix',
+            'text',
+            [
+                'label' => __('Table Prefix'),
+                'title' => __('Table Prefix'),
+                'name' => 'prefix',
+                'required' => true,
+                'disabled' => $isElementDisabled,
+                'after_element_html' => '<small>…and your WP MySQL table prefix.</small>',
+            ]
+        );
+
+        /**
+         * Check is single store mode
+         */
+        if (!$this->_storeManager->isSingleStoreMode()) {
+            $field = $fieldset->addField(
+                'store_id',
+                'select',
+                [
+                    'name' => 'store_id',
+                    'label' => __('Store View'),
+                    'title' => __('Store View'),
+                    'required' => true,
+                    'values' => $this->_systemStore->getStoreValuesForForm(false, true),
+                    'disabled' => $isElementDisabled,
+                ]
+            );
+            $renderer = $this->getLayout()->createBlock(
+                'Magento\Backend\Block\Store\Switcher\Form\Renderer\Fieldset\Element'
+            );
+            $field->setRenderer($renderer);
+        } else {
+            $fieldset->addField(
+                'store_id',
+                'hidden',
+                ['name' => 'store_id', 'value' => $this->_storeManager->getStore(true)->getId()]
+            );
+
+            $data['store_id'] = $this->_storeManager->getStore(true)->getId();
+        }
+
+        $this->_eventManager->dispatch('magefan_blog_import_wordpress_prepare_form', ['form' => $form]);
+
+        /*if (empty($data['homepageurl'])) {
+            $data['homepageurl'] = $this->getUrl('blog', ['_store' => 1]);
+        }*/
+
+        if (empty($data['prefix'])) {
+            $data['prefix'] = 'wp_';
+        }
+
+        if (empty($data['dbhost'])) {
+            $data['dbhost'] = 'localhost';
+        }
+
+        $data['type'] = 'wordpress';
+
+        $form->setValues($data);
+
+        $this->setForm($form);
+
+
+        return parent::_prepareForm();
+    }
+}

+ 29 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml;
+
+/**
+ * Admin blog post
+ */
+class Post extends \Magento\Backend\Block\Widget\Grid\Container
+{
+    /**
+     * Constructor
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        $this->_controller = 'adminhtml_post';
+        $this->_blockGroup = 'Magefan_Blog';
+        $this->_headerText = __('Post');
+        $this->_addButtonLabel = __('Add New Post');
+        parent::_construct();
+    }
+}

+ 113 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit.php

@@ -0,0 +1,113 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post;
+
+/**
+ * Admin blog post
+ */
+class Edit extends \Magento\Backend\Block\Widget\Form\Container
+{
+    /**
+     * Core registry
+     *
+     * @var \Magento\Framework\Registry
+     */
+    protected $_coreRegistry = null;
+
+    /**
+     * @param \Magento\Backend\Block\Widget\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Widget\Context $context,
+        \Magento\Framework\Registry $registry,
+        array $data = []
+    ) {
+        $this->_coreRegistry = $registry;
+        parent::__construct($context, $data);
+    }
+
+    /**
+     * Initialize cms page edit block
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        $this->_objectId = 'id';
+        $this->_blockGroup = 'Magefan_Blog';
+        $this->_controller = 'adminhtml_post';
+
+        parent::_construct();
+
+        if ($this->_isAllowedAction('Magefan_Blog::post')) {
+            $this->buttonList->add(
+                'saveandcontinue',
+                [
+                    'label' => __('Save and Continue Edit'),
+                    'class' => 'save',
+                    'data_attribute' => [
+                        'mage-init' => [
+                            'button' => ['event' => 'saveAndContinueEdit', 'target' => '#edit_form'],
+                        ],
+                    ]
+                ],
+                -100
+            );
+        } else {
+            $this->buttonList->remove('save');
+        }
+
+        if (!$this->_isAllowedAction('Magefan_Blog::post')) {
+            $this->buttonList->remove('delete');
+        }
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+
+    /**
+     * Getter of url for "Save and Continue" button
+     * tab_id will be replaced by desired by JS later
+     *
+     * @return string
+     */
+    protected function _getSaveAndContinueUrl()
+    {
+        return $this->getUrl('*/*/save', ['_current' => true, 'back' => 'edit', 'active_tab' => '{{tab_id}}']);
+    }
+
+    /**
+     * Prepare layout
+     *
+     * @return \Magento\Framework\View\Element\AbstractBlock
+     */
+    protected function _prepareLayout()
+    {
+        $this->_formScripts[] = "
+            function toggleEditor() {
+                if (tinyMCE.getInstanceById('post_content') == null) {
+                    tinyMCE.execCommand('mceAddControl', false, 'post_content');
+                } else {
+                    tinyMCE.execCommand('mceRemoveControl', false, 'post_content');
+                }
+            };
+        ";
+        return parent::_prepareLayout();
+    }
+}

+ 38 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Form.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post\Edit;
+
+/**
+ * Adminhtml cms page edit form block
+ *
+ * @author      Magento Core Team <core@magentocommerce.com>
+ */
+class Form extends \Magento\Backend\Block\Widget\Form\Generic
+{
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create([
+            'data' => [
+                'id' => 'edit_form',
+                'action' => $this->getData('action'),
+                'method' => 'post',
+                'enctype' => 'multipart/form-data',
+            ]
+        ]);
+        $form->setUseContainer(true);
+        $this->setForm($form);
+        return parent::_prepareForm();
+    }
+}

+ 155 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/Content.php

@@ -0,0 +1,155 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post\Edit\Tab;
+
+/**
+ * Admin blog post edit form content tab
+ */
+class Content extends \Magento\Backend\Block\Widget\Form\Generic implements
+    \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * @var \Magento\Cms\Model\Wysiwyg\Config
+     */
+    protected $_wysiwygConfig;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param \Magento\Framework\Data\FormFactory $formFactory
+     * @param \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Framework\Registry $registry,
+        \Magento\Framework\Data\FormFactory $formFactory,
+        \Magento\Cms\Model\Wysiwyg\Config $wysiwygConfig,
+        array $data = []
+    ) {
+        $this->_wysiwygConfig = $wysiwygConfig;
+        parent::__construct($context, $registry, $formFactory, $data);
+    }
+
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        /** @var $model \Magefan\Blog\Model\Post */
+        $model = $this->_coreRegistry->registry('current_model');
+
+        /*
+         * Checking if user have permissions to save information
+         */
+        $isElementDisabled = !$this->_isAllowedAction('Magefan_Blog::post');
+
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create();
+
+        $form->setHtmlIdPrefix('post_');
+
+        $fieldset = $form->addFieldset(
+            'content_fieldset',
+            ['legend' => __('Content'), 'class' => 'fieldset-wide']
+        );
+
+        $wysiwygConfig = $this->_wysiwygConfig->getConfig(['tab_id' => $this->getTabId()]);
+
+        $fieldset->addField(
+            'content_heading',
+            'text',
+            [
+                'name' => 'post[content_heading]',
+                'label' => __('Content Heading'),
+                'title' => __('Content Heading'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $contentField = $fieldset->addField(
+            'content',
+            'editor',
+            [
+                'name' => 'post[content]',
+                'style' => 'height:36em;',
+                'required' => true,
+                'disabled' => $isElementDisabled,
+                'config' => $wysiwygConfig
+            ]
+        );
+
+        // Setting custom renderer for content field to remove label column
+        $renderer = $this->getLayout()->createBlock(
+            'Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element'
+        )->setTemplate(
+            'Magento_Cms::page/edit/form/renderer/content.phtml'
+        );
+        $contentField->setRenderer($renderer);
+
+        $this->_eventManager->dispatch('magefan_blog_post_edit_tab_content_prepare_form', ['form' => $form]);
+        $form->setValues($model->getData());
+        $this->setForm($form);
+
+        return parent::_prepareForm();
+    }
+
+    /**
+     * Prepare label for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabLabel()
+    {
+        return __('Content');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Content');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+}

+ 272 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/Main.php

@@ -0,0 +1,272 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post\Edit\Tab;
+
+/**
+ * Admin blog post edit form main tab
+ */
+class Main extends \Magento\Backend\Block\Widget\Form\Generic implements \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * @var \Magento\Store\Model\System\Store
+     */
+    protected $_systemStore;
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Category\Collection
+     */
+    protected $_categoryCollection;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Framework\Registry $registry
+     * @param \Magento\Framework\Data\FormFactory $formFactory
+     * @param \Magento\Store\Model\System\Store $systemStore
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Framework\Registry $registry,
+        \Magento\Framework\Data\FormFactory $formFactory,
+        \Magento\Store\Model\System\Store $systemStore,
+        \Magefan\Blog\Model\ResourceModel\Category\Collection $categoryCollection,
+        array $data = []
+    ) {
+        $this->_systemStore = $systemStore;
+        $this->_categoryCollection = $categoryCollection;
+        parent::__construct($context, $registry, $formFactory, $data);
+    }
+
+    /**
+     * Prepare form
+     *
+     * @return $this
+     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+     */
+    protected function _prepareForm()
+    {
+        /* @var $model \Magento\Cms\Model\Page */
+        $model = $this->_coreRegistry->registry('current_model');
+
+        /*
+         * Checking if user have permissions to save information
+         */
+        $isElementDisabled = !$this->_isAllowedAction('Magefan_Blog::post');
+
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create();
+
+        $form->setHtmlIdPrefix('post_');
+
+        $fieldset = $form->addFieldset('base_fieldset', ['legend' => __('Post Information')]);
+
+        if ($model->getId()) {
+            $fieldset->addField('post_id', 'hidden', ['name' => 'id']);
+        }
+
+        $fieldset->addField(
+            'title',
+            'text',
+            [
+                'name' => 'post[title]',
+                'label' => __('Post Title'),
+                'title' => __('Post Title'),
+                'required' => true,
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $fieldset->addField(
+            'identifier',
+            'text',
+            [
+                'name' => 'post[identifier]',
+                'label' => __('URL Key'),
+                'title' => __('URL Key'),
+                'class' => 'validate-identifier',
+                'note' => __('Relative to Web Site Base URL'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $fieldset->addField(
+            'is_active',
+            'select',
+            [
+                'label' => __('Status'),
+                'title' => __('Post Status'),
+                'name' => 'post[is_active]',
+                'required' => true,
+                'options' => $model->getAvailableStatuses(),
+                'disabled' => $isElementDisabled
+            ]
+        );
+        if (!$model->getId()) {
+            $model->setData('is_active', $isElementDisabled ? '0' : '1');
+        }
+
+        /**
+         * Check is single store mode
+         */
+        if (!$this->_storeManager->isSingleStoreMode()) {
+            $field = $fieldset->addField(
+                'store_id',
+                'multiselect',
+                [
+                    'name' => 'post[stores][]',
+                    'label' => __('Store View'),
+                    'title' => __('Store View'),
+                    'required' => true,
+                    'values' => $this->_systemStore->getStoreValuesForForm(false, true),
+                    'disabled' => $isElementDisabled,
+                ]
+            );
+            $renderer = $this->getLayout()->createBlock(
+                'Magento\Backend\Block\Store\Switcher\Form\Renderer\Fieldset\Element'
+            );
+            $field->setRenderer($renderer);
+        } else {
+            $fieldset->addField(
+                'store_id',
+                'hidden',
+                ['name' => 'post[stores][]', 'value' => $this->_storeManager->getStore(true)->getId()]
+            );
+            $model->setStoreId($this->_storeManager->getStore(true)->getId());
+        }
+
+        $categories[] = ['label' => __('Please select'), 'value' => 0];
+        $collection = $this->_categoryCollection
+            ->setOrder('position')
+            ->getTreeOrderedArray();
+
+        foreach($collection as $item) {
+            $categories[] = array(
+                'label' => $this->_getSpaces($item->getLevel()).' '.$item->getTitle() . ($item->getIsActive() ? '' : ' ('.__('Disabled').')' ),
+                'value' => $item->getId() ,
+            );
+        }
+
+        $field = $fieldset->addField(
+            'categories',
+            'multiselect',
+            [
+                'name' => 'post[categories][]',
+                'label' => __('Categories'),
+                'title' => __('Categories'),
+                'values' => $categories,
+                'disabled' => $isElementDisabled,
+                'style' => 'width:100%',
+            ]
+        );
+
+        if (is_array($model->getData('featured_img'))) {
+            $model->setData('featured_img', $model->getData('featured_img')['value']);
+        }
+        $fieldset->addField(
+            'featured_img',
+            'image',
+            [
+                'title' => __('Featured Image'),
+                'label' => __('Featured Image'),
+                'name' => 'post[featured_img]',
+                'note' => __('Allow image type: jpg, jpeg, gif, png'),
+            ]
+        );
+
+        $dateFormat = $this->_localeDate->getDateFormat(
+            \IntlDateFormatter::SHORT
+        );
+
+        $fieldset->addField(
+            'publish_time',
+            'date',
+            [
+                'name' => 'post[publish_time]',
+                'label' => __('Publish At'),
+                'date_format' => $dateFormat,
+                'disabled' => $isElementDisabled,
+                'class' => 'validate-date validate-date-range date-range-custom_theme-from'
+            ]
+        );
+
+
+        $this->_eventManager->dispatch('magefan_blog_post_edit_tab_main_prepare_form', ['form' => $form]);
+
+        $form->setValues($model->getData());
+        $this->setForm($form);
+
+        return parent::_prepareForm();
+    }
+
+    /**
+     * Generate spaces
+     * @param  int $n
+     * @return string
+     */
+    protected function _getSpaces($n)
+    {
+        $s = '';
+        for($i = 0; $i < $n; $i++) {
+            $s .= '--- ';
+        }
+
+        return $s;
+    }
+
+    /**
+     * Prepare label for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabLabel()
+    {
+        return __('Post Information');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Post Information');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+}

+ 122 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/Meta.php

@@ -0,0 +1,122 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post\Edit\Tab;
+
+/**
+ * Admin blog post edit form meta tab
+ */
+class Meta extends \Magento\Backend\Block\Widget\Form\Generic implements \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * Prepare form
+     *
+     * @return $this
+     */
+    protected function _prepareForm()
+    {
+        /* @var $model \Magefan\Blog\Model\Category */
+        $model = $this->_coreRegistry->registry('current_model');
+
+        /*
+         * Checking if user have permissions to save information
+         */
+        $isElementDisabled = !$this->_isAllowedAction('Magefan_Blog::post');
+
+        /** @var \Magento\Framework\Data\Form $form */
+        $form = $this->_formFactory->create();
+
+        $form->setHtmlIdPrefix('post_');
+
+        $fieldset = $form->addFieldset(
+            'meta_fieldset',
+            ['legend' => __('Meta Data'), 'class' => 'fieldset-wide']
+        );
+
+        $fieldset->addField(
+            'meta_keywords',
+            'textarea',
+            [
+                'name' => 'post[meta_keywords]',
+                'label' => __('Keywords'),
+                'title' => __('Meta Keywords'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $fieldset->addField(
+            'meta_description',
+            'textarea',
+            [
+                'name' => 'post[meta_description]',
+                'label' => __('Description'),
+                'title' => __('Meta Description'),
+                'disabled' => $isElementDisabled
+            ]
+        );
+
+        $this->_eventManager->dispatch('magefan_blog_post_edit_tab_meta_prepare_form', ['form' => $form]);
+
+        $form->setValues($model->getData());
+
+        $this->setForm($form);
+
+        return parent::_prepareForm();
+    }
+
+    /**
+     * Prepare label for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabLabel()
+    {
+        return __('Meta Data');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Meta Data');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+
+    /**
+     * Check permission for passed action
+     *
+     * @param string $resourceId
+     * @return bool
+     */
+    protected function _isAllowedAction($resourceId)
+    {
+        return $this->_authorization->isAllowed($resourceId);
+    }
+}

+ 318 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/RelatedPosts.php

@@ -0,0 +1,318 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post\Edit\Tab;
+
+use Magento\Backend\Block\Widget\Grid\Column;
+use Magento\Backend\Block\Widget\Grid\Extended;
+
+/**
+ * Admin blog post edit form related posts tab
+ */
+class RelatedPosts extends Extended implements \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * Core registry
+     *
+     * @var \Magento\Framework\Registry
+     */
+    protected $_coreRegistry = null;
+
+    /**
+     * @var \Magento\Catalog\Model\Post\Attribute\Source\Status
+     */
+    protected $_status;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Backend\Helper\Data $backendHelper
+     * @param \Magento\Catalog\Model\Post\Attribute\Source\Status $status
+     * @param \Magento\Framework\Registry $coreRegistry
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Backend\Helper\Data $backendHelper,
+        \Magento\Catalog\Model\Product\Attribute\Source\Status $status,
+        \Magento\Framework\Registry $coreRegistry,
+        array $data = []
+    ) {
+        $this->_status = $status;
+        $this->_coreRegistry = $coreRegistry;
+        parent::__construct($context, $backendHelper, $data);
+    }
+
+    /**
+     * Set grid params
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        parent::_construct();
+        $this->setId('related_posts_section');
+        $this->setDefaultSort('post_id');
+        $this->setUseAjax(true);
+        if ($this->getPost() && $this->getPost()->getId()) {
+            $this->setDefaultFilter(['in_posts' => 1]);
+        }
+        if ($this->isReadonly()) {
+            $this->setFilterVisibility(false);
+        }
+    }
+
+    /**
+     * Retrieve currently edited post model
+     *
+     * @return array|null
+     */
+    public function getPost()
+    {
+        return $this->_coreRegistry->registry('current_model');
+    }
+
+    /**
+     * Add filter
+     *
+     * @param Column $column
+     * @return $this
+     */
+    protected function _addColumnFilterToCollection($column)
+    {
+        // Set custom filter for in post flag
+        if ($column->getId() == 'in_posts') {
+            $postIds = $this->_getSelectedPosts();
+            if (empty($postIds)) {
+                $postIds = 0;
+            }
+            if ($column->getFilter()->getValue()) {
+                $this->getCollection()->addFieldToFilter('post_id', ['in' => $postIds]);
+            } else {
+                if ($postIds) {
+                    $this->getCollection()->addFieldToFilter('post_id', ['nin' => $postIds]);
+                }
+            }
+        } else {
+            parent::_addColumnFilterToCollection($column);
+        }
+        return $this;
+    }
+
+    /**
+     * Prepare collection
+     *
+     * @return Extended
+     */
+    protected function _prepareCollection()
+    {
+        $post = $this->getPost();
+        $collection = $post->getCollection()
+        	->addFieldToFilter('post_id', array('neq' => $post->getId()));
+
+
+        $this->setCollection($collection);
+        return parent::_prepareCollection();
+    }
+
+    /**
+     * Checks when this block is readonly
+     *
+     * @return bool
+     */
+    public function isReadonly()
+    {
+        return false;
+    }
+
+    /**
+     * Add columns to grid
+     *
+     * @return $this
+     */
+    protected function _prepareColumns()
+    {
+        if (!$this->isReadonly()) {
+            $this->addColumn(
+                'in_posts',
+                [
+                    'type' => 'checkbox',
+                    'name' => 'in_posts',
+                    'values' => $this->_getSelectedPosts(),
+                    'align' => 'center',
+                    'index' => 'post_id',
+                    'header_css_class' => 'col-select',
+                    'column_css_class' => 'col-select'
+                ]
+            );
+        }
+
+        $this->addColumn(
+            'post_id',
+            [
+                'header' => __('ID'),
+                'sortable' => true,
+                'index' => 'post_id',
+                'header_css_class' => 'col-id',
+                'column_css_class' => 'col-id'
+            ]
+        );
+
+        $this->addColumn(
+            'title',
+            [
+                'header' => __('Title'),
+                'index' => 'title',
+                'header_css_class' => 'col-name',
+                'column_css_class' => 'col-name'
+            ]
+        );
+
+        $this->addColumn(
+            'identifier',
+            [
+                'header' => __('URL Key'),
+                'index' => 'identifier',
+                'header_css_class' => 'col-name',
+                'column_css_class' => 'col-name'
+            ]
+        );
+
+        /**
+         * Check is single store mode
+         */
+        if (!$this->_storeManager->isSingleStoreMode()) {
+            $this->addColumn(
+                'store_id',
+                [
+                    'header' => __('Store View'),
+                    'index' => 'store_id',
+                    'type' => 'store',
+                    'store_all' => true,
+                    'store_view' => true,
+                    'sortable' => false,
+                ]
+            );
+        }
+
+        $this->addColumn(
+            'is_active',
+            [
+                'header' => __('Status'),
+                'index' => 'is_active',
+                'type' => 'options',
+                'options' => $this->_status->getOptionArray(),
+                'header_css_class' => 'col-status',
+                'column_css_class' => 'col-status',
+                'frame_callback' => array(
+                    $this->getLayout()->createBlock('Magefan\Blog\Block\Adminhtml\Grid\Column\Statuses'),
+                    'decorateStatus'
+                ),
+            ]
+        );
+
+        $this->addColumn(
+            'position',
+            [
+                'header' => __('Position'),
+                'name' => 'position',
+                'type' => 'number',
+                'validate_class' => 'validate-number',
+                'index' => 'position',
+                'editable' => true,
+                'edit_only' => false,
+                'sortable' => false,
+                'filter' => false,
+                'header_css_class' => 'col-position',
+                'column_css_class' => 'col-position'
+            ]
+        );
+
+        return parent::_prepareColumns();
+    }
+
+    /**
+     * Rerieve grid URL
+     *
+     * @return string
+     */
+    public function getGridUrl()
+    {
+        return $this->getData(
+            'grid_url'
+        ) ? $this->getData(
+            'grid_url'
+        ) : $this->getUrl(
+            'blog/post/relatedPostsGrid',
+            ['_current' => true]
+        );
+    }
+
+    /**
+     * Retrieve selected related posts
+     *
+     * @return array
+     */
+    protected function _getSelectedPosts()
+    {
+        $posts = $this->getPostsRelated();
+        if (!is_array($posts)) {
+            $posts = array_keys($this->getSelectedRelatedPosts());
+        }
+        return $posts;
+    }
+
+    /**
+     * Retrieve related posts
+     *
+     * @return array
+     */
+    public function getSelectedRelatedPosts()
+    {
+        $posts = [];
+        foreach ($this->_coreRegistry->registry('current_model')->getRelatedPosts() as $post) {
+            $posts[$post->getId()] = ['position' => $post->getPosition()];
+        }
+        return $posts;
+    }
+
+
+    public function getTabLabel()
+    {
+        return __('Related Posts');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Related Posts');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+}

+ 372 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tab/RelatedProducts.php

@@ -0,0 +1,372 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post\Edit\Tab;
+
+use Magento\Backend\Block\Widget\Grid\Column;
+use Magento\Backend\Block\Widget\Grid\Extended;
+
+/**
+ * Admin blog post edit form related products tab
+ */
+class RelatedProducts extends Extended implements \Magento\Backend\Block\Widget\Tab\TabInterface
+{
+    /**
+     * Core registry
+     *
+     * @var \Magento\Framework\Registry
+     */
+    protected $_coreRegistry = null;
+
+    /**
+     * @var \Magento\Catalog\Model\Post\Attribute\Source\Status
+     */
+    protected $_status;
+
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
+     */
+    protected $_productCollectionFactory;
+
+    /**
+     * @var \Magento\Catalog\Model\Product\Visibility
+     */
+    protected $_visibility;
+
+    /**
+     * @var \Magento\Store\Model\WebsiteFactory
+     */
+    protected $_websiteFactory;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Backend\Helper\Data $backendHelper
+     * @param \Magento\Catalog\Model\Post\Attribute\Source\Status $status
+     * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
+     * @param \Magento\Catalog\Model\Product\Visibility $visibility
+     * @param \Magento\Store\Model\WebsiteFactory $websiteFactory
+     * @param \Magento\Framework\Registry $coreRegistry
+     * @param array $data
+     *
+     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Backend\Helper\Data $backendHelper,
+        \Magento\Catalog\Model\Product\Attribute\Source\Status $status,
+        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
+        \Magento\Catalog\Model\Product\Visibility $visibility,
+        \Magento\Store\Model\WebsiteFactory $websiteFactory,
+        \Magento\Framework\Registry $coreRegistry,
+        array $data = []
+    ) {
+        $this->_status = $status;
+        $this->_productCollectionFactory = $productCollectionFactory;
+        $this->_visibility = $visibility;
+        $this->_websiteFactory = $websiteFactory;
+        $this->_coreRegistry = $coreRegistry;
+        parent::__construct($context, $backendHelper, $data);
+    }
+
+    /**
+     * Set grid params
+     *
+     * @return void
+     */
+    protected function _construct()
+    {
+        parent::_construct();
+        $this->setId('related_products_section');
+        $this->setDefaultSort('post_id');
+        $this->setUseAjax(true);
+        if ($this->getPost() && $this->getPost()->getId()) {
+            $this->setDefaultFilter(['in_products' => 1]);
+        }
+        if ($this->isReadonly()) {
+            $this->setFilterVisibility(false);
+        }
+    }
+
+    /**
+     * Retrieve currently edited post model
+     *
+     * @return array|null
+     */
+    public function getPost()
+    {
+        return $this->_coreRegistry->registry('current_model');
+    }
+
+    /**
+     * Add filter
+     *
+     * @param Column $column
+     * @return $this
+     */
+    protected function _addColumnFilterToCollection($column)
+    {
+        // Set custom filter for in post flag
+        if ($column->getId() == 'in_products') {
+            $productIds = $this->_getSelectedProducts();
+            if (empty($productIds)) {
+                $productIds = 0;
+            }
+            if ($column->getFilter()->getValue()) {
+                $this->getCollection()->addFieldToFilter('entity_id', ['in' => $productIds]);
+            } else {
+                if ($productIds) {
+                    $this->getCollection()->addFieldToFilter('entity_id', ['nin' => $productIds]);
+                }
+            }
+        } else {
+            parent::_addColumnFilterToCollection($column);
+        }
+        return $this;
+    }
+
+    /**
+     * Prepare collection
+     *
+     * @return Extended
+     */
+    protected function _prepareCollection()
+    {
+        $post = $this->getPost();
+        $collection = $this->_productCollectionFactory->create()
+            ->addAttributeToSelect('*')
+            ->addWebsiteNamesToResult();
+
+        $this->setCollection($collection);
+        return parent::_prepareCollection();
+    }
+
+    /**
+     * Checks when this block is readonly
+     *
+     * @return bool
+     */
+    public function isReadonly()
+    {
+        //return $this->getPost() && $this->getPost()->getRelatedReadonly();
+        return false;
+    }
+
+    /**
+     * Add columns to grid
+     *
+     * @return $this
+     */
+    protected function _prepareColumns()
+    {
+        if (!$this->isReadonly()) {
+            $this->addColumn(
+                'in_products',
+                [
+                    'type' => 'checkbox',
+                    'name' => 'in_products',
+                    'values' => $this->_getSelectedProducts(),
+                    'align' => 'center',
+                    'index' => 'entity_id',
+                    'header_css_class' => 'col-select',
+                    'column_css_class' => 'col-select'
+                ]
+            );
+        }
+
+        $this->addColumn(
+            'entity_id',
+            [
+                'header' => __('ID'),
+                'sortable' => true,
+                'index' => 'entity_id',
+                'header_css_class' => 'col-id',
+                'column_css_class' => 'col-id'
+            ]
+        );
+
+        $this->addColumn(
+            'name',
+            [
+                'header' => __('Name'),
+                'index' => 'name',
+                'header_css_class' => 'col-name',
+                'column_css_class' => 'col-name'
+            ]
+        );
+
+
+        $this->addColumn(
+            'sku',
+            [
+                'header' => __('SKU'),
+                'index' => 'sku',
+                'header_css_class' => 'col-sku',
+                'column_css_class' => 'col-sku'
+            ]
+        );
+
+        $this->addColumn(
+            'visibility',
+            [
+                'header' => __('Visibility'),
+                'index' => 'visibility',
+                'type' => 'options',
+                'options' => $this->_visibility->getOptionArray(),
+                'header_css_class' => 'col-visibility',
+                'column_css_class' => 'col-visibility'
+            ]
+        );
+
+        if (!$this->_storeManager->isSingleStoreMode()) {
+            $this->addColumn(
+                'websites',
+                [
+                    'header' => __('Websites'),
+                    'sortable' => false,
+                    'index' => 'websites',
+                    'type' => 'options',
+                    'options' => $this->_websiteFactory->create()->getCollection()->toOptionHash(),
+                    'header_css_class' => 'col-websites',
+                    'column_css_class' => 'col-websites',
+                    'filter_condition_callback' => array($this, '_filterWebsiteConditionCallback')
+                ]
+            );
+        }
+
+        $this->addColumn(
+            'status',
+            [
+                'header' => __('Status'),
+                'index' => 'status',
+                'type' => 'options',
+                'options' => $this->_status->getOptionArray(),
+                'header_css_class' => 'col-status',
+                'column_css_class' => 'col-status',
+                'frame_callback' => array(
+                    $this->getLayout()->createBlock('Magefan\Blog\Block\Adminhtml\Grid\Column\Statuses'),
+                    'decorateStatus'
+                ),
+            ]
+        );
+
+        $this->addColumn(
+            'position',
+            [
+                'header' => __('Position'),
+                'name' => 'position',
+                'type' => 'number',
+                'validate_class' => 'validate-number',
+                'index' => 'position',
+                'editable' => true,
+                'edit_only' => false,
+                'sortable' => false,
+                'filter' => false,
+                'header_css_class' => 'col-position',
+                'column_css_class' => 'col-position'
+            ]
+        );
+
+        return parent::_prepareColumns();
+    }
+
+    /**
+     * Add website filter
+     * @param  \Magento\Catalog\Model\ResourceModel\Product\Collection $collection
+     * @param  $column
+     * @return $this
+     */
+    protected function _filterWebsiteConditionCallback($collection, $column)
+    {
+        if ($column->getFilter()->getValue()) {
+            $this->getCollection()->addWebsiteFilter();
+        }
+
+        return $this;
+    }
+
+    /**
+     * Rerieve grid URL
+     *
+     * @return string
+     */
+    public function getGridUrl()
+    {
+        return $this->getData(
+            'grid_url'
+        ) ? $this->getData(
+            'grid_url'
+        ) : $this->getUrl(
+            'blog/post/relatedProductsGrid',
+            ['_current' => true]
+        );
+    }
+
+    /**
+     * Retrieve selected related products
+     *
+     * @return array
+     */
+    protected function _getSelectedProducts()
+    {
+        $products = $this->getProductsRelated();
+        if (!is_array($products)) {
+            $products = array_keys($this->getSelectedRelatedProducts());
+        }
+        return $products;
+    }
+
+    /**
+     * Retrieve related products
+     *
+     * @return array
+     */
+    public function getSelectedRelatedProducts()
+    {
+        $products = [];
+        foreach ($this->_coreRegistry->registry('current_model')->getRelatedProducts() as $product) {
+            $products[$product->getId()] = ['position' => $product->getPosition()];
+        }
+        return $products;
+    }
+
+
+    public function getTabLabel()
+    {
+        return __('Related Products');
+    }
+
+    /**
+     * Prepare title for tab
+     *
+     * @return \Magento\Framework\Phrase
+     */
+    public function getTabTitle()
+    {
+        return __('Related Products');
+    }
+
+    /**
+     * Returns status flag about this tab can be shown or not
+     *
+     * @return bool
+     */
+    public function canShowTab()
+    {
+        return true;
+    }
+
+    /**
+     * Returns status flag about this tab hidden or not
+     *
+     * @return bool
+     */
+    public function isHidden()
+    {
+        return false;
+    }
+}

+ 48 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Edit/Tabs.php

@@ -0,0 +1,48 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\Post\Edit;
+
+/**
+ * Admin page left menu
+ */
+class Tabs extends \Magento\Backend\Block\Widget\Tabs
+{
+    /**
+     * @return void
+     */
+    protected function _construct()
+    {
+        parent::_construct();
+        $this->setId('post_tabs');
+        $this->setDestElementId('edit_form');
+        $this->setTitle(__('Post Information'));
+    }
+
+    protected function _beforeToHtml()
+    {
+        $this->addTab(
+            'related_posts_section',
+            [
+                'label' => __('Related Posts'),
+                'url' => $this->getUrl('blog/post/relatedPosts', ['_current' => true]),
+                'class' => 'ajax',
+            ]
+        );
+
+        $this->addTab(
+            'related_products_section',
+            [
+                'label' => __('Related Products'),
+                'url' => $this->getUrl('blog/post/relatedProducts', ['_current' => true]),
+                'class' => 'ajax',
+            ]
+        );
+        return parent::_beforeToHtml();
+    }
+}

+ 169 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Helper/Form/Gallery.php

@@ -0,0 +1,169 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+/**
+ * Blog post gallery
+ */
+namespace Magefan\Blog\Block\Adminhtml\Post\Helper\Form;
+
+use Magento\Framework\Registry;
+
+class Gallery extends \Magento\Framework\View\Element\AbstractBlock
+{
+    /**
+     * Gallery field name suffix
+     *
+     * @var string
+     */
+    protected $fieldNameSuffix = 'post';
+
+    /**
+     * Gallery html id
+     *
+     * @var string
+     */
+    protected $htmlId = 'media_gallery';
+
+    /**
+     * Gallery name
+     *
+     * @var string
+     */
+    protected $name = 'media_gallery';
+
+    /**
+     * Html id for data scope
+     *
+     * @var string
+     */
+    protected $image = 'image';
+
+    /**
+     * @var string
+     */
+    protected $formName = 'blog_post_form';
+
+    /**
+     * @var \Magento\Framework\Data\Form
+     */
+    protected $form;
+
+    /**
+     * @var Registry
+     */
+    protected $registry;
+
+    /**
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param Registry $registry
+     * @param \Magento\Framework\Data\Form $form
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Context $context,
+        Registry $registry,
+        \Magento\Framework\Data\Form $form,
+        $data = []
+    ) {
+        $this->registry = $registry;
+        $this->form = $form;
+        parent::__construct($context, $data);
+    }
+
+    /**
+     * @return string
+     */
+    public function getElementHtml()
+    {
+        return $this->getContentHtml();
+    }
+
+    /**
+     * Get product images
+     *
+     * @return array|null
+     */
+    public function getImages()
+    {
+        $result = array();
+        $gallery = $this->registry->registry('current_model')->getGalleryImages();
+
+        if (count($gallery)) {
+            $result['images'] = array();
+            $position = 1;
+            foreach ($gallery as $image) {
+                $result['images'][] = [
+                    'value_id' => $image->getFile(),
+                    'file' => $image->getFile(),
+                    'label' => basename($image->getFile()),
+                    'position' => $position,
+                    'url' => $image->getUrl(),
+                ];
+                $position++;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Prepares content block
+     *
+     * @return string
+     */
+    public function getContentHtml()
+    {
+        $content = $this->getChildBlock('content')
+            ->setId($this->getHtmlId() . '_content')
+            ->setElement($this)
+            ->setFormName($this->formName);
+        $galleryJs = $content->getJsObjectName();
+        $content->getUploader()->getConfig()->setMegiaGallery($galleryJs);
+        return $content->toHtml();
+    }
+
+    /**
+     * @return string
+     */
+    protected function getHtmlId()
+    {
+        return $this->htmlId;
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFieldNameSuffix()
+    {
+        return $this->fieldNameSuffix;
+    }
+
+    /**
+     * @return string
+     */
+    public function getDataScopeHtmlId()
+    {
+        return $this->image;
+    }
+
+    /**
+     * @return string
+     */
+    public function toHtml()
+    {
+        return $this->getElementHtml();
+    }
+}

+ 142 - 0
app/code/Magefan/Blog/Block/Adminhtml/Post/Helper/Form/Gallery/Content.php

@@ -0,0 +1,142 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+/**
+ * Blog post gallery content
+ */
+namespace Magefan\Blog\Block\Adminhtml\Post\Helper\Form\Gallery;
+
+use Magento\Backend\Block\Media\Uploader;
+use Magento\Framework\View\Element\AbstractBlock;
+
+class Content extends \Magento\Backend\Block\Widget
+{
+    /**
+     * @var string
+     */
+    protected $_template = 'post/helper/gallery.phtml';
+
+    /**
+     * @var \Magento\Framework\Json\EncoderInterface
+     */
+    protected $_jsonEncoder;
+
+    /**
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Backend\Block\Template\Context $context,
+        \Magento\Framework\Json\EncoderInterface $jsonEncoder,
+        array $data = []
+    ) {
+        $this->_jsonEncoder = $jsonEncoder;
+        parent::__construct($context, $data);
+    }
+
+    /**
+     * @return AbstractBlock
+     */
+    protected function _prepareLayout()
+    {
+        $this->addChild('uploader', 'Magento\Backend\Block\Media\Uploader');
+
+        $this->getUploader()->getConfig()->setUrl(
+            $this->_urlBuilder->addSessionParam()->getUrl('blog/post_upload/gallery')
+        )->setFileField(
+            'image'
+        )->setFilters(
+            [
+                'images' => [
+                    'label' => __('Images (.gif, .jpg, .png)'),
+                    'files' => ['*.gif', '*.jpg', '*.jpeg', '*.png'],
+                ],
+            ]
+        );
+
+        $this->_eventManager->dispatch('blog_post_gallery_prepare_layout', ['block' => $this]);
+
+        return parent::_prepareLayout();
+    }
+
+    /**
+     * Retrieve uploader block
+     *
+     * @return Uploader
+     */
+    public function getUploader()
+    {
+        return $this->getChildBlock('uploader');
+    }
+
+    /**
+     * Retrieve uploader block html
+     *
+     * @return string
+     */
+    public function getUploaderHtml()
+    {
+        return $this->getChildHtml('uploader');
+    }
+
+    /**
+     * @return string
+     */
+    public function getJsObjectName()
+    {
+        return $this->getHtmlId() . 'JsObject';
+    }
+
+    /**
+     * @return string
+     */
+    public function getAddImagesButton()
+    {
+        return $this->getButtonHtml(
+            __('Add New Images'),
+            $this->getJsObjectName() . '.showUploader()',
+            'add',
+            $this->getHtmlId() . '_add_images_button'
+        );
+    }
+
+    /**
+     * @return string
+     */
+    public function getImagesJson()
+    {
+        $value = $this->getElement()->getImages();
+        if (is_array($value) &&
+            array_key_exists('images', $value) &&
+            is_array($value['images']) &&
+            count($value['images'])
+        ) {
+            $images = $this->sortImagesByPosition($value['images']);
+
+            return $this->_jsonEncoder->encode($images);
+        }
+        return '[]';
+    }
+
+    /**
+     * Sort images array by position key
+     *
+     * @param array $images
+     * @return array
+     */
+    private function sortImagesByPosition($images)
+    {
+        if (is_array($images)) {
+            usort($images, function ($imageA, $imageB) {
+                return ($imageA['position'] < $imageB['position']) ? -1 : 1;
+            });
+        }
+        return $images;
+    }
+}

+ 52 - 0
app/code/Magefan/Blog/Block/Adminhtml/System/Config/Form/Info.php

@@ -0,0 +1,52 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Adminhtml\System\Config\Form;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Admin blog configurations information block
+ */
+class Info extends \Magento\Config\Block\System\Config\Form\Field
+{
+    /**
+     * @var \Magento\Framework\Module\ModuleListInterface
+     */
+    protected $moduleList;
+
+    /**
+     * @param \Magento\Framework\Module\ModuleListInterface $moduleList
+     * @param \Magento\Backend\Block\Template\Context $context
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\Module\ModuleListInterface $moduleList,
+        \Magento\Backend\Block\Template\Context $context,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->moduleList       = $moduleList;
+    }
+
+    /**
+     * Return info block html
+     * @param  \Magento\Framework\Data\Form\Element\AbstractElement $element
+     * @return string
+     */
+    public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element)
+    {
+        $m = $this->moduleList->getOne($this->getModuleName());
+        $html = '<div style="padding:10px;background-color:#f8f8f8;border:1px solid #ddd;margin-bottom:7px;">
+            Blog Extension v' . $m['setup_version'] . ' was developed by <a href="http://magefan.com/" target="_blank">MageFan</a>.
+        </div>';
+
+        return $html;
+    }
+
+}

+ 26 - 0
app/code/Magefan/Blog/Block/Amp/Ldjson/Post.php

@@ -0,0 +1,26 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Amp\Ldjson;
+
+/**
+ * Blog post list ldJson block
+ */
+class Post extends \Magefan\Blog\Block\Post\View\Richsnippets
+{
+    /**
+     * Retrieve page structure structure data in JSON
+     *
+     * @return string
+     */
+    public function getJson()
+    {
+        $json = parent::getOptions();
+        return str_replace('\/', '/', json_encode($json));
+    }
+}

+ 50 - 0
app/code/Magefan/Blog/Block/Amp/Ldjson/PostList.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Amp\Ldjson;
+
+/**
+ * Blog post list ldJson block
+ */
+if (class_exists('\Plumrocket\Amp\Block\Page\Head\Ldjson\Cms')) {
+    class PostListIntermediate extends \Plumrocket\Amp\Block\Page\Head\Ldjson\Cms {}
+} else {
+    class PostListIntermediate extends \Magento\Framework\View\Element\AbstractBlock {}
+}
+
+class PostList extends PostListIntermediate
+{
+    /**
+     * Retrieve page structure structure data in JSON
+     *
+     * @return string
+     */
+    public function getJson()
+    {
+        $time = time();
+        if (!$this->_cmsPage->getCreationTime()) {
+            $this->_cmsPage->setCreationTime(
+                date('Y-m-01 00:00:00', $time - 86400 * 150)
+            );
+        }
+
+        if (!$this->_cmsPage->getUpdateTime()) {
+            $this->_cmsPage->setUpdateTime(
+                date('Y-m-01 00:00:00', $time)
+            );
+        }
+
+        if (!$this->_cmsPage->getTitle()) {
+            $this->_cmsPage->setTitle(
+                $this->pageConfig->getTitle()->get()
+            );
+        }
+
+        return parent::getJson();
+    }
+}

+ 70 - 0
app/code/Magefan/Blog/Block/Amp/Og/Post.php

@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Amp\Og;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post opengraph for amp
+ */
+
+if (class_exists('\Plumrocket\Amp\Block\Page\Head\Og\AbstractOg')) {
+    class PostIntermediate extends \Plumrocket\Amp\Block\Page\Head\Og\AbstractOg {}
+} else {
+    class PostIntermediate extends \Magento\Framework\View\Element\AbstractBlock {}
+}
+
+class Post extends PostIntermediate
+{
+    /**
+     * Retrieve open graph params
+     *
+     * @return array
+     */
+    public function getOgParams()
+    {
+        $params = parent::getOgParams();
+        $post = $this->getPost();
+
+        return array_merge($params, [
+            'type' => $post->getOgType() ?: 'article',
+            'url' => $this->_helper->getCanonicalUrl($post->getPostUrl()),
+            'image' => (string)$post->getImageUrl(),
+        ]);
+    }
+
+    /**
+     * Retrieve current post
+     *
+     * @return \Magefan\Blog\Model\Post
+     */
+    public function getPost()
+    {
+        return $this->_coreRegistry->registry('current_blog_post');
+    }
+
+    /**
+     * Retrieve page main image
+     *
+     * @return string | null
+     */
+    public function getImage()
+    {
+        $image = $this->getPost()->getOgImage();
+
+        if (!$image) {
+            $image = $this->getPost()->getFirstImage();
+        }
+
+        if ($image) {
+            return $this->stripTags($image);
+        }
+
+    }
+
+}

+ 85 - 0
app/code/Magefan/Blog/Block/Archive/PostList.php

@@ -0,0 +1,85 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Archive;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog archive posts list
+ */
+class PostList extends \Magefan\Blog\Block\Post\PostList
+{
+    /**
+     * Prepare posts collection
+     * @return \Magefan\Blog\Model\ResourceModel\Post\Collection
+     */
+    protected function _preparePostCollection()
+    {
+        parent::_preparePostCollection();
+        $this->_postCollection->addArchiveFilter(
+            $this->getYear(),
+            $this->getMonth()
+        );
+    }
+
+    /**
+     * Get archive month
+     * @return string
+     */
+    public function getMonth()
+    {
+        return (int)$this->_coreRegistry->registry('current_blog_archive_month');
+    }
+
+    /**
+     * Get archive year
+     * @return string
+     */
+    public function getYear()
+    {
+        return (int)$this->_coreRegistry->registry('current_blog_archive_year');
+    }
+
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        $title = $this->_getTitle();
+        $this->_addBreadcrumbs($title, 'blog_search');
+        $this->pageConfig->getTitle()->set($title);
+        $this->pageConfig->addRemotePageAsset(
+            $this->_url->getUrl(
+                $this->getYear() . '-' . str_pad($this->getMonth(), 2, '0', STR_PAD_LEFT),
+                \Magefan\Blog\Model\Url::CONTROLLER_ARCHIVE
+            ),
+            'canonical',
+            ['attributes' => ['rel' => 'canonical']]
+        );
+        $this->pageConfig->setRobots('NOINDEX,FOLLOW');
+
+        return parent::_prepareLayout();
+    }
+
+    /**
+     * Retrieve title
+     * @return string
+     */
+    protected function _getTitle()
+    {
+        $time = strtotime($this->getYear().'-'.$this->getMonth().'-01');
+        return sprintf(
+            __('Monthly Archives: %s %s'),
+            __(date('F', $time)), date('Y', $time)
+        );
+    }
+
+}

+ 63 - 0
app/code/Magefan/Blog/Block/Author/PostList.php

@@ -0,0 +1,63 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Author;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog author posts list
+ */
+class PostList extends \Magefan\Blog\Block\Post\PostList
+{
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        parent::_preparePostCollection();
+        if ($author = $this->getAuthor()) {
+            $this->_postCollection->addAuthorFilter($author);
+        }
+    }
+
+    /**
+     * Retrieve author instance
+     *
+     * @return \Magefan\Blog\Model\Author
+     */
+    public function getAuthor()
+    {
+        return $this->_coreRegistry->registry('current_blog_author');
+    }
+
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        if ($author = $this->getAuthor()) {
+            $this->_addBreadcrumbs($author->getTitle(), 'blog_author');
+            $this->pageConfig->addBodyClass('blog-author-' . $author->getIdentifier());
+            $this->pageConfig->getTitle()->set($author->getTitle());
+            $this->pageConfig->addRemotePageAsset(
+                $author->getAuthorUrl(),
+                'canonical',
+                ['attributes' => ['rel' => 'canonical']]
+            );
+            $this->pageConfig->setRobots('NOINDEX,FOLLOW');
+        }
+
+        return parent::_prepareLayout();
+    }
+
+}

+ 84 - 0
app/code/Magefan/Blog/Block/Catalog/Product/RelatedPosts.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Catalog\Product;
+
+use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Framework\View\Element\AbstractBlock;
+
+/**
+ * Blog post related posts block
+ */
+class RelatedPosts extends \Magefan\Blog\Block\Post\PostList\AbstractList
+{
+
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        $pageSize = (int) $this->_scopeConfig->getValue(
+            'mfblog/product_page/number_of_related_posts',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+        if (!$pageSize) {
+            $pageSize = 5;
+        }
+        $this->setPageSize($pageSize);
+
+        parent::_preparePostCollection();
+
+        $product = $this->getProduct();
+        $this->_postCollection->getSelect()->joinLeft(
+            ['rl' => $product->getResource()->getTable('magefan_blog_post_relatedproduct')],
+            'main_table.post_id = rl.post_id',
+            ['position']
+        )->where(
+            'rl.related_id = ?',
+            $product->getId()
+        );
+    }
+
+    /**
+     * Retrieve true if Display Related Posts enabled
+     * @return boolean
+     */
+    public function displayPosts()
+    {
+        return (bool) $this->_scopeConfig->getValue(
+            'mfblog/product_page/related_posts_enabled',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve posts instance
+     *
+     * @return \Magefan\Blog\Model\Category
+     */
+    public function getProduct()
+    {
+        if (!$this->hasData('product')) {
+            $this->setData('product',
+                $this->_coreRegistry->registry('current_product')
+            );
+        }
+        return $this->getData('product');
+    }
+
+    /**
+     * Get Block Identities
+     * @return Array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Catalog\Model\Product::CACHE_TAG . '_relatedposts_'.$this->getPost()->getId()  ];
+    }
+}

+ 82 - 0
app/code/Magefan/Blog/Block/Category/Info.php

@@ -0,0 +1,82 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Category;
+
+/**
+ * Blog category info
+ */
+class Info extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * @var \Magento\Cms\Model\Template\FilterProvider
+     */
+    protected $_filterProvider;
+
+    /**
+     * @var Magento\Framework\Registry
+     */
+    protected $_coreRegistry;
+
+    /**
+     * @var \Magefan\Blog\Model\Url
+     */
+    protected $_url;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+
+     * @param \Magento\Framework\Registry $coreRegistry,
+     * @param \Magento\Cms\Model\Template\FilterProvider $filterProvider
+     * @param \Magefan\Blog\Model\Url $url
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magento\Framework\Registry $coreRegistry,
+        \Magento\Cms\Model\Template\FilterProvider $filterProvider,
+        \Magefan\Blog\Model\Url $url,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_coreRegistry = $coreRegistry;
+        $this->_filterProvider = $filterProvider;
+        $this->_url = $url;
+    }
+
+    /**
+     * Retrieve category instance
+     *
+     * @return \Magefan\Blog\Model\Category
+     */
+    public function getCategory()
+    {
+        return $this->_coreRegistry->registry('current_blog_category');
+    }
+
+    /**
+     * Retrieve post content
+     *
+     * @return string
+     */
+    public function getContent()
+    {
+        $category = $this->getCategory();
+        $key = 'filtered_content';
+        if (!$category->hasData($key)) {
+            $cotent = $this->_filterProvider->getPageFilter()->filter(
+                $category->getContent()
+            );
+            $category->setData($key, $cotent);
+        }
+        return $category->getData($key);
+    }
+
+}

+ 106 - 0
app/code/Magefan/Blog/Block/Category/View.php

@@ -0,0 +1,106 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Category;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog category view
+ */
+class View extends \Magefan\Blog\Block\Post\PostList
+{
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        parent::_preparePostCollection();
+        if ($category = $this->getCategory()) {
+            $this->_postCollection->addCategoryFilter($category);
+        }
+    }
+
+    /**
+     * Retrieve category instance
+     *
+     * @return \Magefan\Blog\Model\Category
+     */
+    public function getCategory()
+    {
+        return $this->_coreRegistry->registry('current_blog_category');
+    }
+
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        $category = $this->getCategory();
+        if ($category) {
+            $this->_addBreadcrumbs($category);
+            $this->pageConfig->addBodyClass('blog-category-' . $category->getIdentifier());
+            $this->pageConfig->getTitle()->set($category->getMetaTitle());
+            $this->pageConfig->setKeywords($category->getMetaKeywords());
+            $this->pageConfig->setDescription($category->getMetaDescription());
+            $this->pageConfig->addRemotePageAsset(
+                $category->getCanonicalUrl(),
+                'canonical',
+                ['attributes' => ['rel' => 'canonical']]
+            );
+
+            $pageMainTitle = $this->getLayout()->getBlock('page.main.title');
+            if ($pageMainTitle) {
+                $pageMainTitle->setPageTitle(
+                    $this->escapeHtml($category->getTitle())
+                );
+            }
+        }
+
+        return parent::_prepareLayout();
+    }
+
+    /**
+     * Prepare breadcrumbs
+     *
+     * @param  string $title
+     * @param  string $key
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    protected function _addBreadcrumbs($title = null, $key = null)
+    {
+        parent::_addBreadcrumbs();
+        if ($breadcrumbsBlock = $this->getBreadcrumbsBlock()) {
+            $category = $this->getCategory();
+            $parentCategories = [];
+            while ($parentCategory = $category->getParentCategory()) {
+                $parentCategories[] = $category = $parentCategory;
+            }
+
+            for ($i = count($parentCategories) - 1; $i >= 0; $i--) {
+                $category = $parentCategories[$i];
+                $breadcrumbsBlock->addCrumb('blog_parent_category_' . $category->getId(), [
+                    'label' => $category->getTitle(),
+                    'title' => $category->getTitle(),
+                    'link'  => $category->getCategoryUrl()
+                ]);
+            }
+
+            $category = $this->getCategory();
+            $breadcrumbsBlock->addCrumb('blog_category',[
+                'label' => $category->getTitle(),
+                'title' => $category->getTitle()
+            ]);
+        }
+    }
+}

+ 50 - 0
app/code/Magefan/Blog/Block/Index.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog index block
+ */
+class Index extends \Magefan\Blog\Block\Post\PostList
+{
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        $this->_addBreadcrumbs();
+        $this->pageConfig->getTitle()->set($this->_getConfigValue('title'));
+        $this->pageConfig->setKeywords($this->_getConfigValue('meta_keywords'));
+        $this->pageConfig->setDescription($this->_getConfigValue('meta_description'));
+        $this->pageConfig->addRemotePageAsset(
+            $this->_url->getBaseUrl(),
+            'canonical',
+            ['attributes' => ['rel' => 'canonical']]
+        );
+
+        return parent::_prepareLayout();
+    }
+
+    /**
+     * Retrieve blog title
+     * @return string
+     */
+    protected function _getConfigValue($param)
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/index_page/'.$param,
+            ScopeInterface::SCOPE_STORE
+        );
+    }
+
+}

+ 70 - 0
app/code/Magefan/Blog/Block/Link.php

@@ -0,0 +1,70 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block;
+
+/**
+ * Class Link
+ */
+class Link extends \Magento\Framework\View\Element\Html\Link
+{
+    /**
+     * @var \Magefan\Blog\Model\Url
+     */
+    protected $_url;
+
+    /**
+     * @param \Magento\Framework\View\Element\Template\Context $context
+     * @param \Magefan\Blog\Model\Url $url
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magefan\Blog\Model\Url $url,
+        array $data = []
+    ) {
+        $this->_url = $url;
+        parent::__construct($context, $data);
+    }
+
+    /**
+     * @return string
+     */
+    public function getHref()
+    {
+        return $this->_url->getBaseUrl();
+    }
+
+    /**
+     * @return string
+     */
+    public function getLabel()
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/index_page/title',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Render block HTML
+     *
+     * @return string
+     */
+    protected function _toHtml()
+    {
+        if (!$this->_scopeConfig->getValue(
+            \Magefan\Blog\Helper\Config::XML_PATH_EXTENSION_ENABLED,
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        )) {
+            return '';
+        }
+
+        return parent::_toHtml();
+    }
+}

+ 149 - 0
app/code/Magefan/Blog/Block/Post/AbstractPost.php

@@ -0,0 +1,149 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Abstract post мшуц block
+ */
+abstract class AbstractPost extends \Magento\Framework\View\Element\Template
+{
+
+    /**
+     * Deprecated property. Do not use it.
+     * @var \Magento\Cms\Model\Template\FilterProvider
+     */
+    protected $_filterProvider;
+
+    /**
+     * @var \Magefan\Blog\Model\Post
+     */
+    protected $_post;
+
+    /**
+     * Page factory
+     *
+     * @var \Magefan\Blog\Model\PostFactory
+     */
+    protected $_postFactory;
+
+    /**
+     * @var Magento\Framework\Registry
+     */
+    protected $_coreRegistry;
+
+    /**
+     * @var string
+     */
+    protected $_defaultPostInfoBlock = 'Magefan\Blog\Block\Post\Info';
+
+    /**
+     * @var \Magefan\Blog\Model\Url
+     */
+    protected $_url;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magento\Cms\Model\Page $post
+     * @param \Magento\Framework\Registry $coreRegistry,
+     * @param \Magento\Cms\Model\Template\FilterProvider $filterProvider
+     * @param \Magento\Cms\Model\PageFactory $postFactory
+     * @param \Magefan\Blog\Model\Url $url
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magefan\Blog\Model\Post $post,
+        \Magento\Framework\Registry $coreRegistry,
+        \Magento\Cms\Model\Template\FilterProvider $filterProvider,
+        \Magefan\Blog\Model\PostFactory $postFactory,
+        \Magefan\Blog\Model\Url $url,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_post = $post;
+        $this->_coreRegistry = $coreRegistry;
+        $this->_filterProvider = $filterProvider;
+        $this->_postFactory = $postFactory;
+        $this->_url = $url;
+    }
+
+    /**
+     * Retrieve post instance
+     *
+     * @return \Magefan\Blog\Model\Post
+     */
+    public function getPost()
+    {
+        if (!$this->hasData('post')) {
+            $this->setData('post',
+                $this->_coreRegistry->registry('current_blog_post')
+            );
+        }
+        return $this->getData('post');
+    }
+
+    /**
+     * Retrieve post short content
+     *
+     * @return string
+     */
+    public function getShorContent()
+    {
+        return $this->getPost()->getShortFilteredContent();
+    }
+
+    /**
+     * Retrieve post content
+     *
+     * @return string
+     */
+    public function getContent()
+    {
+        return $this->getPost()->getFilteredContent();
+    }
+
+    /**
+     * Retrieve post info html
+     *
+     * @return string
+     */
+    public function getInfoHtml()
+    {
+        return $this->getInfoBlock()->toHtml();
+    }
+
+    /**
+     * Retrieve post info block
+     *
+     * @return \Magefan\Blog\Block\Post\Info
+     */
+    public function getInfoBlock()
+    {
+        $k = 'info_block';
+        if (!$this->hasData($k)) {
+            $blockName = $this->getPostInfoBlockName();
+            if ($blockName) {
+                $block = $this->getLayout()->getBlock($blockName);
+            }
+
+            if (empty($block)) {
+                $block = $this->getLayout()->createBlock($this->_defaultPostInfoBlock, uniqid(microtime()));
+            }
+
+            $this->setData($k, $block);
+        }
+
+        return $this->getData($k)->setPost($this->getPost());
+    }
+
+}

+ 59 - 0
app/code/Magefan/Blog/Block/Post/Info.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post info block
+ */
+class Info extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * Block template file
+     * @var string
+     */
+    protected $_template = 'post/info.phtml';
+
+    /**
+     * DEPRECATED METHOD!!!!
+     * Retrieve formated posted date
+     * @var string
+     * @return string
+     */
+    public function getPostedOn($format = 'Y-m-d H:i:s')
+    {
+        return $this->getPost()->getPublishDate($format);
+    }
+
+    /**
+     * Retrieve 1 if display author information is enabled
+     * @return int
+     */
+    public function authorEnabled()
+    {
+        return (int) $this->_scopeConfig->getValue(
+            'mfblog/author/enabled',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve 1 if author page is enabled
+     * @return int
+     */
+    public function authorPageEnabled()
+    {
+        return (int) $this->_scopeConfig->getValue(
+            'mfblog/author/page_enabled',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+}

+ 151 - 0
app/code/Magefan/Blog/Block/Post/PostList.php

@@ -0,0 +1,151 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post list block
+ */
+class PostList extends \Magefan\Blog\Block\Post\PostList\AbstractList
+{
+    /**
+     * Block template file
+     * @var string
+     */
+    protected $_defaultToolbarBlock = 'Magefan\Blog\Block\Post\PostList\Toolbar';
+
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        $page = $this->_request->getParam(
+            \Magefan\Blog\Block\Post\PostList\Toolbar::PAGE_PARM_NAME
+        );
+
+        if ($page > 1) {
+            $this->pageConfig->setRobots('NOINDEX,FOLLOW');
+        }
+
+        return parent::_prepareLayout();
+    }
+
+    /**
+     * Retrieve post html
+     * @param  \Magefan\Blog\Model\Post $post
+     * @return string
+     */
+    public function getPostHtml($post)
+    {
+        return $this->getChildBlock('blog.posts.list.item')->setPost($post)->toHtml();
+    }
+
+    /**
+     * Retrieve Toolbar Block
+     * @return \Magefan\Blog\Block\Post\PostList\Toolbar
+     */
+    public function getToolbarBlock()
+    {
+        $blockName = $this->getToolbarBlockName();
+
+        if ($blockName) {
+            $block = $this->getLayout()->getBlock($blockName);
+            if ($block) {
+                return $block;
+            }
+        }
+        $block = $this->getLayout()->createBlock($this->_defaultToolbarBlock, uniqid(microtime()));
+        return $block;
+    }
+
+    /**
+     * Retrieve Toolbar Html
+     * @return string
+     */
+    public function getToolbarHtml()
+    {
+        return $this->getChildHtml('toolbar');
+    }
+
+    /**
+     * Before block to html
+     *
+     * @return $this
+     */
+    protected function _beforeToHtml()
+    {
+        $toolbar = $this->getToolbarBlock();
+
+        // called prepare sortable parameters
+        $collection = $this->getPostCollection();
+
+        // set collection to toolbar and apply sort
+        $toolbar->setCollection($collection);
+        $this->setChild('toolbar', $toolbar);
+
+        return parent::_beforeToHtml();
+    }
+
+    /**
+     * Prepare breadcrumbs
+     *
+     * @param  string $title
+     * @param  string $key
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    protected function _addBreadcrumbs($title = null, $key = null)
+    {
+        if ($breadcrumbsBlock = $this->getBreadcrumbsBlock()) {
+            $breadcrumbsBlock->addCrumb(
+                'home',
+                [
+                    'label' => __('Home'),
+                    'title' => __('Go to Home Page'),
+                    'link' => $this->_storeManager->getStore()->getBaseUrl()
+                ]
+            );
+
+            $blogTitle = $this->_scopeConfig->getValue(
+                'mfblog/index_page/title',
+                ScopeInterface::SCOPE_STORE
+            );
+            $breadcrumbsBlock->addCrumb(
+                'blog',
+                [
+                    'label' => __($blogTitle),
+                    'title' => __($blogTitle),
+                    'link' => $title ? $this->_url->getBaseUrl() : null,
+                ]
+            );
+
+            if ($title) {
+                $breadcrumbsBlock->addCrumb($key ?: 'blog_item', ['label' => $title, 'title' => $title]);
+            }
+        }
+    }
+
+    /**
+     * Retrieve breadcrumbs block
+     *
+     * @return mixed
+     */
+    protected function getBreadcrumbsBlock()
+    {
+        if ($this->_scopeConfig->getValue('web/default/show_cms_breadcrumbs', ScopeInterface::SCOPE_STORE)) {
+            return $this->getLayout()->getBlock('breadcrumbs');
+        }
+
+        return false;
+    }
+
+}

+ 120 - 0
app/code/Magefan/Blog/Block/Post/PostList/AbstractList.php

@@ -0,0 +1,120 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\PostList;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Abstract blog post list block
+ */
+abstract class AbstractList extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * @var \Magento\Cms\Model\Template\FilterProvider
+     */
+    protected $_filterProvider;
+
+    /**
+     * @var \Magento\Cms\Model\Page
+     */
+    protected $_post;
+
+    /**
+     * @var \Magento\Framework\Registry
+     */
+    protected $_coreRegistry;
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory
+     */
+    protected $_postCollectionFactory;
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Post\Collection
+     */
+    protected $_postCollection;
+
+    /**
+     * @var \Magefan\Blog\Model\Url
+     */
+    protected $_url;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magento\Framework\Registry $coreRegistry
+     * @param \Magento\Cms\Model\Template\FilterProvider $filterProvider
+     * @param \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory $postCollectionFactory
+     * @param \Magefan\Blog\Model\Url $url
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magento\Framework\Registry $coreRegistry,
+        \Magento\Cms\Model\Template\FilterProvider $filterProvider,
+        \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory $postCollectionFactory,
+        \Magefan\Blog\Model\Url $url,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_coreRegistry = $coreRegistry;
+        $this->_filterProvider = $filterProvider;
+        $this->_postCollectionFactory = $postCollectionFactory;
+        $this->_url = $url;
+    }
+
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        $this->_postCollection = $this->_postCollectionFactory->create()
+            ->addActiveFilter()
+            ->addStoreFilter($this->_storeManager->getStore()->getId())
+            ->setOrder('publish_time', 'DESC');
+
+        if ($this->getPageSize()) {
+            $this->_postCollection->setPageSize($this->getPageSize());
+        }
+    }
+
+    /**
+     * Prepare posts collection
+     *
+     * @return \Magefan\Blog\Model\ResourceModel\Post\Collection
+     */
+    public function getPostCollection()
+    {
+        if (is_null($this->_postCollection)) {
+            $this->_preparePostCollection();
+        }
+
+        return $this->_postCollection;
+    }
+
+    /**
+     * Render block HTML
+     *
+     * @return string
+     */
+    protected function _toHtml()
+    {
+        if (!$this->_scopeConfig->getValue(
+            \Magefan\Blog\Helper\Config::XML_PATH_EXTENSION_ENABLED,
+            ScopeInterface::SCOPE_STORE
+        )) {
+            return '';
+        }
+
+        return parent::_toHtml();
+    }
+}

+ 17 - 0
app/code/Magefan/Blog/Block/Post/PostList/Item.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\PostList;
+
+/**
+ * Post list item
+ */
+class Item extends \Magefan\Blog\Block\Post\AbstractPost
+{
+
+}

+ 131 - 0
app/code/Magefan/Blog/Block/Post/PostList/Toolbar.php

@@ -0,0 +1,131 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\PostList;
+
+/**
+ * Blog posts list toolbar
+ */
+class Toolbar extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * Page GET parameter name
+     */
+    const PAGE_PARM_NAME = 'page';
+
+    /**
+     * Products collection
+     *
+     * @var \Magento\Framework\Model\Resource\Db\Collection\AbstractCollection
+     */
+    protected $_collection = null;
+
+    /**
+     * Default block template
+     * @var string
+     */
+    protected $_template = 'post/list/toolbar.phtml';
+
+    /**
+     * Set collection to pager
+     *
+     * @param \Magento\Framework\Data\Collection $collection
+     * @return $this
+     */
+    public function setCollection($collection)
+    {
+        $this->_collection = $collection;
+
+        $this->_collection->setCurPage($this->getCurrentPage());
+
+        // we need to set pagination only if passed value integer and more that 0
+        $limit = (int)$this->getLimit();
+        if ($limit) {
+            $this->_collection->setPageSize($limit);
+        }
+        if ($this->getCurrentOrder()) {
+            $this->_collection->setOrder($this->getCurrentOrder(), $this->getCurrentDirection());
+        }
+        return $this;
+    }
+
+    /**
+     * Return products collection instance
+     *
+     * @return \Magento\Framework\Model\Resource\Db\Collection\AbstractCollection
+     */
+    public function getCollection()
+    {
+        return $this->_collection;
+    }
+
+    /**
+     * Get specified posts limit display per page
+     *
+     * @return string
+     */
+    public function getLimit()
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/post_list/posts_per_page', 
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Return current page from request
+     *
+     * @return int
+     */
+    public function getCurrentPage()
+    {
+        $page = (int) $this->_request->getParam(self::PAGE_PARM_NAME);
+        return $page ? $page : 1;
+    }
+
+    /**
+     * Render pagination HTML
+     *
+     * @return string
+     */
+    public function getPagerHtml()
+    {
+        $pagerBlock = $this->getChildBlock('post_list_toolbar_pager');
+        if ($pagerBlock instanceof \Magento\Framework\DataObject) {
+            /* @var $pagerBlock \Magento\Theme\Block\Html\Pager */
+
+            $pagerBlock->setUseContainer(
+                false
+            )->setShowPerPage(
+                false
+            )->setShowAmounts(
+                false
+            )->setPageVarName(
+                'page'
+            )->setFrameLength(
+                $this->_scopeConfig->getValue(
+                    'design/pagination/pagination_frame',
+                    \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+                )
+            )->setJump(
+                $this->_scopeConfig->getValue(
+                    'design/pagination/pagination_frame_skip',
+                    \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+                )
+            )->setLimit(
+                $this->getLimit()
+            )->setCollection(
+                $this->getCollection()
+            );
+            return $pagerBlock->toHtml();
+        }
+
+        return '';
+    }
+
+}

+ 95 - 0
app/code/Magefan/Blog/Block/Post/PostList/Toolbar/Pager.php

@@ -0,0 +1,95 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\PostList\Toolbar;
+
+use Magefan\Blog\Model\Config\Source\LazyLoad;
+
+/**
+ * Blog posts list toolbar pager
+ */
+class Pager extends \Magento\Theme\Block\Html\Pager
+{
+
+    /**
+     * Retrieve url of all pages
+     *
+     * @return string
+     */
+    public function getPagesUrls()
+    {
+        $urls = [];
+        for ($page = $this->getCurrentPage() + 1; $page <= $this->getLastPageNum(); $page++) {
+            $urls[$page] = $this->getPageUrl($page);
+        }
+
+        return $urls;
+    }
+
+    /**
+     * Retrieve true olny if can use lazyload
+     *
+     * @return bool
+     */
+    public function useLazyload()
+    {
+        $lastPage = $this->getLastPageNum();
+        $currentPage = $this->getCurrentPage();
+
+        return $this->getLazyloadMode()
+            && $this->getCollection()->getSize()
+            && $lastPage > 1
+            && $currentPage < $lastPage;
+    }
+
+    /**
+     * Retrieve lazyload json config string
+     * @param array $config
+     *
+     * @return string
+     */
+    public function getLazyloadConfig(array $config = [])
+    {
+        $config = array_merge([
+            'page_url' => $this->getPagesUrls(),
+            'current_page' => $this->getCurrentPage(),
+            'last_page' => $this->getLastPageNum(),
+            'padding' => $this->getLazyloadPadding(),
+            'list_wrapper' => $this->getListWrapper(),
+            'auto_trigger' => $this->getLazyloadMode() == LazyLoad::ENABLED_WITH_AUTO_TRIGER,
+        ], $config);
+
+        return json_encode($config);
+    }
+
+    /**
+     * Retrieve lazyload mod
+     *
+     * @return int
+     */
+    public function getLazyloadMode()
+    {
+        return (int) $this->_scopeConfig->getValue(
+            'mfblog/post_list/lazyload_enabled',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve lazyload padding
+     *
+     * @return int
+     */
+    public function getLazyloadPadding()
+    {
+        return (int) $this->_scopeConfig->getValue(
+            'mfblog/post_list/lazyload_padding',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+}

+ 110 - 0
app/code/Magefan/Blog/Block/Post/View.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Post;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post view
+ */
+class View extends AbstractPost
+{
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        $post = $this->getPost();
+        if ($post) {
+            $this->_addBreadcrumbs($post->getTitle(), 'blog_post');
+            $this->pageConfig->addBodyClass('blog-post-' . $post->getIdentifier());
+            $this->pageConfig->getTitle()->set($post->getMetaTitle());
+            $this->pageConfig->setKeywords($post->getMetaKeywords());
+            $this->pageConfig->setDescription($post->getMetaDescription());
+            $this->pageConfig->addRemotePageAsset(
+                $post->getCanonicalUrl(),
+                'canonical',
+                ['attributes' => ['rel' => 'canonical']]
+            );
+
+            $pageMainTitle = $this->getLayout()->getBlock('page.main.title');
+            if ($pageMainTitle) {
+                $pageMainTitle->setPageTitle(
+                    $this->escapeHtml($post->getTitle())
+                );
+            }
+
+            if ($post->getIsPreviewMode()) {
+                $this->pageConfig->setRobots('NOINDEX,FOLLOW');
+            }
+        }
+
+        return parent::_prepareLayout();
+    }
+
+    /**
+     * Prepare breadcrumbs
+     *
+     * @param  string $title
+     * @param  string $key
+     * @throws \Magento\Framework\Exception\LocalizedException
+     * @return void
+     */
+    protected function _addBreadcrumbs($title = null, $key = null)
+    {
+        if ($this->_scopeConfig->getValue('web/default/show_cms_breadcrumbs', ScopeInterface::SCOPE_STORE)
+            && ($breadcrumbsBlock = $this->getLayout()->getBlock('breadcrumbs'))
+        ) {
+            $breadcrumbsBlock->addCrumb(
+                'home',
+                [
+                    'label' => __('Home'),
+                    'title' => __('Go to Home Page'),
+                    'link' => $this->_storeManager->getStore()->getBaseUrl()
+                ]
+            );
+
+            $blogTitle = $this->_scopeConfig->getValue(
+                'mfblog/index_page/title',
+                ScopeInterface::SCOPE_STORE
+            );
+            $breadcrumbsBlock->addCrumb(
+                'blog',
+                [
+                    'label' => __($blogTitle),
+                    'title' => __($blogTitle),
+                    'link' => $this->_url->getBaseUrl()
+                ]
+            );
+
+            $parentCategories = [];
+            $parentCategory = $this->getPost()->getParentCategory();
+            while ($parentCategory ) {
+                $parentCategories[] = $parentCategory;
+                $parentCategory = $parentCategory->getParentCategory();
+            }
+
+            for ($i = count($parentCategories) - 1; $i >= 0; $i--) {
+                $parentCategory = $parentCategories[$i];
+                $breadcrumbsBlock->addCrumb('blog_parent_category_' . $parentCategory->getId(), [
+                    'label' => $parentCategory->getTitle(),
+                    'title' => $parentCategory->getTitle(),
+                    'link'  => $parentCategory->getCategoryUrl()
+                ]);
+            }
+
+            $breadcrumbsBlock->addCrumb($key, [
+                'label' => $title ,
+                'title' => $title
+            ]);
+        }
+    }
+
+}

+ 124 - 0
app/code/Magefan/Blog/Block/Post/View/Comments.php

@@ -0,0 +1,124 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\View;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post comments block
+ */
+class Comments extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * @var \Magento\Framework\Locale\ResolverInterface
+     */
+    protected $_localeResolver;
+
+    /**
+     * @var \Magento\Framework\Registry
+     */
+    protected $_coreRegistry;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magento\Framework\Registry $coreRegistry,
+     * @param \Magento\Cms\Model\Page $post
+     * @param \Magento\Framework\Registry $coreRegistry,
+     * @param \Magento\Cms\Model\Template\FilterProvider $filterProvider
+     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
+     * @param \Magento\Cms\Model\PageFactory $postFactory
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magento\Framework\Registry $coreRegistry,
+        \Magento\Framework\Locale\ResolverInterface $localeResolver,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_coreRegistry = $coreRegistry;
+        $this->_localeResolver = $localeResolver;
+    }
+
+    /**
+     * Block template file
+     * @var string
+     */
+    protected $_template = 'post/view/comments.phtml';
+
+    /**
+     * Retrieve comments type
+     * @return bool
+     */
+    public function getCommentsType()
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/post_view/comments/type', ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve number of comments to display
+     * @return int
+     */
+    public function getNumberOfComments()
+    {
+        return (int)$this->_scopeConfig->getValue(
+            'mfblog/post_view/comments/number_of_comments', ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve facebook app id
+     * @return string
+     */
+    public function getFacebookAppId()
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/post_view/comments/fb_app_id', ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve disqus forum shortname
+     * @return string
+     */
+    public function getDisqusShortname()
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/post_view/comments/disqus_forum_shortname', ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve locale code
+     * @return string
+     */
+    public function getLocaleCode()
+    {
+        return $this->_localeResolver->getLocale();
+    }
+
+    /**
+     * Retrieve posts instance
+     *
+     * @return \Magefan\Blog\Model\Category
+     */
+    public function getPost()
+    {
+        if (!$this->hasData('post')) {
+            $this->setData('post',
+                $this->_coreRegistry->registry('current_blog_post')
+            );
+        }
+        return $this->getData('post');
+    }
+}

+ 19 - 0
app/code/Magefan/Blog/Block/Post/View/Gallery.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Post\View;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post media gallery images
+ */
+class Gallery extends \Magefan\Blog\Block\Post\AbstractPost
+{
+
+
+}

+ 149 - 0
app/code/Magefan/Blog/Block/Post/View/NextPrev.php

@@ -0,0 +1,149 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\View;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post next and prev post links
+ */
+class NextPrev extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * Previous post
+     *
+     * @var \Magefan\Blog\Model\Post
+     */
+    protected $_prevPost;
+
+    /**
+     * Next post
+     *
+     * @var \Magefan\Blog\Model\Post
+     */
+    protected $_nextPost;
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory
+     */
+    protected $_postCollectionFactory;
+
+    /**
+     * @var Magento\Framework\Registry
+     */
+    protected $_coreRegistry;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory $_tagCollectionFactory
+     * @param \Magento\Framework\Registry $coreRegistry
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory $postCollectionFactory,
+        \Magento\Framework\Registry $coreRegistry,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_postCollectionFactory = $postCollectionFactory;
+        $this->_coreRegistry = $coreRegistry;
+    }
+
+    /**
+     * Retrieve true if need to display next-prev links
+     *
+     * @return boolean
+     */
+    public function displayLinks()
+    {
+        return (bool)$this->_scopeConfig->getValue(
+            'mfblog/post_view/nextprev/enabled',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve prev post
+     * @return \Magefan\Blog\Model\Post || bool
+     */
+    public function getPrevPost()
+    {
+        if ($this->_prevPost === null) {
+            $this->_prevPost = false;
+            $collection = $this->_getFrontendCollection()->addFieldToFilter(
+                'publish_time', [
+                    'gteq' => $this->getPost()->getPublishTime()
+                ])
+                ->setOrder('publish_time', 'ASC')
+                ->setPageSize(1);
+
+            $post = $collection->getFirstItem();
+
+            if ($post->getId()) {
+                $this->_prevPost = $post;
+            }
+        }
+
+        return $this->_prevPost;
+    }
+
+    /**
+     * Retrieve next post
+     * @return \Magefan\Blog\Model\Post || bool
+     */
+    public function getNextPost()
+    {
+        if ($this->_nextPost === null) {
+            $this->_nextPost = false;
+            $collection = $this->_getFrontendCollection()->addFieldToFilter(
+                'publish_time', [
+                    'lteq' => $this->getPost()->getPublishTime()
+                ])
+                ->setOrder('publish_time', 'DESC')
+                ->setPageSize(1);
+
+            $post = $collection->getFirstItem();
+
+            if ($post->getId()) {
+                $this->_nextPost = $post;
+            }
+        }
+
+        return $this->_nextPost;
+    }
+
+    /**
+     * Retrieve post collection with frontend filters and order
+     * @return bool
+     */
+    protected function _getFrontendCollection()
+    {
+        $collection = $this->_postCollectionFactory->create();
+        $collection->addActiveFilter()
+            ->addFieldToFilter('post_id', ['neq' => $this->getPost()->getId()])
+            ->addStoreFilter($this->_storeManager->getStore()->getId())
+            ->setOrder('publish_time', 'DESC')
+            ->setPageSize(1);
+        return $collection;
+    }
+
+    /**
+     * Retrieve post instance
+     *
+     * @return \Magefan\Blog\Model\Post
+     */
+    public function getPost()
+    {
+        return $this->_coreRegistry->registry('current_blog_post');
+    }
+
+}

+ 84 - 0
app/code/Magefan/Blog/Block/Post/View/Opengraph.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Post\View;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post view opengraph
+ */
+class Opengraph extends \Magefan\Blog\Block\Post\AbstractPost
+{
+    /**
+     * Retrieve page type
+     *
+     * @return string
+     */
+    public function getType()
+    {
+        return $this->stripTags(
+            $this->getPost()->getOgType()
+        );
+    }
+
+    /**
+     * Retrieve page title
+     *
+     * @return string
+     */
+    public function getTitle()
+    {
+        return $this->stripTags(
+            $this->getPost()->getOgTitle()
+        );
+    }
+
+    /**
+     * Retrieve page short description
+     *
+     * @return string
+     */
+    public function getDescription()
+    {
+        return $this->stripTags(
+            $this->getPost()->getOgDescription()
+        );
+    }
+
+    /**
+     * Retrieve page url
+     *
+     * @return string
+     */
+    public function getPageUrl()
+    {
+        return $this->stripTags(
+            $this->getPost()->getPostUrl()
+        );
+    }
+
+    /**
+     * Retrieve page main image
+     *
+     * @return string | null
+     */
+    public function getImage()
+    {
+        $image = $this->getPost()->getOgImage();
+
+        if (!$image) {
+            $image = $this->getPost()->getFirstImage();
+        }
+
+        if ($image) {
+            return $this->stripTags($image);
+        }
+
+    }
+
+}

+ 73 - 0
app/code/Magefan/Blog/Block/Post/View/RelatedPosts.php

@@ -0,0 +1,73 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\View;
+
+use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Framework\View\Element\AbstractBlock;
+
+/**
+ * Blog post related posts block
+ */
+class RelatedPosts extends \Magefan\Blog\Block\Post\PostList\AbstractList
+{
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        $pageSize = (int) $this->_scopeConfig->getValue(
+            'mfblog/post_view/related_posts/number_of_posts',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+
+        $this->_postCollection = $this->getPost()->getRelatedPosts()
+            ->addActiveFilter()
+            ->setPageSize($pageSize ?: 5);
+
+        $this->_postCollection->getSelect()->order('rl.position', 'ASC');
+    }
+
+    /**
+     * Retrieve true if Display Related Posts enabled
+     * @return boolean
+     */
+    public function displayPosts()
+    {
+        return (bool) $this->_scopeConfig->getValue(
+            'mfblog/post_view/related_posts/enabled',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve posts instance
+     *
+     * @return \Magefan\Blog\Model\Category
+     */
+    public function getPost()
+    {
+        if (!$this->hasData('post')) {
+            $this->setData('post',
+                $this->_coreRegistry->registry('current_blog_post')
+            );
+        }
+        return $this->getData('post');
+    }
+
+    /**
+     * Get Block Identities
+     * @return Array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Cms\Model\Page::CACHE_TAG . '_relatedposts_'.$this->getPost()->getId()  ];
+    }
+}

+ 138 - 0
app/code/Magefan/Blog/Block/Post/View/RelatedProducts.php

@@ -0,0 +1,138 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Post\View;
+
+use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Framework\View\Element\AbstractBlock;
+
+/**
+ * Blog post related products block
+ */
+class RelatedProducts extends \Magento\Catalog\Block\Product\AbstractProduct
+    implements \Magento\Framework\DataObject\IdentityInterface
+{
+    /**
+     * @var \Magento\Catalog\Model\ResourceModel\Product\Collection
+     */
+    protected $_itemCollection;
+
+    /**
+     * Catalog product visibility
+     *
+     * @var \Magento\Catalog\Model\Product\Visibility
+     */
+    protected $_catalogProductVisibility;
+
+    /**
+     * @var \Magento\Framework\Module\Manager
+     */
+    protected $_moduleManager;
+
+    /**
+     * Related products block construct
+     * @param \Magento\Catalog\Block\Product\Context $context
+     * @param \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility
+     * @param \Magento\Framework\Module\Manager $moduleManager
+     * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $data
+     */
+    public function __construct(
+        \Magento\Catalog\Block\Product\Context $context,
+        \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility,
+        \Magento\Framework\Module\Manager $moduleManager,
+        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
+        array $data = []
+    ) {
+        $this->_catalogProductVisibility = $catalogProductVisibility;
+        $this->_moduleManager = $moduleManager;
+        parent::__construct($context, $data );
+    }
+
+    /**
+     * Premare block data
+     * @return $this
+     */
+    protected function _prepareCollection()
+    {
+        $post = $this->getPost();
+
+        $this->_itemCollection = $post->getRelatedProducts()
+            ->addAttributeToSelect('required_options');
+
+        if ($this->_moduleManager->isEnabled('Magento_Checkout')) {
+            $this->_addProductAttributesAndPrices($this->_itemCollection);
+        }
+
+        $this->_itemCollection->setVisibility($this->_catalogProductVisibility->getVisibleInCatalogIds());
+
+        $this->_itemCollection->setPageSize(
+            (int) $this->_scopeConfig->getValue(
+                'mfblog/post_view/related_products/number_of_products',
+                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+            )
+        );
+
+        $this->_itemCollection->getSelect()->order('rl.position', 'ASC');
+
+        $this->_itemCollection->load();
+
+        foreach ($this->_itemCollection as $product) {
+            $product->setDoNotUseCategoryId(true);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Retrieve true if Display Related Products enabled
+     * @return boolean
+     */
+    public function displayProducts()
+    {
+        return (bool) $this->_scopeConfig->getValue(
+            'mfblog/post_view/related_products/enabled',
+            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * @return \Magento\Catalog\Model\ResourceModel\Product\Collection
+     */
+    public function getItems()
+    {
+        if (is_null($this->_itemCollection)) {
+            $this->_prepareCollection();
+        }
+        return $this->_itemCollection;
+    }
+
+    /**
+     * Retrieve posts instance
+     *
+     * @return \Magefan\Blog\Model\Category
+     */
+    public function getPost()
+    {
+        if (!$this->hasData('post')) {
+            $this->setData('post',
+                $this->_coreRegistry->registry('current_blog_post')
+            );
+        }
+        return $this->getData('post');
+    }
+
+    /**
+     * Get Block Identities
+     * @return Array
+     */
+    public function getIdentities()
+    {
+        $post = $this->getPost();
+        return $post ? [ \Magento\Cms\Model\Page::CACHE_TAG . '_relatedproducts_' . $post->getId() ] : [];
+    }
+}

+ 115 - 0
app/code/Magefan/Blog/Block/Post/View/Richsnippets.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+namespace Magefan\Blog\Block\Post\View;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog post view rich snippets
+ */
+class Richsnippets extends Opengraph
+{
+    /**
+     * @param  array
+     */
+    protected $_options;
+
+    /**
+     * Retrieve snipet params
+     *
+     * @return array
+     */
+    public function getOptions()
+    {
+        if ($this->_options === null) {
+            $post = $this->getPost();
+
+            $logoBlock = $this->getLayout()->getBlock('logo');
+            if (!$logoBlock) {
+                $logoBlock = $this->getLayout()->getBlock('amp.logo');
+            }
+
+            $this->_options = [
+                '@context' => 'http://schema.org',
+                '@type' => 'BlogPosting',
+                '@id' => $post->getPostUrl(),
+                'author' => $this->getAuthor(),
+                'headline' => $this->getTitle(),
+                'description' => $this->getDescription(),
+                'datePublished' => $post->getPublishDate('c'),
+                'dateModified' => $post->getUpdateDate('c'),
+                'image' => [
+                    '@type' => 'ImageObject',
+                    'url' => $this->getImage() ?:
+                        ($logoBlock ? $logoBlock->getLogoSrc() : ''),
+                    'width' => 720,
+                    'height' => 720,
+                ],
+                'publisher' => [
+                    '@type' => 'Organization',
+                    'name' => $this->getPublisher(),
+                    'logo' => [
+                        '@type' => 'ImageObject',
+                        'url' => $logoBlock ? $logoBlock->getLogoSrc() : '',
+                    ],
+                ],
+                'mainEntityOfPage' => $this->_url->getBaseUrl(),
+            ];
+        }
+
+        return $this->_options;
+    }
+
+    /**
+     * Retrieve author name
+     *
+     * @return array
+     */
+    public function getAuthor()
+    {
+        if ($author = $this->getPost()->getAuthor()) {
+            if ($author->getTitle()) {
+                return $author->getTitle();
+            }
+        }
+
+        // if no author name return name of publisher
+        return $this->getPublisher();
+    }
+
+    /**
+     * Retrieve publisher name
+     *
+     * @return array
+     */
+    public function getPublisher()
+    {
+        $publisher =  $this->_scopeConfig->getValue(
+            'general/store_information/name',
+            ScopeInterface::SCOPE_STORE
+        );
+
+        if (!$publisher) {
+            $publisher = 'Magento2 Store';
+        }
+
+        return $publisher;
+    }
+
+    /**
+     * Render html output
+     *
+     * @return string
+     */
+    protected function _toHtml()
+    {
+        return '<script type="application/ld+json">'
+            . json_encode($this->getOptions())
+            . '</script>';
+    }
+}

+ 54 - 0
app/code/Magefan/Blog/Block/Rss/Feed.php

@@ -0,0 +1,54 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Rss;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog ree feed block
+ */
+class Feed extends \Magefan\Blog\Block\Post\PostList\AbstractList
+{
+    /**
+     * Retrieve rss feed url
+     * @return string
+     */
+    public function getLink()
+    {
+        return $this->_url->getUrl('feed', 'rss');
+    }
+
+    /**
+     * Retrieve rss feed title
+     * @return string
+     */
+    public function getTitle()
+    {
+    	 return $this->_scopeConfig->getValue('mfblog/rss_feed/title', ScopeInterface::SCOPE_STORE);
+    }
+
+    /**
+     * Retrieve rss feed description
+     * @return string
+     */
+    public function getDescription()
+    {
+    	 return $this->_scopeConfig->getValue('mfblog/rss_feed/description', ScopeInterface::SCOPE_STORE);
+    }
+
+    /**
+     * Retrieve block identities
+     * @return array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Cms\Model\Page::CACHE_TAG . '_blog_rss_feed'  ];
+    }
+
+}

+ 64 - 0
app/code/Magefan/Blog/Block/Search/PostList.php

@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Search;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog search result block
+ */
+class PostList extends \Magefan\Blog\Block\Post\PostList
+{
+	/**
+	 * Retrieve query
+	 * @return string
+	 */
+    public function getQuery()
+    {
+        return urldecode($this->getRequest()->getParam('q'));
+    }
+
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        parent::_preparePostCollection();
+        $this->_postCollection->addSearchFilter(
+            $this->getQuery()
+        );
+    }
+
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        $title = $this->_getTitle();
+        $this->_addBreadcrumbs($title, 'blog_search');
+        $this->pageConfig->getTitle()->set($title);
+        $this->pageConfig->setRobots('NOINDEX,FOLLOW');
+
+        return parent::_prepareLayout();
+    }
+
+    /**
+     * Retrieve title
+     * @return string
+     */
+    protected function _getTitle()
+    {
+        return __('Search "%1"', $this->getQuery());
+    }
+
+}

+ 56 - 0
app/code/Magefan/Blog/Block/Sidebar.php

@@ -0,0 +1,56 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog sidebar block
+ */
+class Sidebar extends \Magento\Framework\View\Element\Text
+{
+
+    /**
+     * Render html output
+     *
+     * @return string
+     */
+    protected function _toHtml()
+    {
+        $this->setText('');
+        $childNames = $this->getChildNames();
+
+        usort($childNames, [$this, 'sortChilds']);
+
+        $layout = $this->getLayout();
+        foreach ($childNames as $child) {
+            $this->addText($layout->renderElement($child, false));
+        }
+
+        return parent::_toHtml();
+    }
+
+    /**
+     * Sort by sort order param
+     * @param  string $a
+     * @param  string $b
+     * @return boolean
+     */
+    public function sortChilds($a, $b)
+    {
+        $layout = $this->getLayout();
+        $blockA = $layout->getBlock($a);
+        $blockB = $layout->getBlock($b);
+        if ($blockA && $blockB) {
+            $r = $blockA->getSortOrder() > $blockB->getSortOrder() ? 1 : - 1;
+            return $r;
+        }
+    }
+
+}

+ 104 - 0
app/code/Magefan/Blog/Block/Sidebar/Archive.php

@@ -0,0 +1,104 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Sidebar;
+
+/**
+ * Blog sidebar archive block
+ */
+class Archive extends \Magefan\Blog\Block\Post\PostList\AbstractList
+{
+    use Widget;
+
+    /**
+     * @var string
+     */
+    protected $_widgetKey = 'archive';
+
+    /**
+     * Available months
+     * @var array
+     */
+    protected $_months;
+
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        parent::_preparePostCollection();
+        $this->_postCollection->getSelect()->group(
+            'MONTH(main_table.publish_time)',
+            'DESC'
+        );
+    }
+
+    /**
+     * Retrieve available months
+     * @return array
+     */
+    public function getMonths()
+    {
+        if (is_null($this->_months)) {
+            $this->_months = [];
+            $this->_preparePostCollection();
+            foreach($this->_postCollection as $post) {
+                $time = strtotime($post->getData('publish_time'));
+                $this->_months[date('Y-m', $time)] = $time;
+            }
+        }
+
+
+        return $this->_months;
+    }
+
+    /**
+     * Retrieve year by time
+     * @param  int $time
+     * @return string
+     */
+    public function getYear($time)
+    {
+        return date('Y', $time);
+    }
+
+    /**
+     * Retrieve month by time
+     * @param  int $time
+     * @return string
+     */
+    public function getMonth($time)
+    {
+        return __(date('F', $time));
+    }
+
+    /**
+     * Retrieve archive url by time
+     * @param  int $time
+     * @return string
+     */
+    public function getTimeUrl($time)
+    {
+        return $this->_url->getUrl(
+            date('Y-m', $time),
+            \Magefan\Blog\Model\Url::CONTROLLER_ARCHIVE
+        );
+    }
+
+    /**
+     * Retrieve blog identities
+     * @return array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Cms\Model\Block::CACHE_TAG . '_blog_archive_widget'];
+    }
+
+}

+ 89 - 0
app/code/Magefan/Blog/Block/Sidebar/Categories.php

@@ -0,0 +1,89 @@
+<?php
+/**
+ * Copyright © 2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Sidebar;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog sidebar categories block
+ */
+class Categories extends \Magento\Framework\View\Element\Template
+{
+    use Widget;
+
+    /**
+     * @var string
+     */
+    protected $_widgetKey = 'categories';
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Category\Collection
+     */
+    protected $_categoryCollection;
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magefan\Blog\Model\ResourceModel\Category\Collection $categoryCollection
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magefan\Blog\Model\ResourceModel\Category\Collection $categoryCollection,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_categoryCollection = $categoryCollection;
+    }
+
+    /**
+     * Get grouped categories
+     * @return \Magefan\Blog\Model\ResourceModel\Category\Collection
+     */
+    public function getGroupedChilds()
+    {
+        $k = 'grouped_childs';
+        if (!$this->hasData($k)) {
+            $array = $this->_categoryCollection
+                ->addActiveFilter()
+                ->addStoreFilter($this->_storeManager->getStore()->getId())
+                ->setOrder('position')
+                ->getTreeOrderedArray();
+
+            $this->setData($k, $array);
+        }
+
+        return $this->getData($k);
+    }
+
+    /**
+     * Retrieve true if need to show posts count
+     * @return int
+     */
+    public function showPostsCount()
+    {
+        $key = 'show_posts_count';
+        if (!$this->hasData($key)) {
+            $this->setData($key, (bool)$this->_scopeConfig->getValue(
+                'mfblog/sidebar/'.$this->_widgetKey.'/show_posts_count', ScopeInterface::SCOPE_STORE
+            ));
+        }
+        return $this->getData($key);
+    }
+
+
+    /**
+     * Retrieve block identities
+     * @return array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Cms\Model\Block::CACHE_TAG . '_blog_categories_widget'  ];
+    }
+}

+ 46 - 0
app/code/Magefan/Blog/Block/Sidebar/Recent.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Sidebar;
+
+/**
+ * Blog sidebar categories block
+ */
+class Recent extends \Magefan\Blog\Block\Post\PostList\AbstractList
+{
+    use Widget;
+
+    /**
+     * @var string
+     */
+    protected $_widgetKey = 'recent_posts';
+
+    /**
+     * @return $this
+     */
+    public function _construct()
+    {
+        $this->setPageSize(
+            (int) $this->_scopeConfig->getValue(
+                'mfblog/sidebar/'.$this->_widgetKey.'/posts_per_page',
+                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+            )
+        );
+        return parent::_construct();
+    }
+
+    /**
+     * Retrieve block identities
+     * @return array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Cms\Model\Block::CACHE_TAG . '_blog_recent_posts_widget'  ];
+    }
+
+}

+ 38 - 0
app/code/Magefan/Blog/Block/Sidebar/Rss.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Sidebar;
+
+/**
+ * Blog sidebar rss
+ */
+class Rss extends \Magento\Framework\View\Element\Template
+{
+    use Widget;
+
+    /**
+     * @var string
+     */
+    protected $_widgetKey = 'rss_feed';
+
+    /**
+     * Available months
+     * @var array
+     */
+    protected $_months;
+
+    /**
+     * Retrieve blog identities
+     * @return array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Cms\Model\Block::CACHE_TAG . '_blog_rss_widget'  ];
+    }
+
+}

+ 62 - 0
app/code/Magefan/Blog/Block/Sidebar/Search.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Sidebar;
+
+/**
+ * Blog sidebar categories block
+ */
+class Search extends \Magento\Framework\View\Element\Template
+{
+	use Widget;
+
+	/**
+     * @var \Magefan\Blog\Model\Url
+     */
+    protected $_url;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magefan\Blog\Model\Url $url
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magefan\Blog\Model\Url $url,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_url = $url;
+    }
+
+	/**
+     * @var string
+     */
+    protected $_widgetKey = 'search';
+
+	/**
+	 * Retrieve query
+	 * @return string
+	 */
+	public function getQuery()
+	{
+		return urldecode($this->getRequest()->getParam('q', ''));
+	}
+
+	/**
+	 * Retrieve serch form action url
+	 * @return string
+	 */
+	public function getFormUrl()
+	{
+		return $this->_url->getUrl('', \Magefan\Blog\Model\Url::CONTROLLER_SEARCH);
+	}
+
+}

+ 137 - 0
app/code/Magefan/Blog/Block/Sidebar/TagClaud.php

@@ -0,0 +1,137 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Sidebar;
+
+/**
+ * Blog tag claud sidebar block
+ */
+class TagClaud extends \Magento\Framework\View\Element\Template
+{
+    use Widget;
+
+    /**
+     * @var string
+     */
+    protected $_widgetKey = 'tag_claud';
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Tag\CollectionFactory
+     */
+    protected $_tagCollectionFactory;
+
+    /**
+     * @var \Magefan\Blog\Model\ResourceModel\Tag\Collection
+     */
+    protected $_tags;
+
+    /**
+     * @var int
+     */
+    protected $_maxCount;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magefan\Blog\Model\ResourceModel\Tag\CollectionFactory $_tagCollectionFactory
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magefan\Blog\Model\ResourceModel\Tag\CollectionFactory $tagCollectionFactory,
+        array $data = []
+    ) {
+        parent::__construct($context, $data);
+        $this->_tagCollectionFactory = $tagCollectionFactory;
+    }
+
+    /**
+     * Retrieve tags
+     * @return array
+     */
+    public function getTags()
+    {
+        if ($this->_tags === null) {
+            $this->_tags = $this->_tagCollectionFactory->create();
+            $resource = $this->_tags->getResource();
+            $this->_tags->getSelect()->joinLeft(
+                ['pt' => $resource->getTable('magefan_blog_post_tag')],
+                'main_table.tag_id = pt.tag_id',
+                []
+            )->joinLeft(
+                ['p' => $resource->getTable('magefan_blog_post')],
+                'p.post_id = pt.post_id',
+                []
+            )->joinLeft(
+                ['ps' => $resource->getTable('magefan_blog_post_store')],
+                'p.post_id = ps.post_id',
+                ['count' => 'count(main_table.tag_id)']
+            )->group(
+                'main_table.tag_id'
+            )->where(
+                'ps.store_id IN (?)',
+                [0, (int)$this->_storeManager->getStore()->getId()]
+            );
+        }
+
+        return $this->_tags;
+    }
+
+    /**
+     * Retrieve max tag number
+     * @return array
+     */
+    public function getMaxCount()
+    {
+        if ($this->_maxCount == null) {
+            $this->_maxCount = 0;
+            foreach ($this->getTags() as $tag) {
+                $count = $tag->getCount();
+                if ($count > $this->_maxCount) {
+                    $this->_maxCount = $count;
+                }
+            }
+        }
+        return $this->_maxCount;
+    }
+
+    /**
+     * Retrieve tag class
+     * @return array
+     */
+    public function getTagClass($tag)
+    {
+        $maxCount = $this->getMaxCount();
+        $percent = floor(($tag->getCount() / $maxCount) * 100);
+
+        if ($percent < 20) {
+            return 'smallest';
+        }
+        if ($percent >= 20 and $percent < 40) {
+            return 'small';
+        }
+        if ($percent >= 40 and $percent < 60) {
+            return 'medium';
+        }
+        if ($percent >= 60 and $percent < 80) {
+            return 'large';
+        }
+        return 'largest';
+    }
+
+    /**
+     * Retrieve block identities
+     * @return array
+     */
+    public function getIdentities()
+    {
+        return [\Magento\Cms\Model\Block::CACHE_TAG . '_blog_tag_claud_widget'  ];
+    }
+
+}

+ 45 - 0
app/code/Magefan/Blog/Block/Sidebar/Widget.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Sidebar;
+
+/**
+ * Blog sidebar widget trait
+ */
+trait Widget
+{
+    /**
+     * Retrieve block sort order
+     * @return int
+     */
+    public function getSortOrder()
+    {
+        if (!$this->hasData('sort_order')) {
+            $this->setData('sort_order', $this->_scopeConfig->getValue(
+                'mfblog/sidebar/'.$this->_widgetKey.'/sort_order', \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+            ));
+        }
+        return (int) $this->getData('sort_order');
+    }
+
+    /**
+     * Retrieve block html
+     *
+     * @return string
+     */
+    protected function _toHtml()
+    {
+        if ($this->_scopeConfig->getValue(
+            'mfblog/sidebar/'.$this->_widgetKey.'/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+        )) {
+            return parent::_toHtml();
+        }
+
+        return '';
+    }
+}

+ 53 - 0
app/code/Magefan/Blog/Block/Social/AddThis.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace Magefan\Blog\Block\Social;
+
+use Magento\Store\Model\ScopeInterface;
+
+class AddThis extends \Magento\Framework\View\Element\Template
+{
+    /**
+     * Retrieve AddThis status
+     *
+     * @return boolean
+     */
+    public function getAddThisEnabled()
+    {
+        return (bool)$this->_scopeConfig->getValue(
+            'mfblog/social/add_this_enabled', ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve AddThis publisher id
+     *
+     * @return boolean
+     */
+    public function getAddThisPubId()
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/social/add_this_pubid', ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    /**
+     * Retrieve AddThis language code
+     *
+     * @return boolean
+     */
+    public function getAddThisLanguage()
+    {
+        return $this->_scopeConfig->getValue(
+            'mfblog/social/add_this_language', ScopeInterface::SCOPE_STORE
+        );
+    }
+
+    public function toHtml()
+    {
+        if (!$this->getAddThisEnabled() || !$this->getAddThisPubId()) {
+            return '';
+        }
+
+        return parent::toHtml();
+    }
+}

+ 63 - 0
app/code/Magefan/Blog/Block/Tag/PostList.php

@@ -0,0 +1,63 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Tag;
+
+use Magento\Store\Model\ScopeInterface;
+
+/**
+ * Blog tag posts list
+ */
+class PostList extends \Magefan\Blog\Block\Post\PostList
+{
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        parent::_preparePostCollection();
+        if ($tag = $this->getTag()) {
+            $this->_postCollection->addTagFilter($tag);
+        }
+    }
+
+    /**
+     * Retrieve tag instance
+     *
+     * @return \Magefan\Blog\Model\Tag
+     */
+    public function getTag()
+    {
+        return $this->_coreRegistry->registry('current_blog_tag');
+    }
+
+    /**
+     * Preparing global layout
+     *
+     * @return $this
+     */
+    protected function _prepareLayout()
+    {
+        if ($tag = $this->getTag()) {
+            $this->_addBreadcrumbs($tag->getTitle(), 'blog_tag');
+            $this->pageConfig->addBodyClass('blog-tag-' . $tag->getIdentifier());
+            $this->pageConfig->getTitle()->set($tag->getTitle());
+            $this->pageConfig->addRemotePageAsset(
+                $tag->getTagUrl(),
+                'canonical',
+                ['attributes' => ['rel' => 'canonical']]
+            );
+            $this->pageConfig->setRobots('NOINDEX,FOLLOW');
+        }
+
+        return parent::_prepareLayout();
+    }
+
+}

+ 134 - 0
app/code/Magefan/Blog/Block/Widget/Recent.php

@@ -0,0 +1,134 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Block\Widget;
+
+/**
+ * Blog recent posts widget
+ */
+class Recent extends \Magefan\Blog\Block\Post\PostList\AbstractList implements \Magento\Widget\Block\BlockInterface
+{
+
+    /**
+     * @var \Magefan\Blog\Model\CategoryFactory
+     */
+    protected $_categoryFactory;
+
+    /**
+     * @var \Magefan\Blog\Model\Category
+     */
+    protected $_category;
+
+    /**
+     * Construct
+     *
+     * @param \Magento\Framework\View\Element\Context $context
+     * @param \Magento\Framework\Registry $coreRegistry
+     * @param \Magento\Cms\Model\Template\FilterProvider $filterProvider
+     * @param \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory $postCollectionFactory
+     * @param \Magefan\Blog\Model\Url $url
+     * @param \Magefan\Blog\Model\CategoryFactory $categoryFactory
+     * @param array $data
+     */
+    public function __construct(
+        \Magento\Framework\View\Element\Template\Context $context,
+        \Magento\Framework\Registry $coreRegistry,
+        \Magento\Cms\Model\Template\FilterProvider $filterProvider,
+        \Magefan\Blog\Model\ResourceModel\Post\CollectionFactory $postCollectionFactory,
+        \Magefan\Blog\Model\Url $url,
+        \Magefan\Blog\Model\CategoryFactory $categoryFactory,
+        array $data = []
+    ) {
+        parent::__construct($context, $coreRegistry, $filterProvider, $postCollectionFactory, $url, $data);
+        $this->_categoryFactory = $categoryFactory;
+    }
+
+    /**
+     * Set blog template
+     *
+     * @return this
+     */
+    public function _toHtml()
+    {
+        $this->setTemplate(
+            $this->getData('custom_template') ?: 'widget/recent.phtml'
+        );
+
+        return parent::_toHtml();
+    }
+
+    /**
+     * Retrieve block title
+     *
+     * @return string
+     */
+    public function getTitle()
+    {
+        return $this->getData('title') ?: __('Recent Blog Posts');
+    }
+
+    /**
+     * Prepare posts collection
+     *
+     * @return void
+     */
+    protected function _preparePostCollection()
+    {
+        $size = $this->getData('number_of_posts');
+        if (!$size) {
+            $size = (int) $this->_scopeConfig->getValue(
+                'mfblog/sidebar/recent_posts/posts_per_page',
+                \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+            );
+        }
+
+        $this->setPageSize($size);
+
+        parent::_preparePostCollection();
+
+        if ($category = $this->getCategory()) {
+            $this->_postCollection->addCategoryFilter($category);
+        }
+    }
+
+    /**
+     * Retrieve category instance
+     *
+     * @return \Magefan\Blog\Model\Category
+     */
+    public function getCategory()
+    {
+        if ($this->_category === null) {
+            if ($categoryId = $this->getData('category_id')) {
+                $category = $this->_categoryFactory->create();
+                $category->load($categoryId);
+
+                $storeId = $this->_storeManager->getStore()->getId();
+                if ($category->isVisibleOnStore($storeId)) {
+                    $category->setStoreId($storeId);
+                    return $this->_category = $category;
+                }
+            }
+
+            $this->_category = false;
+        }
+
+        return $this->_category;
+    }
+
+    /**
+     * Retrieve post short content
+     * @param  \Magefan\Blog\Model\Post $post
+     *
+     * @return string
+     */
+    public function getShorContent($post)
+    {
+        return $post->getShortFilteredContent();
+    }
+}

+ 461 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Actions.php

@@ -0,0 +1,461 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml;
+
+/**
+ * Abstract admin controller
+ */
+abstract class Actions extends \Magento\Backend\App\Action
+{
+    /**
+     * Form session key
+     * @var string
+     */
+    protected $_formSessionKey;
+
+    /**
+     * Allowed Key
+     * @var string
+     */
+    protected $_allowedKey;
+
+    /**
+     * Model class name
+     * @var string
+     */
+    protected $_modelClass;
+
+    /**
+     * Active menu key
+     * @var string
+     */
+    protected $_activeMenu;
+
+    /**
+     * Store config section key
+     * @var string
+     */
+    protected $_configSection;
+
+    /**
+     * Request id key
+     * @var string
+     */
+    protected $_idKey = 'id';
+
+    /**
+     * Status field name
+     * @var string
+     */
+    protected $_statusField     = 'status';
+
+    /**
+     * Save request params key
+     * @var string
+     */
+    protected $_paramsHolder;
+
+    /**
+     * Model Object
+     * @var \Magento\Framework\Model\AbstractModel
+     */
+    protected $_model;
+
+    /**
+     * Core registry
+     *
+     * @var \Magento\Framework\Registry
+     */
+    protected $_coreRegistry = null;
+
+    /**
+     * Action execute
+     * @return \Magento\Framework\Controller\ResultInterface
+     */
+    public function execute()
+    {
+        $_preparedActions = ['index', 'grid', 'new', 'edit', 'save', 'duplicate', 'delete', 'config', 'massStatus'];
+        $_action = $this->getRequest()->getActionName();
+        if (in_array($_action, $_preparedActions)) {
+            $method = '_'.$_action.'Action';
+
+            $this->_beforeAction();
+            $this->$method();
+            $this->_afterAction();
+        }
+    }
+
+    /**
+     * Index action
+     * @return void
+     */
+    protected function _indexAction()
+    {
+        if ($this->getRequest()->getParam('ajax')) {
+            $this->_forward('grid');
+            return;
+        }
+
+        $this->_view->loadLayout();
+        $this->_setActiveMenu($this->_activeMenu);
+        $title = __('Manage %1', $this->_getModel(false)->getOwnTitle(true));
+        $this->_view->getPage()->getConfig()->getTitle()->prepend($title);
+        $this->_addBreadcrumb($title, $title);
+        $this->_view->renderLayout();
+    }
+
+    /**
+     * Grid action
+     * @return void
+     */
+    protected function _gridAction()
+    {
+        $this->_view->loadLayout(false);
+        $this->_view->renderLayout();
+    }
+
+    /**
+     * New action
+     * @return void
+     */
+    protected function _newAction()
+    {
+        $this->_forward('edit');
+    }
+
+    /**
+     * Edit action
+     * @return void
+     */
+    public function _editAction()
+    {
+        $model = $this->_getModel();
+
+        $this->_getRegistry()->register('current_model', $model);
+
+        $this->_view->loadLayout();
+        $this->_setActiveMenu($this->_activeMenu);
+
+        $title = $model->getOwnTitle();
+
+        if ($model->getId()) {
+            $breadcrumbTitle = __('Edit %1', $title);
+            $breadcrumbLabel = $breadcrumbTitle;
+        } else {
+            $breadcrumbTitle = __('New %1', $title);
+            $breadcrumbLabel = __('Create %1', $title);
+        }
+        $this->_view->getPage()->getConfig()->getTitle()->prepend(__($title));
+        $this->_view->getPage()->getConfig()->getTitle()->prepend(
+            $model->getId() ? $this->_getModelName($model) : __('New %1', $title)
+        );
+
+        $this->_addBreadcrumb($breadcrumbLabel, $breadcrumbTitle);
+
+        // restore data
+        $values = $this->_getSession()->getData($this->_formSessionKey, true);
+        if ($this->_paramsHolder) {
+            $values = isset($values[$this->_paramsHolder]) ? $values[$this->_paramsHolder] : null;
+        }
+
+        if ($values) {
+            $model->addData($values);
+        }
+
+        $this->_view->renderLayout();
+    }
+
+    /**
+     * Retrieve model name
+     * @param  boolean $plural
+     * @return string
+     */
+    protected function _getModelName(\Magento\Framework\Model\AbstractModel $model)
+    {
+        return $model->getName() ?: $model->getTitle();
+    }
+
+    /**
+     * Save action
+     * @return void
+     */
+    public function _saveAction()
+    {
+        $request = $this->getRequest();
+        if (!$request->isPost()) {
+            $this->getResponse()->setRedirect($this->getUrl('*/*'));
+        }
+        $model = $this->_getModel();
+
+        try {
+            $params = $this->_paramsHolder ? $request->getParam($this->_paramsHolder) : $request->getParams();
+            $idFieldName = $model->getResource()->getIdFieldName();
+            if (isset($params[$idFieldName]) && empty($params[$idFieldName])) {
+                unset($params[$idFieldName]);
+            }
+            $model->addData($params);
+
+            $this->_beforeSave($model, $request);
+            $model->save();
+            $this->_afterSave($model, $request);
+
+            $this->messageManager->addSuccess(__('%1 has been saved.', $model->getOwnTitle()));
+            $this->_setFormData(false);
+        } catch (\Magento\Framework\Exception\LocalizedException $e) {
+            $this->messageManager->addError(nl2br($e->getMessage()));
+            $this->_setFormData();
+        } catch (\Exception $e) {
+            $this->messageManager->addException(
+                $e,
+                __('Something went wrong while saving this %1. %2',
+                    strtolower($model->getOwnTitle()),
+                    $e->getMessage()
+                )
+            );
+            $this->_setFormData();
+        }
+
+        $hasError = (bool)$this->messageManager->getMessages()->getCountByType(
+            \Magento\Framework\Message\MessageInterface::TYPE_ERROR
+        );
+
+        if ($request->getParam('isAjax')) {
+            $block = $this->_objectManager->create('Magento\Framework\View\Layout')->getMessagesBlock();
+            $block->setMessages($this->messageManager->getMessages(true));
+
+            $this->getResponse()->setBody(json_encode(
+                [
+                    'messages' => $block->getGroupedHtml(),
+                    'error' => $hasError,
+                    'model' => $model->toArray(),
+                ]
+            ));
+        } else {
+            if ($hasError || $request->getParam('back')) {
+                $this->_redirect('*/*/edit', [$this->_idKey => $model->getId()]);
+            } else {
+                $this->_redirect('*/*');
+            }
+        }
+
+    }
+
+    /**
+     * Duplicat action
+     * @return void
+     */
+    protected function _duplicateAction()
+    {
+        try {
+            $originModel = $this->_getModel();
+            if (!$originModel->getId()) {
+                throw new \Exception("Item is not longer exist.", 1);
+            }
+
+            $model = $originModel->duplicate();
+
+            $this->messageManager->addSuccess(__('%1 has been duplicated.', $model->getOwnTitle()));
+            $this->_redirect('*/*/edit', [$this->_idKey => $model->getId()]);
+        } catch (Exception $e) {
+            $this->messageManager->addException(
+                $e,
+                __('Something went wrong while saving this %1. %2',
+                    strtolower($model->getOwnTitle()),
+                    $e->getMessage()
+                )
+            );
+            $this->_redirect('*/*/edit', [$this->_idKey => $originModel->getId()]);
+        }
+    }
+
+    /**
+     * Before model Save action
+     * @return void
+     */
+    protected function _beforeSave($model, $request) {}
+
+    /**
+     * After model action
+     * @return void
+     */
+    protected function _afterSave($model, $request) {}
+
+    /**
+     * Before action
+     * @return void
+     */
+    protected function _beforeAction() {}
+
+    /**
+     * After action
+     * @return void
+     */
+    protected function _afterAction() {}
+
+    /**
+     * Delete action
+     * @return void
+     */
+    protected function _deleteAction()
+    {
+        $ids = $this->getRequest()->getParam($this->_idKey);
+
+        if (!is_array($ids)) {
+            $ids = [$ids];
+        }
+
+        $error = false;
+        try {
+            foreach($ids as $id) {
+                $this->_objectManager->create($this->_modelClass)->setId($id)->delete();
+            }
+        } catch (\Magento\Framework\Exception\LocalizedException $e) {
+            $error = true;
+            $this->messageManager->addError($e->getMessage());
+        } catch (\Exception $e) {
+            $error = true;
+            $this->messageManager->addException(
+                $e,
+                __("We can't delete %1 right now. %2",
+                    strtolower($this->_getModel(false)->getOwnTitle()),
+                    $e->getMessage()
+                )
+            );
+        }
+
+        if (!$error) {
+            $this->messageManager->addSuccess(
+                __('%1 have been deleted.', $this->_getModel(false)->getOwnTitle(count($ids) > 1))
+            );
+        }
+
+        $this->_redirect('*/*');
+    }
+
+    /**
+     * Change status action
+     * @return void
+     */
+    protected function _massStatusAction()
+    {
+        $ids = $this->getRequest()->getParam($this->_idKey);
+
+        if (!is_array($ids)) {
+            $ids = [$ids];
+        }
+
+        $model = $this->_getModel(false);
+
+        $error = false;
+
+        try {
+
+            $status = $this->getRequest()->getParam('status');
+            $statusFieldName = $this->_statusField;
+
+            if (is_null($status)) {
+                throw new \Exception(__('Parameter "Status" missing in request data.'));
+            }
+
+            if (is_null($statusFieldName)) {
+                throw new \Exception(__('Status Field Name is not specified.'));
+            }
+
+            foreach($ids as $id) {
+                $this->_objectManager->create($this->_modelClass)
+                    ->load($id)
+                    ->setData($this->_statusField, $status)
+                    ->save();
+            }
+
+        } catch (\Magento\Framework\Exception\LocalizedException $e) {
+            $error = true;
+            $this->messageManager->addError($e->getMessage());
+        } catch (\Exception $e) {
+            $error = true;
+            $this->messageManager->addException(
+                $e,
+                __("We can't change status of %1 right now. %2",
+                    strtolower($model->getOwnTitle()),
+                    $e->getMessage()
+                )
+            );
+        }
+
+        if (!$error) {
+            $this->messageManager->addSuccess(
+                __('%1 status have been changed.', $model->getOwnTitle(count($ids) > 1))
+            );
+        }
+
+        $this->_redirect('*/*');
+
+    }
+
+    /**
+     * Go to config section action
+     * @return void
+     */
+    protected function _configAction()
+    {
+        $this->_redirect('admin/system_config/edit', ['section' => $this->_configSection()]);
+    }
+
+    /**
+     * Set form data
+     * @return $this
+     */
+    protected function _setFormData($data = null)
+    {
+        $this->_getSession()->setData($this->_formSessionKey,
+            is_null($data) ? $this->getRequest()->getParams() : $data);
+
+        return $this;
+    }
+
+    /**
+     * Get core registry
+     * @return void
+     */
+    protected function _getRegistry()
+    {
+        if (is_null($this->_coreRegistry)) {
+            $this->_coreRegistry = $this->_objectManager->get('\Magento\Framework\Registry');
+        }
+        return $this->_coreRegistry;
+    }
+
+    /**
+     * Check is allowed access
+     *
+     * @return bool
+     */
+    protected function _isAllowed()
+    {
+        return $this->_authorization->isAllowed($this->_allowedKey);
+    }
+
+    /**
+     * Retrieve model object
+     * @return \Magento\Framework\Model\AbstractModel
+     */
+    protected function _getModel($load = true)
+    {
+        if (is_null($this->_model)) {
+            $this->_model = $this->_objectManager->create($this->_modelClass);
+
+            $id = (int)$this->getRequest()->getParam($this->_idKey);
+            if ($id && $load) {
+                $this->_model->load($id);
+            }
+        }
+        return $this->_model;
+    }
+
+}

+ 45 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml;
+
+/**
+ * Admin blog category edit controller
+ */
+class Category extends Actions
+{
+	/**
+	 * Form session key
+	 * @var string
+	 */
+    protected $_formSessionKey  = 'blog_category_form_data';
+
+    /**
+     * Allowed Key
+     * @var string
+     */
+    protected $_allowedKey      = 'Magefan_Blog::category';
+
+    /**
+     * Model class name
+     * @var string
+     */
+    protected $_modelClass      = 'Magefan\Blog\Model\Category';
+
+    /**
+     * Active menu key
+     * @var string
+     */
+    protected $_activeMenu      = 'Magefan_Blog::category';
+
+    /**
+     * Status field name
+     * @var string
+     */
+    protected $_statusField     = 'is_active';
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/Delete.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category delete controller
+ */
+class Delete extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/Duplicate.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category duplicate controller
+ */
+class Duplicate extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/Edit.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category edit controller
+ */
+class Edit extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/Grid.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category grid controller
+ */
+class Grid extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/Index.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category list controller
+ */
+class Index extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/MassStatus.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category change status controller
+ */
+class MassStatus extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/NewAction.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category create new controller
+ */
+class NewAction extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+
+}

+ 59 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Category/Save.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Category;
+
+/**
+ * Blog category save controller
+ */
+class Save extends \Magefan\Blog\Controller\Adminhtml\Category
+{
+    /**
+     * Before model save
+     * @param  \Magefan\Blog\Model\Category $model
+     * @param  \Magento\Framework\App\Request\Http $request
+     * @return void
+     */
+    protected function _beforeSave($model, $request)
+    {
+        /* Prepare dates */
+        $dateFilter = $this->_objectManager->create('Magento\Framework\Stdlib\DateTime\Filter\Date');
+        $data = $model->getData();
+
+        $filterRules = [];
+        foreach (['custom_theme_from', 'custom_theme_to'] as $dateField) {
+            if (!empty($data[$dateField])) {
+                $filterRules[$dateField] = $dateFilter;
+            }
+        }
+
+        $inputFilter = new \Zend_Filter_Input(
+            $filterRules,
+            [],
+            $data
+        );
+        $data = $inputFilter->getUnescaped();
+        $model->setData($data);
+    }
+     
+    /**
+     * After model save
+     * @param  \Magefan\Blog\Model\Category $model
+     * @param  \Magento\Framework\App\Request\Http $request
+     * @return void
+     */
+    protected function _afterSave($model, $request)
+    {
+        $model->addData(
+            [
+                'parent_id' => $model->getParentId(),
+                'level' => $model->getLevel(),
+            ]
+        );
+    }
+}

+ 34 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Import/Aw.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Import;
+
+/**
+ * Blog aw import controller
+ */
+class Aw extends \Magento\Backend\App\Action
+{
+    /**
+     * Prepare aw import
+     * @return \Magento\Framework\Controller\ResultInterface
+     */
+    public function execute()
+    {
+        $this->_redirect('*/*/');
+    }
+
+    /**
+     * Check is allowed access
+     *
+     * @return bool
+     */
+    protected function _isAllowed()
+    {
+        return $this->_authorization->isAllowed('Magefan_Blog::import');
+    }
+}

+ 39 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Import/Index.php

@@ -0,0 +1,39 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Import;
+
+/**
+ * Blog available imports list controller
+ */
+class Index extends \Magento\Backend\App\Action
+{
+	/**
+     * Start available import execute
+     * @return \Magento\Framework\Controller\ResultInterface
+     */
+    public function execute()
+    {
+        $this->_view->loadLayout();
+        $this->_setActiveMenu('Magefan_Blog::import');
+        $title = __('Blog Import');
+        $this->_view->getPage()->getConfig()->getTitle()->prepend($title);
+        $this->_addBreadcrumb($title, $title);
+        $this->_view->renderLayout();
+    }
+
+    /**
+     * Check is allowed access
+     *
+     * @return bool
+     */
+    protected function _isAllowed()
+    {
+        return $this->_authorization->isAllowed('Magefan_Blog::import');
+    }
+}

+ 83 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Import/Run.php

@@ -0,0 +1,83 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Import;
+
+/**
+ * Run import controller
+ */
+class Run extends \Magento\Backend\App\Action
+{
+	/**
+     * Run import
+     * @return \Magento\Framework\Controller\ResultInterface
+     */
+    public function execute()
+    {
+        set_time_limit(0);
+
+        $data = $this->getRequest()->getPost();
+        $type = '';
+        try {
+            if (empty($data['type'])) {
+                throw new \Exception(__('Blog import type is not specified.'), 1);
+            }
+
+            $_type = ucfirst($data['type']);
+            $import = $this->_objectManager->create('\Magefan\Blog\Model\Import\\'.$_type);
+            $type = $data['type'];
+            $import->prepareData($data)->execute();
+
+            $stats = $import->getImportStatistic();
+
+            if ($stats->getData('imported_count')) {
+                if (!$stats->getData('skipped_count')) {
+                    $this->messageManager->addSuccess(__(
+                        'The import process was completed successfully. %1 posts and %2 categories where imported.',
+                        $stats->getData('imported_posts_count'),
+                        $stats->getData('imported_categories_count')
+                    ));
+                } else {
+                    $this->messageManager->addNotice(__(
+                        'The import process completed. %1 posts and %2 categories and %3 tags where imported. Some posts or categories or tags where skipped.<br/> %3 %4',
+                        $stats->getData('imported_posts_count'),
+                        $stats->getData('imported_categories_count'),
+                        $stats->getData('imported_tags_count'),
+                        $stats->getData('skipped_posts') ? __('Skipped Posts') . ': '. implode(', ', $stats->getData('skipped_posts')) . '.<br/>' : '',
+                        $stats->getData('skipped_posts') ? __('Skipped Categories') . ': '. implode(', ', $stats->getData('skipped_categories')) . '. ' : '',
+                        $stats->getData('skipped_posts') ? __('Skipped Tags') . ': '. implode(', ', $stats->getData('skipped_tags')) . '. ' : ''
+                    ));
+                }
+            } else {
+                if (!$stats->getData('skipped_count')) {
+                    $this->messageManager->addNotice(__('Nothing to import.'));
+                } else {
+                    throw new \Exception(__('Can not make import.'), 1);
+                }
+            }
+
+            $this->_getSession()->setData('import_'.$type.'_form_data', null);
+            $this->_redirect('*/*/');
+
+        } catch (\Exception $e) {
+            $this->messageManager->addException($e, __('Something went wrong: ').' '.$e->getMessage());
+            $this->_getSession()->setData('import_'.$type.'_form_data', $data);
+            $this->_redirect('*/*/'.$type);
+        }
+    }
+
+    /**
+     * Check is allowed access
+     *
+     * @return bool
+     */
+    protected function _isAllowed()
+    {
+        return $this->_authorization->isAllowed('Magefan_Blog::import');
+    }
+}

+ 46 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Import/Wordpress.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright © 2016 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Import;
+
+/**
+ * Blog prepare wordpress import controller
+ */
+class Wordpress extends \Magento\Backend\App\Action
+{
+	/**
+     * Prepare wordpress import
+     * @return \Magento\Framework\Controller\ResultInterface
+     */
+    public function execute()
+    {
+        $this->_view->loadLayout();
+        $this->_setActiveMenu('Magefan_Blog::import');
+        $title = __('Blog Import from WordPress (beta)');
+        $this->_view->getPage()->getConfig()->getTitle()->prepend($title);
+        $this->_addBreadcrumb($title, $title);
+
+        $config = new \Magento\Framework\DataObject(
+            (array)$this->_getSession()->getData('import_wordpress_form_data', true) ?: []
+        );
+
+        $this->_objectManager->get('\Magento\Framework\Registry')->register('import_config', $config);
+
+        $this->_view->renderLayout();
+    }
+
+    /**
+     * Check is allowed access
+     *
+     * @return bool
+     */
+    protected function _isAllowed()
+    {
+        return $this->_authorization->isAllowed('Magefan_Blog::import');
+    }
+}

+ 46 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post.php

@@ -0,0 +1,46 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml;
+
+/**
+ * Admin blog post edit controller
+ */
+class Post extends Actions
+{
+	/**
+	 * Form session key
+	 * @var string
+	 */
+    protected $_formSessionKey  = 'blog_post_form_data';
+
+    /**
+     * Allowed Key
+     * @var string
+     */
+    protected $_allowedKey      = 'Magefan_Blog::post';
+
+    /**
+     * Model class name
+     * @var string
+     */
+    protected $_modelClass      = 'Magefan\Blog\Model\Post';
+
+    /**
+     * Active menu key
+     * @var string
+     */
+    protected $_activeMenu      = 'Magefan_Blog::post';
+
+    /**
+     * Status field name
+     * @var string
+     */
+    protected $_statusField     = 'is_active';
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/Delete.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post delete controller
+ */
+class Delete extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/Duplicate.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post duplicate controller
+ */
+class Duplicate extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/Edit.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post edit controller
+ */
+class Edit extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/Grid.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post grid controller
+ */
+class Grid extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/Index.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post list controller
+ */
+class Index extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/MassStatus.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post change status controller
+ */
+class MassStatus extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/NewAction.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post create new controller
+ */
+class NewAction extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+
+}

+ 40 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/Preview.php

@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright © 2015-2017 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post preview controller
+ */
+class Preview extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+    public function execute()
+    {
+    	try {
+            $post = $this->_getModel();
+            if (!$post->getId()) {
+                throw new \Exception("Item is not longer exist.", 1);
+            }
+
+            $previewUrl = $this->_objectManager->get('\Magefan\Blog\Model\PreviewUrl');
+            $redirectUrl = $previewUrl->getUrl(
+                $post,
+                $previewUrl::CONTROLLER_POST
+            );
+
+            $this->getResponse()->setRedirect($redirectUrl);
+
+        } catch (\Exception $e) {
+            $this->messageManager->addException(
+                $e,
+                __('Something went wrong %1', $e->getMessage())
+            );
+            $this->_redirect('*/*/edit', [$this->_idKey => $post->getId()]);
+        }
+    }
+}

+ 33 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/RelatedPosts.php

@@ -0,0 +1,33 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post related posts controller
+ */
+class RelatedPosts extends \Magefan\Blog\Controller\Adminhtml\Post
+{
+    /**
+     * View related posts action
+     *
+     * @return \Magento\Framework\Controller\ResultInterface
+     */
+    public function execute()
+    {
+        $model = $this->_getModel();
+        $this->_getRegistry()->register('current_model', $model);
+
+        $this->_view->loadLayout()
+            ->getLayout()
+            ->getBlock('blog.post.edit.tab.relatedposts')
+            ->setPostsRelated($this->getRequest()->getPost('posts_related', null));
+
+        $this->_view->renderLayout();
+    }
+}

+ 17 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/RelatedPostsGrid.php

@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright © 2015 Ihor Vansach (ihor@magefan.com). All rights reserved.
+ * See LICENSE.txt for license details (http://opensource.org/licenses/osl-3.0.php).
+ *
+ * Glory to Ukraine! Glory to the heroes!
+ */
+
+namespace Magefan\Blog\Controller\Adminhtml\Post;
+
+/**
+ * Blog post related posts grid controller
+ */
+class RelatedPostsGrid extends RelatedPosts
+{
+
+}

+ 0 - 0
app/code/Magefan/Blog/Controller/Adminhtml/Post/RelatedProducts.php


Някои файлове не бяха показани, защото твърде много файлове са промени