PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/vendor/magento/module-tax-import-export/Model/Rate/CsvImportHandler.php

https://gitlab.com/yousafsyed/easternglamor
PHP | 299 lines | 161 code | 21 blank | 117 comment | 17 complexity | 3fe84562ce60fe191c398a6cb26849dc MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright © 2016 Magento. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\TaxImportExport\Model\Rate;
  7. /**
  8. * Tax Rate CSV Import Handler
  9. */
  10. class CsvImportHandler
  11. {
  12. /**
  13. * Collection of publicly available stores
  14. *
  15. * @var \Magento\Store\Model\ResourceModel\Store\Collection
  16. */
  17. protected $_publicStores;
  18. /**
  19. * Region collection prototype
  20. *
  21. * The instance is used to retrieve regions based on country code
  22. *
  23. * @var \Magento\Directory\Model\ResourceModel\Region\Collection
  24. */
  25. protected $_regionCollection;
  26. /**
  27. * Country factory
  28. *
  29. * @var \Magento\Directory\Model\CountryFactory
  30. */
  31. protected $_countryFactory;
  32. /**
  33. * Tax rate factory
  34. *
  35. * @var \Magento\Tax\Model\Calculation\RateFactory
  36. */
  37. protected $_taxRateFactory;
  38. /**
  39. * CSV Processor
  40. *
  41. * @var \Magento\Framework\File\Csv
  42. */
  43. protected $csvProcessor;
  44. /**
  45. * @param \Magento\Store\Model\ResourceModel\Store\Collection $storeCollection
  46. * @param \Magento\Directory\Model\ResourceModel\Region\Collection $regionCollection
  47. * @param \Magento\Directory\Model\CountryFactory $countryFactory
  48. * @param \Magento\Tax\Model\Calculation\RateFactory $taxRateFactory
  49. * @param \Magento\Framework\File\Csv $csvProcessor
  50. */
  51. public function __construct(
  52. \Magento\Store\Model\ResourceModel\Store\Collection $storeCollection,
  53. \Magento\Directory\Model\ResourceModel\Region\Collection $regionCollection,
  54. \Magento\Directory\Model\CountryFactory $countryFactory,
  55. \Magento\Tax\Model\Calculation\RateFactory $taxRateFactory,
  56. \Magento\Framework\File\Csv $csvProcessor
  57. ) {
  58. // prevent admin store from loading
  59. $this->_publicStores = $storeCollection->setLoadDefault(false);
  60. $this->_regionCollection = $regionCollection;
  61. $this->_countryFactory = $countryFactory;
  62. $this->_taxRateFactory = $taxRateFactory;
  63. $this->csvProcessor = $csvProcessor;
  64. }
  65. /**
  66. * Retrieve a list of fields required for CSV file (order is important!)
  67. *
  68. * @return array
  69. */
  70. public function getRequiredCsvFields()
  71. {
  72. // indexes are specified for clarity, they are used during import
  73. return [
  74. 0 => __('Code'),
  75. 1 => __('Country'),
  76. 2 => __('State'),
  77. 3 => __('Zip/Post Code'),
  78. 4 => __('Rate'),
  79. 5 => __('Zip/Post is Range'),
  80. 6 => __('Range From'),
  81. 7 => __('Range To')
  82. ];
  83. }
  84. /**
  85. * Import Tax Rates from CSV file
  86. *
  87. * @param array $file file info retrieved from $_FILES array
  88. * @return void
  89. * @throws \Magento\Framework\Exception\LocalizedException
  90. */
  91. public function importFromCsvFile($file)
  92. {
  93. if (!isset($file['tmp_name'])) {
  94. throw new \Magento\Framework\Exception\LocalizedException(__('Invalid file upload attempt.'));
  95. }
  96. $ratesRawData = $this->csvProcessor->getData($file['tmp_name']);
  97. // first row of file represents headers
  98. $fileFields = $ratesRawData[0];
  99. $validFields = $this->_filterFileFields($fileFields);
  100. $invalidFields = array_diff_key($fileFields, $validFields);
  101. $ratesData = $this->_filterRateData($ratesRawData, $invalidFields, $validFields);
  102. // store cache array is used to quickly retrieve store ID when handling locale-specific tax rate titles
  103. $storesCache = $this->_composeStoreCache($validFields);
  104. $regionsCache = [];
  105. foreach ($ratesData as $rowIndex => $dataRow) {
  106. // skip headers
  107. if ($rowIndex == 0) {
  108. continue;
  109. }
  110. $regionsCache = $this->_importRate($dataRow, $regionsCache, $storesCache);
  111. }
  112. }
  113. /**
  114. * Filter file fields (i.e. unset invalid fields)
  115. *
  116. * @param array $fileFields
  117. * @return string[] filtered fields
  118. */
  119. protected function _filterFileFields(array $fileFields)
  120. {
  121. $filteredFields = $this->getRequiredCsvFields();
  122. $requiredFieldsNum = count($this->getRequiredCsvFields());
  123. $fileFieldsNum = count($fileFields);
  124. // process title-related fields that are located right after required fields with store code as field name)
  125. for ($index = $requiredFieldsNum; $index < $fileFieldsNum; $index++) {
  126. $titleFieldName = $fileFields[$index];
  127. if ($this->_isStoreCodeValid($titleFieldName)) {
  128. // if store is still valid, append this field to valid file fields
  129. $filteredFields[$index] = $titleFieldName;
  130. }
  131. }
  132. return $filteredFields;
  133. }
  134. /**
  135. * Filter rates data (i.e. unset all invalid fields and check consistency)
  136. *
  137. * @param array $rateRawData
  138. * @param array $invalidFields assoc array of invalid file fields
  139. * @param array $validFields assoc array of valid file fields
  140. * @return array
  141. * @throws \Magento\Framework\Exception\LocalizedException
  142. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  143. */
  144. protected function _filterRateData(array $rateRawData, array $invalidFields, array $validFields)
  145. {
  146. $validFieldsNum = count($validFields);
  147. foreach ($rateRawData as $rowIndex => $dataRow) {
  148. // skip empty rows
  149. if (count($dataRow) <= 1) {
  150. unset($rateRawData[$rowIndex]);
  151. continue;
  152. }
  153. // unset invalid fields from data row
  154. foreach ($dataRow as $fieldIndex => $fieldValue) {
  155. if (isset($invalidFields[$fieldIndex])) {
  156. unset($rateRawData[$rowIndex][$fieldIndex]);
  157. }
  158. }
  159. // check if number of fields in row match with number of valid fields
  160. if (count($rateRawData[$rowIndex]) != $validFieldsNum) {
  161. throw new \Magento\Framework\Exception\LocalizedException(__('Invalid file format.'));
  162. }
  163. }
  164. return $rateRawData;
  165. }
  166. /**
  167. * Compose stores cache
  168. *
  169. * This cache is used to quickly retrieve store ID when handling locale-specific tax rate titles
  170. *
  171. * @param string[] $validFields list of valid CSV file fields
  172. * @return array
  173. */
  174. protected function _composeStoreCache($validFields)
  175. {
  176. $storesCache = [];
  177. $requiredFieldsNum = count($this->getRequiredCsvFields());
  178. $validFieldsNum = count($validFields);
  179. // title related fields located right after required fields
  180. for ($index = $requiredFieldsNum; $index < $validFieldsNum; $index++) {
  181. foreach ($this->_publicStores as $store) {
  182. $storeCode = $validFields[$index];
  183. if ($storeCode === $store->getCode()) {
  184. $storesCache[$index] = $store->getId();
  185. }
  186. }
  187. }
  188. return $storesCache;
  189. }
  190. /**
  191. * Check if public store with specified code still exists
  192. *
  193. * @param string $storeCode
  194. * @return boolean
  195. */
  196. protected function _isStoreCodeValid($storeCode)
  197. {
  198. $isStoreCodeValid = false;
  199. foreach ($this->_publicStores as $store) {
  200. if ($storeCode === $store->getCode()) {
  201. $isStoreCodeValid = true;
  202. break;
  203. }
  204. }
  205. return $isStoreCodeValid;
  206. }
  207. /**
  208. * Import single rate
  209. *
  210. * @param array $rateData
  211. * @param array $regionsCache cache of regions of already used countries (is used to optimize performance)
  212. * @param array $storesCache cache of stores related to tax rate titles
  213. * @return array regions cache populated with regions related to country of imported tax rate
  214. * @throws \Magento\Framework\Exception\LocalizedException
  215. */
  216. protected function _importRate(array $rateData, array $regionsCache, array $storesCache)
  217. {
  218. // data with index 1 must represent country code
  219. $countryCode = $rateData[1];
  220. $country = $this->_countryFactory->create()->loadByCode($countryCode, 'iso2_code');
  221. if (!$country->getId()) {
  222. throw new \Magento\Framework\Exception\LocalizedException(__('One of the countries has invalid code.'));
  223. }
  224. $regionsCache = $this->_addCountryRegionsToCache($countryCode, $regionsCache);
  225. // data with index 2 must represent region code
  226. $regionCode = $rateData[2];
  227. if (!empty($regionsCache[$countryCode][$regionCode])) {
  228. $regionId = $regionsCache[$countryCode][$regionCode] == '*' ? 0 : $regionsCache[$countryCode][$regionCode];
  229. // data with index 3 must represent postcode
  230. $postCode = empty($rateData[3]) ? null : $rateData[3];
  231. $modelData = [
  232. 'code' => $rateData[0],
  233. 'tax_country_id' => $rateData[1],
  234. 'tax_region_id' => $regionId,
  235. 'tax_postcode' => $postCode,
  236. 'rate' => $rateData[4],
  237. 'zip_is_range' => $rateData[5],
  238. 'zip_from' => $rateData[6],
  239. 'zip_to' => $rateData[7],
  240. ];
  241. // try to load existing rate
  242. /** @var $rateModel \Magento\Tax\Model\Calculation\Rate */
  243. $rateModel = $this->_taxRateFactory->create()->loadByCode($modelData['code']);
  244. $rateModel->addData($modelData);
  245. // compose titles list
  246. $rateTitles = [];
  247. foreach ($storesCache as $fileFieldIndex => $storeId) {
  248. $rateTitles[$storeId] = $rateData[$fileFieldIndex];
  249. }
  250. $rateModel->setTitle($rateTitles);
  251. $rateModel->save();
  252. }
  253. return $regionsCache;
  254. }
  255. /**
  256. * Add regions of the given country to regions cache
  257. *
  258. * @param string $countryCode
  259. * @param array $regionsCache
  260. * @return array
  261. */
  262. protected function _addCountryRegionsToCache($countryCode, array $regionsCache)
  263. {
  264. if (!isset($regionsCache[$countryCode])) {
  265. $regionsCache[$countryCode] = [];
  266. // add 'All Regions' to the list
  267. $regionsCache[$countryCode]['*'] = '*';
  268. $regionCollection = clone $this->_regionCollection;
  269. $regionCollection->addCountryFilter($countryCode);
  270. if ($regionCollection->getSize()) {
  271. foreach ($regionCollection as $region) {
  272. $regionsCache[$countryCode][$region->getCode()] = $region->getRegionId();
  273. }
  274. }
  275. }
  276. return $regionsCache;
  277. }
  278. }