diff --git a/Api/Data/PostInterface.php b/Api/Data/PostInterface.php new file mode 100644 index 0000000..fa6ef6f --- /dev/null +++ b/Api/Data/PostInterface.php @@ -0,0 +1,54 @@ +forwardFactory->create(); + return $forward->setController('post')->forward('list'); + } +} diff --git a/Controller/Post/Detail.php b/Controller/Post/Detail.php new file mode 100644 index 0000000..ca47c16 --- /dev/null +++ b/Controller/Post/Detail.php @@ -0,0 +1,27 @@ +eventManager->dispatch('rubenromao_blog_post_detail_view', [ + 'request' => $this->request, + ]); + + return $this->pageFactory->create(); + } +} diff --git a/Controller/Post/ListAction.php b/Controller/Post/ListAction.php new file mode 100644 index 0000000..395fe93 --- /dev/null +++ b/Controller/Post/ListAction.php @@ -0,0 +1,19 @@ +pageFactory->create(); + } +} diff --git a/Model/Post.php b/Model/Post.php new file mode 100644 index 0000000..c4cd7c5 --- /dev/null +++ b/Model/Post.php @@ -0,0 +1,39 @@ +_init(ResourceModel\Post::class); + } + + public function getTitle() + { + return $this->getData(self::TITLE); + } + + public function setTitle($title) + { + return $this->setData(self::TITLE, $title); + } + + public function getContent() + { + return $this->getData(self::CONTENT); + } + + public function setContent($content) + { + return $this->setData(self::CONTENT, $content); + } + + public function getCreatedAt() + { + return $this->getData(self::CREATED_AT); + } +} diff --git a/Model/PostRepository.php b/Model/PostRepository.php new file mode 100644 index 0000000..215d130 --- /dev/null +++ b/Model/PostRepository.php @@ -0,0 +1,55 @@ +postFactory->create(); + $this->postResourceModel->load($post, $id); + + if (!$post->getId()) { + throw new NoSuchEntityException(__('The blog post with "%1" ID doesn\'t exist.', $id)); + } + + return $post; + } + + public function save(PostInterface $post): PostInterface + { + try { + $this->postResourceModel->save($post); + } catch (\Exception $exception) { + throw new CouldNotSaveException(__($exception->getMessage())); + } + + return $post; + } + + public function deleteById(int $id): bool + { + $post = $this->getById($id); + + try { + $this->postResourceModel->delete($post); + } catch (\Exception $exception) { + throw new CouldNotDeleteException(__($exception->getMessage())); + } + + return true; + } +} diff --git a/Model/ResourceModel/Post.php b/Model/ResourceModel/Post.php new file mode 100644 index 0000000..7db6c69 --- /dev/null +++ b/Model/ResourceModel/Post.php @@ -0,0 +1,16 @@ +_init(self::MAIN_TABLE, self::ID_FIELD_NAME); + } +} diff --git a/Model/ResourceModel/Post/Collection.php b/Model/ResourceModel/Post/Collection.php new file mode 100644 index 0000000..16fd8cd --- /dev/null +++ b/Model/ResourceModel/Post/Collection.php @@ -0,0 +1,15 @@ +_init(Post::class, PostResourceModel::class); + } +} diff --git a/Observer/LogPostDetailView.php b/Observer/LogPostDetailView.php new file mode 100644 index 0000000..135df31 --- /dev/null +++ b/Observer/LogPostDetailView.php @@ -0,0 +1,22 @@ +getData('request'); + $this->logger->info('blog post detail viewed', [ + 'params' => $request->getParams(), + ]); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..32f4083 --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# Rubenromao_Blog module + +Custom Magento v2.4.6-p3 Blog module to cover Magento 2 concepts and design patterns. + +It covers the app structure, how routing & controllers work, how to extend core code, +dependency injection & interfaces, different design patterns and usages, ways to modify the page layout, and understand data management. + +The module is not intended to be used in production. +It is a sample module to be used as a reference for Magento 2 development. + +The module is based on the [Magento 2.4.6-p3](https://devdocs.magento.com/guides/v2.4/release-notes/bk-release-notes.html) version. + +It provides the following functionality: +Custom database table to store blog posts. +Custom model to manage blog posts. +Custom Web API endpoints to create, update, delete, and get blog posts. +Custom admin grid to manage blog posts. +Custom admin form to create and edit blog posts. +Custom frontend page to create a blog post. +Custom frontend page to edit a blog post. +Custom frontend page to delete a blog post. +Custom frontend page to display blog posts. + +## Installation details + +To install use composer or copy files manually. +It is recommended to install the module in a development environment first. +The + +### Install using composer + +``` +composer require rubenromao/blog +``` + +Run the following command to enable the module: + +``` +bin/magento module:enable Rubenromao_Blog +``` + +You must run the following commands after the module installation using magento-cli + + ``` + bin/magento setup:upgrade + bin/magento setup:di:compile + bin/magento setup:static-content:deploy -f (optional if you are in developer mode) + bin/magento cache:flush + ``` + +For information about a module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.4/install-gde/install/cli/install-cli-subcommands-enable.html). + +## Extensibility + +The Rubenromao_Blog module contains extensibility points that you can interact with. +Web API, Service contracts, plugins, events, and observers enable you to extend and customize the Magento application. +You can interact with the following extension points: + +Extension developers can interact with the Rubenromao_Blog module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Rubenromao_Blog module. + +### Layouts + +The module introduces layout handles in the `view/frontend/layout` directory. +You can extend these layouts in your custom modules and themes. + +For more information about a layout in Magento 2, see the [Layout documentation](https://devdocs.magento.com/guides/v2.4/frontend-dev-guide/layouts/layout-overview.html). + +### UI components + +You can extend product and category updates using the UI components located in the `view/adminhtml/ui_component` directory. +Or you can extend the UI components located in the `view/base/ui_component` directory. + +For information about a UI component in Magento 2, see [Overview of UI components](https://devdocs.magento.com/guides/v2.4/ui_comp_guide/bk-ui_comps.html). + +## Additional information + +The Rubenromao_Blog module creates a new database table `rubenromao_blog_post` during the installation process. +This table stores blog posts. + +For information about significant changes in patch releases, see [Release information](https://devdocs.magento.com/guides/v2.4/release-notes/bk-release-notes.html). diff --git a/Setup/Patch/Data/PopulateBlogPosts.php b/Setup/Patch/Data/PopulateBlogPosts.php new file mode 100644 index 0000000..6741d31 --- /dev/null +++ b/Setup/Patch/Data/PopulateBlogPosts.php @@ -0,0 +1,42 @@ +moduleDataSetup->startSetup(); + + $post = $this->postFactory->create(); + $post->setData([ + 'title' => 'An awesome post', + 'content' => 'This is totally awesome!', + ]); + $this->postRepository->save($post); + + $this->moduleDataSetup->endSetup(); + } +} diff --git a/Setup/Patch/Data/PopulateBlogPosts1.php b/Setup/Patch/Data/PopulateBlogPosts1.php new file mode 100644 index 0000000..a7c1884 --- /dev/null +++ b/Setup/Patch/Data/PopulateBlogPosts1.php @@ -0,0 +1,52 @@ +moduleDataSetup->startSetup(); + + $posts = [ + [ + 'title' => 'Today is sunny', + 'content' => 'The weather has been great all week.', + ], + [ + 'title' => 'My movie review', + 'content' => 'I give this movie 5 out of 5 stars!', + ], + ]; + + foreach ($posts as $postData) { + $post = $this->postFactory->create(); + $post->setData($postData); + $this->postRepository->save($post); + } + + $this->moduleDataSetup->endSetup(); + } +} diff --git a/ViewModel/Post.php b/ViewModel/Post.php new file mode 100644 index 0000000..72c0f96 --- /dev/null +++ b/ViewModel/Post.php @@ -0,0 +1,34 @@ +collection->getItems(); + } + + public function getCount(): int + { + return $this->collection->count(); + } + + public function getDetail(): PostInterface + { + $id = (int) $this->request->getParam('id'); + return $this->postRepository->getById($id); + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..80f394c --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "rubenromao/m2-module-blog", + "version": "1.0.0", + "description": "Custom Blog module that covers most of the M2 concepts and design patterns.", + "type": "magento2-module", + "require": { + "magento/framework": "*" + }, + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Rubenromao\\Blog\\": "" + } + } +} diff --git a/etc/db_schema.xml b/etc/db_schema.xml new file mode 100644 index 0000000..3229f7e --- /dev/null +++ b/etc/db_schema.xml @@ -0,0 +1,14 @@ + + + + + + + + + + +
+
diff --git a/etc/db_schema_whitelist.json b/etc/db_schema_whitelist.json new file mode 100644 index 0000000..3d97214 --- /dev/null +++ b/etc/db_schema_whitelist.json @@ -0,0 +1,13 @@ +{ + "rubenromao_blog_post": { + "column": { + "id": true, + "title": true, + "content": true, + "created_at": true + }, + "constraint": { + "PRIMARY": true + } + } +} diff --git a/etc/di.xml b/etc/di.xml new file mode 100644 index 0000000..a2b3bec --- /dev/null +++ b/etc/di.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/etc/frontend/events.xml b/etc/frontend/events.xml new file mode 100644 index 0000000..4d669cc --- /dev/null +++ b/etc/frontend/events.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/etc/frontend/routes.xml b/etc/frontend/routes.xml new file mode 100644 index 0000000..10834ed --- /dev/null +++ b/etc/frontend/routes.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/etc/module.xml b/etc/module.xml new file mode 100644 index 0000000..ea56d30 --- /dev/null +++ b/etc/module.xml @@ -0,0 +1,5 @@ + + + + diff --git a/etc/webapi.xml b/etc/webapi.xml new file mode 100644 index 0000000..428e876 --- /dev/null +++ b/etc/webapi.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/i18n/en_US.csv b/i18n/en_US.csv new file mode 100644 index 0000000..e39035e --- /dev/null +++ b/i18n/en_US.csv @@ -0,0 +1 @@ +"You have no items in your wish list.","You don't have any items in your wish list." diff --git a/registration.php b/registration.php new file mode 100644 index 0000000..db7b3d1 --- /dev/null +++ b/registration.php @@ -0,0 +1,10 @@ + + + + + + + Rubenromao\Blog\ViewModel\Post + + + + + + + Rubenromao\Blog\ViewModel\Post + + + + + + + diff --git a/view/frontend/layout/blog_post_list.xml b/view/frontend/layout/blog_post_list.xml new file mode 100644 index 0000000..2e965fc --- /dev/null +++ b/view/frontend/layout/blog_post_list.xml @@ -0,0 +1,14 @@ + + + + + + + Rubenromao\Blog\ViewModel\Post + + + + + diff --git a/view/frontend/templates/post/detail.phtml b/view/frontend/templates/post/detail.phtml new file mode 100644 index 0000000..51473d7 --- /dev/null +++ b/view/frontend/templates/post/detail.phtml @@ -0,0 +1,12 @@ +getData('post_vm'); +$post = $postVm->getDetail(); +?> +
+

escapeHtml($post->getTitle()) ?>

+

getCreatedAt()) ?>

+

escapeHtml($post->getContent(), ['em', 'p', 'strong']) ?>

+
diff --git a/view/frontend/templates/post/list.phtml b/view/frontend/templates/post/list.phtml new file mode 100644 index 0000000..05c537d --- /dev/null +++ b/view/frontend/templates/post/list.phtml @@ -0,0 +1,19 @@ +getData('post_vm'); +?> +
+

+ +

getCount()) ?>

+
diff --git a/view/frontend/templates/post/sidebar.phtml b/view/frontend/templates/post/sidebar.phtml new file mode 100644 index 0000000..a869a32 --- /dev/null +++ b/view/frontend/templates/post/sidebar.phtml @@ -0,0 +1,18 @@ +getData('post_vm'); +?> +
+

+ +
diff --git a/view/frontend/templates/wishlist/sidebar.phtml b/view/frontend/templates/wishlist/sidebar.phtml new file mode 100644 index 0000000..36124b6 --- /dev/null +++ b/view/frontend/templates/wishlist/sidebar.phtml @@ -0,0 +1,103 @@ + +getData('wishlistDataViewModel'); +?> +isAllow()): ?> +
+
+ escapeHtml($block->getTitle()) ?> + + + +
+
+ escapeHtml(__('Last Added Items')) ?> + +
    +
  1. +
    + + + +
    + + + + + +
    +
    + +
    + + + escapeHtml(__('Add to Cart')) ?> + + + + + +
    + + +
    +
    +
    +
  2. +
+ + + +
escapeHtml(__('You have no items in your wish list.')) ?>
+
+ + escapeHtml(__('Browse for items')) ?> + +
+ +
+
+ +