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)):
+?>
+
+ = __('Country Code: %1', $viewModel->getCountryCode()) ?>
+
+ isUsUser()): ?>
+ = $block->getChildHtml('product.geo.info.us') ?>
+
+ = $block->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%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 @@
+
+
+
+
+
diff --git a/app/code/Mindarc/GoogleFeed/etc/module.xml b/app/code/Mindarc/GoogleFeed/etc/module.xml
new file mode 100644
index 0000000..aee4e21
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/etc/module.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Mindarc/GoogleFeed/registration.php b/app/code/Mindarc/GoogleFeed/registration.php
new file mode 100644
index 0000000..811e585
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/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_GoogleFeed',
+ __DIR__
+);
\ No newline at end of file
diff --git a/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_edit.xml b/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_edit.xml
new file mode 100755
index 0000000..40a4d01
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_edit.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_index.xml b/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_index.xml
new file mode 100755
index 0000000..9f92329
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_index.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_preview.xml b/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_preview.xml
new file mode 100755
index 0000000..42e4ac6
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/view/adminhtml/layout/google_profile_preview.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+ Mindarc\GoogleFeed\Block\Adminhtml\Profile\Preview
+
+
+
+
+
diff --git a/app/code/Mindarc/GoogleFeed/view/adminhtml/templates/profile/preview.phtml b/app/code/Mindarc/GoogleFeed/view/adminhtml/templates/profile/preview.phtml
new file mode 100644
index 0000000..0bb19e1
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/view/adminhtml/templates/profile/preview.phtml
@@ -0,0 +1,104 @@
+
+ * @copyright Copyright (c) 2019 Mindarc Pty Ltd. (https://www.mindarc.com.au/)
+ */
+?>
+getData('viewModel');
+$xml = $viewModel->getFeed();
+?>
+
+
+
+
+
+
+ = __('URL of the Google feed file:') ?>
+
+
+
+
= $viewModel->getFeedUrl() ?>
+
+
= __('No feed file or URL found for this profile. Generate the feed and check again.') ?>
+
+
+
+
+
+
+ = __('Feed file preview (sample):') ?>
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Mindarc/GoogleFeed/view/adminhtml/ui_component/google_profile_form.xml b/app/code/Mindarc/GoogleFeed/view/adminhtml/ui_component/google_profile_form.xml
new file mode 100755
index 0000000..0f7493a
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/view/adminhtml/ui_component/google_profile_form.xml
@@ -0,0 +1,129 @@
+
+
+
diff --git a/app/code/Mindarc/GoogleFeed/view/adminhtml/ui_component/google_profile_listing.xml b/app/code/Mindarc/GoogleFeed/view/adminhtml/ui_component/google_profile_listing.xml
new file mode 100755
index 0000000..b7c6002
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/view/adminhtml/ui_component/google_profile_listing.xml
@@ -0,0 +1,130 @@
+
+
+
+
+ -
+
- google_profile_listing.google_profile_listing_data_source
+
+
+
+
+
+
+ google_profile_columns
+
+ google_profile_listing.google_profile_listing_data_source
+
+
+
+
+
+ entity_id
+
+
+
+ Mindarc_GoogleFeed::profiles
+
+
+ id
+ entity_id
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ store_id
+
+ componentType = column, index = ${ $.index }:visible
+
+
+
+
+
+
+
+
+
+ textRange
+
+ asc
+
+
+
+
+ textRange
+
+ false
+
+
+
+
+
+ ui/grid/cells/html
+ false
+
+
+
+
+
+ select
+ select
+
+
+
+
+
+
+ select
+ select
+
+
+
+
+
+ dateRange
+ date
+
+
+
+
+
+ entity_id
+ false
+ 107
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Mindarc/GoogleFeed/view/frontend/email/generation_error_email.html b/app/code/Mindarc/GoogleFeed/view/frontend/email/generation_error_email.html
new file mode 100644
index 0000000..6e1186e
--- /dev/null
+++ b/app/code/Mindarc/GoogleFeed/view/frontend/email/generation_error_email.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{inlinecss file="css/email-inline.css"}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ There have been error occurred during the Google feed generation. Please find the below errors.
+
+
+ {{var errors|raw}}
+
+
+ |
+
+
+ |
+
+
+
+
+