├── Api ├── SubscriptionInterface.php └── SubscriptionRepositoryInterface.php ├── Block └── Product │ └── View.php ├── Cron └── Email.php ├── Helper └── Data.php ├── Model ├── ResourceModel │ ├── Subscription.php │ └── Subscription │ │ └── Collection.php ├── Subscription.php ├── SubscriptionInterface.php ├── SubscriptionManagement.php └── SubscriptionRepository.php ├── Observer └── ProductSaveAfter.php ├── README.md ├── Setup ├── InstallData.php └── InstallSchema.php ├── composer.json ├── etc ├── adminhtml │ └── system.xml ├── config.xml ├── crontab.xml ├── di.xml ├── email_templates.xml ├── events.xml ├── module.xml └── webapi.xml ├── i18n └── en_US.csv ├── registration.php └── view └── frontend ├── email └── back_in_stock.html ├── layout └── catalog_product_view_type_configurable.xml ├── templates └── product │ └── configurable │ ├── subscribe_form.phtml │ └── subscribe_link.phtml └── web └── css └── subscribe-form.css /Api/SubscriptionInterface.php: -------------------------------------------------------------------------------- 1 | stockItem = $stockItem; 49 | $this->moduleHelper = $moduleHelper; 50 | 51 | parent::__construct($context, $urlEncoder, $jsonEncoder, $string, $productHelper, $productTypeConfig, $localeFormat, $customerSession, $productRepository, $priceCurrency, $data); 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function getServiceUrl() 58 | { 59 | $storeCode = $this->_storeManager->getStore()->getCode(); 60 | 61 | return '/rest/' . $storeCode . '/V1/in-stock-notify/:email/subscribe'; 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getHeaderContent() 68 | { 69 | $blockId = $this->moduleHelper->getPopupHeaderCmsBlockId(); 70 | 71 | if (!$blockId) { 72 | return ''; 73 | } 74 | 75 | return $this->getLayout() 76 | ->createBlock(\Magento\Cms\Block\Block::class) 77 | ->setBlockId($blockId) 78 | ->toHtml(); 79 | } 80 | 81 | /** 82 | * @return array 83 | */ 84 | public function getConfigurableAttributes() 85 | { 86 | $sortingOrder = $this->moduleHelper->getAttributesSortingOrder(); 87 | 88 | $attributes = $this->getProduct()->getTypeInstance()->getUsedProductAttributes($this->getProduct()); 89 | 90 | usort($attributes, function ($a, $b) use ($sortingOrder) { 91 | return array_search($a->getAttributeCode(), $sortingOrder) - array_search($b->getAttributeCode(), $sortingOrder); 92 | }); 93 | 94 | return $attributes; 95 | } 96 | 97 | public function getConfigurableAttributeOptions($attribute) 98 | { 99 | $options = []; 100 | 101 | foreach ($this->getOutOfStockSimpleProducts() as $product) { 102 | if (!isset($options[$product->getData($attribute->getAttributeCode())])) { 103 | $options[$product->getData($attribute->getAttributeCode())] = [ 104 | 'label' => $product->getResource()->getAttribute($attribute->getAttributeCode()) 105 | ->getFrontend()->getValue($product), 106 | 'product_ids' => [] 107 | ]; 108 | } 109 | 110 | $options[$product->getData($attribute->getAttributeCode())]['product_ids'][] = $product->getId(); 111 | } 112 | 113 | return $options; 114 | } 115 | 116 | /** 117 | * @return Product[] 118 | */ 119 | public function getSimpleProducts() 120 | { 121 | return $this->getProduct()->getTypeInstance()->getUsedProducts($this->getProduct()); 122 | } 123 | 124 | /** 125 | * @return array 126 | */ 127 | public function getOutOfStockSimpleProducts() 128 | { 129 | return array_filter($this->getSimpleProducts(), function ($product) { 130 | $websiteId = $this->getProduct()->getStore()->getWebsiteId(); 131 | 132 | return !$this->stockItem->verifyStock($product->getId(), $websiteId) || 133 | !$this->stockItem->getStockQty($product->getId(), $websiteId); 134 | }); 135 | } 136 | 137 | /** 138 | * @inheritDoc 139 | */ 140 | protected function _toHtml() 141 | { 142 | $isConfigurable = $this->getProduct()->getTypeId() == Configurable::TYPE_CODE; 143 | $isModuleEnabled = $this->moduleHelper->isEnabled(); 144 | 145 | if (!$isConfigurable || !$isModuleEnabled) { 146 | return ''; 147 | } 148 | 149 | if (!count($this->getOutOfStockSimpleProducts())) { 150 | return ''; 151 | } 152 | 153 | return parent::_toHtml(); 154 | } 155 | } -------------------------------------------------------------------------------- /Cron/Email.php: -------------------------------------------------------------------------------- 1 | moduleHelper = $moduleHelper; 42 | $this->subscriptionsFactory = $subscriptionsFactory; 43 | $this->subscriptionRepository = $subscriptionRepository; 44 | $this->logger = $logger; 45 | } 46 | 47 | /** 48 | * @param Subscription $subscription 49 | * @return string 50 | */ 51 | private function getSubscriptionInfo(Subscription $subscription) 52 | { 53 | return 'subscription for email: ' . $subscription->getEmail() . 54 | ', product_id: ' . $subscription->getProductId(); 55 | } 56 | 57 | /** 58 | * @param $msg 59 | * @return string 60 | */ 61 | private function formatMessage($msg) 62 | { 63 | return self::LOG_PREFIX . $msg; 64 | } 65 | 66 | /** 67 | * @param $msg 68 | */ 69 | private function info($msg) 70 | { 71 | $this->logger->info($this->formatMessage($msg)); 72 | } 73 | 74 | /** 75 | * @param $msg 76 | */ 77 | private function error($msg) 78 | { 79 | $this->logger->error($this->formatMessage($msg)); 80 | } 81 | 82 | /** 83 | * Send emails with subscriptions 84 | */ 85 | public function execute() 86 | { 87 | $this->info('Check cron mailer'); 88 | 89 | if (!$this->moduleHelper->isScheduledNotifications()) { 90 | return; 91 | } 92 | 93 | $this->info('Start mailing'); 94 | 95 | $subscriptions = $this->subscriptionsFactory->create(); 96 | 97 | foreach ($subscriptions as $subscription) { 98 | $this->info('Check ' . $this->getSubscriptionInfo($subscription)); 99 | if ($subscription->isReady()) { 100 | $this->info('Ready for process ' . $this->getSubscriptionInfo($subscription)); 101 | try { 102 | $subscription->proceed(); 103 | 104 | $this->info('Was send ' . $this->getSubscriptionInfo($subscription)); 105 | 106 | $this->subscriptionRepository->delete($subscription); 107 | } catch (\Exception $e) { 108 | $this->error( 109 | 'Error for ' . $this->getSubscriptionInfo($subscription) . 110 | 'Exception: ' . $e->getMessage() 111 | ); 112 | } 113 | } else { 114 | $this->info('Not ready for process ' . $this->getSubscriptionInfo($subscription)); 115 | } 116 | } 117 | 118 | $this->info('Stop mailing'); 119 | } 120 | } -------------------------------------------------------------------------------- /Helper/Data.php: -------------------------------------------------------------------------------- 1 | searchCriteriaBuilder = $searchCriteriaBuilder; 60 | $this->subscriptionRepository = $subscriptionRepository; 61 | $this->transportBuilder = $transportBuilder; 62 | $this->inlineTranslation = $inlineTranslation; 63 | $this->templatesFactory = $templatesFactory; 64 | 65 | parent::__construct($context); 66 | } 67 | 68 | /** 69 | * @return bool 70 | */ 71 | public function isEnabled() 72 | { 73 | return $this->scopeConfig->isSetFlag( 74 | self::XML_PATH_IS_ENABLED, 75 | ScopeInterface::SCOPE_STORE 76 | ); 77 | } 78 | 79 | /** 80 | * @return bool 81 | */ 82 | public function isScheduledNotifications() 83 | { 84 | return $this->scopeConfig->isSetFlag( 85 | self::XML_PATH_IS_SCHEDULED, 86 | ScopeInterface::SCOPE_STORE 87 | ); 88 | } 89 | 90 | /** 91 | * @param Subscription $subscription 92 | * @return array 93 | */ 94 | protected function getEmailTemplateVariables(Subscription $subscription) 95 | { 96 | return [ 97 | 'product' => $subscription->getProduct() 98 | ]; 99 | } 100 | 101 | /** 102 | * @return string 103 | */ 104 | public function getEmailFrom() 105 | { 106 | return $this->scopeConfig->getValue( 107 | self::XML_PATH_EMAIL_FROM, 108 | ScopeInterface::SCOPE_STORE 109 | ); 110 | } 111 | 112 | /** 113 | * @return string 114 | */ 115 | public function getNameFrom() 116 | { 117 | return $this->scopeConfig->getValue( 118 | self::XML_PATH_NAME_FROM, 119 | ScopeInterface::SCOPE_STORE 120 | ); 121 | } 122 | 123 | /** 124 | * @param Subscription $subscription 125 | */ 126 | public function sendMail(Subscription $subscription) 127 | { 128 | $this->inlineTranslation->suspend(); 129 | 130 | $template = $this->templatesFactory->create()->addFieldToFilter('template_code', self::EMAIL_TEMPLATE)->getFirstItem(); 131 | 132 | $identifier = $template->getId() ? $template->getId() : self::EMAIL_TEMPLATE; 133 | 134 | $transport = $this->transportBuilder->setTemplateIdentifier($identifier) 135 | ->setTemplateOptions([ 136 | 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, 137 | 'store' => $subscription->getStoreId() 138 | ])->setTemplateVars($this->getEmailTemplateVariables($subscription) 139 | )->setFrom([ 140 | 'email' => $this->getEmailFrom(), 141 | 'name' => $this->getNameFrom(), 142 | ])->addTo($subscription->getEmail()) 143 | ->getTransport(); 144 | 145 | $transport->sendMessage(); 146 | 147 | $this->inlineTranslation->resume(); 148 | } 149 | 150 | /** 151 | * @return array 152 | */ 153 | public function getAttributesSortingOrder() 154 | { 155 | $rawValue = $this->scopeConfig->getValue( 156 | self::XML_PATH_ATTRIBUTES_SORTING_ORDER, 157 | ScopeInterface::SCOPE_STORE 158 | ); 159 | 160 | return array_map('trim', explode(',', $rawValue)); 161 | } 162 | 163 | /** 164 | * @return string 165 | */ 166 | public function getPopupHeaderCmsBlockId() 167 | { 168 | return $this->scopeConfig->getValue( 169 | self::XML_PATH_POPUP_HEADER_CMS_BLOCK_ID, 170 | ScopeInterface::SCOPE_STORE 171 | ); 172 | } 173 | } 174 | 175 | -------------------------------------------------------------------------------- /Model/ResourceModel/Subscription.php: -------------------------------------------------------------------------------- 1 | _init(self::TABLE_NAME, self::ID_FIELD_NAME); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Model/ResourceModel/Subscription/Collection.php: -------------------------------------------------------------------------------- 1 | _init( 12 | \JustCoded\BackInStockConfigurable\Model\Subscription::class, 13 | \JustCoded\BackInStockConfigurable\Model\ResourceModel\Subscription::class 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Model/Subscription.php: -------------------------------------------------------------------------------- 1 | moduleHelper = $moduleHelper; 46 | $this->productRepository = $productRepository; 47 | $this->stockRegistry = $stockRegistry; 48 | 49 | parent::__construct($context, $registry, $resource, $resourceCollection, $data); 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | protected function _construct() 56 | { 57 | $this->_init('JustCoded\BackInStockConfigurable\Model\ResourceModel\Subscription'); 58 | } 59 | 60 | /** 61 | * @inheritdoc 62 | */ 63 | public function getIdentities() 64 | { 65 | return [self::CACHE_TAG . '_' . $this->getId()]; 66 | } 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | public function proceed() 72 | { 73 | $this->moduleHelper->sendMail($this); 74 | } 75 | 76 | /** 77 | * @inheritdoc 78 | */ 79 | public function getProduct() 80 | { 81 | return $this->productRepository->getById($this->getProductId()); 82 | } 83 | 84 | /** 85 | * @inheritdoc 86 | */ 87 | public function isReady($product = null) 88 | { 89 | if (!$product) { 90 | $product = $this->getProduct(); 91 | } 92 | 93 | $stockData = $product->getStockData(); 94 | 95 | if ($stockData) { 96 | $stockItem = new DataObject($stockData); 97 | } else { 98 | $stockItem = $this->stockRegistry->getStockItem($product->getId(), $product->getStore()->getWebsiteId()); 99 | } 100 | 101 | return 0 < $stockItem->getQty() && $stockItem->getIsInStock(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Model/SubscriptionInterface.php: -------------------------------------------------------------------------------- 1 | subscriptionRepository = $subscriptionRepository; 55 | $this->subscriptionFactory = $subscriptionFactory; 56 | $this->productRepository = $productRepository; 57 | $this->searchCriteriaBuilder = $searchCriteriaBuilder; 58 | $this->jsonHelper = $jsonHelper; 59 | $this->storeManager = $storeManager; 60 | } 61 | 62 | /** 63 | * @param array $simple 64 | * @param array $configurable 65 | * 66 | * @return Product[] 67 | */ 68 | protected function findProductsByOptions($simple, $configurable) 69 | { 70 | $configurableProduct = $this->productRepository->getById($configurable['id']); 71 | 72 | return array_filter( 73 | $configurableProduct->getTypeInstance()->getUsedProducts($configurableProduct), 74 | function ($product) use ($simple) { 75 | return $product->getId() === $simple['id']; 76 | } 77 | ); 78 | } 79 | 80 | /** 81 | * @inheritdoc 82 | */ 83 | public function subscribe( 84 | $email, 85 | $simple, 86 | $configurable 87 | ) { 88 | $products = $this->findProductsByOptions( 89 | $this->jsonHelper->jsonDecode($simple), 90 | $this->jsonHelper->jsonDecode($configurable) 91 | ); 92 | 93 | foreach ($products as $product) { 94 | if (!$product->getId()) { 95 | throw new NoSuchEntityException(__('Can\'t find product with such criteria.')); 96 | } 97 | 98 | if (!$this->subscriptionRepository->getByEmailAndProductId($email, $product->getId())) { 99 | $subscription = $this->subscriptionFactory->create() 100 | ->setEmail($email) 101 | ->setProductId($product->getId()) 102 | ->setStoreId($this->storeManager->getStore()->getId()); 103 | 104 | $this->subscriptionRepository->save($subscription); 105 | } 106 | } 107 | 108 | return ['success' => true]; 109 | } 110 | } -------------------------------------------------------------------------------- /Model/SubscriptionRepository.php: -------------------------------------------------------------------------------- 1 | objectFactory = $objectFactory; 46 | $this->collectionFactory = $collectionFactory; 47 | $this->searchResultsFactory = $searchResultsFactory; 48 | $this->searchCriteriaBuilder = $searchCriteriaBuilder; 49 | } 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | public function save(SubscriptionInterface $object) 55 | { 56 | try { 57 | $object->save(); 58 | } catch (\Exception $e) { 59 | throw new CouldNotSaveException(__($e->getMessage())); 60 | } 61 | 62 | return $object; 63 | } 64 | 65 | /** 66 | * @inheritdoc 67 | */ 68 | public function getById($id) 69 | { 70 | $object = $this->objectFactory->create(); 71 | $object->load($id); 72 | if (!$object->getId()) { 73 | throw new NoSuchEntityException(__('Object with id "%1" does not exist.', $id)); 74 | } 75 | 76 | return $object; 77 | } 78 | 79 | /** 80 | * @inheritdoc 81 | */ 82 | public function getByEmailAndProductId($email, $productId) 83 | { 84 | $searchCriteria = $this->searchCriteriaBuilder 85 | ->addFilter('email', $email) 86 | ->addFilter('product_id', $productId) 87 | ->create(); 88 | 89 | $searchResult = $this->getList($searchCriteria)->getItems(); 90 | 91 | if ($searchResult && isset($searchResult[0])) { 92 | return $searchResult[0]->getId(); 93 | } 94 | 95 | return null; 96 | } 97 | 98 | /** 99 | * @inheritdoc 100 | */ 101 | public function delete(SubscriptionInterface $object) 102 | { 103 | try { 104 | $object->delete(); 105 | } catch (\Exception $exception) { 106 | throw new CouldNotDeleteException(__($exception->getMessage())); 107 | } 108 | 109 | return true; 110 | } 111 | 112 | /** 113 | * @inheritdoc 114 | */ 115 | public function deleteById($id) 116 | { 117 | return $this->delete($this->getById($id)); 118 | } 119 | 120 | /** 121 | * @inheritdoc 122 | */ 123 | public function getList(SearchCriteria $criteria) 124 | { 125 | $searchResults = $this->searchResultsFactory->create(); 126 | $searchResults->setSearchCriteria($criteria); 127 | $collection = $this->collectionFactory->create(); 128 | foreach ($criteria->getFilterGroups() as $filterGroup) { 129 | $fields = []; 130 | $conditions = []; 131 | foreach ($filterGroup->getFilters() as $filter) { 132 | $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq'; 133 | $fields[] = $filter->getField(); 134 | $conditions[] = [$condition => $filter->getValue()]; 135 | } 136 | if ($fields) { 137 | $collection->addFieldToFilter($fields, $conditions); 138 | } 139 | } 140 | $searchResults->setTotalCount($collection->getSize()); 141 | $sortOrders = $criteria->getSortOrders(); 142 | if ($sortOrders) { 143 | /** @var SortOrder $sortOrder */ 144 | foreach ($sortOrders as $sortOrder) { 145 | $collection->addOrder( 146 | $sortOrder->getField(), 147 | ($sortOrder->getDirection() == SortOrder::SORT_ASC) ? 'ASC' : 'DESC' 148 | ); 149 | } 150 | } 151 | $collection->setCurPage($criteria->getCurrentPage()); 152 | $collection->setPageSize($criteria->getPageSize()); 153 | $objects = []; 154 | foreach ($collection as $objectModel) { 155 | $objects[] = $objectModel; 156 | } 157 | $searchResults->setItems($objects); 158 | 159 | return $searchResults; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Observer/ProductSaveAfter.php: -------------------------------------------------------------------------------- 1 | searchCriteriaBuilder = $searchCriteriaBuilder; 35 | $this->subscriptionRepository = $subscriptionRepository; 36 | $this->moduleHelper = $moduleHelper; 37 | } 38 | 39 | /** 40 | * Process product notifications 41 | * 42 | * @inheritdoc 43 | */ 44 | public function execute(Observer $observer) 45 | { 46 | if ($this->moduleHelper->isScheduledNotifications()) { 47 | return; 48 | } 49 | 50 | $product = $observer->getProduct(); 51 | 52 | $searchCriteria = $this->searchCriteriaBuilder 53 | ->addFilter('product_id', $product->getId()) 54 | ->create(); 55 | 56 | $subscriptions = $this->subscriptionRepository->getList($searchCriteria)->getItems(); 57 | 58 | /** @var Subscription $subscription */ 59 | foreach ($subscriptions as $subscription) { 60 | if ($subscription->isReady($product)) { 61 | $subscription->proceed(); 62 | 63 | $this->subscriptionRepository->delete($subscription); 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | Default product alerts in Magento 2 work only for the whole configurable products. This module allows customers to select specific configurations to get notified about. 4 | 5 | ## Description 6 | 7 | A configurable product has a “Can’t find your configuration?” link that opens a product alert popup and will be included at the end of `product.info.extrahint` container by default. 8 | 9 | ![alt tag](https://i.imgur.com/HI2GCjA.png) 10 | 11 | Parameters in the product alert popup have dependency on the first attribute. Attribute order can be changed in the module settings. 12 | 13 | ![alt tag](https://i.imgur.com/hBDKHk4.png) 14 | 15 | ## Installation 16 | 17 | This module is composer ready so you can install it via command (do not forget to add this repo to the `composer.json` before): 18 | 19 | ```sh 20 | composer require justcoded/backinstock-configurable:* 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### General settings 26 | 27 | Enable the module: 28 | 29 | `Stores -> Configuration -> JUSTCODED -> Back in Stock Configurable -> Enable` 30 | 31 | Set the attribute order in the subscription popup: 32 | 33 | `Stores -> Configuration -> JUSTCODED -> Back in Stock Configurable -> Attributes Sorting Order Inside of Subscribe Popup` 34 | 35 | Set CMS block id of popup header: 36 | 37 | `Stores -> Configuration -> JUSTCODED -> Back in Stock Configurable -> Header CMS Block Identifier of Subscribe Popup` 38 | 39 | Enable product alert sending by cron (otherwise they are sent on product save process which may take longer): 40 | 41 | `Stores -> Configuration -> JUSTCODED -> Back in Stock Configurable -> Send Notifications by Schedule` 42 | 43 | If product alerts are sent by cron, cron expression to trigger the cron is set under : 44 | 45 | `Stores -> Configuration -> JUSTCODED -> Back in Stock Configurable -> Send Notification Schedule` 46 | 47 | ## Email templates 48 | 49 | If you want to customize email templates for product alerts, just create a new email template `back_in_stock_configurable_notification_email_template` or load a default template Back In Stock Configurable Notification and use it as an example. 50 | 51 | ## Compatibility 52 | 53 | Fully tested with Magento 2.1.6 54 | 55 | ## Contact 56 | 57 | Follow our blog at [http://justcoded.com/blog](http://justcoded.com/blog) 58 | 59 | ## Maintainers 60 | 61 | - [Oleg Biriukov]() 62 | 63 | ## License 64 | 65 | The MIT License (MIT) 66 | 67 | Copyright © 2017 JustCoded 68 | 69 | Permission is hereby granted free of charge to any person obtaining a copy of this software and associated documentation files (the "Software") to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 70 | 71 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 72 | 73 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Setup/InstallData.php: -------------------------------------------------------------------------------- 1 | Please select the options and enter your email, and we will notify you once this product is back in stock.'; 15 | const DEFAULT_POPUP_HEADER_CMS_BLOCK_TITLE = 'Back In Stock Configurable Popup Header'; 16 | 17 | /** 18 | * @var BlockFactory 19 | */ 20 | private $blockFactory; 21 | 22 | /** 23 | * @var BlockRepository 24 | */ 25 | private $blockRepository; 26 | 27 | public function __construct( 28 | BlockFactory $blockFactory, 29 | BlockRepository $blockRepository 30 | ) { 31 | $this->blockFactory = $blockFactory; 32 | $this->blockRepository = $blockRepository; 33 | } 34 | 35 | /** 36 | * @param ModuleDataSetup $installer 37 | */ 38 | public function installPopupCmsBlocks(ModuleDataSetup $installer) 39 | { 40 | $block = $this->blockFactory->create()->setData([ 41 | 'identifier' => self::DEFAULT_POPUP_HEADER_CMS_BLOCK_ID, 42 | 'content' => self::DEFAULT_POPUP_HEADER_CMS_BLOCK_CONTENT, 43 | 'title' => self::DEFAULT_POPUP_HEADER_CMS_BLOCK_TITLE, 44 | 'is_active' => 1, 45 | 'stores' => [0] 46 | ]); 47 | 48 | $this->blockRepository->save($block); 49 | } 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | public function install(ModuleDataSetup $setup, ModuleContext $context) 55 | { 56 | $installer = $setup; 57 | $installer->startSetup(); 58 | 59 | $this->installPopupCmsBlocks($installer); 60 | 61 | $installer->endSetup(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Setup/InstallSchema.php: -------------------------------------------------------------------------------- 1 | getConnection()->newTable( 20 | $installer->getTable(Subscription::TABLE_NAME) 21 | )->addColumn( 22 | Subscription::ID_FIELD_NAME, 23 | Table::TYPE_INTEGER, 24 | null, 25 | [ 'identity' => true, 'nullable' => false, 'primary' => true, 'unsigned' => true, ], 26 | 'Entity ID' 27 | )->addColumn( 28 | 'email', 29 | Table::TYPE_TEXT, 30 | 255, 31 | [ 'nullable' => false, ], 32 | 'Email' 33 | )->addColumn( 34 | 'product_id', 35 | Table::TYPE_INTEGER, 36 | 255, 37 | [ 'nullable' => false, ], 38 | 'Product Id' 39 | )->addColumn( 40 | 'store_id', 41 | Table::TYPE_SMALLINT, 42 | null, 43 | ['unsigned' => true, 'nullable' => true], 44 | 'Store ID' 45 | )->addColumn( 46 | 'creation_time', 47 | Table::TYPE_TIMESTAMP, 48 | null, 49 | [ 'nullable' => false, 'default' => Table::TIMESTAMP_INIT, ], 50 | 'Creation Time' 51 | )->addColumn( 52 | 'update_time', 53 | Table::TYPE_TIMESTAMP, 54 | null, 55 | [ 'nullable' => false, 'default' => Table::TIMESTAMP_INIT_UPDATE, ], 56 | 'Modification Time' 57 | )->addColumn( 58 | 'is_active', 59 | Table::TYPE_SMALLINT, 60 | null, 61 | [ 'nullable' => false, 'default' => '1', ], 62 | 'Is Active' 63 | )->addIndex( 64 | $installer->getIdxName( 65 | Subscription::TABLE_NAME, 66 | ['email', 'product_id'], 67 | AdapterInterface::INDEX_TYPE_UNIQUE 68 | ), 69 | ['email', 'product_id'], 70 | ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] 71 | )->addIndex($installer->getIdxName(Subscription::TABLE_NAME, ['store_id']), ['store_id'] 72 | )->addForeignKey( 73 | $installer->getFkName(Subscription::TABLE_NAME, 'store_id', 'store', 'store_id'), 74 | 'store_id', 75 | $installer->getTable('store'), 76 | 'store_id', 77 | Table::ACTION_CASCADE, 78 | Table::ACTION_CASCADE 79 | ); 80 | 81 | $installer->getConnection()->createTable($table); 82 | } 83 | 84 | /** 85 | * @inheritdoc 86 | */ 87 | public function install(SchemaSetup $setup, ModuleContext $context) 88 | { 89 | $installer = $setup; 90 | $installer->startSetup(); 91 | 92 | $this->installSubscriptionsTable($installer); 93 | 94 | $installer->endSetup(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "justcoded/backinstock-configurable", 3 | "description": "Magento back in stock notifications for configurable product", 4 | "require": { 5 | "php": "~5.5.22|~5.6.0|7.0.2|7.0.4|~7.0.6" 6 | }, 7 | "type": "magento2-module", 8 | "autoload": { 9 | "files": [ 10 | "registration.php" 11 | ], 12 | "psr-4": { 13 | "JustCoded\\BackInStockConfigurable\\": "" 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | justcoded 10 | JustCoded_BackInStockConfigurable::configuration 11 | 12 | 13 | 14 | 15 | Magento\Config\Model\Config\Source\Yesno 16 | 17 | 18 | 19 | comma separated attribute codes 20 | 21 | 1 22 | 23 | 24 | 25 | 26 | 27 | 1 28 | 29 | 30 | 31 | 32 | if this option is no, then all notifications will be send on product save (which will be bad for performance) 33 | Magento\Config\Model\Config\Source\Yesno 34 | 35 | 1 36 | 37 | 38 | 39 | 40 | cron expression 41 | 42 | 1 43 | 1 44 | 45 | 46 | 47 |
48 |
49 |
-------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1 9 | */30 * * * * 10 | back-in-stock-configurable-popup-header 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /etc/crontab.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | justcoded_back_in_stock_configurable/settings/cron_expr_send_notifications 7 | 8 | 9 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /etc/email_templates.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |