diff --git a/app/code/Mindarc/GeoIP/Block/Product/View/Info.php b/app/code/Mindarc/GeoIP/Block/Product/View/Info.php new file mode 100755 index 0000000..d38d87d --- /dev/null +++ b/app/code/Mindarc/GeoIP/Block/Product/View/Info.php @@ -0,0 +1,52 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GeoIP\Block\Product\View; + +/** + * Class Info + * @package Mindarc\GeoIP\Model + */ +class Info implements \Magento\Framework\View\Element\Block\ArgumentInterface +{ + /** + * @var \Mindarc\GeoIP\Model\GeoIP + */ + private $geoIP; + + /** + * Info constructor. + * @param \Mindarc\GeoIP\Model\GeoIP $geoIP + */ + public function __construct( + \Mindarc\GeoIP\Model\GeoIP $geoIP + ) { + $this->geoIP = $geoIP; + } + + /** + * is the accessing user from US + * + * @return bool + */ + public function isUsUser() + { + $countryCode = $this->geoIP->getCountryCode(); + return $countryCode == 'US'; + } + + /** + * @return string + */ + public function getCountryCode() + { + return $countryCode = $this->geoIP->getCountryCode(); + } +} diff --git a/app/code/Mindarc/GeoIP/Model/GeoIP.php b/app/code/Mindarc/GeoIP/Model/GeoIP.php new file mode 100755 index 0000000..da2fb0c --- /dev/null +++ b/app/code/Mindarc/GeoIP/Model/GeoIP.php @@ -0,0 +1,125 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GeoIP\Model; + +/** + * Class GeoIP + * @package Mindarc\GeoIP\Model + */ +class GeoIP +{ + /** + * ip check service + */ + const GEO_IP_SERVICE_URL = 'http://ip-api.com/json/'; + + /** + * curl connection timeout + */ + const CURL_CONNECTION_TIMEOUT = '15'; + + /** + * curl reponse timeout + */ + const CURL_RESPONSE_TIMEOUT = '15'; + + /** + * @var array + */ + private $countryData; + + /** + * get country code + * + * @return mixed|null + */ + public function getCountryCode() + { + if (empty($this->countryData)) { + $this->getUserCountry(); + } + if (!empty($this->countryData['countryCode'])) { + return $this->countryData['countryCode']; + } + return null; + } + + /** + * get and store country data + */ + private function getUserCountry() + { + $ip = $this->getIP(); + $this->countryData = $this->getCountryByIP($ip); + } + + /** + * get the IP address of the user + * + * @return string + */ + private function getIP() + { + $ip = ''; + if (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } elseif (isset($_SERVER['HTTP_X_FORWARDED'])) { + $ip = $_SERVER['HTTP_X_FORWARDED']; + } elseif (isset($_SERVER['HTTP_FORWARDED_FOR'])) { + $ip = $_SERVER['HTTP_FORWARDED_FOR']; + } elseif (isset($_SERVER['HTTP_FORWARDED'])) { + $ip = $_SERVER['HTTP_FORWARDED']; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + return $ip; + } + + /** + * retrieve country data for the ip + * + * @param $ip + * @return mixed|string + */ + private function getCountryByIP($ip) + { + $country = ''; + try { + $ch = curl_init(); + if (false === $ch) { + $error = __('failed to initialize service'); + } + + curl_setopt($ch, CURLOPT_URL, self::GEO_IP_SERVICE_URL . $ip); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, self::CURL_RESPONSE_TIMEOUT); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::CURL_CONNECTION_TIMEOUT); + + $content = curl_exec($ch); + if ($content === false) { + $error = __('Service not responding'); + } + + if ($content) { + $country = json_decode($content, true); + } else { + $error = __('Encode Error'); + } + curl_close($ch); + } catch (\Exception $e) { + $error = sprintf('Curl failed with error #%d: %s', $e->getCode(), $e->getMessage()); + } + + return $country; + } +} diff --git a/app/code/Mindarc/GeoIP/Observer/ControllerActionPreDispatchObserver.php b/app/code/Mindarc/GeoIP/Observer/ControllerActionPreDispatchObserver.php new file mode 100644 index 0000000..b60f143 --- /dev/null +++ b/app/code/Mindarc/GeoIP/Observer/ControllerActionPreDispatchObserver.php @@ -0,0 +1,110 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GeoIP\Observer; + +/** + * Class ControllerActionPreDispatchObserver + * @package Mindarc\GeoIP\Observer + */ +class ControllerActionPreDispatchObserver implements \Magento\Framework\Event\ObserverInterface +{ + /** + * scope config xml path for restriction status + */ + const XML_PATH_GEOIP_RESTRICTION_ENABLED = 'geoip/restriction/enabled'; + + /** + * scope config xml path for restricted countries + */ + const XML_PATH_GEOIP_RESTRICtED_COUNTRIES = 'geoip/restriction/countries'; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var \Mindarc\GeoIP\Model\GeoIP + */ + private $geoIP; + + /** + * @var \Magento\Framework\App\Response\Http + */ + private $response; + + /** + * @var \Magento\Framework\App\ActionFlag + */ + private $actionFlag; + + /** + * ControllerActionPreDispatchObserver constructor. + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Mindarc\GeoIP\Model\GeoIP $geoIP + * @param \Magento\Framework\App\Response\Http $response + * @param \Magento\Framework\App\ActionFlag $actionFlag + */ + public function __construct( + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Mindarc\GeoIP\Model\GeoIP $geoIP, + \Magento\Framework\App\Response\Http $response, + \Magento\Framework\App\ActionFlag $actionFlag + ) { + $this->scopeConfig = $scopeConfig; + $this->storeManager = $storeManager; + $this->geoIP = $geoIP; + $this->response = $response; + $this->actionFlag = $actionFlag; + } + + /** + * @param \Magento\Framework\Event\Observer $observer + * @return \Magento\Backend\Model\View\Result\Forward|void + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + if ($this->scopeConfig->isSetFlag( + self::XML_PATH_GEOIP_RESTRICTION_ENABLED, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $this->storeManager->getStore()->getId()) + ) { + // get the list of countries from the store configurations + $restrictedCountries = $this->scopeConfig->getValue( + self::XML_PATH_GEOIP_RESTRICtED_COUNTRIES, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $this->storeManager->getStore()->getId() + ); + + if (!empty($restrictedCountries)) { + $restrictedCountries = explode(',', $restrictedCountries); + $userCountryCode = $this->geoIP->getCountryCode(); + + $requestUri = $observer->getRequest()->getUriString(); + // if the country is a restricted one and the url is not the 404 page + if (in_array($userCountryCode, $restrictedCountries) && strpos($requestUri, 'no-route') === false) { + // redirect to the 404 page + $this->response->setRedirect('/no-route'); + $this->actionFlag->set('', \Magento\Framework\App\ActionInterface::FLAG_NO_DISPATCH, true); + + return; + } + } + } + } +} \ No newline at end of file diff --git a/app/code/Mindarc/GeoIP/README.md b/app/code/Mindarc/GeoIP/README.md new file mode 100644 index 0000000..8c49734 --- /dev/null +++ b/app/code/Mindarc/GeoIP/README.md @@ -0,0 +1,3 @@ +

Mindarc_GeoIP Module

+ +This module will manage the content based on the IP and country where the users are accessing from. \ No newline at end of file diff --git a/app/code/Mindarc/GeoIP/Setup/InstallData.php b/app/code/Mindarc/GeoIP/Setup/InstallData.php new file mode 100755 index 0000000..19fe95b --- /dev/null +++ b/app/code/Mindarc/GeoIP/Setup/InstallData.php @@ -0,0 +1,76 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GeoIP\Setup; + +use Magento\Cms\Model\BlockFactory; +use Magento\Framework\Setup\InstallDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; + +/** + * Class InstallData + * @package Mindarc\GeoIP\Setup + */ +class InstallData implements InstallDataInterface +{ + /** + * @var BlockFactory + */ + private $blockFactory; + + /** + * InstallData constructor. + * @param BlockFactory $modelBlockFactory + */ + public function __construct( + BlockFactory $modelBlockFactory + ) { + $this->blockFactory = $modelBlockFactory; + } + + /** + * {@inheritdoc} + */ + public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + $cmsBlocks = [ + [ + 'title' => 'Product Information - US', + 'identifier' => 'product_information_us', + 'content' => '
Static block content for US users
', + + ], + [ + 'title' => 'Product Information - Global', + 'identifier' => 'product_information_global', + 'content' => '
Static block content for Global users
', + ], + + ]; + + foreach ($cmsBlocks as $data) { + $cmsBlock = $this->blockFactory->create(); + $cmsBlock->getResource()->load($cmsBlock, $data['identifier']); + if (!$cmsBlock->getData()) { + $cmsBlock->setData($data); + } else { + $cmsBlock->addData($data); + } + $cmsBlock->setStores([\Magento\Store\Model\Store::DEFAULT_STORE_ID]); + $cmsBlock->setIsActive(1); + $cmsBlock->save(); + } + + $setup->endSetup(); + } +} diff --git a/app/code/Mindarc/GeoIP/composer.json b/app/code/Mindarc/GeoIP/composer.json new file mode 100644 index 0000000..4cdd1f8 --- /dev/null +++ b/app/code/Mindarc/GeoIP/composer.json @@ -0,0 +1,24 @@ +{ + "name": "mindarc/module-geoip", + "description": "Mindarc Magento 2 module for IP restrictions", + "require": { + "php": "~5.5.0|~5.6.0|~7.0.0|~7.1.0", + "magento/module-config": "*", + "magento/framework": "*", + "geoip/geoip": "~1.16" + }, + "type": "magento2-module", + "version": "100.23.0", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Mindarc\\GeoIP\\": "" + } + } +} diff --git a/app/code/Mindarc/GeoIP/etc/acl.xml b/app/code/Mindarc/GeoIP/etc/acl.xml new file mode 100644 index 0000000..c9bf5ef --- /dev/null +++ b/app/code/Mindarc/GeoIP/etc/acl.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/code/Mindarc/GeoIP/etc/adminhtml/system.xml b/app/code/Mindarc/GeoIP/etc/adminhtml/system.xml new file mode 100644 index 0000000..2148128 --- /dev/null +++ b/app/code/Mindarc/GeoIP/etc/adminhtml/system.xml @@ -0,0 +1,35 @@ + + + + + + + +
+ + mindarc + Mindarc_GeoIP::configuration + + + + + Magento\Config\Model\Config\Source\Yesno + + + + Magento\Directory\Model\Config\Source\Country + 1 + + +
+
+
\ No newline at end of file diff --git a/app/code/Mindarc/GeoIP/etc/config.xml b/app/code/Mindarc/GeoIP/etc/config.xml new file mode 100644 index 0000000..1a1ad9d --- /dev/null +++ b/app/code/Mindarc/GeoIP/etc/config.xml @@ -0,0 +1,21 @@ + + + + + + + 1 + RU,CN + + + + \ No newline at end of file diff --git a/app/code/Mindarc/GeoIP/etc/frontend/events.xml b/app/code/Mindarc/GeoIP/etc/frontend/events.xml new file mode 100755 index 0000000..9f8953a --- /dev/null +++ b/app/code/Mindarc/GeoIP/etc/frontend/events.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/code/Mindarc/GeoIP/etc/module.xml b/app/code/Mindarc/GeoIP/etc/module.xml new file mode 100644 index 0000000..d46481b --- /dev/null +++ b/app/code/Mindarc/GeoIP/etc/module.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/code/Mindarc/GeoIP/registration.php b/app/code/Mindarc/GeoIP/registration.php new file mode 100644 index 0000000..f0546e3 --- /dev/null +++ b/app/code/Mindarc/GeoIP/registration.php @@ -0,0 +1,14 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Mindarc_GeoIP', + __DIR__ +); \ No newline at end of file diff --git a/app/code/Mindarc/GeoIP/view/frontend/layout/catalog_product_view.xml b/app/code/Mindarc/GeoIP/view/frontend/layout/catalog_product_view.xml new file mode 100755 index 0000000..d518c20 --- /dev/null +++ b/app/code/Mindarc/GeoIP/view/frontend/layout/catalog_product_view.xml @@ -0,0 +1,32 @@ + + + + + + + + Mindarc\GeoIP\Block\Product\View\Info + + + + product_information_us + + + + + product_information_global + + + + + + diff --git a/app/code/Mindarc/GeoIP/view/frontend/templates/product/view/info.phtml b/app/code/Mindarc/GeoIP/view/frontend/templates/product/view/info.phtml new file mode 100644 index 0000000..357e5dd --- /dev/null +++ b/app/code/Mindarc/GeoIP/view/frontend/templates/product/view/info.phtml @@ -0,0 +1,26 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +/** @var $viewModel \Mindarc\GeoIP\Block\Product\View\Info */ +$viewModel = $block->getData('viewModel'); +$countryCode = $viewModel->getCountryCode(); +if (!empty($countryCode)): +?> +
+ getCountryCode()) ?> + + isUsUser()): ?> + getChildHtml('product.geo.info.us') ?> + + getChildHtml('product.geo.info.global') ?> + + +
+ \ No newline at end of file diff --git a/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/BackButton.php b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/BackButton.php new file mode 100755 index 0000000..b7b53d0 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/BackButton.php @@ -0,0 +1,43 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; + +/** + * Class BackButton + * @package Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit + */ +class BackButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Back'), + 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), + 'class' => 'back', + 'sort_order' => 10 + ]; + } + + /** + * Get URL for back (reset) button + * + * @return string + */ + public function getBackUrl() + { + return $this->getUrl('*/*/'); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/DeleteButton.php b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/DeleteButton.php new file mode 100755 index 0000000..fafaba7 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/DeleteButton.php @@ -0,0 +1,47 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; + +/** + * Class DeleteButton + * @package Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit + */ +class DeleteButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @return array + */ + public function getButtonData() + { + $data = []; + if ($this->getEntityId()) { + $data = [ + 'label' => __('Delete'), + 'class' => 'delete', + 'on_click' => 'deleteConfirm(\'' . __( + 'Are you sure to delete this record?' + ) . '\', \'' . $this->getDeleteUrl() . '\')', + 'sort_order' => 20, + ]; + } + return $data; + } + + /** + * @return string + */ + public function getDeleteUrl() + { + return $this->getUrl('*/*/delete', ['id' => $this->getEntityId()]); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/GenericButton.php b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/GenericButton.php new file mode 100755 index 0000000..87dc742 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/GenericButton.php @@ -0,0 +1,64 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit; + +/** + * Class GenericButton + * @package Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit + */ +class GenericButton +{ + /** + * @var Context + */ + protected $context; + + /** + * @var \Magento\Framework\Registry + */ + protected $registry; + + /** + * GenericButton constructor. + * @param \Magento\Backend\Block\Widget\Context $context + * @param \Magento\Framework\Registry $coreRegistry + */ + public function __construct( + \Magento\Backend\Block\Widget\Context $context, + \Magento\Framework\Registry $coreRegistry + ) { + $this->context = $context; + $this->registry = $coreRegistry; + } + + /** + * Return Master Data ID + * + * @return int|null + */ + public function getEntityId() + { + $masterData = $this->registry->registry('current_profile'); + return $masterData ? $masterData->getId() : null; + } + + /** + * Generate url by route and parameters + * + * @param string $route + * @param array $params + * @return string + */ + public function getUrl($route = '', $params = []) + { + return $this->context->getUrlBuilder()->getUrl($route, $params); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/SaveAndContinueButton.php b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/SaveAndContinueButton.php new file mode 100755 index 0000000..d7a189c --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/SaveAndContinueButton.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; + +/** + * Class SaveAndContinueButton + * @package Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit + */ +class SaveAndContinueButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Save and Continue Edit'), + 'class' => 'save', + 'data_attribute' => [ + 'mage-init' => [ + 'button' => ['event' => 'saveAndContinueEdit'], + ], + ], + 'sort_order' => 80, + ]; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/SaveButton.php b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/SaveButton.php new file mode 100755 index 0000000..48afa13 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Edit/SaveButton.php @@ -0,0 +1,36 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit; + +use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface; + +/** + * Class SaveButton + * @package Mindarc\GoogleFeed\Block\Adminhtml\Profile\Edit + */ +class SaveButton extends GenericButton implements ButtonProviderInterface +{ + /** + * @return array + */ + public function getButtonData() + { + return [ + 'label' => __('Save'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 90, + ]; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Preview.php b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Preview.php new file mode 100755 index 0000000..99785c8 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Block/Adminhtml/Profile/Preview.php @@ -0,0 +1,99 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Block\Adminhtml\Profile; + +/** + * Class Preview + * @package Mindarc\GoogleFeed\Block\Adminhtml\Profile + */ +class Preview implements \Magento\Framework\View\Element\Block\ArgumentInterface +{ + /** + * @var \Magento\Framework\Registry + */ + private $registry; + + /** + * @var mixed + */ + private $profile; + + /** + * @var \Magento\Framework\UrlInterface + */ + private $urlBuilder; + + /** + * Preview constructor. + * @param \Magento\Framework\Registry $registry + * @param \Magento\Framework\UrlInterface $urlBuilder + */ + public function __construct( + \Magento\Framework\Registry $registry, + \Magento\Framework\UrlInterface $urlBuilder + ) { + $this->registry = $registry; + $this->urlBuilder = $urlBuilder; + $this->profile = $this->registry->registry('current_profile'); + } + + /** + * get feed url of the profile + * + * @return mixed + */ + public function getFeedUrl() + { + return $this->profile->getUrl(); + } + + /** + * Get trimmed xml content for preview + * + * @param bool $limit + * @return bool|mixed|string + */ + public function getFeed($limit = true) + { + $feedUrl = $this->getFeedUrl(); + if ($feedUrl) { + try { + // getting xml content from the feed url + $xml = file_get_contents($feedUrl); + // trim to 10000 chars + if ($limit) { + $xml = substr($xml, 0, 10000); + } + // replace double quotes with single quotes, and remove line breaks to work with the js function. + $xml = str_replace(PHP_EOL, '', str_replace('"', '\'', trim(preg_replace('/\s\s+/', '~~', + $xml)))); + + } catch (\Exception $e) { + return false; + } + return $xml; + } + + return false; + } + + /** + * build and return back url + * + * @return string + */ + public function getBackUrl() + { + return $this->urlBuilder->getUrl( + 'google/profile/' + ); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Console/Command/Execute.php b/app/code/Mindarc/GoogleFeed/Console/Command/Execute.php new file mode 100755 index 0000000..1097285 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Console/Command/Execute.php @@ -0,0 +1,67 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Console\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Class Execute + * @package Mindarc\GoogleFeed\Console\Command + */ +class Execute extends Command +{ + /** + * console command + */ + const COMMAND_GENERATE_FEED = 'google-feed:generate'; + + /** + * @var \Mindarc\GoogleFeed\Model\FeedGenerator + */ + private $feedGenerator; + + /** + * Execute constructor. + * @param \Mindarc\GoogleFeed\Model\FeedGenerator $feedGenerator + */ + public function __construct( + \Mindarc\GoogleFeed\Model\FeedGenerator $feedGenerator + ) { + parent::__construct(); + $this->feedGenerator = $feedGenerator; + } + + /** + * @inheritdoc + */ + protected function configure() + { + $this->setName(self::COMMAND_GENERATE_FEED) + ->setDescription('Generate google feeds according the profiles'); + } + + /** + * @inheritdoc + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $result = $this->feedGenerator->generate(); + + if ($result) { // result is error + $output->writeln('Error(s):'); + $output->writeln('' . implode("\n", $result) . ''); + } + + $output->writeln('' . __('Finished') . ''); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Delete.php b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Delete.php new file mode 100755 index 0000000..4a0d45b --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Delete.php @@ -0,0 +1,75 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Controller\Adminhtml\Profile; + +/** + * Class Delete + * @package Mindarc\GoogleFeed\Controller\Adminhtml\Profile + */ +class Delete extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Mindarc_GoogleFeed::delete'; + + /** + * @var \Mindarc\GoogleFeed\Model\Profile + */ + protected $profile; + + /** + * Delete constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Mindarc\GoogleFeed\Model\Profile $profile + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Mindarc\GoogleFeed\Model\Profile $profile + ) { + parent::__construct($context); + $this->profile = $profile; + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|void + */ + public function execute() + { + // check if we know what should be deleted + $profileId = $this->getRequest()->getParam('id'); + if ($profileId) { + try { + // init model and delete + $model = $this->profile->load($profileId); + $model->delete(); + // display success message + $this->messageManager->addSuccess(__('You deleted the profile.')); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->messageManager->addError($e->getMessage()); + } catch (\Exception $e) { + $this->messageManager->addError( + __('Something went wrong while deleting profile data. Please try again.') + ); + $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); + } + } else { + // display error message + $this->messageManager->addError(__('We cannot find an profile to delete.')); + + } + // go to grid + $this->_redirect('google/profile/'); + return; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Edit.php b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Edit.php new file mode 100755 index 0000000..33deddc --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Edit.php @@ -0,0 +1,76 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Controller\Adminhtml\Profile; + +/** + * Class Edit + * @package Mindarc\GoogleFeed\Controller\Adminhtml\Profile + */ +class Edit extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Mindarc_GoogleFeed::form'; + + /** + * @var \Mindarc\GoogleFeed\Model\Profile + */ + protected $profile; + + /** + * Edit constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Mindarc\GoogleFeed\Model\Profile $profile + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Mindarc\GoogleFeed\Model\Profile $profile + ) { + parent::__construct($context); + $this->profile = $profile; + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|void + */ + public function execute() + { + $profileId = $this->getRequest()->getParam('id'); + $model = $this->profile->initProfile($profileId); + + if (!$model->getId() && $profileId) { + $this->messageManager->addError(__('This profile no longer exists.')); + $this->_redirect('*/*/'); + return; + } + + $data = $this->_getSession()->getFormData(true); + if (!empty($data)) { + $model->addData($data); + } + + $this->_view->loadLayout(); + $this->_setActiveMenu('Mindarc_GoogleFeed::profiles'); + $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Profiles')); + $this->_view->getPage()->getConfig()->getTitle()->prepend( + $model->getId() ? 'Edit - ' . $model->getProfileName() : __('New Profile') + ); + + $this->_addBreadcrumb( + $profileId ? __('Edit Profile') : __('New Profile'), + $profileId ? __('Edit Profile') : __('New Profile') + ); + $this->_view->renderLayout(); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Generate.php b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Generate.php new file mode 100755 index 0000000..9516ac7 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Generate.php @@ -0,0 +1,71 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Controller\Adminhtml\Profile; + +/** + * Class Generate + * @package Mindarc\GoogleFeed\Controller\Adminhtml\Profile + */ +class Generate extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Mindarc_GoogleFeed::generate'; + + /** + * @var \Mindarc\GoogleFeed\Model\FeedGenerator + */ + private $feedGenerator; + + /** + * Generate constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Mindarc\GoogleFeed\Model\FeedGenerator $feedGenerator + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Mindarc\GoogleFeed\Model\FeedGenerator $feedGenerator + ) { + parent::__construct($context); + $this->feedGenerator = $feedGenerator; + } + + /** + * @return \Magento\Backend\Model\View\Result\Redirect|\Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function execute() + { + $profileId = $this->getRequest()->getParam('id'); + if ($profileId) { + $result = $this->feedGenerator->process($profileId); + } + + // result is error (not an empty array of errors and not the product count of the profile) + if (!empty($result) && !is_int($result)) { + // display error message + $this->messageManager->addError( + sprintf(__('Error(s): %s'), implode(', ', $result)) + ); + } else { + // display success message + $this->messageManager->addSuccess(__('The profile has been generated.')); + } + + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT); + // go to preview + return $resultRedirect->setPath('google/profile/'); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Index.php b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Index.php new file mode 100755 index 0000000..5c8639b --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Index.php @@ -0,0 +1,54 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Controller\Adminhtml\Profile; + +/** + * Class Index + * @package Mindarc\GoogleFeed\Controller\Adminhtml\Profile + */ +class Index extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Mindarc_GoogleFeed::profiles'; + + /** + * @var \Magento\Framework\View\Result\PageFactory + */ + protected $resultPageFactory; + + /** + * Index constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Magento\Framework\View\Result\PageFactory $resultPageFactory + ) { + parent::__construct($context); + $this->resultPageFactory = $resultPageFactory; + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|\Magento\Framework\View\Result\Page + */ + public function execute() + { + $resultPage = $this->resultPageFactory->create(); + $resultPage->getConfig()->getTitle()->prepend((__('Google Profiles'))); + + return $resultPage; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/NewAction.php b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/NewAction.php new file mode 100755 index 0000000..9b5d432 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/NewAction.php @@ -0,0 +1,54 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Controller\Adminhtml\Profile; + + +/** + * Class NewAction + * @package Mindarc\GoogleFeed\Controller\Adminhtml\Profile + */ +class NewAction extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Mindarc_GoogleFeed::form'; + + /** + * @var \Magento\Backend\Model\View\Result\ForwardFactory + */ + protected $resultForwardFactory; + + /** + * NewAction constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory + ) { + parent::__construct($context); + $this->resultForwardFactory = $resultForwardFactory; + } + + /** + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Forward|\Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ + $resultForward = $this->resultForwardFactory->create(); + return $resultForward->forward('edit'); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Preview.php b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Preview.php new file mode 100755 index 0000000..5451116 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Preview.php @@ -0,0 +1,74 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Controller\Adminhtml\Profile; + +/** + * Class Preview + * @package Mindarc\GoogleFeed\Controller\Adminhtml\Profile + */ +class Preview extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Mindarc_GoogleFeed::profiles'; + + /** + * @var \Mindarc\GoogleFeed\Model\Profile + */ + protected $profile; + + /** + * Preview constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Mindarc\GoogleFeed\Model\Profile $profile + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Mindarc\GoogleFeed\Model\Profile $profile + ) { + parent::__construct($context); + $this->profile = $profile; + } + + /** + * Edit master data + * + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|void + */ + public function execute() + { + $profileId = $this->getRequest()->getParam('id'); + $model = $this->profile->initProfile($profileId); + + if (!$model->getId() && $profileId) { + $this->messageManager->addError(__('This profile no longer exists.')); + $this->_redirect('*/*/'); + return; + } + + $this->_view->loadLayout(); + $this->_setActiveMenu('Mindarc_GoogleFeed::profiles'); + + $pageTitle = __('Preview Feed - %1', $model->getProfileName()); + + $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Profile')); + $this->_view->getPage()->getConfig()->getTitle()->prepend($pageTitle); + + $this->_addBreadcrumb( + $pageTitle, + $pageTitle + ); + $this->_view->renderLayout(); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Save.php b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Save.php new file mode 100755 index 0000000..6661961 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Controller/Adminhtml/Profile/Save.php @@ -0,0 +1,99 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Controller\Adminhtml\Profile; + +/** + * Class Save + * @package Mindarc\GoogleFeed\Controller\Adminhtml\Profile + */ +class Save extends \Magento\Backend\App\Action +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Mindarc_GoogleFeed::save'; + + /** + * @var \Mindarc\GoogleFeed\Model\Profile + */ + protected $profile; + + /** + * Save constructor. + * @param \Magento\Backend\App\Action\Context $context + * @param \Mindarc\GoogleFeed\Model\Profile $profile + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Mindarc\GoogleFeed\Model\Profile $profile + ) { + parent::__construct($context); + $this->profile = $profile; + } + + /** + * Create new master data + * + * @return \Magento\Framework\Controller\ResultInterface + */ + public function execute() + { + $redirectBack = $this->getRequest()->getParam('back', false); + /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT); + $data = $this->getRequest()->getPostValue(); + if (!$data) { + return $resultRedirect->setPath('adminhtml/*/'); + } + + /** @var \Mindarc\GoogleFeed\Model\Profile $model */ + $model = $this->profile->initProfile((int)$this->getRequest()->getParam('entity_id')); + if (!$this->isProfileExist($model)) { + $this->messageManager->addError(__('This profile does not exist.')); + return $resultRedirect->setPath('adminhtml/*/'); + } + + try { + if (!empty($data)) { + $model->addData($data); + $this->_getSession()->setFormData($data); + } + $model->save(); + $this->_getSession()->setFormData(false); + $this->messageManager->addSuccess(__('You saved the profile.')); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $redirectBack = true; + $this->messageManager->addError($e->getMessage()); + } catch (\Exception $e) { + $redirectBack = true; + $this->messageManager->addError(__('We cannot save the profile.')); + $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); + } + + return ($redirectBack) + ? $resultRedirect->setPath('google/profile/edit', ['id' => $model->getId(), '_current' => true]) + : $resultRedirect->setPath('google/profile/'); + } + + /** + * Check if profile exist + * + * @param \Mindarc\GoogleFeed\Model\Profile $model + * @return bool + */ + protected function isProfileExist(\Mindarc\GoogleFeed\Model\Profile $model) + { + $entityId = $this->getRequest()->getParam('id'); + return (!$model->getId() && $entityId) ? false : true; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Cron/Generator.php b/app/code/Mindarc/GoogleFeed/Cron/Generator.php new file mode 100644 index 0000000..4447032 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Cron/Generator.php @@ -0,0 +1,42 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Cron; + +/** + * Class Generator + * @package Mindarc\GoogleFeed\Cron + */ +class Generator +{ + /** + * @var \Mindarc\GoogleFeed\Model\FeedGenerator + */ + private $feedGenerator; + + /** + * Generator constructor. + * @param \Mindarc\GoogleFeed\Model\FeedGenerator $feedGenerator + */ + public function __construct( + \Mindarc\GoogleFeed\Model\FeedGenerator $feedGenerator + ) { + parent::__construct(); + $this->feedGenerator = $feedGenerator; + } + + /** + * @throws \Exception + */ + public function execute() + { + $this->feedGenerator->generate(); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/FeedGenerator.php b/app/code/Mindarc/GoogleFeed/Model/FeedGenerator.php new file mode 100644 index 0000000..b464c7d --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/FeedGenerator.php @@ -0,0 +1,592 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model; + +use Mindarc\GoogleFeed\Model\Profile\Source\GenerationStatus as Status; + +/** + * Class FeedGenerator + * @package Mindarc\GoogleFeed\Model + */ +class FeedGenerator +{ + /** + * store config xml path for generation enabled + */ + const XML_PATH_GENERATION_ENABLED = 'google_feed/generate/enabled'; + + /** + * EMAIL_RECIPIENTS + */ + const XML_PATH_ERROR_RECIPIENT = 'google_feed/generate/recipients'; + + /** + * EMAIL_SENDER + */ + const XML_PATH_ERROR_IDENTITY = 'google_feed/generate/identity'; + + /** + * EMAIL_TEMPLATE + */ + const XML_PATH_ERROR_TEMPLATE = 'google_feed/generate/template'; + + /** + * feed generation sub dir + */ + const FEED_GENERATION_SUB_DIR = 'google_feed'; + + /** + * feed file format + */ + const FEED_FILE_FORMAT = 'xml'; + + /** + * @var Profile + */ + private $profile; + + /** + * @var \Magento\Framework\App\State + */ + private $appState; + + /** + * @var \Magento\Framework\Stdlib\DateTime\DateTime + */ + private $dateTime; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var \Magento\Framework\Filesystem\Directory\Write + */ + protected $directory; + + /** + * @var \Magento\Framework\Filesystem\File\Write + */ + private $stream; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var ResourceModel\Catalog\ProductFactory + */ + private $productFactory; + + /** + * @var \Magento\Framework\Pricing\Helper\Data + */ + private $currency; + + /** + * @var \Magento\Framework\Mail\Template\TransportBuilder + */ + private $transportBuilder; + + /** + * @var \Magento\Framework\App\Filesystem\DirectoryList + */ + private $directoryList; + + /** + * @var \Magento\Framework\Filesystem\Io\File + */ + private $file; + + /** + * @var Status + */ + private $status; + + /** + * @var \Magento\Catalog\Model\Indexer\Product\Flat\State + */ + private $indexerState; + + + private $converter; + + /** + * @var float + */ + private $conversionRate; + + /** + * FeedGenerator constructor. + * @param Profile $profile + * @param \Magento\Framework\App\State $AppState + * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Framework\Filesystem $filesystem + * @param \Magento\Config\Model\Config\Reader\Source\Deployed\DocumentRoot $documentRoot + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param ResourceModel\Catalog\ProductFactory $productFactory + * @param \Magento\Framework\Pricing\Helper\Data $currency + * @param \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder + * @param \Magento\Framework\App\Filesystem\DirectoryList $directoryList + * @param \Magento\Framework\Filesystem\Io\File $file + * @param Profile\Source\Status $status + * @param \Magento\Catalog\Model\Indexer\Product\Flat\State $indexerState + * @param \Mindarc\GoogleFeed\Service\CurrencyConverter $converter + * @throws \Magento\Framework\Exception\FileSystemException + */ + public function __construct( + Profile $profile, + \Magento\Framework\App\State $AppState, + \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Framework\Filesystem $filesystem, + \Magento\Config\Model\Config\Reader\Source\Deployed\DocumentRoot $documentRoot, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Mindarc\GoogleFeed\Model\ResourceModel\Catalog\ProductFactory $productFactory, + \Magento\Directory\Model\Currency $currency, + \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder, + \Magento\Framework\App\Filesystem\DirectoryList $directoryList, + \Magento\Framework\Filesystem\Io\File $file, + \Mindarc\GoogleFeed\Model\Profile\Source\Status $status, + \Magento\Catalog\Model\Indexer\Product\Flat\State $indexerState, + \Mindarc\GoogleFeed\Service\CurrencyConverter $converter + ) { + $this->profile = $profile; + $this->appState = $AppState; + $this->dateTime = $dateTime; + $this->scopeConfig = $scopeConfig; + $this->directory = $filesystem->getDirectoryWrite($documentRoot->getPath()); + $this->storeManager = $storeManager; + $this->productFactory = $productFactory; + $this->currency = $currency; + $this->transportBuilder = $transportBuilder; + $this->directoryList = $directoryList; + $this->file = $file; + $this->status = $status; + $this->indexerState = $indexerState; + $this->converter = $converter; + } + + /** + * function to call for cron and cli scopes, so the area code is simulated + * + * @param null $profileId + * @return mixed + * @throws \Exception + */ + public function generate($profileId = null) + { + // Emulate the Area Code + $areaCode = \Magento\Framework\App\Area::AREA_FRONTEND; + return $this->appState->emulateAreaCode( + $areaCode, + [$this, 'process'], + [$profileId] + ); + } + + /** + * run the process + * + * @param null $profileId + * @return array + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function process($profileId = null) + { + $errors = []; + + // determine the currency conversion rate at the beginning, so can use for each product price + $this->conversionRate = $this->converter->getRate('AUD', 'USD'); + + // if feed generation is disabled by configurations + if (!$this->isGenerationEnabled()) { + $message = __('Feed generation is disabled by store configuration.'); + return [$message]; + } + + // if catalog product flat indexer is not enabled by configurations + if (!$this->indexerState->isFlatEnabled()) { + $message = __('catalog_product_flat is not and should be enabled. (Store > Configuration > Catalog > Store Front > Use Flat Catalog Product)'); + return [$message]; + } + + // get profile collection to execute + $profileCollection = $this->profile->getProfileCollection($profileId); + // if there are no items in the collection to execute + if (empty($profileCollection->getSize())) { + $message = __('No enabled profiles to run in the selection.'); + return [$message]; + } + + foreach ($profileCollection as $profile) { + // running the profile to generate the xml + $result = $this->runProfile($profile); + + // $result is error + if (!is_int($result)) { + $errors[] = $result; + } + } + + // if error occurs, send out emails for the nominated recipients + if ($errors) { + $errors[] = $this->sendErrorEmail($errors); + return $errors; + } + + + return $errors; + } + + /** + * if error occurs, send out emails for the nominated recipients + * + * @param $errors + * @return bool|string + */ + private function sendErrorEmail($errors) + { + // get comma separated list of recipients from store config + $recipients = $this->scopeConfig->getValue( + self::XML_PATH_ERROR_RECIPIENT + ); + + if ($recipients) { + try { + $this->transportBuilder->setTemplateIdentifier( + $this->scopeConfig->getValue( + self::XML_PATH_ERROR_TEMPLATE + ) + )->setTemplateOptions( + [ + 'area' => \Magento\Framework\App\Area::AREA_FRONTEND, + 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID, + ] + )->setTemplateVars( + ['errors' => implode("
", $errors)] + )->setFrom( + $this->scopeConfig->getValue( + self::XML_PATH_ERROR_IDENTITY + ) + ); + + foreach (explode(',', $recipients) as $email) { + $this->transportBuilder->addTo(trim($email)); + } + + $transport = $this->transportBuilder->getTransport(); + $transport->sendMessage(); + } catch (\Magento\Framework\Exception\MailException $e) { + return $e->getMessage(); + } + } + return true; + } + + /** + * execute individual google feed profile + * + * @param $profile + * @return int|string|void + */ + public function runProfile($profile) + { + $storeId = $profile->getStoreId(); + + // setting store id to the store manager so the Image URL's will be taken correctly + $this->storeManager->setCurrentStore($storeId); + + $profile->setGenerationStatus(Status::STATUS_RUNNING); + $profile->save(); + + // generate the xml feed file for the export profile + $result = $this->generateXml($profile); + + if (is_int($result)) { // no errors in the generation process + $profileId = $profile->getId(); + $fileName = $profile->getFilename(); + $fileFormat = self::FEED_FILE_FORMAT; + $storeUrl = $this->storeManager->getStore($profile->getStoreId())->getBaseUrl(); + $feedLink = $storeUrl . 'media/' . self::FEED_GENERATION_SUB_DIR . + '/' . $storeId . '_' . $profileId . '/' . $fileName . '.' . $fileFormat; + $profile->setGenerationStatus(Status::STATUS_FINISHED) + ->setGeneratedTime( // last generated time + $this->dateTime->date('Y-m-d H:i:s') + )->setUrl( // feed public url + $feedLink + ); + } else { + $profile->setGenerationStatus(Status::STATUS_ERROR); + } + $profile->save(); + + // Reverting back the store id in store manager + $this->storeManager->setCurrentStore(0); + + return $result; + } + + /** + * @param $profile + * @return int|string|void + */ + private function generateXml($profile) + { + try { + // generate the xml feed file for the export profile + $this->initFeed($profile); + $result = $this->createFeed($profile); + $this->finalizeFeed(); + + return $result; + } catch (\Magento\Framework\Exception\LocalizedException $e) { + return $e->getMessage(); + } catch (\Exception $e) { + return $e->getMessage(); + } + } + + /** + * initialize the xml feed file and writing the header elements to the xml feed + * + * @param $profile + * @throws \Magento\Framework\Exception\FileSystemException + */ + private function initFeed($profile) + { + $storeId = $profile->getStoreId(); + + // title element + $title = sprintf('%s', $profile->getProfileName()); + + // description element + $description = sprintf('%s', __('Google Shopping Feed for Magento Store')); + + // link element + $link = sprintf('%s', $this->storeManager->getStore($storeId)->getBaseUrl()); + + // deader and starting element set + $start = '' . + PHP_EOL . + '' . + PHP_EOL . + '' . + PHP_EOL . + $title . + PHP_EOL . + $link . + PHP_EOL . + $description . + PHP_EOL; + + $profileId = $profile->getId(); + $fileName = $profile->getFilename(); + $fileFormat = self::FEED_FILE_FORMAT; + + // relative file path from document root + $path = 'pub/media/' . self::FEED_GENERATION_SUB_DIR . + '/' . $storeId . '_' . $profileId . '/' . $fileName . '.' . $fileFormat; + $this->stream = $this->directory->openFile($path); + + $fileHeader = sprintf($start); + // writing header and starting elements into the xml file + $this->stream->write($fileHeader); + } + + /** + * prepare xml content for an individual product + * + * @param $product + * @param $profile + * @return string + */ + private function prepareProductRow($product, $profile) + { + $row = ''; + $feedElements = $profile->getFeedElements(); + if (!empty($feedElements)) { + foreach ($feedElements as $feedElement) { + $value = null; + + if (!empty($feedElement['element'])) { + $element = $feedElement['element']; + + if (!empty($feedElement['default'])) { + $value = $feedElement['default']; + } elseif (!empty($feedElement['magento']) && $product->getData($feedElement['magento'])) { + // or get the product's magento attribute value + $value = $product->getData($feedElement['magento']); + } + + // if the value is not empty + if (!empty($value)) { + // tweaking and formatting the values + switch ($element) { + case 'description': + // removing line breaks as extra line breaks can break the xml + $value = trim(preg_replace('/\s\s+/', ' ', $value)); + break; + case 'link': + // format the product url + $value = htmlspecialchars($this->getUrl($product)); + break; + case 'g:price': + // Price format + $value = $this->currency->format($value, [], false); + break; + case 'g:converted_price': + if (empty($this->conversionRate)) { // if no conversion rate, omit the element + continue; + } + // Price convert and format + $value = $this->currency->format( + $this->converter->convert($value, $this->conversionRate), + [], + false + ); + break; + case 'g:image_link': + $images = $product->getImages(); + if ($images) { + foreach ($images->getCollection() as $key => $image) { + $value = $image->getUrl(); + } + } + break; + } + + if (!empty($feedElement['cdata']) && $feedElement['cdata'] == '1') { + // wrap with CDATA tag, if set + $value = PHP_EOL . '' . PHP_EOL; + } + + $row .= sprintf('<%s>%s', $element, $value, $element) . PHP_EOL; + } + } + } + } + + return '' . PHP_EOL . $row . ''; + } + + /** + * Get store base url + * + * @param $storeId + * @param string $type + * @return string + */ + protected function getStoreBaseUrl($storeId, $type = \Magento\Framework\UrlInterface::URL_TYPE_LINK) + { + /** @var \Magento\Store\Model\Store $store */ + $store = $this->storeManager->getStore($storeId); + $isSecure = $store->isUrlSecure(); + return rtrim($store->getBaseUrl($type, $isSecure), '/') . '/'; + } + + /** + * Get url + * + * @param $product + * @param string $type + * @return string + */ + protected function getUrl($product, $type = \Magento\Framework\UrlInterface::URL_TYPE_LINK) + { + $url = ''; + if (!empty($product['url'])) { + $url = $product['url']; + } + return $this->getStoreBaseUrl($product['store_id'], $type) . ltrim($url, '/'); + } + + /** + * @param $profile + * @return int|void + * @throws \Magento\Framework\Exception\FileSystemException + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Zend_Db_Statement_Exception + */ + private function createFeed($profile) + { + $products = $this->productFactory->create()->getCollection($profile); + + foreach ($products as $product) { + // generate product data for the feed xml + $xmlRow = $this->prepareProductRow($product, $profile); + // writing the product row into the feed xml + $this->writeRow($xmlRow); + } + + return count($products); + } + + /** + * adding closing tags to end the feed elements + * + * @throws \Magento\Framework\Exception\FileSystemException + */ + private function finalizeFeed() + { + if ($this->stream) { + $end = '' . + PHP_EOL . + ''; + $this->stream->write(sprintf($end)); + $this->stream->close(); + } + } + + /** + * write individual row to feed file + * + * @param $row + * @throws \Magento\Framework\Exception\FileSystemException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function writeRow($row) + { + $this->getStream()->write($row . PHP_EOL); + } + + /** + * Get file handler + * + * @return \Magento\Framework\Filesystem\File\WriteInterface + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getStream() + { + if ($this->stream) { + return $this->stream; + } else { + throw new \Magento\Framework\Exception\LocalizedException(__('File handler unreachable')); + } + } + + /** + * check if the generation is enabled from store config + * + * @return bool + */ + private function isGenerationEnabled() + { + // check if scheduled generation enabled + return $this->scopeConfig->isSetFlag( + self::XML_PATH_GENERATION_ENABLED, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/Profile.php b/app/code/Mindarc/GoogleFeed/Model/Profile.php new file mode 100755 index 0000000..6a77691 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/Profile.php @@ -0,0 +1,120 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model; + +/** + * Class Profile + * @package Mindarc\GoogleFeed\Model + */ +class Profile extends \Magento\Framework\Model\AbstractModel +{ + /** + * Init resource model + * + * @return void + */ + protected function _construct() + { + $this->_init(\Mindarc\GoogleFeed\Model\ResourceModel\Profile::class); + } + + /** + * Load Export Profile from the request + * + * @param null $profileId + * @return $this + */ + public function initProfile($profileId = null) + { + if ($profileId) { + // TODO - use entity manager to load model + $this->load($profileId); + } + if (!$this->_registry->registry('current_profile')) { + $this->_registry->register('current_profile', $this); + } + return $this; + } + + /** + * @param $profileId + * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getProfileCollection($profileId) + { + $profileCollection = $this->getResourceCollection(); + if (!empty($profileId)) { + if (is_array($profileId)) { + $profileCollection->addFieldToFilter('entity_id', ['in' => $profileId]); + } else { + $profileCollection->addFieldToFilter('entity_id', $profileId); + } + } + + return $profileCollection->addFieldToFilter( + 'status', + ['eq' => \Mindarc\GoogleFeed\Model\Profile\Source\Status::STATUS_ENABLED] + ); + } + + /** + * feed element data + * + * @return array + */ + public function getFeedElements() + { + return [ + [ + 'element' => 'title', + 'magento' => 'name', + 'cdata' => 1 + ], + [ + 'element' => 'link', + 'magento' => 'url_key', + 'cdata' => 1 + ], + [ + 'element' => 'description', + 'magento' => 'description', + 'cdata' => 1 + ], + [ + 'element' => 'g:image_link', + 'magento' => 'image', + 'cdata' => 1 + ], + [ + 'element' => 'g:price', + 'magento' => 'price', + 'cdata' => 0 + ], + [ + 'element' => 'g:converted_price', + 'magento' => 'price', + 'cdata' => 0 + ], + [ + 'element' => 'g:condition', + 'magento' => '', + 'default' => 'New', + 'cdata' => 0 + ], + [ + 'element' => 'g:id', + 'magento' => 'sku', + 'cdata' => 0 + ], + ]; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/Profile/DataProvider.php b/app/code/Mindarc/GoogleFeed/Model/Profile/DataProvider.php new file mode 100755 index 0000000..e3ea77d --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/Profile/DataProvider.php @@ -0,0 +1,84 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model\Profile; + +/** + * Class DataProvider + * @package Mindarc\GoogleFeed\Model\Profile + */ +class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider +{ + /** + * @var + */ + protected $collection; + + /** + * @var DataPersistorInterface + */ + protected $dataPersistor; + + /** + * @var array + */ + protected $loadedData; + + /** + * DataProvider constructor. + * @param string $name + * @param string $primaryFieldName + * @param string $requestFieldName + * @param \Mindarc\GoogleFeed\Model\ResourceModel\Profile\CollectionFactory $dataCollectionFactory + * @param \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor + * @param array $meta + * @param array $data + */ + public function __construct( + $name, + $primaryFieldName, + $requestFieldName, + \Mindarc\GoogleFeed\Model\ResourceModel\Profile\CollectionFactory $dataCollectionFactory, + \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor, + array $meta = [], + array $data = [] + ) { + $this->collection = $dataCollectionFactory->create(); + $this->dataPersistor = $dataPersistor; + parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); + } + + /** + * Get data + * + * @return array + */ + public function getData() + { + if (isset($this->loadedData)) { + return $this->loadedData; + } + $items = $this->collection->getItems(); + /** @var \Mindarc\GoogleFeed\Model\Profile $data */ + foreach ($items as $data) { + $this->loadedData[$data->getId()] = $data->getData(); + } + + $data = $this->dataPersistor->get('current_profile'); + if (!empty($data)) { + $data = $this->collection->getNewEmptyItem(); + $data->setData($data); + $this->loadedData[$data->getId()] = $data->getData(); + $this->dataPersistor->clear('current_profile'); + } + + return $this->loadedData; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/Profile/Source.php b/app/code/Mindarc/GoogleFeed/Model/Profile/Source.php new file mode 100644 index 0000000..42caae1 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/Profile/Source.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model\Profile; + +/** + * Class Source + * @package Mindarc\GoogleFeed\Model\Profile + */ +abstract class Source implements \Magento\Framework\Option\ArrayInterface +{ + /** + * @return array + */ + public function toOptionArray() + { + return []; + } + + /** + * @return array + */ + public function toIndexedArray() + { + $options = $this->toOptionArray(); + $indexedArray = []; + foreach ($options as $item) { + $indexedArray[$item['value']] = $item['label']; + } + return $indexedArray; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/Profile/Source/GenerationStatus.php b/app/code/Mindarc/GoogleFeed/Model/Profile/Source/GenerationStatus.php new file mode 100755 index 0000000..65e2d38 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/Profile/Source/GenerationStatus.php @@ -0,0 +1,70 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model\Profile\Source; + +use Magento\Framework\Data\OptionSourceInterface; + +/** + * Class GenerationStatus + * @package Mindarc\GoogleFeed\Model\Profile\Source + */ +class GenerationStatus implements OptionSourceInterface +{ + /** + * Master data statuses + */ + const STATUS_NEW = 0; + const STATUS_RUNNING = 1; + const STATUS_ERROR = 2; + const STATUS_FINISHED = 3; + + /** + * Get options + * + * @return array + */ + public function toOptionArray() + { + $availableOptions = $this->getAvailableStatuses(); + $options = []; + foreach ($availableOptions as $key => $value) { + $options[] = [ + 'label' => $value, + 'value' => $key, + ]; + } + return $options; + } + + /** + * Prepare Master data statuses. + * + * @return array + */ + public function getAvailableStatuses() + { + return [ + self::STATUS_NEW => __('NEW'), + self::STATUS_RUNNING => __('RUNNING'), + self::STATUS_ERROR => __('ERROR'), + self::STATUS_FINISHED => __('FINISHED'), + ]; + } + + /** + * @param $id + * @return mixed + */ + public function getStatusById($id) + { + return $this->getAvailableStatuses()[$id]; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/Profile/Source/Status.php b/app/code/Mindarc/GoogleFeed/Model/Profile/Source/Status.php new file mode 100755 index 0000000..eae8a0c --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/Profile/Source/Status.php @@ -0,0 +1,66 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model\Profile\Source; + +use Magento\Framework\Data\OptionSourceInterface; + +/** + * Class Status + * @package Mindarc\GoogleFeed\Model\Profile\Source + */ +class Status implements OptionSourceInterface +{ + /** + * Master data statuses + */ + const STATUS_ENABLED = 1; + const STATUS_DISABLED = 0; + + /** + * Get options + * + * @return array + */ + public function toOptionArray() + { + $availableOptions = $this->getAvailableStatuses(); + $options = []; + foreach ($availableOptions as $key => $value) { + $options[] = [ + 'label' => $value, + 'value' => $key, + ]; + } + return $options; + } + + /** + * Prepare Master data statuses. + * + * @return array + */ + public function getAvailableStatuses() + { + return [ + self::STATUS_ENABLED => __('ENABLED'), + self::STATUS_DISABLED => __('DISABLED'), + ]; + } + + /** + * @param $id + * @return mixed + */ + public function getStatusById($id) + { + return $this->getAvailableStatuses()[$id]; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Catalog/Product.php b/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Catalog/Product.php new file mode 100755 index 0000000..9472751 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Catalog/Product.php @@ -0,0 +1,290 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model\ResourceModel\Catalog; + +use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\Store\Model\Store; +use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Product\Visibility; + +/** + * Class Product + * @package Mindarc\GoogleFeed\Model\ResourceModel\Catalog + */ +class Product extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +{ + /** + * image no selection + */ + const NOT_SELECTED_IMAGE = 'no_selection'; + + /** + * Collection Zend Db select + * + * @var \Magento\Framework\DB\Select + */ + private $select; + + /** + * Attribute cache + * + * @var array + */ + private $attributesCache = []; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Product + */ + private $productResource; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var \Magento\Catalog\Model\Product\Visibility + */ + private $productVisibility; + + /** + * @var \Magento\Catalog\Model\Product + */ + private $productModel; + + /** + * @var \Magento\Catalog\Helper\Image + */ + private $catalogImageHelper; + + /** + * Product constructor. + * @param \Magento\Framework\Model\ResourceModel\Db\Context $context + * @param \Magento\Catalog\Model\ResourceModel\Product $productResource + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param Visibility $productVisibility + * @param null $connectionName + * @param \Magento\Catalog\Model\Product|null $productModel + * @param \Magento\Catalog\Helper\Image|null $catalogImageHelper + */ + public function __construct( + \Magento\Framework\Model\ResourceModel\Db\Context $context, + \Magento\Catalog\Model\ResourceModel\Product $productResource, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Catalog\Model\Product\Visibility $productVisibility, + $connectionName = null, + \Magento\Catalog\Model\Product $productModel = null, + \Magento\Catalog\Helper\Image $catalogImageHelper = null + ) { + $this->productResource = $productResource; + $this->storeManager = $storeManager; + $this->productVisibility = $productVisibility; + $this->productModel = $productModel ?: ObjectManager::getInstance()->get(\Magento\Catalog\Model\Product::class); + $this->catalogImageHelper = $catalogImageHelper ?: ObjectManager::getInstance() + ->get(\Magento\Catalog\Helper\Image::class); + parent::__construct($context, $connectionName); + } + + /** + * @return void + */ + protected function _construct() + { + $this->_init('catalog_product_entity', 'entity_id'); + } + + /** + * Get category collection array + * + * @param $exportProfile + * @return array|bool + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Zend_Db_Statement_Exception + */ + public function getCollection($exportProfile) + { + $storeId = $exportProfile->getStoreId(); + + $products = []; + + /* @var $store Store */ + $store = $this->storeManager->getStore($storeId); + if (!$store) { + return false; + } + + $connection = $this->getConnection(); + + // product visibilities to select ['catalog', 'search', 'catalog,search'] + $productVisibility = [ + Visibility::VISIBILITY_IN_CATALOG, + Visibility::VISIBILITY_IN_SEARCH, + Visibility::VISIBILITY_BOTH, + ]; + + // array to collect selects from flat table [default select] + $select = ['entity_id']; + + // feed element attributes + $feedElements = $exportProfile->getFeedElements(); + if (!empty($feedElements)) { + $attributes = []; + foreach ($feedElements as $element) { + // if magento attribute assigned to the element + if (!empty($element['magento'])) { + // magento attribute code + $attributeCode = $element['magento']; + if (in_array($attributeCode, $attributes)) { + continue; + } + $attributes[$element['element']] = $attributeCode; + + // add attribute to select + $select[] = $this->addSelect($attributeCode); + } + } + } + + $this->select = $connection->select()->from( + ['e' => $this->getTable('catalog_product_flat_' . $storeId)], + $select + )->joinLeft( // url rewrite + ['url_rewrite' => $this->getTable('url_rewrite')], + 'e.entity_id = url_rewrite.entity_id AND url_rewrite.is_autogenerated = 1 AND url_rewrite.metadata IS NULL' + . $connection->quoteInto(' AND url_rewrite.store_id = ?', $store->getId()) + . $connection->quoteInto(' AND url_rewrite.entity_type = ?', ProductUrlRewriteGenerator::ENTITY_TYPE), + ['url' => 'request_path'] + )->where('e.visibility IN (?)', $productVisibility);// visibility filter + $query = $connection->query($this->select); + + while ($row = $query->fetch()) { + $product = $this->prepareProduct($row, $store->getId()); + $products[$product->getId()] = $product; + } + + return $products; + } + + /** + * @param $attributeCode + * @return bool|\Magento\Framework\DB\Select + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function addSelect($attributeCode) + { + $attribute = $this->getAttribute($attributeCode); + if ($attribute['frontend_input'] == 'select') { + $attributeCode = $attributeCode . '_value as ' . $attributeCode; + } + + return $attributeCode; + } + + /** + * Get attribute data by attribute code + * + * @param $attributeCode + * @return mixed + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getAttribute($attributeCode) + { + if (!isset($this->attributesCache[$attributeCode])) { + $attribute = $this->productResource->getAttribute($attributeCode); + + $this->attributesCache[$attributeCode] = [ + 'entity_type_id' => $attribute->getEntityTypeId(), + 'attribute_id' => $attribute->getId(), + 'table' => $attribute->getBackend()->getTable(), + 'is_global' => $attribute->getIsGlobal() == + \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, + 'backend_type' => $attribute->getBackendType(), + 'frontend_input' => $attribute->getFrontendInput() + ]; + } + return $this->attributesCache[$attributeCode]; + } + + /** + * Prepare product + * + * @param array $productRow + * @param $storeId + * @return \Magento\Framework\DataObject + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function prepareProduct(array $productRow, $storeId) + { + $product = new \Magento\Framework\DataObject(); + + $product['id'] = $productRow[$this->getIdFieldName()]; + if (empty($productRow['url'])) { + $productRow['url'] = 'catalog/product/view/id/' . $product->getId(); + } + $product->addData($productRow); + $this->loadProductImages($product, $storeId); + + return $product; + } + + /** + * Load product images + * + * @param \Magento\Framework\DataObject $product + * @param int $storeId + * @return void + */ + private function loadProductImages($product, $storeId) + { + // Get product images + $imagesCollection = []; + if ($product->getImage() && $product->getImage() != self::NOT_SELECTED_IMAGE) { + $imagesCollection = [ + new \Magento\Framework\DataObject( + ['url' => $this->getProductImageUrl($product->getImage())] + ), + ]; + } + + if ($imagesCollection) { + // Determine thumbnail path + $thumbnail = $product->getThumbnail(); + if ($thumbnail && $product->getThumbnail() != self::NOT_SELECTED_IMAGE) { + $thumbnail = $this->getProductImageUrl($thumbnail); + } else { + $thumbnail = $imagesCollection[0]->getUrl(); + } + + $product->setImages( + new \Magento\Framework\DataObject( + ['collection' => $imagesCollection, 'title' => $product->getName(), 'thumbnail' => $thumbnail] + ) + ); + } + } + + /** + * Get product image URL from image filename and path + * + * @param string $image + * @return string + */ + private function getProductImageUrl($image) + { + $productObject = $this->productModel; + $imgUrl = $this->catalogImageHelper + ->init($productObject, 'product_page_image_large') + ->setImageFile($image) + ->getUrl(); + + return $imgUrl; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Profile.php b/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Profile.php new file mode 100755 index 0000000..b84ef26 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Profile.php @@ -0,0 +1,30 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model\ResourceModel; + +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; + +/** + * Class Profile + * @package Mindarc\GoogleFeed\Model\ResourceModel + */ +class Profile extends AbstractDb +{ + /** + * Initialize table nad PK name + * + * @return void + */ + protected function _construct() + { + $this->_init('google_profile', 'entity_id'); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Profile/Collection.php b/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Profile/Collection.php new file mode 100755 index 0000000..d429483 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Model/ResourceModel/Profile/Collection.php @@ -0,0 +1,38 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Model\ResourceModel\Profile; + +use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; + +/** + * Class Collection + * @package Mindarc\GoogleFeed\Model\ResourceModel\Profile + */ +class Collection extends AbstractCollection +{ + /** + * @var string + */ + protected $_idFieldName = 'entity_id'; + + /** + * Resource initialization + * + * @return void + */ + protected function _construct() + { + $this->_init( + \Mindarc\GoogleFeed\Model\Profile::class, + \Mindarc\GoogleFeed\Model\ResourceModel\Profile::class + ); + } +} diff --git a/app/code/Mindarc/GoogleFeed/README.md b/app/code/Mindarc/GoogleFeed/README.md new file mode 100644 index 0000000..43c553e --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/README.md @@ -0,0 +1,4 @@ +

Mindarc_GoogleFeed Module

+ +This module is used to generate xml feed files for Google with products in Magento based export profiles. +Export profiles define the settings of the feed which will be used for feed genaration. diff --git a/app/code/Mindarc/GoogleFeed/Service/CurrencyConverter.php b/app/code/Mindarc/GoogleFeed/Service/CurrencyConverter.php new file mode 100644 index 0000000..d429f54 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Service/CurrencyConverter.php @@ -0,0 +1,117 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Service; + +/** + * Class CurrencyConverter + * @package Mindarc\GoogleFeed\Service + */ +class CurrencyConverter +{ + /** + * currency convert url + */ + const FIXER_CONVERT_URL = 'https://free.currencyconverterapi.com/api/v6/convert'; + + /** + * fixer api key store config path + */ + const XML_PATH_FIXER_API_KEY = 'google_feed/converter/api_key'; + + /** + * curl connection timeout + */ + const CURL_CONNECTION_TIMEOUT = '15'; + + /** + * curl reponse timeout + */ + const CURL_REPONSE_TIMEOUT = '15'; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + + /** + * Fixer constructor. + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + */ + public function __construct( + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + ) { + $this->scopeConfig = $scopeConfig; + } + + /** + * get the currency conversion rate from the API + * + * @param string $fromCurrency + * @param string $toCurrency + * @return int|mixed + */ + public function getRate($fromCurrency = 'AUD', $toCurrency = 'USD') + { + $error = ''; + $rate = 0; + try { + $ch = curl_init(); + if (false === $ch) { + $error = __('failed to initialize service'); + } + + $apiKey = $this->scopeConfig->getValue( + self::XML_PATH_FIXER_API_KEY + ); + + if (!empty($apiKey)) { + curl_setopt( + $ch, + CURLOPT_URL, + self::FIXER_CONVERT_URL . + __('?q=%1_%2&compact=ultra&apiKey=%3', $fromCurrency, $toCurrency, $apiKey) + ); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, self::CURL_REPONSE_TIMEOUT); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::CURL_CONNECTION_TIMEOUT); + + $content = curl_exec($ch); + if ($content === false) { + $error = __('Service not responding'); + } + + if ($content) { + $rate = json_decode($content, true); + $rate = $rate[sprintf('%s_%s', $fromCurrency, $toCurrency)]; + } else { + $error = __('Encode Error'); + } + } + curl_close($ch); + } catch (\Exception $e) { + $error = sprintf('Curl failed with error #%d: %s', $e->getCode(), $e->getMessage()); + } + + return $rate; + } + + /** + * convert the amount according to the rate + * + * @param $amount + * @param $rate + * @return float|int + */ + public function convert($amount, $rate) + { + return $amount * $rate; + } +} diff --git a/app/code/Mindarc/GoogleFeed/Setup/InstallSchema.php b/app/code/Mindarc/GoogleFeed/Setup/InstallSchema.php new file mode 100644 index 0000000..b314afb --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Setup/InstallSchema.php @@ -0,0 +1,126 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Setup; + +use Magento\Framework\Setup\InstallSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * Class InstallSchema + * @package Mindarc\GoogleFeed\Setup + */ +class InstallSchema implements InstallSchemaInterface +{ + /** + * {@inheritdoc} + */ + public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $installer = $setup; + + $installer->startSetup(); + $connection = $installer->getConnection(); + + $table = $installer->getTable('google_profile'); + + if ($connection->isTableExists($table) === true) { + $connection->dropTable($table); + } + + /** + * Create table 'google_profile' + */ + $table = $installer + ->getConnection() + ->newTable( + $table + )->addColumn( + 'entity_id', + \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + null, + ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true], + 'Feed profile id' + )->addColumn( + 'profile_name', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 255, + ['nullable' => false, 'default' => ''], + 'Profile name' + )->addColumn( + 'filename', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 255, + ['nullable' => true, 'default' => null], + 'Feed file name' + )->addColumn( + 'url', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 255, + ['nullable' => true, 'default' => null], + 'Feed file path' + )->addColumn( + 'status', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + 5, + ['default' => 1], + 'Status - 0 = DISABLED, 1 = ENABLED' + )->addColumn( + 'generation_status', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + 5, + ['default' => 0], + 'Status - 0 = NEW, 1 = RUNNING, 2 = ERROR, 3 = FINISHED' + )->addColumn( + 'message', + \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 255, + [], + 'Message' + )->addColumn( + 'store_id', + \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + 5, + ['unsigned' => true, 'nullable' => false, 'default' => 1], + 'Store id' + )->addColumn( + 'generated_time', + \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + null, + ['nullable' => true], + 'Last generated Time' + )->addColumn( + 'created_at', + \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT], + 'Profile created at' + )->addColumn( + 'updated_at', + \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + null, + ['nullable' => false, 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE], + 'Profile updated at' + )->addForeignKey( + $installer->getFkName('google_profile', 'store_id', 'store', 'store_id'), + 'store_id', + $installer->getTable('store'), + 'store_id', + \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE + )->setComment( + 'Google feed profiles' + ); + + $installer->getConnection()->createTable($table); + + $installer->endSetup(); + } +} diff --git a/app/code/Mindarc/GoogleFeed/Ui/Component/Listing/Profile/Columns/Renderer/Actions.php b/app/code/Mindarc/GoogleFeed/Ui/Component/Listing/Profile/Columns/Renderer/Actions.php new file mode 100755 index 0000000..eaa23f8 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/Ui/Component/Listing/Profile/Columns/Renderer/Actions.php @@ -0,0 +1,131 @@ + + * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/) + */ + +namespace Mindarc\GoogleFeed\Ui\Component\Listing\Profile\Columns\Renderer; + +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\UiComponentFactory; +use Magento\Ui\Component\Listing\Columns\Column; +use Magento\Framework\UrlInterface; + +/** + * Class Actions + * @package Mindarc\GoogleFeed\Ui\Component\Listing\Profile\Columns\Renderer + */ +class Actions extends Column +{ + /** Url path */ + const URL_PATH_GENERATE = 'google/profile/generate'; + const URL_PATH_PREVIEW = 'google/profile/preview'; + const URL_PATH_EDIT = 'google/profile/edit'; + const URL_PATH_DELETE = 'google/profile/delete'; + + /** + * @var UrlInterface + */ + protected $urlBuilder; + + /** + * @var \Magento\Framework\AuthorizationInterface + */ + protected $authorization; + + /** + * Actions constructor. + * @param ContextInterface $context + * @param UiComponentFactory $uiComponentFactory + * @param UrlInterface $urlBuilder + * @param \Magento\Framework\AuthorizationInterface $authorization + * @param array $components + * @param array $data + */ + public function __construct( + ContextInterface $context, + UiComponentFactory $uiComponentFactory, + UrlInterface $urlBuilder, + \Magento\Framework\AuthorizationInterface $authorization, + array $components = [], + array $data = [] + ) { + $this->urlBuilder = $urlBuilder; + $this->authorization = $authorization; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * Prepare Data Source + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + foreach ($dataSource['data']['items'] as & $item) { + $name = $this->getData('name'); + + // if saving profiles allowed + if ($this->authorization->isAllowed('Mindarc_GoogleFeed::form')) { + $item[$name]['edit'] = [ + 'href' => $this->urlBuilder->getUrl( + self::URL_PATH_EDIT, + ['id' => $item['entity_id']] + ), + 'label' => __('Edit') + ]; + } + + // if viewing profiles allowed + if ($this->authorization->isAllowed('Mindarc_GoogleFeed::profiles')) { + $item[$name]['preview'] = [ + 'href' => $this->urlBuilder->getUrl( + self::URL_PATH_PREVIEW, + ['id' => $item['entity_id']] + ), + 'label' => __('Preview'), + ]; + } + + // if generation allowed and profile is not disabled + if ($this->authorization->isAllowed('Mindarc_GoogleFeed::generate') + && $item['status'] != \Mindarc\GoogleFeed\Model\Profile\Source\Status::STATUS_DISABLED) { + $item[$name]['generate'] = [ + 'href' => $this->urlBuilder->getUrl( + self::URL_PATH_GENERATE, + ['id' => $item['entity_id']] + ), + 'label' => __('Generate'), + 'confirm' => [ + 'title' => __('Generate'), + 'message' => __('Are you sure you want generate the feed now?') + ], + ]; + } + + // if deleting profiles allowed + if ($this->authorization->isAllowed('Mindarc_GoogleFeed::delete')) { + $item[$name]['delete'] = [ + 'href' => $this->urlBuilder->getUrl( + self::URL_PATH_DELETE, + ['id' => $item['entity_id']] + ), + 'label' => __('Delete'), + 'confirm' => [ + 'title' => __('Delete'), + 'message' => __('Are you sure to delete this profile?') + ] + ]; + } + } + } + + return $dataSource; + } +} diff --git a/app/code/Mindarc/GoogleFeed/composer.json b/app/code/Mindarc/GoogleFeed/composer.json new file mode 100644 index 0000000..7fb9d28 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/composer.json @@ -0,0 +1,23 @@ +{ + "name": "mindarc/module-googlefeed", + "description": "Mindarc Magento 2 module for Google feed generation", + "require": { + "php": "~5.5.0|~5.6.0|~7.0.0|~7.1.0", + "magento/module-config": "*", + "magento/framework": "*" + }, + "type": "magento2-module", + "version": "100.23.0", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Mindarc\\GoogleFeed\\": "" + } + } +} diff --git a/app/code/Mindarc/GoogleFeed/etc/acl.xml b/app/code/Mindarc/GoogleFeed/etc/acl.xml new file mode 100644 index 0000000..77e1172 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/acl.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/code/Mindarc/GoogleFeed/etc/adminhtml/menu.xml b/app/code/Mindarc/GoogleFeed/etc/adminhtml/menu.xml new file mode 100644 index 0000000..9737eb8 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/adminhtml/menu.xml @@ -0,0 +1,36 @@ + + +    + + + + + + + \ No newline at end of file diff --git a/app/code/Mindarc/GoogleFeed/etc/adminhtml/routes.xml b/app/code/Mindarc/GoogleFeed/etc/adminhtml/routes.xml new file mode 100644 index 0000000..50b4b26 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/adminhtml/routes.xml @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/app/code/Mindarc/GoogleFeed/etc/adminhtml/system.xml b/app/code/Mindarc/GoogleFeed/etc/adminhtml/system.xml new file mode 100644 index 0000000..548820f --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/adminhtml/system.xml @@ -0,0 +1,49 @@ + + + + + + + +
+ + mindarc + Mindarc_GoogleFeed::configuration + + + + + Magento\Config\Model\Config\Source\Yesno + + + + Comma separated email addressed to receive error emails. + + + + Magento\Config\Model\Config\Source\Email\Identity + + + + Email template chosen based on theme fallback when "Default" option is selected. + Magento\Config\Model\Config\Source\Email\Template + + + + + + + + +
+
+
\ No newline at end of file diff --git a/app/code/Mindarc/GoogleFeed/etc/config.xml b/app/code/Mindarc/GoogleFeed/etc/config.xml new file mode 100644 index 0000000..53037ba --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/config.xml @@ -0,0 +1,23 @@ + + + + + + + 1 + + + 9efcebcad9f3d4134aaa + + + + \ No newline at end of file diff --git a/app/code/Mindarc/GoogleFeed/etc/crontab.xml b/app/code/Mindarc/GoogleFeed/etc/crontab.xml new file mode 100644 index 0000000..f858bee --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/crontab.xml @@ -0,0 +1,18 @@ + + + + + + 0 0 * * * + + + diff --git a/app/code/Mindarc/GoogleFeed/etc/di.xml b/app/code/Mindarc/GoogleFeed/etc/di.xml new file mode 100644 index 0000000..0cce05a --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/di.xml @@ -0,0 +1,33 @@ + + + + + + google_profile + Mindarc\GoogleFeed\Model\ResourceModel\Profile + + + + + + GoogleProfileCollection + + + + + + + Mindarc\GoogleFeed\Console\Command\Execute + + + + diff --git a/app/code/Mindarc/GoogleFeed/etc/email_templates.xml b/app/code/Mindarc/GoogleFeed/etc/email_templates.xml new file mode 100644 index 0000000..450c827 --- /dev/null +++ b/app/code/Mindarc/GoogleFeed/etc/email_templates.xml @@ -0,0 +1,14 @@ + + + +