├── Api ├── AuthorRepositoryInterface.php └── Data │ ├── AuthorInterface.php │ └── AuthorSearchResultsInterface.php ├── Block ├── Adminhtml │ └── Author │ │ └── Edit │ │ └── Buttons │ │ ├── Back.php │ │ ├── Delete.php │ │ ├── Generic.php │ │ ├── Reset.php │ │ ├── Save.php │ │ └── SaveAndContinue.php ├── Author │ ├── ListAuthor.php │ ├── ListAuthor │ │ ├── Rss.php │ │ └── Rss │ │ │ └── Link.php │ └── ViewAuthor.php ├── Image.php └── ImageBuilder.php ├── Controller ├── Adminhtml │ ├── Author.php │ └── Author │ │ ├── Delete.php │ │ ├── Edit.php │ │ ├── File │ │ └── Upload.php │ │ ├── Image │ │ └── Upload.php │ │ ├── Index.php │ │ ├── InlineEdit.php │ │ ├── MassAction.php │ │ ├── MassDelete.php │ │ ├── MassDisable.php │ │ ├── MassEnable.php │ │ ├── NewAction.php │ │ ├── Save.php │ │ └── Upload.php ├── Author │ ├── Index.php │ ├── Rss.php │ └── View.php ├── RegistryConstants.php └── Router.php ├── Helper └── Image.php ├── LICENSE ├── Model ├── Author.php ├── Author │ ├── DataProvider.php │ ├── Rss.php │ └── Url.php ├── AuthorFactory.php ├── AuthorRepository.php ├── FactoryInterface.php ├── Image.php ├── Output.php ├── ResourceModel │ ├── Author.php │ └── Author │ │ ├── Collection.php │ │ └── Grid │ │ ├── Collection.php │ │ └── ServiceCollection.php ├── Routing │ ├── Entity.php │ └── RoutableInterface.php ├── Source │ ├── AbstractSource.php │ ├── Country.php │ └── Options.php ├── Uploader.php └── UploaderPool.php ├── Plugin └── Block │ └── Topmenu.php ├── README.md ├── Setup ├── InstallSchema.php └── Uninstall.php ├── Test └── Unit │ ├── Model │ ├── Author │ │ ├── DataProviderTest.php │ │ ├── RssTest.php │ │ └── UrlTest.php │ ├── AuthorTest.php │ ├── Source │ │ └── CountryTest.php │ ├── UploaderPoolTest.php │ └── UploaderTest.php │ └── Ui │ ├── Component │ └── Listing │ │ └── Column │ │ ├── AuthorActionsTest.php │ │ ├── AvatarTest.php │ │ └── Store │ │ └── OptionsTest.php │ └── DataProvider │ └── Author │ └── Form │ └── Modifier │ └── AuthorDataTest.php ├── Ui ├── Component │ └── Listing │ │ └── Column │ │ ├── AuthorActions.php │ │ ├── Avatar.php │ │ └── Store │ │ └── Options.php └── DataProvider │ └── Author │ └── Form │ └── Modifier │ └── AuthorData.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── di.xml │ ├── menu.xml │ ├── routes.xml │ └── system.xml ├── config.xml ├── di.xml ├── frontend │ ├── di.xml │ └── routes.xml └── module.xml ├── i18l └── en_US.csv ├── registration.php └── view ├── adminhtml ├── layout │ ├── sample_news_author_edit.xml │ └── sample_news_author_index.xml ├── ui_component │ ├── sample_news_author_form.xml │ └── sample_news_author_listing.xml └── web │ └── template │ ├── file-preview.html │ └── image-preview.html ├── base └── web │ └── images │ └── author │ └── placeholder │ └── avatar.jpg └── frontend ├── layout ├── sample_news_author_index.xml └── sample_news_author_view.xml └── templates ├── author ├── list.phtml └── view.phtml ├── image.phtml ├── image_with_borders.phtml └── rss └── link.phtml /Api/AuthorRepositoryInterface.php: -------------------------------------------------------------------------------- 1 | __('Back'), 33 | 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), 34 | 'class' => 'back', 35 | 'sort_order' => 10 36 | ]; 37 | } 38 | 39 | /** 40 | * Get URL for back (reset) button 41 | * 42 | * @return string 43 | */ 44 | public function getBackUrl() 45 | { 46 | return $this->getUrl('*/*/'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Block/Adminhtml/Author/Edit/Buttons/Delete.php: -------------------------------------------------------------------------------- 1 | getAuthorId()) { 33 | $data = [ 34 | 'label' => __('Delete Author'), 35 | 'class' => 'delete', 36 | 'on_click' => 'deleteConfirm(\'' . __( 37 | 'Are you sure you want to do this?' 38 | ) . '\', \'' . $this->getDeleteUrl() . '\')', 39 | 'sort_order' => 20, 40 | ]; 41 | } 42 | return $data; 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function getDeleteUrl() 49 | { 50 | return $this->getUrl('*/*/delete', ['author_id' => $this->getAuthorId()]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Block/Adminhtml/Author/Edit/Buttons/Generic.php: -------------------------------------------------------------------------------- 1 | context = $context; 45 | $this->authorRepository = $authorRepository; 46 | } 47 | 48 | /** 49 | * Return Author page ID 50 | * 51 | * @return int|null 52 | */ 53 | public function getAuthorId() 54 | { 55 | try { 56 | return $this->authorRepository->getById( 57 | $this->context->getRequest()->getParam('author_id') 58 | )->getId(); 59 | } catch (NoSuchEntityException $e) { 60 | return null; 61 | } 62 | } 63 | 64 | /** 65 | * Generate url by route and parameters 66 | * 67 | * @param string $route 68 | * @param array $params 69 | * @return string 70 | */ 71 | public function getUrl($route = '', $params = []) 72 | { 73 | return $this->context->getUrlBuilder()->getUrl($route, $params); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Block/Adminhtml/Author/Edit/Buttons/Reset.php: -------------------------------------------------------------------------------- 1 | __('Reset'), 33 | 'class' => 'reset', 34 | 'on_click' => 'location.reload();', 35 | 'sort_order' => 30 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Block/Adminhtml/Author/Edit/Buttons/Save.php: -------------------------------------------------------------------------------- 1 | __('Save Author'), 33 | 'class' => 'save primary', 34 | 'data_attribute' => [ 35 | 'mage-init' => ['button' => ['event' => 'save']], 36 | 'form-role' => 'save', 37 | ], 38 | 'sort_order' => 90, 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Block/Adminhtml/Author/Edit/Buttons/SaveAndContinue.php: -------------------------------------------------------------------------------- 1 | __('Save and Continue Edit'), 34 | 'class' => 'save', 35 | 'data_attribute' => [ 36 | 'mage-init' => [ 37 | 'button' => ['event' => 'saveAndContinueEdit'], 38 | ], 39 | ], 40 | 'sort_order' => 80, 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Block/Author/ListAuthor.php: -------------------------------------------------------------------------------- 1 | authorCollectionFactory = $authorCollectionFactory; 56 | $this->urlFactory = $urlFactory; 57 | parent::__construct($context, $data); 58 | } 59 | 60 | /** 61 | * @return \Sample\News\Model\ResourceModel\Author\Collection 62 | */ 63 | public function getAuthors() 64 | { 65 | if (is_null($this->authors)) { 66 | $this->authors = $this->authorCollectionFactory->create() 67 | ->addFieldToSelect('*') 68 | ->addFieldToFilter('is_active', Author::STATUS_ENABLED) 69 | ->addStoreFilter($this->_storeManager->getStore()->getId()) 70 | ->setOrder('name', 'ASC'); 71 | } 72 | return $this->authors; 73 | } 74 | 75 | /** 76 | * @return $this 77 | */ 78 | protected function _prepareLayout() 79 | { 80 | parent::_prepareLayout(); 81 | /** @var \Magento\Theme\Block\Html\Pager $pager */ 82 | $pager = $this->getLayout()->createBlock(Pager::class, 'sample_news.author.list.pager'); 83 | $pager->setCollection($this->getAuthors()); 84 | $this->setChild('pager', $pager); 85 | $this->getAuthors()->load(); 86 | return $this; 87 | } 88 | 89 | /** 90 | * @return string 91 | */ 92 | public function getPagerHtml() 93 | { 94 | return $this->getChildHtml('pager'); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Block/Author/ListAuthor/Rss.php: -------------------------------------------------------------------------------- 1 | rssModel = $rssModel; 74 | $this->urlModel = $urlModel; 75 | $this->authorCollectionFactory = $authorCollectionFactory; 76 | $this->storeManager = $storeManager; 77 | parent::__construct($context, $data); 78 | } 79 | 80 | /** 81 | * @return int 82 | */ 83 | protected function getStoreId() 84 | { 85 | $storeId = (int)$this->getRequest()->getParam('store_id'); 86 | if ($storeId == null) { 87 | $storeId = $this->storeManager->getStore()->getId(); 88 | } 89 | return $storeId; 90 | } 91 | 92 | /** 93 | * @return array 94 | */ 95 | public function getRssData() 96 | { 97 | $url = $this->urlModel->getListUrl(); 98 | $data = [ 99 | 'title' => __('Authors'), 100 | 'description' => __('Authors'), 101 | 'link' => $url, 102 | 'charset' => 'UTF-8' 103 | ]; 104 | $collection = $this->authorCollectionFactory->create(); 105 | $collection->addStoreFilter($this->getStoreId()); 106 | $collection->addFieldToFilter('is_active', Author::STATUS_ENABLED); 107 | $collection->addFieldToFilter('in_rss', 1); 108 | foreach ($collection as $item) { 109 | /** @var \Sample\News\Model\Author $item */ 110 | $description = '
%s
'; 111 | $description = sprintf($description, $item->getAuthorUrl(), $item->getName()); 112 | $data['entries'][] = [ 113 | 'title' => $item->getName(), 114 | 'link' => $item->getAuthorUrl(), 115 | 'description' => $description, 116 | ]; 117 | } 118 | return $data; 119 | } 120 | 121 | /** 122 | * Check if RSS feed allowed 123 | * 124 | * @return mixed 125 | */ 126 | public function isAllowed() 127 | { 128 | return $this->rssModel->isRssEnabled(); 129 | } 130 | 131 | /** 132 | * Get information about all feeds this Data Provider is responsible for 133 | * 134 | * @return array 135 | */ 136 | public function getFeeds() 137 | { 138 | $feeds = []; 139 | $feeds[] = [ 140 | 'label' => __('Authors'), 141 | 'link' => $this->rssModel->getRssLink(), 142 | ]; 143 | $result = ['group' => __('News'), 'feeds' => $feeds]; 144 | return $result; 145 | } 146 | 147 | /** 148 | * @return bool 149 | */ 150 | public function isAuthRequired() 151 | { 152 | return false; 153 | } 154 | 155 | /** 156 | * @return int 157 | */ 158 | public function getCacheLifetime() 159 | { 160 | $lifetime = $this->_scopeConfig->getValue( 161 | self::CACHE_LIFETIME_CONFIG_PATH, 162 | ScopeInterface::SCOPE_STORE 163 | ); 164 | return $lifetime ?: null; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Block/Author/ListAuthor/Rss/Link.php: -------------------------------------------------------------------------------- 1 | rssModel = $rssModel; 42 | parent::__construct($context, $data); 43 | } 44 | 45 | /** 46 | * @return string 47 | */ 48 | public function isRssEnabled() 49 | { 50 | return $this->rssModel->isRssEnabled(); 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getLabel() 57 | { 58 | return __('Subscribe to RSS Feed'); 59 | } 60 | /** 61 | * @return string 62 | */ 63 | public function getLink() 64 | { 65 | return $this->rssModel->getRssLink(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Block/Author/ViewAuthor.php: -------------------------------------------------------------------------------- 1 | coreRegistry = $registry; 50 | $this->imageBuilder = $imageBuilder; 51 | parent::__construct($context, $data); 52 | } 53 | 54 | /** 55 | * get current author 56 | * 57 | * @return \Sample\News\Model\Author 58 | */ 59 | public function getCurrentAuthor() 60 | { 61 | return $this->coreRegistry->registry('current_author'); 62 | } 63 | 64 | /** 65 | * @param $entity 66 | * @param $imageId 67 | * @param array $attributes 68 | * @return \Sample\News\Block\Image 69 | */ 70 | public function getImage($entity, $imageId, $attributes = []) 71 | { 72 | return $this->imageBuilder->setEntity($entity) 73 | ->setImageId($imageId) 74 | ->setAttributes($attributes) 75 | ->create(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Block/Image.php: -------------------------------------------------------------------------------- 1 | setTemplate($data['template']); 54 | unset($data['template']); 55 | } 56 | parent::__construct($context, $data); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Block/ImageBuilder.php: -------------------------------------------------------------------------------- 1 | helperFactory = $helperFactory; 67 | $this->imageFactory = $imageFactory; 68 | $this->entityCode = $entityCode; 69 | } 70 | 71 | /** 72 | * @param \Magento\Framework\Model\AbstractModel $entity 73 | * @return $this 74 | */ 75 | public function setEntity(AbstractModel $entity) 76 | { 77 | $this->entity = $entity; 78 | return $this; 79 | } 80 | 81 | /** 82 | * Set image ID 83 | * 84 | * @param string $imageId 85 | * @return $this 86 | */ 87 | public function setImageId($imageId) 88 | { 89 | $this->imageId = $imageId; 90 | return $this; 91 | } 92 | 93 | /** 94 | * Set custom attributes 95 | * 96 | * @param array $attributes 97 | * @return $this 98 | */ 99 | public function setAttributes(array $attributes) 100 | { 101 | if ($attributes) { 102 | $this->attributes = $attributes; 103 | } 104 | return $this; 105 | } 106 | 107 | /** 108 | * Retrieve image custom attributes for HTML element 109 | * 110 | * @return string 111 | */ 112 | protected function getCustomAttributes() 113 | { 114 | $result = []; 115 | foreach ($this->attributes as $name => $value) { 116 | $result[] = $name . '="' . $value . '"'; 117 | } 118 | return !empty($result) ? implode(' ', $result) : ''; 119 | } 120 | 121 | /** 122 | * Calculate image ratio 123 | * 124 | * @param ImageHelper $helper 125 | * @return float|int 126 | */ 127 | protected function getRatio(ImageHelper $helper) 128 | { 129 | $width = $helper->getWidth(); 130 | $height = $helper->getHeight(); 131 | if ($width && $height) { 132 | return $height / $width; 133 | } 134 | return 1; 135 | } 136 | 137 | /** 138 | * Create image block 139 | * 140 | * @return \Sample\News\Block\Image 141 | */ 142 | public function create() 143 | { 144 | /** @var ImageHelper $helper */ 145 | $helper = $this->helperFactory 146 | ->create([ 147 | 'entityCode' => $this->entityCode 148 | ]) 149 | ->init( 150 | $this->entity, 151 | $this->imageId, 152 | $this->attributes 153 | ); 154 | 155 | $template = $helper->getFrame() 156 | ? 'Sample_News::image.phtml' 157 | : 'Sample_News::image_with_borders.phtml'; 158 | 159 | $imagesize = $helper->getResizedImageInfo(); 160 | 161 | $data = [ 162 | 'data' => [ 163 | 'template' => $template, 164 | 'image_url' => $helper->getUrl(), 165 | 'width' => $helper->getWidth(), 166 | 'height' => $helper->getHeight(), 167 | 'label' => $helper->getLabel(), 168 | 'ratio' => $this->getRatio($helper), 169 | 'custom_attributes' => $this->getCustomAttributes(), 170 | 'resized_image_width' => !empty($imagesize[0]) ? $imagesize[0] : $helper->getWidth(), 171 | 'resized_image_height' => !empty($imagesize[1]) ? $imagesize[1] : $helper->getHeight(), 172 | ], 173 | ]; 174 | 175 | return $this->imageFactory->create($data); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author.php: -------------------------------------------------------------------------------- 1 | coreRegistry = $registry; 75 | $this->authorRepository = $authorRepository; 76 | $this->resultPageFactory = $resultPageFactory; 77 | $this->dateFilter = $dateFilter; 78 | parent::__construct($context); 79 | } 80 | 81 | /** 82 | * filter dates 83 | * 84 | * @param array $data 85 | * @return array 86 | */ 87 | public function filterData($data) 88 | { 89 | $inputFilter = new \Zend_Filter_Input( 90 | ['dob' => $this->dateFilter], 91 | [], 92 | $data 93 | ); 94 | $data = $inputFilter->getUnescaped(); 95 | if (isset($data['awards'])) { 96 | if (is_array($data['awards'])) { 97 | $data['awards'] = implode(',', $data['awards']); 98 | } 99 | } 100 | return $data; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/Delete.php: -------------------------------------------------------------------------------- 1 | resultRedirectFactory->create(); 32 | $id = $this->getRequest()->getParam('author_id'); 33 | if ($id) { 34 | try { 35 | $this->authorRepository->deleteById($id); 36 | $this->messageManager->addSuccessMessage(__('The author has been deleted.')); 37 | $resultRedirect->setPath('sample_news/*/'); 38 | return $resultRedirect; 39 | } catch (NoSuchEntityException $e) { 40 | $this->messageManager->addErrorMessage(__('The author no longer exists.')); 41 | return $resultRedirect->setPath('sample_news/*/'); 42 | } catch (LocalizedException $e) { 43 | $this->messageManager->addErrorMessage($e->getMessage()); 44 | return $resultRedirect->setPath('sample_news/author/edit', ['author_id' => $id]); 45 | } catch (\Exception $e) { 46 | $this->messageManager->addErrorMessage(__('There was a problem deleting the author')); 47 | return $resultRedirect->setPath('sample_news/author/edit', ['author_id' => $id]); 48 | } 49 | } 50 | $this->messageManager->addErrorMessage(__('We can\'t find a author to delete.')); 51 | $resultRedirect->setPath('sample_news/*/'); 52 | return $resultRedirect; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/Edit.php: -------------------------------------------------------------------------------- 1 | getRequest()->getParam('author_id'); 33 | $this->coreRegistry->register(RegistryConstants::CURRENT_AUTHOR_ID, $authorId); 34 | 35 | return $authorId; 36 | } 37 | 38 | /** 39 | * Edit or create author 40 | * 41 | * @return \Magento\Backend\Model\View\Result\Page 42 | */ 43 | public function execute() 44 | { 45 | $authorId = $this->_initAuthor(); 46 | 47 | /** @var \Magento\Backend\Model\View\Result\Page $resultPage */ 48 | $resultPage = $this->resultPageFactory->create(); 49 | $resultPage->setActiveMenu('Sample_News::author'); 50 | $resultPage->getConfig()->getTitle()->prepend(__('Authors')); 51 | $resultPage->addBreadcrumb(__('News'), __('News')); 52 | $resultPage->addBreadcrumb(__('Authors'), __('Authors'), $this->getUrl('sample_news/author')); 53 | 54 | if ($authorId === null) { 55 | $resultPage->addBreadcrumb(__('New Author'), __('New Author')); 56 | $resultPage->getConfig()->getTitle()->prepend(__('New Author')); 57 | } else { 58 | $resultPage->addBreadcrumb(__('Edit Author'), __('Edit Author')); 59 | $resultPage->getConfig()->getTitle()->prepend( 60 | $this->authorRepository->getById($authorId)->getName() 61 | ); 62 | } 63 | return $resultPage; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/File/Upload.php: -------------------------------------------------------------------------------- 1 | resultPageFactory->create(); 33 | $resultPage->setActiveMenu('Sample_News::author'); 34 | $resultPage->getConfig()->getTitle()->prepend(__('Authors')); 35 | $resultPage->addBreadcrumb(__('News'), __('News')); 36 | $resultPage->addBreadcrumb(__('Authors'), __('Authors')); 37 | return $resultPage; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/InlineEdit.php: -------------------------------------------------------------------------------- 1 | dataObjectProcessor = $dataObjectProcessor; 79 | $this->dataObjectHelper = $dataObjectHelper; 80 | $this->jsonFactory = $jsonFactory; 81 | $this->authorResourceModel = $authorResourceModel; 82 | parent::__construct($registry, $authorRepository, $resultPageFactory, $dateFilter, $context); 83 | } 84 | 85 | /** 86 | * @return \Magento\Framework\Controller\ResultInterface 87 | */ 88 | public function execute() 89 | { 90 | /** @var \Magento\Framework\Controller\Result\Json $resultJson */ 91 | $resultJson = $this->jsonFactory->create(); 92 | $error = false; 93 | $messages = []; 94 | 95 | $postItems = $this->getRequest()->getParam('items', []); 96 | if (!($this->getRequest()->getParam('isAjax') && count($postItems))) { 97 | return $resultJson->setData([ 98 | 'messages' => [__('Please correct the data sent.')], 99 | 'error' => true, 100 | ]); 101 | } 102 | 103 | foreach (array_keys($postItems) as $authorId) { 104 | /** @var \Sample\News\Model\Author|AuthorInterface $author */ 105 | $author = $this->authorRepository->getById((int)$authorId); 106 | try { 107 | $authorData = $this->filterData($postItems[$authorId]); 108 | $this->dataObjectHelper->populateWithArray($author, $authorData , AuthorInterface::class); 109 | $this->authorResourceModel->saveAttribute($author, array_keys($authorData)); 110 | } catch (LocalizedException $e) { 111 | $messages[] = $this->getErrorWithAuthorId($author, $e->getMessage()); 112 | $error = true; 113 | } catch (\RuntimeException $e) { 114 | $messages[] = $this->getErrorWithAuthorId($author, $e->getMessage()); 115 | $error = true; 116 | } catch (\Exception $e) { 117 | $messages[] = $this->getErrorWithAuthorId( 118 | $author, 119 | __('Something went wrong while saving the author.') 120 | ); 121 | $error = true; 122 | } 123 | } 124 | 125 | return $resultJson->setData([ 126 | 'messages' => $messages, 127 | 'error' => $error 128 | ]); 129 | } 130 | 131 | /** 132 | * Add author id to error message 133 | * 134 | * @param Author $author 135 | * @param string $errorText 136 | * @return string 137 | */ 138 | protected function getErrorWithAuthorId(Author $author, $errorText) 139 | { 140 | return '[Author ID: ' . $author->getId() . '] ' . $errorText; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/MassAction.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 74 | $this->collectionFactory = $collectionFactory; 75 | $this->successMessage = $successMessage; 76 | $this->errorMessage = $errorMessage; 77 | parent::__construct($registry, $authorRepository, $resultPageFactory, $dateFilter, $context); 78 | } 79 | 80 | /** 81 | * @param AuthorModel $author 82 | * @return mixed 83 | */ 84 | protected abstract function massAction(AuthorModel $author); 85 | 86 | /** 87 | * execute action 88 | * 89 | * @return \Magento\Backend\Model\View\Result\Redirect 90 | */ 91 | public function execute() 92 | { 93 | try { 94 | $collection = $this->filter->getCollection($this->collectionFactory->create()); 95 | $collectionSize = $collection->getSize(); 96 | foreach ($collection as $author) { 97 | $this->massAction($author); 98 | } 99 | $this->messageManager->addSuccessMessage(__($this->successMessage, $collectionSize)); 100 | } catch (LocalizedException $e) { 101 | $this->messageManager->addErrorMessage($e->getMessage()); 102 | } catch (\Exception $e) { 103 | $this->messageManager->addExceptionMessage($e, __($this->errorMessage)); 104 | } 105 | $redirectResult = $this->resultRedirectFactory->create(); 106 | $redirectResult->setPath('sample_news/*/index'); 107 | return $redirectResult; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/MassDelete.php: -------------------------------------------------------------------------------- 1 | authorRepository->delete($author); 31 | return $this; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/MassDisable.php: -------------------------------------------------------------------------------- 1 | setIsActive($this->isActive); 36 | $this->authorRepository->save($author); 37 | return $this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/MassEnable.php: -------------------------------------------------------------------------------- 1 | resultForwardFactory = $resultForwardFactory; 46 | parent::__construct($context); 47 | } 48 | 49 | 50 | /** 51 | * forward to edit 52 | * 53 | * @return \Magento\Backend\Model\View\Result\Forward 54 | */ 55 | public function execute() 56 | { 57 | $resultForward = $this->resultForwardFactory->create(); 58 | $resultForward->forward('edit'); 59 | return $resultForward; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/Save.php: -------------------------------------------------------------------------------- 1 | authorFactory = $authorFactory; 76 | $this->dataObjectProcessor = $dataObjectProcessor; 77 | $this->dataObjectHelper = $dataObjectHelper; 78 | $this->uploaderPool = $uploaderPool; 79 | parent::__construct($registry, $authorRepository, $resultPageFactory, $dateFilter, $context); 80 | } 81 | 82 | /** 83 | * run the action 84 | * 85 | * @return \Magento\Backend\Model\View\Result\Redirect 86 | */ 87 | public function execute() 88 | { 89 | /** @var \Sample\News\Api\Data\AuthorInterface $author */ 90 | $author = null; 91 | $data = $this->getRequest()->getPostValue(); 92 | $id = !empty($data['author_id']) ? $data['author_id'] : null; 93 | $resultRedirect = $this->resultRedirectFactory->create(); 94 | try { 95 | if ($id) { 96 | $author = $this->authorRepository->getById((int)$id); 97 | } else { 98 | unset($data['author_id']); 99 | $author = $this->authorFactory->create(); 100 | } 101 | $avatar = $this->getUploader('image')->uploadFileAndGetName('avatar', $data); 102 | $data['avatar'] = $avatar; 103 | $resume = $this->getUploader('file')->uploadFileAndGetName('resume', $data); 104 | $data['resume'] = $resume; 105 | $this->dataObjectHelper->populateWithArray($author, $data, AuthorInterface::class); 106 | $this->authorRepository->save($author); 107 | $this->messageManager->addSuccessMessage(__('You saved the author')); 108 | if ($this->getRequest()->getParam('back')) { 109 | $resultRedirect->setPath('sample_news/author/edit', ['author_id' => $author->getId()]); 110 | } else { 111 | $resultRedirect->setPath('sample_news/author'); 112 | } 113 | } catch (LocalizedException $e) { 114 | $this->messageManager->addErrorMessage($e->getMessage()); 115 | if ($author != null) { 116 | $this->storeAuthorDataToSession( 117 | $this->dataObjectProcessor->buildOutputDataArray( 118 | $author, 119 | AuthorInterface::class 120 | ) 121 | ); 122 | } 123 | $resultRedirect->setPath('sample_news/author/edit', ['author_id' => $id]); 124 | } catch (\Exception $e) { 125 | $this->messageManager->addErrorMessage(__('There was a problem saving the author')); 126 | if ($author != null) { 127 | $this->storeAuthorDataToSession( 128 | $this->dataObjectProcessor->buildOutputDataArray( 129 | $author, 130 | AuthorInterface::class 131 | ) 132 | ); 133 | } 134 | $resultRedirect->setPath('sample_news/author/edit', ['author_id' => $id]); 135 | } 136 | return $resultRedirect; 137 | } 138 | 139 | /** 140 | * @param $type 141 | * @return Uploader 142 | * @throws \Exception 143 | */ 144 | protected function getUploader($type) 145 | { 146 | return $this->uploaderPool->getUploader($type); 147 | } 148 | 149 | /** 150 | * @param $authorData 151 | */ 152 | protected function storeAuthorDataToSession($authorData) 153 | { 154 | $this->_getSession()->setSampleNewsAuthorData($authorData); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Author/Upload.php: -------------------------------------------------------------------------------- 1 | uploader = $uploader; 49 | } 50 | 51 | /** 52 | * Upload file controller action 53 | * 54 | * @return \Magento\Framework\Controller\ResultInterface 55 | */ 56 | public function execute() 57 | { 58 | try { 59 | $result = $this->uploader->saveFileToTmpDir($this->getFieldName()); 60 | 61 | $result['cookie'] = [ 62 | 'name' => $this->_getSession()->getName(), 63 | 'value' => $this->_getSession()->getSessionId(), 64 | 'lifetime' => $this->_getSession()->getCookieLifetime(), 65 | 'path' => $this->_getSession()->getCookiePath(), 66 | 'domain' => $this->_getSession()->getCookieDomain(), 67 | ]; 68 | } catch (\Exception $e) { 69 | $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; 70 | } 71 | return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result); 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | protected function getFieldName() 78 | { 79 | return $this->_request->getParam('field'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Controller/Author/Index.php: -------------------------------------------------------------------------------- 1 | resultPageFactory = $resultPageFactory; 62 | $this->scopeConfig = $scopeConfig; 63 | } 64 | 65 | /** 66 | * @return \Magento\Framework\View\Result\Page 67 | */ 68 | public function execute() 69 | { 70 | $resultPage = $this->resultPageFactory->create(); 71 | $resultPage->getConfig()->getTitle()->set( 72 | $this->scopeConfig->getValue(self::META_TITLE_CONFIG_PATH, ScopeInterface::SCOPE_STORE) 73 | ); 74 | $resultPage->getConfig()->setDescription( 75 | $this->scopeConfig->getValue(self::META_DESCRIPTION_CONFIG_PATH, ScopeInterface::SCOPE_STORE) 76 | ); 77 | $resultPage->getConfig()->setKeywords( 78 | $this->scopeConfig->getValue(self::META_KEYWORDS_CONFIG_PATH, ScopeInterface::SCOPE_STORE) 79 | ); 80 | if ($this->scopeConfig->isSetFlag(self::BREADCRUMBS_CONFIG_PATH, ScopeInterface::SCOPE_STORE)) { 81 | /** @var \Magento\Theme\Block\Html\Breadcrumbs $breadcrumbsBlock */ 82 | $breadcrumbsBlock = $resultPage->getLayout()->getBlock('breadcrumbs'); 83 | if ($breadcrumbsBlock) { 84 | $breadcrumbsBlock->addCrumb( 85 | 'home', 86 | [ 87 | 'label' => __('Home'), 88 | 'link' => $this->_url->getUrl('') 89 | ] 90 | ); 91 | $breadcrumbsBlock->addCrumb( 92 | 'authors', 93 | [ 94 | 'label' => __('Authors'), 95 | ] 96 | ); 97 | } 98 | } 99 | return $resultPage; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Controller/Author/Rss.php: -------------------------------------------------------------------------------- 1 | getRequest()->setParam('type', 'authors'); 31 | parent::execute(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Controller/Author/View.php: -------------------------------------------------------------------------------- 1 | resultForwardFactory = $resultForwardFactory; 85 | $this->authorRepository = $authorRepository; 86 | $this->resultPageFactory = $resultPageFactory; 87 | $this->coreRegistry = $coreRegistry; 88 | $this->urlModel = $urlModel; 89 | $this->scopeConfig = $scopeConfig; 90 | parent::__construct($context); 91 | } 92 | 93 | /** 94 | * @return \Magento\Framework\Controller\Result\Forward|\Magento\Framework\View\Result\Page 95 | */ 96 | public function execute() 97 | { 98 | try { 99 | $authorId = (int)$this->getRequest()->getParam('id'); 100 | $author = $this->authorRepository->getById($authorId); 101 | 102 | if (!$author->getIsActive()) { 103 | throw new \Exception(); 104 | } 105 | } catch (\Exception $e){ 106 | $resultForward = $this->resultForwardFactory->create(); 107 | $resultForward->forward('noroute'); 108 | return $resultForward; 109 | } 110 | 111 | $this->coreRegistry->register('current_author', $author); 112 | 113 | $resultPage = $this->resultPageFactory->create(); 114 | //$title = ($author->getMetaTitle()) ?: $author->getName(); 115 | $resultPage->getConfig()->getTitle()->set($author->getName()); 116 | $resultPage->getConfig()->setDescription($author->getMetaDescription()); 117 | $resultPage->getConfig()->setKeywords($author->getMetaKeywords()); 118 | if ($this->scopeConfig->isSetFlag(self::BREADCRUMBS_CONFIG_PATH, ScopeInterface::SCOPE_STORE)) { 119 | /** @var \Magento\Theme\Block\Html\Breadcrumbs $breadcrumbsBlock */ 120 | $breadcrumbsBlock = $resultPage->getLayout()->getBlock('breadcrumbs'); 121 | if ($breadcrumbsBlock) { 122 | $breadcrumbsBlock->addCrumb( 123 | 'home', 124 | [ 125 | 'label' => __('Home'), 126 | 'link' => $this->_url->getUrl('') 127 | ] 128 | ); 129 | $breadcrumbsBlock->addCrumb( 130 | 'authors', 131 | [ 132 | 'label' => __('Authors'), 133 | 'link' => $this->urlModel->getListUrl() 134 | ] 135 | ); 136 | $breadcrumbsBlock->addCrumb( 137 | 'author-'.$author->getId(), 138 | [ 139 | 'label' => $author->getName() 140 | ] 141 | ); 142 | } 143 | } 144 | 145 | return $resultPage; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Controller/RegistryConstants.php: -------------------------------------------------------------------------------- 1 | actionFactory = $actionFactory; 114 | $this->eventManager = $eventManager; 115 | $this->url = $url; 116 | $this->appState = $appState; 117 | $this->storeManager = $storeManager; 118 | $this->response = $response; 119 | $this->scopeConfig = $scopeConfig; 120 | $this->routingEntities = $routingEntities; 121 | } 122 | 123 | /** 124 | * Validate and Match News Author and modify request 125 | * 126 | * @param \Magento\Framework\App\RequestInterface|\Magento\Framework\HTTP\PhpEnvironment\Request $request 127 | * @return bool 128 | */ 129 | public function match(RequestInterface $request) 130 | { 131 | if (!$this->dispatched) { 132 | $urlKey = trim($request->getPathInfo(), '/'); 133 | $origUrlKey = $urlKey; 134 | /** @var Object $condition */ 135 | $condition = new DataObject(['url_key' => $urlKey, 'continue' => true]); 136 | $this->eventManager->dispatch( 137 | 'sample_news_controller_router_match_before', 138 | ['router' => $this, 'condition' => $condition] 139 | ); 140 | $urlKey = $condition->getUrlKey(); 141 | if ($condition->getRedirectUrl()) { 142 | $this->response->setRedirect($condition->getRedirectUrl()); 143 | $request->setDispatched(true); 144 | return $this->actionFactory->create(Redirect::class); 145 | } 146 | if (!$condition->getContinue()) { 147 | return null; 148 | } 149 | foreach ($this->routingEntities as $entityKey => $entity) { 150 | $match = $this->matchRoute($request, $entity, $urlKey, $origUrlKey); 151 | if ($match === false) { 152 | continue; 153 | } 154 | return $match; 155 | } 156 | } 157 | return null; 158 | } 159 | 160 | /** 161 | * @param RequestInterface|\Magento\Framework\HTTP\PhpEnvironment\Request $request 162 | * @param Entity $entity 163 | * @param $urlKey 164 | * @param $origUrlKey 165 | * @return bool|\Magento\Framework\App\ActionInterface|null 166 | */ 167 | protected function matchRoute(RequestInterface $request, Entity $entity, $urlKey, $origUrlKey) 168 | { 169 | $listKey = $this->scopeConfig->getValue($entity->getListKeyConfigPath(), ScopeInterface::SCOPE_STORE); 170 | if ($listKey) { 171 | if ($urlKey == $listKey) { 172 | $request->setModuleName('sample_news'); 173 | $request->setControllerName($entity->getController()); 174 | $request->setActionName($entity->getListAction()); 175 | $request->setAlias(Url::REWRITE_REQUEST_PATH_ALIAS, $urlKey); 176 | $this->dispatched = true; 177 | return $this->actionFactory->create(Forward::class); 178 | } 179 | } 180 | $prefix = $this->scopeConfig->getValue($entity->getPrefixConfigPath(), ScopeInterface::SCOPE_STORE); 181 | if ($prefix) { 182 | $parts = explode('/', $urlKey); 183 | if ($parts[0] != $prefix || count($parts) != 2) { 184 | return false; 185 | } 186 | $urlKey = $parts[1]; 187 | } 188 | $configSuffix = $this->scopeConfig->getValue($entity->getSuffixConfigPath(), ScopeInterface::SCOPE_STORE); 189 | if ($configSuffix) { 190 | $suffix = substr($urlKey, -strlen($configSuffix) - 1); 191 | if ($suffix != '.'.$configSuffix) { 192 | return false; 193 | } 194 | $urlKey = substr($urlKey, 0, -strlen($configSuffix) - 1); 195 | } 196 | $instance = $entity->getFactory()->create(); 197 | $id = $instance->checkUrlKey($urlKey, $this->storeManager->getStore()->getId()); 198 | if (!$id) { 199 | return null; 200 | } 201 | $request->setModuleName('sample_news'); 202 | $request->setControllerName($entity->getController()); 203 | $request->setActionName($entity->getViewAction()); 204 | $request->setParam($entity->getParam(), $id); 205 | $request->setAlias(Url::REWRITE_REQUEST_PATH_ALIAS, $origUrlKey); 206 | $request->setDispatched(true); 207 | $this->dispatched = true; 208 | return $this->actionFactory->create(Forward::class); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Marius 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Model/Author/DataProvider.php: -------------------------------------------------------------------------------- 1 | collection = $authorCollectionFactory->create(); 56 | $this->pool = $pool; 57 | parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); 58 | $this->meta = $this->prepareMeta($this->meta); 59 | } 60 | 61 | /** 62 | * Prepares Meta 63 | * 64 | * @param array $meta 65 | * @return array 66 | */ 67 | public function prepareMeta(array $meta) 68 | { 69 | $meta = parent::getMeta(); 70 | 71 | /** @var ModifierInterface $modifier */ 72 | foreach ($this->pool->getModifiersInstances() as $modifier) { 73 | $meta = $modifier->modifyMeta($meta); 74 | } 75 | return $meta; 76 | } 77 | 78 | /** 79 | * Get data 80 | * 81 | * @return array 82 | */ 83 | public function getData() 84 | { 85 | /** @var ModifierInterface $modifier */ 86 | foreach ($this->pool->getModifiersInstances() as $modifier) { 87 | $this->data = $modifier->modifyData($this->data); 88 | } 89 | return $this->data; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Model/Author/Rss.php: -------------------------------------------------------------------------------- 1 | urlBuilder = $urlBuilder; 63 | $this->scopeConfig = $scopeConfig; 64 | $this->storeManager = $storeManager; 65 | } 66 | 67 | /** 68 | * @return bool 69 | */ 70 | public function isRssEnabled() 71 | { 72 | return 73 | $this->scopeConfig->getValue(self::GLOBAL_RSS_ACTIVE_CONFIG_PATH, ScopeInterface::SCOPE_STORE) && 74 | $this->scopeConfig->getValue(self::AUTHOR_RSS_ACTIVE_CONFIG_PATH, ScopeInterface::SCOPE_STORE); 75 | } 76 | 77 | /** 78 | * @return string 79 | */ 80 | public function getRssLink() 81 | { 82 | return $this->urlBuilder->getUrl( 83 | self::RSS_PAGE_URL, 84 | ['store' => $this->storeManager->getStore()->getId()] 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Model/Author/Url.php: -------------------------------------------------------------------------------- 1 | urlBuilder = $urlBuilder; 60 | $this->scopeConfig = $scopeConfig; 61 | } 62 | 63 | /** 64 | * @return string 65 | */ 66 | public function getListUrl() 67 | { 68 | $sefUrl = $this->scopeConfig->getValue(self::LIST_URL_CONFIG_PATH, ScopeInterface::SCOPE_STORE); 69 | if ($sefUrl) { 70 | return $this->urlBuilder->getUrl('', ['_direct' => $sefUrl]); 71 | } 72 | return $this->urlBuilder->getUrl('sample_news/author/index'); 73 | } 74 | 75 | /** 76 | * @param Author $author 77 | * @return string 78 | */ 79 | public function getAuthorUrl(Author $author) 80 | { 81 | if ($urlKey = $author->getUrlKey()) { 82 | $prefix = $this->scopeConfig->getValue( 83 | self::URL_PREFIX_CONFIG_PATH, 84 | ScopeInterface::SCOPE_STORE 85 | ); 86 | $suffix = $this->scopeConfig->getValue( 87 | self::URL_SUFFIX_CONFIG_PATH, 88 | ScopeInterface::SCOPE_STORE 89 | ); 90 | $path = (($prefix) ? $prefix . '/' : ''). 91 | $urlKey . 92 | (($suffix) ? '.'. $suffix : ''); 93 | return $this->urlBuilder->getUrl('', ['_direct'=>$path]); 94 | } 95 | return $this->urlBuilder->getUrl('sample_news/author/view', ['id' => $author->getId()]); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Model/AuthorFactory.php: -------------------------------------------------------------------------------- 1 | _objectManager = $objectManager; 48 | $this->_instanceName = $instanceName; 49 | } 50 | 51 | /** 52 | * Create class instance with specified parameters 53 | * 54 | * @param array $data 55 | * @return RoutableInterface|\Sample\News\Model\Author 56 | */ 57 | public function create(array $data = array()) 58 | { 59 | return $this->_objectManager->create($this->_instanceName, $data); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Model/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | templateProcessor = $templateProcessor; 34 | } 35 | 36 | /** 37 | * @param $string 38 | * @return string 39 | */ 40 | public function filterOutput($string) 41 | { 42 | return $this->templateProcessor->filter($string); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Model/ResourceModel/Author/Grid/Collection.php: -------------------------------------------------------------------------------- 1 | _eventPrefix = $eventPrefix; 80 | $this->_eventObject = $eventObject; 81 | $this->_init($model, $resourceModel); 82 | $this->setMainTable($mainTable); 83 | } 84 | 85 | /** 86 | * @return AggregationInterface 87 | */ 88 | public function getAggregations() 89 | { 90 | return $this->aggregations; 91 | } 92 | 93 | /** 94 | * @param AggregationInterface $aggregations 95 | * @return $this 96 | */ 97 | public function setAggregations($aggregations) 98 | { 99 | $this->aggregations = $aggregations; 100 | } 101 | 102 | /** 103 | * Get search criteria. 104 | * 105 | * @return \Magento\Framework\Api\SearchCriteriaInterface|null 106 | */ 107 | public function getSearchCriteria() 108 | { 109 | return null; 110 | } 111 | 112 | /** 113 | * Set search criteria. 114 | * 115 | * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria 116 | * @return $this 117 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 118 | */ 119 | public function setSearchCriteria(SearchCriteriaInterface $searchCriteria = null) 120 | { 121 | return $this; 122 | } 123 | 124 | /** 125 | * Get total count. 126 | * 127 | * @return int 128 | */ 129 | public function getTotalCount() 130 | { 131 | return $this->getSize(); 132 | } 133 | 134 | /** 135 | * Set total count. 136 | * 137 | * @param int $totalCount 138 | * @return $this 139 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 140 | */ 141 | public function setTotalCount($totalCount) 142 | { 143 | return $this; 144 | } 145 | 146 | /** 147 | * Set items list. 148 | * 149 | * @param \Magento\Framework\Api\ExtensibleDataInterface[] $items 150 | * @return $this 151 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 152 | */ 153 | public function setItems(array $items = null) 154 | { 155 | return $this; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Model/ResourceModel/Author/Grid/ServiceCollection.php: -------------------------------------------------------------------------------- 1 | authorRepository = $authorRepository; 63 | $this->simpleDataObjectConverter = $simpleDataObjectConverter; 64 | parent::__construct($entityFactory, $filterBuilder, $searchCriteriaBuilder, $sortOrderBuilder); 65 | } 66 | 67 | /** 68 | * Load customer group collection data from service 69 | * 70 | * @param bool $printQuery 71 | * @param bool $logQuery 72 | * @return $this 73 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 74 | */ 75 | public function loadData($printQuery = false, $logQuery = false) 76 | { 77 | if (!$this->isLoaded()) { 78 | $searchCriteria = $this->getSearchCriteria(); 79 | $searchResults = $this->authorRepository->getList($searchCriteria); 80 | $this->_totalRecords = $searchResults->getTotalCount(); 81 | /** @var AuthorInterface[] $authors */ 82 | $authors = $searchResults->getItems(); 83 | foreach ($authors as $author) { 84 | $authorItem = new DataObject(); 85 | $authorItem->addData( 86 | $this->simpleDataObjectConverter->toFlatArray($author, AuthorInterface::class) 87 | ); 88 | $this->_addItem($authorItem); 89 | } 90 | $this->_setIsLoaded(); 91 | } 92 | return $this; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Model/Routing/Entity.php: -------------------------------------------------------------------------------- 1 | prefixConfigPath = $prefixConfigPath; 62 | $this->suffixConfigPath = $suffixConfigPath; 63 | $this->listKeyConfigPath = $listKeyConfigPath; 64 | $this->factory = $factory; 65 | $this->controller = $controller; 66 | $this->listAction = $listAction; 67 | $this->viewAction = $viewAction; 68 | $this->param = $param; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | public function getPrefixConfigPath() 75 | { 76 | return $this->prefixConfigPath; 77 | } 78 | 79 | /** 80 | * @return string 81 | */ 82 | public function getSuffixConfigPath() 83 | { 84 | return $this->suffixConfigPath; 85 | } 86 | 87 | /** 88 | * @return string 89 | */ 90 | public function getListKeyConfigPath() 91 | { 92 | return $this->listKeyConfigPath; 93 | } 94 | 95 | /** 96 | * @return string 97 | */ 98 | public function getListAction() 99 | { 100 | return $this->listAction; 101 | } 102 | 103 | /** 104 | * @return FactoryInterface 105 | */ 106 | public function getFactory() 107 | { 108 | return $this->factory; 109 | } 110 | 111 | /** 112 | * @return mixed 113 | */ 114 | public function getController() 115 | { 116 | return $this->controller; 117 | } 118 | 119 | /** 120 | * @return string 121 | */ 122 | public function getViewAction() 123 | { 124 | return $this->viewAction; 125 | } 126 | 127 | /** 128 | * @return string 129 | */ 130 | public function getParam() 131 | { 132 | return $this->param; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /Model/Routing/RoutableInterface.php: -------------------------------------------------------------------------------- 1 | options = $options; 36 | } 37 | 38 | /** 39 | * @return array 40 | */ 41 | abstract public function toOptionArray(); 42 | 43 | /** 44 | * @param $value 45 | * @return string 46 | */ 47 | public function getOptionText($value) 48 | { 49 | $options = $this->getOptions(); 50 | if (!is_array($value)) { 51 | $value = explode(',', $value); 52 | } 53 | $texts = []; 54 | foreach ($value as $v) { 55 | if (isset($options[$v])) { 56 | $texts[] = $options[$v]; 57 | } 58 | } 59 | return implode(', ', $texts); 60 | } 61 | /** 62 | * get options as key value pair 63 | * 64 | * @return array 65 | */ 66 | public function getOptions() 67 | { 68 | $options = []; 69 | foreach ($this->toOptionArray() as $values) { 70 | $options[$values['value']] = __($values['label']); 71 | } 72 | return $options; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Model/Source/Country.php: -------------------------------------------------------------------------------- 1 | countryCollectionFactory = $countryCollectionFactory; 40 | parent::__construct($options); 41 | } 42 | 43 | /** 44 | * get options as key value pair 45 | * 46 | * @return array 47 | */ 48 | public function toOptionArray() 49 | { 50 | if (count($this->options) == 0) { 51 | $this->options = $this->countryCollectionFactory->create()->toOptionArray(' '); 52 | } 53 | return $this->options; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Model/Source/Options.php: -------------------------------------------------------------------------------- 1 | options as $values) { 33 | $options[] = [ 34 | 'value' => $values['value'], 35 | 'label' => __($values['label']) 36 | ]; 37 | } 38 | return $options; 39 | 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Model/UploaderPool.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 42 | $this->uploaders = $uploaders; 43 | } 44 | 45 | /** 46 | * @param $type 47 | * @return Uploader 48 | * @throws \Exception 49 | */ 50 | public function getUploader($type) 51 | { 52 | if (!isset($this->uploaders[$type])) { 53 | throw new \Exception("Uploader not found for type: ".$type); 54 | } 55 | if (!is_object($this->uploaders[$type])) { 56 | $this->uploaders[$type] = $this->objectManager->create($this->uploaders[$type]); 57 | 58 | } 59 | $uploader = $this->uploaders[$type]; 60 | if (!($uploader instanceof Uploader)) { 61 | throw new \Exception("Uploader for type {$type} not instance of ". Uploader::class); 62 | } 63 | return $uploader; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Plugin/Block/Topmenu.php: -------------------------------------------------------------------------------- 1 | url = $url; 45 | $this->request = $request; 46 | } 47 | 48 | /** 49 | * @param TopmenuBlock $subject 50 | * @param string $outermostClass 51 | * @param string $childrenWrapClass 52 | * @param int $limit 53 | * @SuppressWarnings("PMD.UnusedFormalParameter") 54 | */ 55 | // @codingStandardsIgnoreStart 56 | public function beforeGetHtml( 57 | TopmenuBlock $subject, 58 | $outermostClass = '', 59 | $childrenWrapClass = '', 60 | $limit = 0 61 | ) { 62 | // @codingStandardsIgnoreEnd 63 | $node = new Node( 64 | $this->getNodeAsArray(), 65 | 'id', 66 | $subject->getMenu()->getTree(), 67 | $subject->getMenu() 68 | ); 69 | $subject->getMenu()->addChild($node); 70 | } 71 | 72 | /** 73 | * @return array 74 | */ 75 | protected function getNodeAsArray() 76 | { 77 | return [ 78 | 'name' => __('Authors'), 79 | 'id' => 'authors-node', 80 | 'url' => $this->url->getListUrl(), 81 | 'has_active' => false, 82 | 'is_active' => in_array($this->request->getFullActionName(), $this->getActiveHandles()) 83 | ]; 84 | } 85 | 86 | /** 87 | * @return array 88 | */ 89 | protected function getActiveHandles() 90 | { 91 | return [ 92 | 'sample_news_author_index', 93 | 'sample_news_author_view' 94 | ]; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Magento 2.0 Sample Module 2 | ==================== 3 | 4 | Last tested on Magento2 version 2.1 5 | 6 | 7 | ![Magento 2 Sample Module](http://i.imgur.com/Ma6v2gs.jpg) 8 | 9 | What I got so far: 10 | ---------- 11 | 12 | - One custom entity Author. flat and related to store views 13 | - Backend section for the entity mentioned above 14 | - Frontend list and view for the entity mentioned above 15 | - List on Author view page on frontend 16 | - Rss feeds for author list. 17 | - Breadcrumbs support for list and view pages. 18 | 19 | The other types of entities and features listed in **the purpose** will follow. 20 | Don't put many hopes in this. Based on the comments on the magento 2 repo the grid system will be changed...A LOT. 21 | 22 | The purpose 23 | ---------- 24 | 25 | ....of this repository is to hold a sample CRUD module for Magento 2.0. 26 | This module should contain the following: 27 | 28 | * 4 Entities. 29 | * 1 Flat - with a store selector. Similar to CMS pages 30 | * 1 Flat but behaving as a tree - with a store selector. Similar to categories but non EAV 31 | * 1 EAV - similar to products 32 | * 1 EAV but behaving as tree - Similar to categories. 33 | * Backend files for managing the entities mentioned above 34 | * Frontend files for list and view each of the entities mentioned above 35 | * RSS feeds for each entity mentioned above 36 | * SOAP & REST API files for the entities mentioned above 37 | * URL rewrites filed for frontend for the entities above 38 | * Files needed for a many to many relation between the entities above and products 39 | * Files needed for a many to many relation between the entities above and categories 40 | * Files needed for a many to many relation between the entities above (among themselves) 41 | * Each entity must support different attribute types: 42 | * Text 43 | * Textarea (with and without WYSIWYG editor) 44 | * Date 45 | * Yes/No 46 | * Dropdown (with different source models) 47 | * Multi-select (with different source models) 48 | * File 49 | * Image 50 | * Decimal 51 | * Integer (signed and unsigned) 52 | * Color 53 | * Each entity should have fronend links to the list page in one of the menu/link areas provided by the default theme 54 | * Each entity must have SEO attributes (meta-title, meta-description, meta-keywords) 55 | * Would be nice to have unit tests for every class in the code - but that's low priority. 56 | * Each entity type must have widgets for frontend (link, short view). 57 | * Each entity must support customer comments. 58 | * Each EAV entity must have a section for managing attributes (similar to product attributes). 59 | 60 | After this is complete (or almost) it will become the base source for the Ultimate Module Creator 2.0 which will be a version for Magento 2.0 of the [Ultimate Module Creator for Magento 1.7](https://github.com/tzyganu/UMC1.9). 61 | 62 | Any other ideas and pieces of code are welcomed even encouraged. 63 | 64 | Install 65 | ----- 66 | 67 | Manually: 68 | To install this module copy the code from this repo to `app/code` folder of your Magento 2 instance, 69 | If you do this after installing Magento 2 you need to run `php bin/magento setup:upgrade` 70 | 71 | Via composer 72 | 73 | - composer config repositories.sample-module-news git git@github.com:tzyganu/Magento2SampleModule.git 74 | - sudo composer require sample/module-news:dev-master 75 | - php bin/magento setup:upgrade 76 | 77 | 78 | Uninstall 79 | -------- 80 | 81 | If you installed it manually: 82 | - remove the folder `app/code/Sample/News` 83 | - drop the tables `sample_news_author_store` and `sample_news_author` (in this order) 84 | - remove the config settings. `DELETE FROM core_config_data WHERE path LIKE 'sample_news/%'` 85 | - remove the module `Sample_News` from `app/etc/config.php` 86 | - remove the module `Sample_News` from table `setup_module`: `DELETE FROM setup_module WHERE module='Sample_News'` 87 | 88 | If you installed it via composer: 89 | - run this in console `bin/magento module:uninstall -r Sample_News`. You might have some problems while uninstalling. See more [details here](http://magento.stackexchange.com/q/123544/146): 90 | -------------------------------------------------------------------------------- /Setup/Uninstall.php: -------------------------------------------------------------------------------- 1 | collectionFactory = $collectionFactory; 52 | $this->configResource = $configResource; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | * @SuppressWarnings(PHPMD.ExcessiveMethodLength) 58 | * @SuppressWarnings(PHPMD.Generic.CodeAnalysis.UnusedFunctionParameter) 59 | */ 60 | // @codingStandardsIgnoreStart 61 | public function uninstall(SchemaSetupInterface $setup, ModuleContextInterface $context) 62 | // @codingStandardsIgnoreEnd 63 | { 64 | //remove tables 65 | if ($setup->tableExists('sample_news_author_store')) { 66 | $setup->getConnection()->dropTable('sample_news_author_store'); 67 | } 68 | if ($setup->tableExists('sample_news_author')) { 69 | $setup->getConnection()->dropTable('sample_news_author'); 70 | } 71 | //remove config settings if any 72 | $collection = $this->collectionFactory->create() 73 | ->addPathFilter('sample_news'); 74 | foreach ($collection as $config) { 75 | $this->deleteConfig($config); 76 | } 77 | } 78 | 79 | /** 80 | * @param AbstractModel $config 81 | * @throws \Exception 82 | */ 83 | protected function deleteConfig(AbstractModel $config) 84 | { 85 | $this->configResource->delete($config); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Test/Unit/Model/Author/DataProviderTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(PoolInterface::class) 40 | ->disableOriginalConstructor() 41 | ->getMock(); 42 | /** @var \PHPUnit_Framework_MockObject_MockObject|ModifierInterface $modifierMock */ 43 | $modifierMock = $this->getMockBuilder(ModifierInterface::class) 44 | ->disableOriginalConstructor() 45 | ->getMock(); 46 | $modifierMock->method('modifyMeta')->willReturn($this->getDummyMeta()); 47 | $modifierMock->method('modifyData')->willReturn($this->getDummyData()); 48 | $poolMock->method('getModifiersInstances')->willReturn([$modifierMock]); 49 | 50 | /** @var ModifierInterface|CollectionFactory $collectionFactoryMock */ 51 | $collectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) 52 | ->disableOriginalConstructor() 53 | ->getMock(); 54 | 55 | $this->dataProvider = new DataProvider( 56 | 'dummy', 57 | 'dummy_id', 58 | 'dummy_id', 59 | $collectionFactoryMock, 60 | $poolMock, 61 | [], 62 | [] 63 | ); 64 | } 65 | 66 | /** 67 | * @return array 68 | */ 69 | protected function getDummyMeta() 70 | { 71 | return ['dummy_meta_key'=>'dummy_meta_value']; 72 | } 73 | 74 | /** 75 | * @return array 76 | */ 77 | protected function getDummyData() 78 | { 79 | return ['dummy_data_key'=>'dummy_data_value']; 80 | } 81 | 82 | /** 83 | * tests DataProvider::prepareMeta() 84 | */ 85 | public function testPrepareMeta() 86 | { 87 | $this->assertEquals($this->getDummyMeta(), $this->dataProvider->prepareMeta([])); 88 | } 89 | 90 | /** 91 | * tests DataProvider::getData() 92 | */ 93 | public function testGetData() 94 | { 95 | $this->assertEquals($this->getDummyData(), $this->dataProvider->getData()); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /Test/Unit/Model/Author/RssTest.php: -------------------------------------------------------------------------------- 1 | urlMockBuilder = $this->getMockBuilder(UrlInterface::class) 55 | ->disableOriginalConstructor() 56 | ->getMock(); 57 | $this->urlMockBuilder 58 | ->expects($this->any()) 59 | ->method('getUrl') 60 | ->willReturnMap( 61 | [ 62 | [ 63 | 'sample_news/author/rss', 64 | [ 65 | 'store' => self::STORE_ID 66 | ], 67 | 'some/url/here' 68 | ] 69 | ] 70 | ); 71 | $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) 72 | ->disableOriginalConstructor() 73 | ->getMock(); 74 | $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) 75 | ->disableOriginalConstructor() 76 | ->getMock(); 77 | 78 | $storeMock = $this->getMockBuilder(StoreInterface::class) 79 | ->disableOriginalConstructor() 80 | ->getMock(); 81 | $storeMock->expects($this->any())->method('getId')->willReturn(self::STORE_ID); 82 | $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); 83 | 84 | $this->rss = new Rss( 85 | $this->urlMockBuilder, 86 | $this->scopeConfigMock, 87 | $this->storeManagerMock 88 | ); 89 | } 90 | 91 | /** 92 | * @test \Sample\News\Model\Author\Rss::isRssEnabled() 93 | * 94 | * with global rss setting disabled 95 | * and author rss disabled 96 | */ 97 | public function testIsRssEnabledGlobalFalseAuthorFalse() 98 | { 99 | $this->scopeConfigMock->expects($this->any()) 100 | ->method('getValue') 101 | ->willReturnMap( 102 | [ 103 | [ 104 | 'rss/config/active', 105 | ScopeInterface::SCOPE_STORE, 106 | null, 107 | false 108 | ], 109 | [ 110 | 'sample_news/author/rss', 111 | ScopeInterface::SCOPE_STORE, 112 | null, 113 | false 114 | ], 115 | ] 116 | ); 117 | $this->assertFalse($this->rss->isRssEnabled()); 118 | } 119 | 120 | /** 121 | * @test \Sample\News\Model\Author\Rss::isRssEnabled() 122 | * 123 | * with global rss setting enabled 124 | * and author rss disabled 125 | */ 126 | public function testIsRssEnabledGlobalTrueAuthorFalse() 127 | { 128 | $this->scopeConfigMock->expects($this->any()) 129 | ->method('getValue') 130 | ->willReturnMap( 131 | [ 132 | [ 133 | 'rss/config/active', 134 | ScopeInterface::SCOPE_STORE, 135 | null, 136 | true 137 | ], 138 | [ 139 | 'sample_news/author/rss', 140 | ScopeInterface::SCOPE_STORE, 141 | null, 142 | false 143 | ], 144 | ] 145 | ); 146 | $this->assertFalse($this->rss->isRssEnabled()); 147 | } 148 | 149 | /** 150 | * @test \Sample\News\Model\Author\Rss::isRssEnabled() 151 | * 152 | * with global rss setting disabled 153 | * and author rss enabled 154 | */ 155 | public function testIsRssEnabledGlobalFalseAuthorTrue() 156 | { 157 | $this->scopeConfigMock->expects($this->any()) 158 | ->method('getValue') 159 | ->willReturnMap( 160 | [ 161 | [ 162 | 'rss/config/active', 163 | ScopeInterface::SCOPE_STORE, 164 | null, 165 | false 166 | ], 167 | [ 168 | 'sample_news/author/rss', 169 | ScopeInterface::SCOPE_STORE, 170 | null, 171 | true 172 | ], 173 | ] 174 | ); 175 | $this->assertFalse($this->rss->isRssEnabled()); 176 | } 177 | 178 | /** 179 | * @test \Sample\News\Model\Author\Rss::isRssEnabled() 180 | * 181 | * with global rss setting enabled 182 | * and author rss enabled 183 | */ 184 | public function testIsRssEnabledGlobalTrueAuthorTrue() 185 | { 186 | $this->scopeConfigMock->expects($this->any()) 187 | ->method('getValue') 188 | ->willReturnMap( 189 | [ 190 | [ 191 | 'rss/config/active', 192 | ScopeInterface::SCOPE_STORE, 193 | null, 194 | true 195 | ], 196 | [ 197 | 'sample_news/author/rss', 198 | ScopeInterface::SCOPE_STORE, 199 | null, 200 | true 201 | ], 202 | ] 203 | ); 204 | $this->assertTrue($this->rss->isRssEnabled()); 205 | } 206 | 207 | /** 208 | * @test \Sample\News\Model\Author\Rss::isRssEnabled() 209 | */ 210 | public function testGetRssLink() 211 | { 212 | $this->assertEquals('some/url/here', $this->rss->getRssLink()); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Test/Unit/Model/Source/CountryTest.php: -------------------------------------------------------------------------------- 1 | objectManager = new ObjectManager($this); 52 | $this->countryCollectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) 53 | ->disableOriginalConstructor() 54 | ->getMock(); 55 | $countryMock = $this->getMockBuilder(CountryModel::class) 56 | ->disableOriginalConstructor() 57 | ->getMock(); 58 | $this->countryCollection = $this->objectManager->getCollectionMock(Collection::class, [$countryMock]); 59 | $this->countryCollectionFactoryMock->method('create')->willReturn($this->countryCollection); 60 | 61 | $this->countryList = new Country($this->countryCollectionFactoryMock); 62 | } 63 | 64 | /** 65 | * @test \Sample\News\Model\Source\Country::toOptionArray result is memoized 66 | */ 67 | public function testMemoizedOptionArray() 68 | { 69 | $this->countryCollection->method('toOptionArray')->willReturn(['baz' => 'qux']); 70 | $this->countryCollection->expects($this->once())->method('toOptionArray'); 71 | $result1 = $this->countryList->toOptionArray(); 72 | $result2 = $this->countryList->toOptionArray(); 73 | $this->assertSame($result1, $result2); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Test/Unit/Model/UploaderPoolTest.php: -------------------------------------------------------------------------------- 1 | objectManager = new ObjectManager($this); 40 | /** @var \PHPUnit_Framework_MockObject_MockObject|ObjectManagerInterface $objectManagerMock */ 41 | $objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) 42 | ->disableOriginalConstructor() 43 | ->getMock(); 44 | /** @var \PHPUnit_Framework_MockObject_MockObject|Uploader $uploaderMock */ 45 | $uploaderMock = $this->getMockBuilder(Uploader::class) 46 | ->disableOriginalConstructor() 47 | ->getMock(); 48 | $objectManagerMock->expects($this->any())->method('create')->willReturn($uploaderMock); 49 | 50 | $dataObject = new DataObject(); 51 | 52 | $this->uploaderPool = new UploaderPool( 53 | $objectManagerMock, 54 | [ 55 | 'uploader1' => Uploader::class, 56 | 'uploader2' => $uploaderMock, 57 | 'uploader3' => $dataObject 58 | ] 59 | ); 60 | } 61 | 62 | /** 63 | * @test \Sample\News\Model\UploaderPool::getUploader() when uploader is not found 64 | * @throws \Exception 65 | */ 66 | public function testGetUploaderNotFound() 67 | { 68 | $type = 'test'; 69 | $this->setExpectedException('\Exception', "Uploader not found for type: ".$type); 70 | $this->uploaderPool->getUploader($type); 71 | } 72 | 73 | /** 74 | * @test \Sample\News\Model\UploaderPool::getUploader() when instantiation is needed 75 | * @throws \Exception 76 | */ 77 | public function testGetUploaderInstantiationNeeded() 78 | { 79 | $type = 'uploader1'; 80 | $this->assertInstanceOf(Uploader::class, $this->uploaderPool->getUploader($type)); 81 | } 82 | 83 | /** 84 | * @test \Sample\News\Model\UploaderPool::getUploader() when instantiation is not needed 85 | * @throws \Exception 86 | */ 87 | public function testGetUploaderInstantiationNotNeeded() 88 | { 89 | $type = 'uploader2'; 90 | $this->assertInstanceOf(Uploader::class, $this->uploaderPool->getUploader($type)); 91 | } 92 | 93 | /** 94 | * @test \Sample\News\Model\UploaderPool::getUploader() with wrong type returned 95 | * @throws \Exception 96 | */ 97 | public function testGetUploaderWrongType() 98 | { 99 | $type = 'uploader3'; 100 | $this->setExpectedException('\Exception', "Uploader for type {$type} not instance of ".Uploader::class); 101 | $this->uploaderPool->getUploader($type); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Test/Unit/Model/UploaderTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(Database::class) 59 | ->disableOriginalConstructor() 60 | ->getMock(); 61 | $coreFileStorageDatabaseMock->method('copyFile')->willReturn($coreFileStorageDatabaseMock); 62 | /** @var \PHPUnit_Framework_MockObject_MockObject|Filesystem $fileSystemMock */ 63 | $fileSystemMock = $this->getMockBuilder(Filesystem::class) 64 | ->disableOriginalConstructor() 65 | ->getMock(); 66 | /** @var \PHPUnit_Framework_MockObject_MockObject|WriteInterface $writeInterfaceMock */ 67 | $writeInterfaceMock = $this->getMockBuilder(WriteInterface::class) 68 | ->disableOriginalConstructor() 69 | ->getMock(); 70 | $writeInterfaceMock->method('renameFile')->willReturn($writeInterfaceMock); 71 | $fileSystemMock->method('getDirectoryWrite')->willReturn($writeInterfaceMock); 72 | /** @var \PHPUnit_Framework_MockObject_MockObject|UploaderFactory $uploaderFactoryMock */ 73 | $uploaderFactoryMock = $this->getMockBuilder(UploaderFactory::class) 74 | ->disableOriginalConstructor() 75 | ->getMock(); 76 | /** @var \PHPUnit_Framework_MockObject_MockObject|Uploader $uploaderMock */ 77 | $uploaderMock = $this->getMockBuilder(Uploader::class) 78 | ->disableOriginalConstructor() 79 | ->getMock(); 80 | $uploaderMock->method('save')->willReturn([ 81 | 'file' => 'file.ext', 82 | 'tmp_name' => 'file.ext', 83 | 'path' => 'path' 84 | ]); 85 | 86 | $uploaderFactoryMock->method('create')->willReturn($uploaderMock); 87 | /** @var \PHPUnit_Framework_MockObject_MockObject|StoreManagerInterface $storeManagerMock */ 88 | $storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) 89 | ->disableOriginalConstructor() 90 | ->getMock(); 91 | /** @var \PHPUnit_Framework_MockObject_MockObject|Store $storeMock */ 92 | $storeMock = $this->getMockBuilder(Store::class) 93 | ->disableOriginalConstructor() 94 | ->getMock(); 95 | $storeMock->method('getBaseUrl')->willReturn('http://example.com/'); 96 | $storeManagerMock->method('getStore')->willReturn($storeMock); 97 | /** @var \PHPUnit_Framework_MockObject_MockObject|LoggerInterface $loggerMock */ 98 | $loggerMock = $this->getMockBuilder(LoggerInterface::class) 99 | ->disableOriginalConstructor() 100 | ->getMock(); 101 | 102 | $this->uploader = new UploaderModel( 103 | $coreFileStorageDatabaseMock, 104 | $fileSystemMock, 105 | $uploaderFactoryMock, 106 | $storeManagerMock, 107 | $loggerMock, 108 | [], 109 | self::BASE_TMP_PATH, 110 | self::BASE_PATH 111 | ); 112 | } 113 | 114 | /** 115 | * @test Sample\News\Model\Uploader::getBaseTmpPath() 116 | */ 117 | public function testGetBaseTmpPath() 118 | { 119 | $this->assertEquals(self::BASE_TMP_PATH, $this->uploader->getBaseTmpPath()); 120 | } 121 | 122 | /** 123 | * @test Sample\News\Model\Uploader::getBasePath 124 | */ 125 | public function testGetBasePath() 126 | { 127 | $this->assertEquals(self::BASE_PATH, $this->uploader->getBasePath()); 128 | } 129 | 130 | /** 131 | * @test Sample\News\Model\Uploader::getAllowedExtensions 132 | */ 133 | public function testGetAllowedExtensions() 134 | { 135 | $this->assertEquals([], $this->uploader->getAllowedExtensions()); 136 | $this->uploader->setAllowedExtensions(['ext']); 137 | $this->assertEquals(['ext'], $this->uploader->getAllowedExtensions()); 138 | } 139 | 140 | /** 141 | * @test Sample\News\Model\Uploader::getFilePath 142 | */ 143 | public function testGetFilePath() 144 | { 145 | $this->assertEquals('path/here/file.ext', $this->uploader->getFilePath('path/here/', '/file.ext')); 146 | $this->assertEquals('path/here/file.ext', $this->uploader->getFilePath('path/here', '/file.ext')); 147 | $this->assertEquals('path/here/file.ext', $this->uploader->getFilePath('path/here/', 'file.ext')); 148 | $this->assertEquals('path/here/file.ext', $this->uploader->getFilePath('path/here', 'file.ext')); 149 | } 150 | 151 | /** 152 | * @test Sample\News\Model\Uploader::moveFileFromTmp 153 | */ 154 | public function testMoveFileFromTmp() 155 | { 156 | $this->assertEquals('dummy', $this->uploader->moveFileFromTmp('dummy')); 157 | } 158 | 159 | /** 160 | * @test Sample\News\Model\Uploader::saveFileToTmpDir 161 | */ 162 | public function testSaveFileToTmpDir() 163 | { 164 | $expected = [ 165 | 'file' => 'file.ext', 166 | 'tmp_name' => 'file.ext', 167 | 'path' => 'path', 168 | 'url' => 'http://example.com/base/tmp/path/file.ext' 169 | ]; 170 | $this->assertEquals($expected, $this->uploader->saveFileToTmpDir('dummy')); 171 | } 172 | 173 | /** 174 | * @test Sample\News\Model\Uploader::uploadFileAndGetName 175 | */ 176 | public function testUploadFileAndGetName() 177 | { 178 | $data = []; 179 | $this->assertEmpty($this->uploader->uploadFileAndGetName('dummy', $data)); 180 | $data = [ 181 | 'dummy' => [ 182 | 'delete' => 1, 183 | 'dummy1' => [ 184 | 'data1', 'data2' 185 | ] 186 | ] 187 | ]; 188 | $this->assertEmpty($this->uploader->uploadFileAndGetName('dummy', $data)); 189 | $data = [ 190 | 'dummy' => [ 191 | [ 192 | 'name' => 'file.ext', 193 | 'tmp_name' => 'dummy', 194 | 'file' => 'file.ext' 195 | ] 196 | ] 197 | ]; 198 | $this->assertEquals('file.ext', $this->uploader->uploadFileAndGetName('dummy', $data)); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /Test/Unit/Ui/Component/Listing/Column/AuthorActionsTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(UrlInterface::class) 36 | ->disableOriginalConstructor() 37 | ->getMock(); 38 | /** @var \PHPUnit_Framework_MockObject_MockObject|ContextInterface $contextMock */ 39 | $contextMock = $this->getMockBuilder(ContextInterface::class) 40 | ->getMockForAbstractClass(); 41 | /** @var \PHPUnit_Framework_MockObject_MockObject|Processor $processor */ 42 | $processor = $this->getMockBuilder(Processor::class) 43 | ->disableOriginalConstructor() 44 | ->getMock(); 45 | $contextMock->expects($this->any())->method('getProcessor')->willReturn($processor); 46 | /** @var \PHPUnit_Framework_MockObject_MockObject|UiComponentFactory $uiComponentFactoryMock */ 47 | $uiComponentFactoryMock = $this->getMockBuilder(UiComponentFactory::class) 48 | ->disableOriginalConstructor() 49 | ->getMock(); 50 | 51 | /** @var \Sample\News\Ui\Component\Listing\Column\AuthorActions $actions */ 52 | $actions = new AuthorActions($contextMock, $uiComponentFactoryMock, $urlBuilderMock); 53 | // Define test input and expectations 54 | $items = [ 55 | 'data' => [ 56 | 'items' => [ 57 | [ 58 | 'author_id' => $authorId 59 | ] 60 | ] 61 | ] 62 | ]; 63 | $name = 'item_name'; 64 | $expectedItems = [ 65 | [ 66 | 'author_id' => $authorId, 67 | $name => [ 68 | 'edit' => [ 69 | 'href' => 'some/url/edit', 70 | 'label' => __('Edit'), 71 | ], 72 | 'delete' => [ 73 | 'href' => 'some/url/delete', 74 | 'label' => __('Delete'), 75 | 'confirm' => [ 76 | 'title' => __('Delete "${ $.$data.name }"'), 77 | 'message' => __('Are you sure you wan\'t to delete the Author "${ $.$data.name }" ?') 78 | ], 79 | ] 80 | ], 81 | ] 82 | ]; 83 | 84 | // Configure mocks and object data 85 | $urlBuilderMock->expects($this->any()) 86 | ->method('getUrl') 87 | ->willReturnMap( 88 | [ 89 | [ 90 | AuthorActions::URL_PATH_EDIT, 91 | [ 92 | 'author_id' => $authorId 93 | ], 94 | 'some/url/edit', 95 | ], 96 | [ 97 | AuthorActions::URL_PATH_DELETE, 98 | [ 99 | 'author_id' => $authorId 100 | ], 101 | 'some/url/delete', 102 | ], 103 | ] 104 | ); 105 | 106 | $actions->setName($name); 107 | $items = $actions->prepareDataSource($items); 108 | // Run test 109 | $this->assertEquals($expectedItems, $items['data']['items']); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Test/Unit/Ui/Component/Listing/Column/AvatarTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(ContextInterface::class) 38 | ->disableOriginalConstructor() 39 | ->getMock(); 40 | /** @var \PHPUnit_Framework_MockObject_MockObject|UiComponentFactory $uiComponentFactoryMock */ 41 | $uiComponentFactoryMock = $this->getMockBuilder(UiComponentFactory::class) 42 | ->disableOriginalConstructor() 43 | ->getMock(); 44 | /** @var \PHPUnit_Framework_MockObject_MockObject|UrlInterface $urlBuilderMock */ 45 | $urlBuilderMock = $this->getMockBuilder(UrlInterface::class) 46 | ->disableOriginalConstructor() 47 | ->getMock(); 48 | /** @var \PHPUnit_Framework_MockObject_MockObject|Uploader $uploaderMock */ 49 | $uploaderMock = $this->getMockBuilder(Uploader::class) 50 | ->disableOriginalConstructor() 51 | ->getMock(); 52 | $uploaderMock->method('getBasePath')->willReturn('/base/path'); 53 | $uploaderMock->method('getBaseUrl')->willReturn('http://example.com'); 54 | $processor = $this->getMockBuilder(Processor::class) 55 | ->disableOriginalConstructor() 56 | ->getMock(); 57 | $contextMock->expects($this->any())->method('getProcessor')->willReturn($processor); 58 | 59 | /** @var \Sample\News\Ui\Component\Listing\Column\Avatar $avatarModel */ 60 | $avatarModel = new Avatar($contextMock, $uiComponentFactoryMock, $urlBuilderMock, $uploaderMock, [], []); 61 | $authorId = 1; 62 | $urlBuilderMock 63 | ->method('getUrl') 64 | ->willReturnMap( 65 | [ 66 | [ 67 | AuthorActions::URL_PATH_EDIT, 68 | [ 69 | 'author_id' => $authorId 70 | ], 71 | 'some/url/here', 72 | ], 73 | ] 74 | ); 75 | $fieldName = 'avatar'; 76 | $avatarModel->setName($fieldName); 77 | $items = [ 78 | 'data' => [ 79 | 'items' => [ 80 | [ 81 | 'author_id' => $authorId, 82 | $fieldName => '/some/image.jpg', 83 | 'name' => 'test name', 84 | ] 85 | ] 86 | ] 87 | ]; 88 | $expectedResult = [ 89 | 'data' => [ 90 | 'items' => [ 91 | [ 92 | 'author_id' => $authorId, 93 | $fieldName => '/some/image.jpg', 94 | 'name' => 'test name', 95 | $fieldName . '_src' => 'http://example.com/base/path/some/image.jpg', 96 | $fieldName . '_alt' => 'test name', 97 | $fieldName . '_link' => 'some/url/here', 98 | $fieldName . '_orig_src' => 'http://example.com/base/path/some/image.jpg' 99 | ] 100 | ] 101 | ] 102 | ]; 103 | $items = $avatarModel->prepareDataSource($items); 104 | $this->assertEquals($expectedResult, $items); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Test/Unit/Ui/Component/Listing/Column/Store/OptionsTest.php: -------------------------------------------------------------------------------- 1 | systemStoreMock = $this->getMockBuilder(Store::class) 66 | ->disableOriginalConstructor() 67 | ->getMock(); 68 | $this->websiteMock = $this->getMock( 69 | Website::class, 70 | ['getId', 'getName'], 71 | [], 72 | '', 73 | false 74 | ); 75 | $this->groupMock = $this->getMock(Group::class, [], [], '', false); 76 | $this->storeMock = $this->getMock(StoreModel::class, [], [], '', false); 77 | $this->escaperMock = $this->getMock(Escaper::class, [], [], '', false); 78 | $this->options = new Options($this->systemStoreMock, $this->escaperMock); 79 | } 80 | 81 | /** 82 | * @test Sample\News\Ui\Component\Listing\Column\Store\Options::toOptionArray() 83 | */ 84 | public function testToOptionArray() 85 | { 86 | $websiteCollection = [$this->websiteMock]; 87 | $groupCollection = [$this->groupMock]; 88 | $storeCollection = [$this->storeMock]; 89 | 90 | $expectedOptions = [ 91 | [ 92 | 'label' => __('All Store Views'), 93 | 'value' => '0' 94 | ], 95 | [ 96 | 'label' => 'Main Website', 97 | 'value' => [ 98 | [ 99 | 'label' => ' Main Website Store', 100 | 'value' => [ 101 | [ 102 | 'label' => ' Default Store View', 103 | 'value' => '1' 104 | ] 105 | ] 106 | ] 107 | ] 108 | ] 109 | ]; 110 | 111 | $this->systemStoreMock->expects($this->once())->method('getWebsiteCollection')->willReturn($websiteCollection); 112 | $this->systemStoreMock->expects($this->once())->method('getGroupCollection')->willReturn($groupCollection); 113 | $this->systemStoreMock->expects($this->once())->method('getStoreCollection')->willReturn($storeCollection); 114 | 115 | $this->websiteMock->expects($this->atLeastOnce())->method('getId')->willReturn('1'); 116 | $this->websiteMock->expects($this->any())->method('getName')->willReturn('Main Website'); 117 | 118 | $this->groupMock->expects($this->atLeastOnce())->method('getWebsiteId')->willReturn('1'); 119 | $this->groupMock->expects($this->atLeastOnce())->method('getId')->willReturn('1'); 120 | $this->groupMock->expects($this->atLeastOnce())->method('getName')->willReturn('Main Website Store'); 121 | 122 | $this->storeMock->expects($this->atLeastOnce())->method('getGroupId')->willReturn('1'); 123 | $this->storeMock->expects($this->atLeastOnce())->method('getName')->willReturn('Default Store View'); 124 | $this->storeMock->expects($this->atLeastOnce())->method('getId')->willReturn('1'); 125 | 126 | $this->escaperMock->expects($this->atLeastOnce())->method('escapeHtml')->willReturnMap( 127 | [ 128 | ['Default Store View', null, 'Default Store View'], 129 | ['Main Website Store', null, 'Main Website Store'], 130 | ['Main Website', null, 'Main Website'] 131 | ] 132 | ); 133 | 134 | $this->assertEquals($expectedOptions, $this->options->toOptionArray()); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Test/Unit/Ui/DataProvider/Author/Form/Modifier/AuthorDataTest.php: -------------------------------------------------------------------------------- 1 | objectManager = new ObjectManager($this); 47 | $this->collectionFactoryMock = $this->getMockBuilder(CollectionFactory::class) 48 | ->disableOriginalConstructor() 49 | ->getMock(); 50 | 51 | $authorData = [ 52 | 'author_id' => 1, 53 | 'name' => 'test', 54 | 'avatar' => '/some/image.jpg', 55 | 'resume' => '/some/file.txt' 56 | ]; 57 | 58 | $mockAuthor = $this->getMock( 59 | Author::class, 60 | [], 61 | [], 62 | '', 63 | false 64 | ); 65 | $mockAuthor->method('getData')->willReturn($authorData); 66 | $mockAuthor->method('getId')->willReturn($authorData['author_id']); 67 | $mockAuthor->method('getAvatarUrl')->willReturn('http://example.com'.$authorData['avatar']); 68 | $mockAuthor->method('getAvatar')->willReturn($authorData['avatar']); 69 | $mockAuthor->method('getResumeUrl')->willReturn('http://example.com'.$authorData['resume']); 70 | $mockAuthor->method('getResume')->willReturn($authorData['resume']); 71 | 72 | $collectionMock = $this->objectManager->getCollectionMock( 73 | Collection::class, 74 | [$mockAuthor] 75 | ); 76 | $collectionMock->method('getItems')->willReturn([$mockAuthor]); 77 | $this->collectionFactoryMock->method('create')->willReturn($collectionMock); 78 | $this->authorDataModifier = new AuthorData($this->collectionFactoryMock); 79 | } 80 | 81 | /** 82 | * @test Sample\News\Ui\DataProvider\Author\Form\Modifier\AuthorData::AuthorData::modfyMeta() 83 | */ 84 | public function testModifyMeta() 85 | { 86 | $this->assertEquals( 87 | $this->getSampleData(), 88 | $this->authorDataModifier->modifyMeta($this->getSampleData()) 89 | ); 90 | } 91 | 92 | /** 93 | * @test modifyData method 94 | */ 95 | public function testModifiyData() 96 | { 97 | $expected = [ 98 | 'key' => 'value', 99 | '1' => [ 100 | 'author_id' => 1, 101 | 'name' => 'test', 102 | 'avatar' => [ 103 | 0 => [ 104 | 'name' => '/some/image.jpg', 105 | 'url' => 'http://example.com/some/image.jpg' 106 | ] 107 | ], 108 | 'resume' => [ 109 | 0 => [ 110 | 'name' => '/some/file.txt', 111 | 'url' => 'http://example.com/some/file.txt' 112 | ] 113 | ] 114 | ] 115 | ]; 116 | $data = $this->authorDataModifier->modifyData($this->getSampleData()); 117 | $this->assertEquals($expected, $data); 118 | } 119 | 120 | /** 121 | * @return array 122 | */ 123 | protected function getSampleData() 124 | { 125 | return ['key' => 'value']; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Ui/Component/Listing/Column/AuthorActions.php: -------------------------------------------------------------------------------- 1 | _urlBuilder = $urlBuilder; 65 | parent::__construct($context, $uiComponentFactory, $components, $data); 66 | } 67 | 68 | 69 | /** 70 | * Prepare Data Source 71 | * 72 | * @param array $dataSource 73 | * @return array 74 | */ 75 | public function prepareDataSource(array $dataSource) 76 | { 77 | if (isset($dataSource['data']['items'])) { 78 | foreach ($dataSource['data']['items'] as & $item) { 79 | if (isset($item['author_id'])) { 80 | $item[$this->getData('name')] = [ 81 | 'edit' => [ 82 | 'href' => $this->_urlBuilder->getUrl( 83 | static::URL_PATH_EDIT, 84 | [ 85 | 'author_id' => $item['author_id'] 86 | ] 87 | ), 88 | 'label' => __('Edit') 89 | ], 90 | 'delete' => [ 91 | 'href' => $this->_urlBuilder->getUrl( 92 | static::URL_PATH_DELETE, 93 | [ 94 | 'author_id' => $item['author_id'] 95 | ] 96 | ), 97 | 'label' => __('Delete'), 98 | 'confirm' => [ 99 | 'title' => __('Delete "${ $.$data.name }"'), 100 | 'message' => __('Are you sure you wan\'t to delete the Author "${ $.$data.name }" ?') 101 | ] 102 | ] 103 | ]; 104 | } 105 | } 106 | } 107 | return $dataSource; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Ui/Component/Listing/Column/Avatar.php: -------------------------------------------------------------------------------- 1 | imageModel = $imageModel; 45 | $this->urlBuilder = $urlBuilder; 46 | parent::__construct($context, $uiComponentFactory, $components, $data); 47 | } 48 | 49 | /** 50 | * Prepare Data Source 51 | * 52 | * @param array $dataSource 53 | * @return array 54 | */ 55 | public function prepareDataSource(array $dataSource) 56 | { 57 | if(isset($dataSource['data']['items'])) { 58 | $fieldName = $this->getData('name'); 59 | foreach($dataSource['data']['items'] as & $item) { 60 | $url = ''; 61 | if($item[$fieldName] != '') { 62 | $url = $this->imageModel->getBaseUrl().$this->imageModel->getBasePath().$item[$fieldName]; 63 | } 64 | $item[$fieldName . '_src'] = $url; 65 | $item[$fieldName . '_alt'] = $this->getAlt($item) ?: ''; 66 | $item[$fieldName . '_link'] = $this->urlBuilder->getUrl( 67 | 'sample_news/author/edit', 68 | ['author_id' => $item['author_id']] 69 | ); 70 | $item[$fieldName . '_orig_src'] = $url; 71 | } 72 | } 73 | 74 | return $dataSource; 75 | } 76 | 77 | /** 78 | * @param array $row 79 | * 80 | * @return null|string 81 | */ 82 | protected function getAlt($row) 83 | { 84 | $altField = $this->getData('config/altField') ?: self::ALT_FIELD; 85 | return isset($row[$altField]) ? $row[$altField] : null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Ui/Component/Listing/Column/Store/Options.php: -------------------------------------------------------------------------------- 1 | options !== null) { 37 | return $this->options; 38 | } 39 | 40 | $this->currentOptions['All Store Views']['label'] = __('All Store Views'); 41 | $this->currentOptions['All Store Views']['value'] = self::ALL_STORE_VIEWS; 42 | 43 | $this->generateCurrentOptions(); 44 | 45 | $this->options = array_values($this->currentOptions); 46 | 47 | return $this->options; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Ui/DataProvider/Author/Form/Modifier/AuthorData.php: -------------------------------------------------------------------------------- 1 | collection = $authorCollectionFactory->create(); 38 | } 39 | 40 | /** 41 | * @param array $meta 42 | * @return array 43 | */ 44 | public function modifyMeta(array $meta) 45 | { 46 | return $meta; 47 | } 48 | 49 | /** 50 | * @param array $data 51 | * @return array|mixed 52 | * @throws \Magento\Framework\Exception\LocalizedException 53 | */ 54 | public function modifyData(array $data) 55 | { 56 | $items = $this->collection->getItems(); 57 | /** @var $author \Sample\News\Model\Author */ 58 | foreach ($items as $author) { 59 | $_data = $author->getData(); 60 | if (isset($_data['avatar'])) { 61 | $avatar = []; 62 | $avatar[0]['name'] = $author->getAvatar(); 63 | $avatar[0]['url'] = $author->getAvatarUrl(); 64 | $_data['avatar'] = $avatar; 65 | } 66 | if (isset($_data['resume'])) { 67 | $resume = []; 68 | $resume[0]['name'] = $author->getResume(); 69 | $resume[0]['url'] = $author->getResumeUrl(); 70 | $_data['resume'] = $resume; 71 | } 72 | $author->setData($_data); 73 | $data[$author->getId()] = $_data; 74 | } 75 | return $data; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample/module-news", 3 | "description": "Magento 2 Sample crud module", 4 | "version": "2.0.0", 5 | "license": "MIT", 6 | "require": { 7 | "php": "~5.6.0|7.0.2|~7.0.6", 8 | "magento/module-backend": "100.1.*", 9 | "magento/module-cms": "101.0.*", 10 | "magento/module-ui": "100.1.*", 11 | "magento/module-store": "100.1.*", 12 | "magento/framework": "100.1.*", 13 | "magento/module-media-storage": "100.1.*", 14 | "magento/module-directory": "100.1.*", 15 | "magento/module-customer": "100.1.*", 16 | "magento/module-rss": "100.1.*" 17 | }, 18 | "type": "magento2-module", 19 | "repositories": [ 20 | { 21 | "type": "git", 22 | "url": "https://github.com/tzyganu/Magento2SampleModule" 23 | } 24 | ], 25 | "autoload": { 26 | "files": [ 27 | "registration.php" 28 | ], 29 | "psr-4": { 30 | "Sample\\News\\": "" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /etc/adminhtml/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | Sample\News\Ui\DataProvider\Author\Form\Modifier\AuthorData 26 | 10 27 | 28 | 29 | 30 | 31 | 32 | 33 | SampleNewsUiDataProviderAuthorFormModifierPool 34 | 35 | 36 | 37 | 38 | A total of %1 record(s) have been deleted. 39 | An error occurred while deleting record(s). 40 | 41 | 42 | 43 | 44 | A total of %1 authors have been disabled. 45 | An error occurred while disabling authors. 46 | 47 | 48 | 49 | 50 | A total of %1 authors have been enabled. 51 | An error occurred while enabling authors. 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | sample_news 28 | Sample_News::news 29 | 30 | 31 | 32 | 33 | Magento\Config\Model\Config\Source\Yesno 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Magento\Config\Model\Config\Source\Yesno 56 | 57 | 58 | 59 | in seconds 60 | 61 | 62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 1 25 | Authors 26 | Authors Description Here 27 | Authors Keywords Here 28 | authors.html 29 | author 30 | html 31 | 1 32 | 600 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | Sample\News\Controller\Router 26 | false 27 | 10 28 | 29 | 30 | 31 | 32 | 33 | 34 | Sample\News\Model\Author\Url::URL_PREFIX_CONFIG_PATH 35 | Sample\News\Model\Author\Url::URL_SUFFIX_CONFIG_PATH 36 | Sample\News\Model\Author\Url::LIST_URL_CONFIG_PATH 37 | Sample\News\Model\AuthorFactory 38 | author 39 | 40 | 41 | 42 | 43 | 44 | SampleNewsRoutingEntityAuthor 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /etc/frontend/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /i18l/en_US.csv: -------------------------------------------------------------------------------- 1 | "A total of %1 authors have been disabled.","A total of %1 authors have been disabled." 2 | "A total of %1 record(s) have been deleted.","A total of %1 record(s) have been deleted." 3 | "A total of %1 authors have been enabled.","A total of %1 authors have been enabled." 4 | "Add New Author","Add New Author" 5 | "All Store Views","All Store Views" 6 | "An error occurred while deleting record(s).","An error occurred while deleting record(s)." 7 | "An error occurred while disabling authors.","An error occurred while disabling authors." 8 | "An error occurred while enabling authors.","An error occurred while enabling authors." 9 | "Are you sure you want to delete selected items?","Are you sure you want to delete selected items?" 10 | "Are you sure you want to delete the Author ""${ $.$data.name }"" ?","Are you sure you want to delete the Author ""${ $.$data.name }"" ?" 11 | "Are you sure you want to do this?","Are you sure you want to do this?" 12 | "Author in Store views","Author in Store views" 13 | "Author Information","Author Information" 14 | Authors,Authors 15 | Avatar,Avatar 16 | "Award 1","Award 1" 17 | "Award 2","Award 2" 18 | "Award 3","Award 3" 19 | Awards,Awards 20 | Back,Back 21 | Biography,Biography 22 | Collaborator,Collaborator 23 | "Could not save the author: %1","Could not save the author: %1" 24 | Country,Country 25 | "Created At","Created At" 26 | "Date of birth","Date of birth" 27 | Delete,Delete 28 | "Delete Author","Delete Author" 29 | "Delete items","Delete items" 30 | "Delete ""${ $.$data.name }""","Delete ""${ $.$data.name }""" 31 | Disable,Disable 32 | DOB,DOB 33 | Edit,Edit 34 | "Edit Author","Edit Author" 35 | Employee,Employee 36 | Enable,Enable 37 | "Enter full name here","Enter full name here" 38 | "File can not be saved to the destination folder.","File can not be saved to the destination folder." 39 | Home,Home 40 | ID,ID 41 | "In RSS","In RSS" 42 | "Is Active","Is Active" 43 | label,label 44 | "Meta Description","Meta Description" 45 | "Meta Keywords","Meta Keywords" 46 | "Meta Title","Meta Title" 47 | Name,Name 48 | "New Author","New Author" 49 | News,News 50 | No,No 51 | "Please correct the data sent.","Please correct the data sent." 52 | "Requested author doesn't exist","Requested author doesn't exist" 53 | Reset,Reset 54 | Resume,Resume 55 | "Save Author","Save Author" 56 | "Save and Continue Edit","Save and Continue Edit" 57 | "Show In RSS","Show In RSS" 58 | "Something went wrong while getting the avatar url.","Something went wrong while getting the avatar url." 59 | "Something went wrong while getting the resume url.","Something went wrong while getting the resume url." 60 | "Something went wrong while saving the author.","Something went wrong while saving the author." 61 | "Something went wrong while saving the file(s).","Something went wrong while saving the file(s)." 62 | "Store View","Store View" 63 | "Subscribe to RSS Feed","Subscribe to RSS Feed" 64 | "There are no authors at this moment","There are no authors at this moment" 65 | "The author has been deleted.","The author has been deleted." 66 | "The author no longer exists.","The author no longer exists." 67 | "There was a problem deleting the author","There was a problem deleting the author" 68 | "There was a problem saving the author","There was a problem saving the author" 69 | Type,Type 70 | "Unable to remove author %1","Unable to remove author %1" 71 | "Updated At","Updated At" 72 | "URL Key","URL Key" 73 | "Url Key","Url Key" 74 | "We can't find a author to delete.","We can't find a author to delete." 75 | Yes,Yes 76 | "You saved the author","You saved the author" 77 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /view/adminhtml/layout/sample_news_author_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | Sample_News::authors 27 | 28 | 29 | 30 | 31 | complex 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /view/adminhtml/web/template/file-preview.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /view/adminhtml/web/template/image-preview.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 11 | 12 | 13 |
14 | 22 |
23 |
24 | 25 |
26 |
27 | x 28 |
29 |
30 | -------------------------------------------------------------------------------- /view/base/web/images/author/placeholder/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tzyganu/Magento2SampleModule/1047ae6ab3b5f607e3896521ff6b7a7ab47eb41a/view/base/web/images/author/placeholder/avatar.jpg -------------------------------------------------------------------------------- /view/frontend/layout/sample_news_author_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /view/frontend/layout/sample_news_author_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /view/frontend/templates/author/list.phtml: -------------------------------------------------------------------------------- 1 | 19 | 20 | getAuthors(); ?> 21 |
22 | getSize() > 0) :?> 23 |
getPagerHtml(); ?>
24 |
25 | 26 | 27 | 32 | 33 |
34 |
getPagerHtml(); ?>
35 | 36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /view/frontend/templates/author/view.phtml: -------------------------------------------------------------------------------- 1 | 19 | 20 | getCurrentAuthor();?> 21 |
22 |
23 | : getName();?> 24 |
25 | getAttributeText('type')) :?> 26 |
27 | : 28 |
29 | 30 | getProcessedBiography()) :?> 31 |
32 | : 33 |
34 | 35 | getDob()) :?> 36 |
37 | : formatDate($dob, \IntlDateFormatter::LONG);?> 38 |
39 | 40 | getAttributeText('awards')) :?> 41 |
42 | : 43 |
44 | 45 | getAttributeText('country')) :?> 46 |
47 | : 48 |
49 | 50 | getResume()) :?> 51 |
52 | 53 |
54 | 55 |
56 | getImage($_author, 'author_view', ['width' => 100, 'height' => 100, 'type' => 'avatar'])->toHtml(); ?> 57 |
58 |
59 | -------------------------------------------------------------------------------- /view/frontend/templates/image.phtml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | getCustomAttributes(); ?> 23 | src="getImageUrl(); ?>" 24 | width="getWidth(); ?>" 25 | height="getHeight(); ?>" 26 | alt="stripTags($block->getLabel(), null, true); ?>" /> 27 | -------------------------------------------------------------------------------- /view/frontend/templates/image_with_borders.phtml: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 23 | 25 | getCustomAttributes(); ?> 27 | src="getImageUrl(); ?>" 28 | width="getResizedImageWidth(); ?>" 29 | height="getResizedImageHeight(); ?>" 30 | alt="stripTags($block->getLabel(), null, true); ?>"/> 31 | 32 | -------------------------------------------------------------------------------- /view/frontend/templates/rss/link.phtml: -------------------------------------------------------------------------------- 1 | 19 | 20 | isRssEnabled()) : ?> 21 | 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------