diff --git a/app/code/Magento/Catalog/Setup/UpgradeData.php b/app/code/Magento/Catalog/Setup/UpgradeData.php new file mode 100644 index 0000000000000..1459f4a31d681 --- /dev/null +++ b/app/code/Magento/Catalog/Setup/UpgradeData.php @@ -0,0 +1,56 @@ +category = $category; + } + + /** + * {@inheritdoc} + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + if (version_compare($context->getVersion(), '2.0.0.2') < 0) { + $newBackendModel = 'Magento\Catalog\Model\Attribute\Backend\Startdate'; + $connection = $setup->getConnection(); + $connection->startSetup(); + $connection->update( + $setup->getTable('eav_attribute'), + ['backend_model' => $newBackendModel], + ['backend_model = ?' => 'Magento\Catalog\Model\Product\Attribute\Backend\Startdate'] + ); + /** @var \Magento\Catalog\Model\Resource\Eav\Attribute $attribute */ + foreach ($this->category->getAttributes() as $attribute) { + if ($attribute->getAttributeCode() == 'custom_design_from') { + $attribute->setBackendModel($newBackendModel); + $attribute->save(); + break; + } + } + $connection->endSetup(); + } + } +} diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml index ccb21b6aadb8e..5672bf07905c4 100644 --- a/app/code/Magento/Catalog/etc/module.xml +++ b/app/code/Magento/Catalog/etc/module.xml @@ -6,7 +6,7 @@ */ --> - + diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 72810dc82611f..7b3303731f345 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -123,6 +123,13 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity */ protected $_attributeTypes = []; + /** + * Product collection + * + * @var \Magento\Catalog\Model\Resource\Product\CollectionFactory + */ + protected $_entityCollectionFactory; + /** * Product collection * @@ -231,7 +238,7 @@ public function __construct( \Magento\Framework\App\Resource $resource, \Magento\Store\Model\StoreManagerInterface $storeManager, \Psr\Log\LoggerInterface $logger, - \Magento\Catalog\Model\Resource\Product\Collection $collection, + \Magento\Catalog\Model\Resource\Product\CollectionFactory $collectionFactory, \Magento\ImportExport\Model\Export\ConfigInterface $exportConfig, \Magento\Catalog\Model\Resource\ProductFactory $productFactory, \Magento\Eav\Model\Resource\Entity\Attribute\Set\CollectionFactory $attrSetColFactory, @@ -243,7 +250,7 @@ public function __construct( \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider, \Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer ) { - $this->_entityCollection = $collection; + $this->_entityCollectionFactory = $collectionFactory; $this->_exportConfig = $exportConfig; $this->_logger = $logger; $this->_productFactory = $productFactory; @@ -669,12 +676,13 @@ protected function setHeaderColumns($customOptionsData, $stockItemRows) } /** - * Get product collection - * - * @return \Magento\Catalog\Model\Resource\Product\Collection + * {@inheritdoc} */ - protected function _getEntityCollection() + protected function _getEntityCollection($resetCollection = false) { + if ($resetCollection || empty($this->_entityCollection)) { + $this->_entityCollection = $this->_entityCollectionFactory->create(); + } return $this->_entityCollection; } @@ -735,7 +743,6 @@ protected function paginateCollection($page, $pageSize) /** * Export process * - * @see https://jira.corp.x.com/browse/MAGETWO-7894 * @return string */ public function export() @@ -743,15 +750,16 @@ public function export() //Execution time may be very long set_time_limit(0); - $this->_prepareEntityCollection($this->_getEntityCollection()); - $this->_getEntityCollection()->setOrder('has_options', 'asc'); - $this->_getEntityCollection()->setStoreId(Store::DEFAULT_STORE_ID); $writer = $this->getWriter(); $page = 0; while (true) { ++$page; + $entityCollection = $this->_getEntityCollection(true); + $entityCollection->setOrder('has_options', 'asc'); + $entityCollection->setStoreId(Store::DEFAULT_STORE_ID); + $this->_prepareEntityCollection($entityCollection); $this->paginateCollection($page, $this->getItemsPerPage()); - if ($this->_getEntityCollection()->count() == 0) { + if ($entityCollection->count() == 0) { break; } $exportData = $this->getExportData(); @@ -761,7 +769,7 @@ public function export() foreach ($exportData as $dataRow) { $writer->writeRow($dataRow); } - if ($this->_getEntityCollection()->getCurPage() >= $this->_getEntityCollection()->getLastPageNumber()) { + if ($entityCollection->getCurPage() >= $entityCollection->getLastPageNumber()) { break; } } diff --git a/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEntity.php index 4ed67c3b79911..c9581ad7e7471 100644 --- a/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEntity.php +++ b/app/code/Magento/ImportExport/Model/Export/Entity/AbstractEntity.php @@ -190,9 +190,10 @@ abstract protected function _getHeaderColumns(); /** * Get entity collection * + * @param bool $resetCollection * @return \Magento\Framework\Data\Collection\Db */ - abstract protected function _getEntityCollection(); + abstract protected function _getEntityCollection($resetCollection = false); /** * Get attributes codes which are appropriate for export. diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php index a83bb8be1c9fe..9b91abf2a13cb 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php @@ -70,17 +70,11 @@ public function testExport() */ public function testExportStockItemAttributesAreFilled() { - $filesystemMock = $this->getMock('Magento\Framework\Filesystem', [], [], '', false); + $fileWrite = $this->getMock('Magento\Framework\Filesystem\File\Write', [], [], '', false); $directoryMock = $this->getMock('Magento\Framework\Filesystem\Directory\Write', [], [], '', false); - - $filesystemMock->expects($this->once())->method('getDirectoryWrite')->will($this->returnValue($directoryMock)); - $directoryMock->expects($this->any())->method('getParentDirectory')->will($this->returnValue('some#path')); - $directoryMock->expects($this->any())->method('isWritable')->will($this->returnValue(true)); - $directoryMock->expects($this->any())->method('isFile')->will($this->returnValue(true)); - $directoryMock->expects( $this->any() )->method( @@ -88,6 +82,10 @@ public function testExportStockItemAttributesAreFilled() )->will( $this->returnValue('some string read from file') ); + $directoryMock->expects($this->once())->method('openFile')->will($this->returnValue($fileWrite)); + + $filesystemMock = $this->getMock('Magento\Framework\Filesystem', [], [], '', false); + $filesystemMock->expects($this->once())->method('getDirectoryWrite')->will($this->returnValue($directoryMock)); $exportAdapter = new \Magento\ImportExport\Model\Export\Adapter\Csv($filesystemMock); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/File/WriteTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/File/WriteTest.php index 67c671e2f253f..6defca2aa919a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/File/WriteTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/File/WriteTest.php @@ -81,7 +81,7 @@ public function writeProvider() ['new1.csv', 'w', 'write check', 11], ['new3.csv', 'a', 'write check', 11], ['new5.csv', 'x', 'write check', 11], - ['new7.csv', 'c', 'write check', 11] + ['new7.csv', 'c', 'write check', 11], ]; } @@ -117,20 +117,21 @@ public function writeAndReadProvider() ['new2.csv', 'w+', 'write check', 11], ['new4.csv', 'a+', 'write check', 11], ['new6.csv', 'x+', 'write check', 11], - ['new8.csv', 'c+', 'write check', 11] + ['new8.csv', 'c+', 'write check', 11], ]; } /** * Writes one CSV row to the file. * - * @dataProvider csvProvider + * @dataProvider csvDataProvider + * @param array $expectedData * @param string $path * @param array $data * @param string $delimiter * @param string $enclosure */ - public function testWriteCsv($path, array $data, $delimiter = ',', $enclosure = '"') + public function testWriteCsv($expectedData, $path, array $data, $delimiter = ',', $enclosure = '"') { $file = $this->getFileInstance($path, 'w+'); $result = $file->writeCsv($data, $delimiter, $enclosure); @@ -138,7 +139,7 @@ public function testWriteCsv($path, array $data, $delimiter = ',', $enclosure = $read = $file->readCsv($result, $delimiter, $enclosure); $file->close(); $this->removeCurrentFile(); - $this->assertEquals($data, $read); + $this->assertEquals($expectedData, $read); } /** @@ -146,11 +147,12 @@ public function testWriteCsv($path, array $data, $delimiter = ',', $enclosure = * * @return array */ - public function csvProvider() + public function csvDataProvider() { return [ - ['newcsv1.csv', ['field1', 'field2'], ',', '"'], - ['newcsv1.csv', ['field1', 'field2'], '%', '@'] + [['field1', 'field2'], 'newcsv1.csv', ['field1', 'field2'], ',', '"'], + [['field1', 'field2'], 'newcsv1.csv', ['field1', 'field2'], '%', '@'], + [[' =field1', 'field2'], 'newcsv1.csv', ['=field1', 'field2'], '%', '@'], ]; } @@ -201,7 +203,7 @@ private function getFileInstance($path, $mode) [ 'path' => $this->currentFilePath, 'driver' => new \Magento\Framework\Filesystem\Driver\File(), - 'mode' => $mode + 'mode' => $mode, ] ); } diff --git a/lib/internal/Magento/Framework/Filesystem/Driver/File.php b/lib/internal/Magento/Framework/Filesystem/Driver/File.php index 67320447f5470..167027a7d8589 100644 --- a/lib/internal/Magento/Framework/Filesystem/Driver/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Driver/File.php @@ -7,8 +7,8 @@ */ namespace Magento\Framework\Filesystem\Driver; -use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Filesystem\DriverInterface; class File implements DriverInterface { @@ -299,7 +299,7 @@ public function copy($source, $destination, DriverInterface $targetDriver = null [ $source, $destination, - $this->getWarningMessage() + $this->getWarningMessage(), ] ) ); @@ -329,7 +329,7 @@ public function symlink($source, $destination, DriverInterface $targetDriver = n [ $source, $destination, - $this->getWarningMessage() + $this->getWarningMessage(), ] ) ); @@ -644,6 +644,16 @@ public function fileWrite($resource, $data) */ public function filePutCsv($resource, array $data, $delimiter = ',', $enclosure = '"') { + /** + * Security enhancement for CSV data processing by Excel-like applications. + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1054702 + */ + foreach ($data as $key => $value) { + if (isset($value[0]) && $value[0] === '=') { + $data[$key] = ' ' . $value; + } + } + $result = @fputcsv($resource, $data, $delimiter, $enclosure); if (!$result) { throw new FileSystemException( diff --git a/lib/internal/Magento/Framework/Filesystem/Io/File.php b/lib/internal/Magento/Framework/Filesystem/Io/File.php index 09e4b21ed521a..352cdb3b5643f 100644 --- a/lib/internal/Magento/Framework/Filesystem/Io/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Io/File.php @@ -177,6 +177,15 @@ public function streamWriteCsv(array $row, $delimiter = ',', $enclosure = '"') if (!$this->_streamHandler) { return false; } + /** + * Security enhancement for CSV data processing by Excel-like applications. + * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1054702 + */ + foreach ($row as $key => $value) { + if (isset($value[0]) && $value[0] === '=') { + $row[$key] = ' ' . $value; + } + } return @fputcsv($this->_streamHandler, $row, $delimiter, $enclosure); } diff --git a/lib/internal/Magento/Framework/Locale/TranslatedLists.php b/lib/internal/Magento/Framework/Locale/TranslatedLists.php index 262bdea1eb734..f67c73cd0dd95 100644 --- a/lib/internal/Magento/Framework/Locale/TranslatedLists.php +++ b/lib/internal/Magento/Framework/Locale/TranslatedLists.php @@ -67,13 +67,13 @@ public function getTranslatedOptionLocales() protected function _getOptionLocales($translatedName = false) { $currentLocale = $this->localeResolver->getLocale(); - $locales = \ResourceBundle::getLocales(null); + $locales = \ResourceBundle::getLocales('') ?: []; $languages = (new LanguageBundle())->get($currentLocale)['Languages']; $countries = (new RegionBundle())->get($currentLocale)['Countries']; $options = []; $allowedLocales = $this->_config->getAllowedLocales(); - foreach ((array)$locales as $locale) { + foreach ($locales as $locale) { if (!in_array($locale, $allowedLocales)) { continue; } @@ -103,7 +103,7 @@ public function getOptionTimezones() { $options = []; $locale = $this->localeResolver->getLocale(); - $zones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL); + $zones = \DateTimeZone::listIdentifiers(\DateTimeZone::ALL) ?: []; foreach ($zones as $code) { $options[] = [ 'label' => \IntlTimeZone::createTimeZone($code)->getDisplayName( @@ -123,9 +123,8 @@ public function getOptionTimezones() public function getOptionWeekdays($preserveCodes = false, $ucFirstCode = false) { $options = []; - $days = (new DataBundle())->get( - $this->localeResolver->getLocale() - )['calendar']['gregorian']['dayNames']['format']['wide']; + $days = (new DataBundle()) + ->get($this->localeResolver->getLocale())['calendar']['gregorian']['dayNames']['format']['wide'] ?: []; $englishDays = (new DataBundle())->get('en_US')['calendar']['gregorian']['dayNames']['format']['abbreviated']; foreach ($days as $code => $name) { $code = $preserveCodes ? $englishDays[$code] : $code; @@ -140,7 +139,7 @@ public function getOptionWeekdays($preserveCodes = false, $ucFirstCode = false) public function getOptionCountries() { $options = []; - $countries = (new RegionBundle())->get($this->localeResolver->getLocale())['Countries']; + $countries = (new RegionBundle())->get($this->localeResolver->getLocale())['Countries'] ?: []; foreach ($countries as $code => $name) { $options[] = ['label' => $name, 'value' => $code]; } @@ -152,7 +151,7 @@ public function getOptionCountries() */ public function getOptionCurrencies() { - $currencies = (new CurrencyBundle())->get($this->localeResolver->getLocale())['Currencies']; + $currencies = (new CurrencyBundle())->get($this->localeResolver->getLocale())['Currencies'] ?: []; $options = []; $allowed = $this->_config->getAllowedCurrencies(); foreach ($currencies as $code => $data) { @@ -169,7 +168,7 @@ public function getOptionCurrencies() */ public function getOptionAllCurrencies() { - $currencies = (new CurrencyBundle())->get($this->localeResolver->getLocale())['Currencies']; + $currencies = (new CurrencyBundle())->get($this->localeResolver->getLocale())['Currencies'] ?: []; $options = []; foreach ($currencies as $code => $data) { $options[] = ['label' => $data[1], 'value' => $code]; diff --git a/lib/internal/Magento/Framework/Setup/Lists.php b/lib/internal/Magento/Framework/Setup/Lists.php index 7934897c890eb..9e3941c15b5bf 100644 --- a/lib/internal/Magento/Framework/Setup/Lists.php +++ b/lib/internal/Magento/Framework/Setup/Lists.php @@ -79,9 +79,9 @@ public function getLocaleList() { $languages = (new LanguageBundle())->get(Resolver::DEFAULT_LOCALE)['Languages']; $countries = (new RegionBundle())->get(Resolver::DEFAULT_LOCALE)['Countries']; - $locales = \ResourceBundle::getLocales(null); + $locales = \ResourceBundle::getLocales('') ?: []; $list = []; - foreach ((array)$locales as $locale) { + foreach ($locales as $locale) { if (!in_array($locale, $this->allowedLocales)) { continue; }