From 85062511ea38bc3cd94b3f07ca0f481c5c107c78 Mon Sep 17 00:00:00 2001 From: temi4sh Date: Wed, 8 Jul 2026 10:52:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B2=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D1=81=D0=B5=D0=BA=D1=86=D0=B8=D0=B9=20=D0=B8=D0=BD=D1=84?= =?UTF-8?q?=D0=BE=D0=B1=D0=BB=D0=BE=D0=BA=D0=B0=20=D0=BD=D0=B0=20XML?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/builders/iblockcategorybuilder.php | 29 +- lib/exchange/exchangemanager.php | 9 + lib/helpermanager.php | 6 +- lib/helpers/iblocksectionexchangehelper.php | 477 ++++++++++++++++++ .../traits/iblock/iblocksectiontrait.php | 17 + templates/IblockCategoryExport.php | 24 +- 6 files changed, 539 insertions(+), 23 deletions(-) create mode 100644 lib/helpers/iblocksectionexchangehelper.php diff --git a/lib/builders/iblockcategorybuilder.php b/lib/builders/iblockcategorybuilder.php index 1404f9ec..717c71d2 100644 --- a/lib/builders/iblockcategorybuilder.php +++ b/lib/builders/iblockcategorybuilder.php @@ -5,6 +5,8 @@ use Sprint\Migration\Exceptions\HelperException; use Sprint\Migration\Exceptions\MigrationException; use Sprint\Migration\Exceptions\RebuildException; +use Sprint\Migration\Exceptions\RestartException; +use Sprint\Migration\Exchange\RestartableWriter; use Sprint\Migration\Locale; use Sprint\Migration\Module; use Sprint\Migration\VersionBuilder; @@ -32,10 +34,12 @@ protected function initialize() * @throws HelperException * @throws RebuildException * @throws MigrationException + * @throws RestartException */ protected function execute() { $helper = $this->getHelperManager(); + $exhelper = $this->getHelperManager()->IblockSectionExchange(); $iblockId = $this->addFieldAndReturn( 'iblock_id', @@ -52,14 +56,23 @@ protected function execute() $this->rebuildField('iblock_id'); } - $sectionTree = $helper->Iblock()->exportSectionsTree($iblockId); + (new RestartableWriter($this, $this->getVersionExchangeDir())) + ->setExchangeResource('iblock_sections.xml') + ->execute( + attributesFn: fn() => $exhelper->getWriterAttributes($iblockId), + totalCountFn: fn() => $exhelper->getWriterRecordsCount($iblockId), + recordsFn: fn($offset, $limit) => $exhelper->getWriterRecordsTag( + $offset, + $limit, + $iblockId + ), + progressFn: fn($value, $totalCount) => $this->outProgress( + 'Progress: ', + $value, + $totalCount + ) + ); - $this->createVersionFile( - Module::getModuleTemplateFile('IblockCategoryExport'), - [ - 'iblock' => $iblock, - 'sectionTree' => $sectionTree, - ] - ); + $this->createVersionFile(Module::getModuleTemplateFile('IblockCategoryExport')); } } diff --git a/lib/exchange/exchangemanager.php b/lib/exchange/exchangemanager.php index 75d56f76..c3892950 100644 --- a/lib/exchange/exchangemanager.php +++ b/lib/exchange/exchangemanager.php @@ -26,6 +26,15 @@ public function IblockElementsImport(): RestartableReader ))->setExchangeResource('iblock_elements.xml'); } + public function IblockSectionsImport(): RestartableReader + { + return (new RestartableReader( + $this->restartable, + $this->getHelperManager()->IblockSectionExchange(), + $this->directory + ))->setExchangeResource('iblock_sections.xml'); + } + public function HlblockElementsImport(): RestartableReader { return (new RestartableReader( diff --git a/lib/helpermanager.php b/lib/helpermanager.php index b87cfc29..cc4c7fd1 100644 --- a/lib/helpermanager.php +++ b/lib/helpermanager.php @@ -14,6 +14,7 @@ use Sprint\Migration\Helpers\HlblockHelper; use Sprint\Migration\Helpers\IblockExchangeHelper; use Sprint\Migration\Helpers\IblockHelper; +use Sprint\Migration\Helpers\IblockSectionExchangeHelper; use Sprint\Migration\Helpers\LangHelper; use Sprint\Migration\Helpers\MedialibExchangeHelper; use Sprint\Migration\Helpers\MedialibHelper; @@ -54,8 +55,9 @@ * @method SubscribeHelper Subscribe() * @method MedialibHelper Medialib() * @method TextHelper Text() - * @method IblockExchangeHelper IblockExchange() - * @method HlblockExchangeHelper HlblockExchange() + * @method IblockExchangeHelper IblockExchange() + * @method IblockSectionExchangeHelper IblockSectionExchange() + * @method HlblockExchangeHelper HlblockExchange() * @method MedialibExchangeHelper MedialibExchange() * @method OrderPropertiesHelper OrderProperties() * @method CultureHelper Culture() diff --git a/lib/helpers/iblocksectionexchangehelper.php b/lib/helpers/iblocksectionexchangehelper.php new file mode 100644 index 00000000..5097df37 --- /dev/null +++ b/lib/helpers/iblocksectionexchangehelper.php @@ -0,0 +1,477 @@ + $this->getIblockUid((int)$iblockId), + ]; + } + + public function getWriterRecordsCount(...$vars): int + { + [$iblockId] = $vars; + + return count($this->getSections((int)$iblockId)); + } + + /** + * @throws HelperException + */ + public function getWriterRecordsTag(int $offset, int $limit, ...$vars): WriterTag + { + [$iblockId] = $vars; + + $tag = new WriterTag('tmp'); + foreach ($this->getSectionSlice((int)$iblockId, $offset, $limit) as $section) { + $tag->addChild($this->createWriterRecordTag((int)$iblockId, $section)); + } + + return $tag; + } + + /** + * @throws HelperException + */ + public function convertReaderRecords(array $attributes, array $records): array + { + $iblockId = $this->getIblockIdByUid($attributes['iblockUid']); + + return array_map( + fn($record) => [ + 'iblock_id' => $iblockId, + 'fields' => $this->convertReaderRecord($iblockId, $record), + ], + $records + ); + } + + /** + * @throws HelperException + */ + protected function getSectionSlice(int $iblockId, int $offset, int $limit): array + { + $sections = $this->ensureSectionXmlIds($this->getSections($iblockId)); + usort($sections, fn($a, $b) => ((int)$a['LEFT_MARGIN'] <=> (int)$b['LEFT_MARGIN'])); + + return array_slice($sections, $offset, $limit); + } + + /** + * @throws HelperException + */ + protected function ensureSectionXmlIds(array $sections): array + { + foreach ($sections as $index => $section) { + $sections[$index] = $this->ensureSectionXmlId($section); + } + + return $sections; + } + + /** + * @throws HelperException + */ + protected function ensureSectionXmlId(array $section): array + { + if (!empty($section['XML_ID'])) { + return $section; + } + + $section['XML_ID'] = $this->makeSectionXmlId($section); + $this->updateSection((int)$section['ID'], ['XML_ID' => $section['XML_ID']]); + + return $section; + } + + protected function makeSectionXmlId(array $section): string + { + $slug = $this->makeCodeSlug((string)($section['NAME'] ?? '')); + if ($slug === '') { + $slug = 'SECTION'; + } + + return sprintf('IBLOCK_SECTION_%d_%s', (int)$section['ID'], $slug); + } + + protected function makeCodeSlug(string $value): string + { + if (class_exists('CUtil')) { + $value = \CUtil::translit($value, 'ru', [ + 'replace_space' => '_', + 'replace_other' => '_', + 'change_case' => 'U', + ]); + } + + $value = strtoupper($value); + $value = preg_replace('/[^A-Z0-9_]+/', '_', $value); + $value = trim((string)$value, '_'); + + return (string)preg_replace('/_+/', '_', $value); + } + + /** + * @throws HelperException + */ + protected function createWriterRecordTag(int $iblockId, array $section): WriterTag + { + $item = new WriterTag('item'); + + foreach ($this->prepareSectionForXml($section) as $name => $value) { + if (str_starts_with((string)$name, 'UF_')) { + $field = $this->createWriterUserFieldTag($iblockId, (string)$name, $value); + } else { + $field = new WriterTag('field', ['name' => $name]); + $this->writeSectionFieldValue($field, $iblockId, (string)$name, $value); + } + + $item->addChild($field); + } + + return $item; + } + + protected function prepareSectionForXml(array $section): array + { + $this->unsetKeys($section, [ + 'ID', + 'IBLOCK_ID', + 'LEFT_MARGIN', + 'RIGHT_MARGIN', + 'DEPTH_LEVEL', + ]); + + return $section; + } + + /** + * @throws HelperException + */ + protected function writeSectionFieldValue(WriterTag $tag, int $iblockId, string $name, mixed $value): void + { + if ($name === 'PICTURE' || $name === 'DETAIL_PICTURE') { + $tag->addFile((int)$value, false); + } elseif ($name === 'IBLOCK_SECTION_ID') { + if (!empty($value)) { + $this->writeParentSectionRef($tag, $iblockId, (int)$value); + } + } else { + $tag->addValue($value, false); + } + } + + /** + * @throws HelperException + */ + protected function writeParentSectionRef(WriterTag $tag, int $iblockId, int $sectionId): void + { + $this->writeSectionRef($tag, $iblockId, $sectionId); + } + + /** + * @throws HelperException + */ + protected function writeSectionRef(WriterTag $tag, int $iblockId, int $sectionId): void + { + $section = $this->getSectionIfExists($iblockId, ['ID' => $sectionId]); + $section = $this->ensureSectionXmlId($section); + + $tag->addValueTag( + $section['NAME'], + array_filter([ + 'section_xml_id' => $section['XML_ID'] ?? '', + 'section_code' => $section['CODE'] ?? '', + ]) + ); + } + + /** + * @throws HelperException + */ + protected function convertReaderRecord(int $iblockId, array $record): array + { + $fields = []; + + foreach ($record['fields'] as $field) { + if (empty($field['name'])) { + continue; + } + + if (str_starts_with((string)$field['name'], 'UF_')) { + $fields[$field['name']] = $this->readUserFieldValue($iblockId, $field); + } elseif ($field['name'] === 'IBLOCK_SECTION_ID') { + $fields[$field['name']] = $this->readParentSection($iblockId, $field); + } else { + $fields[$field['name']] = $this->readFieldValue($field); + } + } + + return $fields; + } + + /** + * @throws HelperException + */ + protected function readParentSection(int $iblockId, array $field): int|false + { + if (empty($field['value'])) { + return false; + } + + return $this->readSectionRef($iblockId, $field['value'][0]); + } + + /** + * @throws HelperException + */ + protected function readSectionRef(int $iblockId, array $value): int + { + if (!empty($value['section_xml_id'])) { + return $this->getSectionIdIfExists($iblockId, ['=XML_ID' => $value['section_xml_id']]); + } + + if (!empty($value['section_code'])) { + return $this->getSectionIdIfExists($iblockId, ['=CODE' => $value['section_code']]); + } + + throw new HelperException("Section reference is empty"); + } + + protected function readFieldValue(array $field): mixed + { + return $field['value'][0]['value'] ?? null; + } + + /** + * @throws HelperException + */ + protected function createWriterUserFieldTag(int $iblockId, string $fieldName, mixed $value): WriterTag + { + $tag = new WriterTag('field', ['name' => $fieldName]); + $field = $this->getSectionUserField($iblockId, $fieldName); + $multiple = (($field['MULTIPLE'] ?? 'N') === 'Y'); + + if (($field['USER_TYPE_ID'] ?? '') === 'enumeration') { + $tag->addValue($this->getUserFieldEnumXmlIdsByIds($field, $value), true); + } elseif (($field['USER_TYPE_ID'] ?? '') === 'file') { + $tag->addFile($value, $multiple); + } elseif (($field['USER_TYPE_ID'] ?? '') === 'iblock_element') { + $this->writeUserFieldIblockElement($tag, $field, $value); + } elseif (($field['USER_TYPE_ID'] ?? '') === 'iblock_section') { + $this->writeUserFieldIblockSection($tag, $field, $value); + } elseif (($field['USER_TYPE_ID'] ?? '') === 'hlblock') { + $this->writeUserFieldHlblockElement($tag, $field, $value); + } else { + $tag->addValue($value, $multiple); + } + + return $tag; + } + + /** + * @throws HelperException + */ + protected function readUserFieldValue(int $iblockId, array $field): mixed + { + $fieldInfo = $this->getSectionUserField($iblockId, (string)$field['name']); + $fieldType = $fieldInfo['USER_TYPE_ID'] ?? ''; + + if ($fieldType === 'enumeration') { + return $this->readUserFieldEnumeration($fieldInfo, $field); + } + + if ($fieldType === 'iblock_element') { + return $this->readUserFieldIblockElement($fieldInfo, $field); + } + + if ($fieldType === 'iblock_section') { + return $this->readUserFieldIblockSection($fieldInfo, $field); + } + + if ($fieldType === 'hlblock') { + return $this->readUserFieldHlblockElement($fieldInfo, $field); + } + + return $this->readUserFieldRawValue($fieldInfo, $field); + } + + protected function readUserFieldRawValue(array $fieldInfo, array $field): mixed + { + $values = array_map(fn($value) => $value['value'], $field['value']); + + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? $values : ($values[0] ?? false); + } + + protected function readUserFieldEnumeration(array $fieldInfo, array $field): mixed + { + $values = array_map( + fn($value) => $this->getUserFieldEnumIdByXmlId($fieldInfo, $value['value']), + $field['value'] + ); + + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? $values : ($values[0] ?? false); + } + + /** + * @throws HelperException + */ + protected function readUserFieldIblockElement(array $fieldInfo, array $field): mixed + { + $linkedIblockId = (int)($fieldInfo['SETTINGS']['IBLOCK_ID'] ?? 0); + if (!$linkedIblockId) { + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? [] : false; + } + + $values = array_map( + fn($value) => (new IblockExchangeHelper())->readValueIblockElement($linkedIblockId, $value), + $field['value'] + ); + + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? $values : ($values[0] ?? false); + } + + /** + * @throws HelperException + */ + protected function readUserFieldIblockSection(array $fieldInfo, array $field): mixed + { + $linkedIblockId = (int)($fieldInfo['SETTINGS']['IBLOCK_ID'] ?? 0); + if (!$linkedIblockId) { + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? [] : false; + } + + $values = array_map( + fn($value) => $this->readSectionRef($linkedIblockId, $value), + $field['value'] + ); + + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? $values : ($values[0] ?? false); + } + + /** + * @throws HelperException + */ + protected function readUserFieldHlblockElement(array $fieldInfo, array $field): mixed + { + $linkedHlblockId = (int)($fieldInfo['SETTINGS']['HLBLOCK_ID'] ?? 0); + if (!$linkedHlblockId) { + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? [] : false; + } + + $values = array_map( + fn($value) => (new HlblockExchangeHelper())->readValueHlblockElement($linkedHlblockId, $value), + $field['value'] + ); + + return (($fieldInfo['MULTIPLE'] ?? 'N') === 'Y') ? $values : ($values[0] ?? false); + } + + /** + * @throws HelperException + */ + protected function writeUserFieldIblockElement(WriterTag $tag, array $field, mixed $value): void + { + $linkedIblockId = (int)($field['SETTINGS']['IBLOCK_ID'] ?? 0); + if (!$linkedIblockId) { + return; + } + + foreach ($this->makeNonEmptyArray($value) as $elementId) { + (new IblockExchangeHelper())->writeValueIblockElement($tag, $linkedIblockId, (int)$elementId); + } + } + + /** + * @throws HelperException + */ + protected function writeUserFieldIblockSection(WriterTag $tag, array $field, mixed $value): void + { + $linkedIblockId = (int)($field['SETTINGS']['IBLOCK_ID'] ?? 0); + if (!$linkedIblockId) { + return; + } + + foreach ($this->makeNonEmptyArray($value) as $sectionId) { + $this->writeSectionRef($tag, $linkedIblockId, (int)$sectionId); + } + } + + /** + * @throws HelperException + */ + protected function writeUserFieldHlblockElement(WriterTag $tag, array $field, mixed $value): void + { + $linkedHlblockId = (int)($field['SETTINGS']['HLBLOCK_ID'] ?? 0); + if (!$linkedHlblockId) { + return; + } + + foreach ($this->makeNonEmptyArray($value) as $elementId) { + (new HlblockExchangeHelper())->writeValueHlblockElement($tag, $linkedHlblockId, (int)$elementId); + } + } + + protected function getUserFieldEnumXmlIdsByIds(array $fieldInfo, mixed $enumIds): array + { + $values = []; + foreach ($this->makeNonEmptyArray($enumIds) as $enumId) { + foreach (($fieldInfo['ENUM_VALUES'] ?? []) as $enum) { + if ((int)$enum['ID'] === (int)$enumId) { + $values[] = !empty($enum['XML_ID']) ? $enum['XML_ID'] : $enum['VALUE']; + break; + } + } + } + + return $values; + } + + protected function getUserFieldEnumIdByXmlId(array $fieldInfo, mixed $xmlId): int|false + { + foreach (($fieldInfo['ENUM_VALUES'] ?? []) as $enum) { + if (!empty($enum['XML_ID']) && (string)$enum['XML_ID'] === (string)$xmlId) { + return (int)$enum['ID']; + } + if ((string)$enum['VALUE'] === (string)$xmlId) { + return (int)$enum['ID']; + } + } + + return false; + } + + /** + * @throws HelperException + */ + protected function getSectionUserField(int $iblockId, string $fieldName): array + { + $key = $iblockId . ':' . $fieldName; + + if (!isset($this->cachedFields[$key])) { + $field = (new UserTypeEntityHelper())->getUserTypeEntity( + 'IBLOCK_' . $iblockId . '_SECTION', + $fieldName + ); + + $this->cachedFields[$key] = is_array($field) ? $field : []; + } + + return $this->cachedFields[$key]; + } +} diff --git a/lib/helpers/traits/iblock/iblocksectiontrait.php b/lib/helpers/traits/iblock/iblocksectiontrait.php index 9c686071..a2353925 100644 --- a/lib/helpers/traits/iblock/iblocksectiontrait.php +++ b/lib/helpers/traits/iblock/iblocksectiontrait.php @@ -329,6 +329,23 @@ public function saveSectionsFromTree(int $iblockId, array $tree, $parentId = fal } } + /** + * @throws HelperException + */ + public function saveSectionRecord(int $iblockId, array $fields): int + { + $this->checkRequiredKeys($fields, ['XML_ID', 'NAME']); + + unset($fields['IBLOCK_ID']); + + $sectionId = $this->getSectionId($iblockId, ['=XML_ID' => $fields['XML_ID']]); + if ($sectionId) { + return $this->updateSection($sectionId, $fields); + } + + return $this->addSection($iblockId, $fields); + } + public function getSectionsTree(int $iblockId): array { $sections = $this->getSections($iblockId); diff --git a/templates/IblockCategoryExport.php b/templates/IblockCategoryExport.php index 22a1ed41..acb4ce5f 100644 --- a/templates/IblockCategoryExport.php +++ b/templates/IblockCategoryExport.php @@ -5,8 +5,6 @@ * @var $extendUse * @var $extendClass * @var $moduleVersion - * @var $iblock - * @var $sectionTree * @var $author * @formatter:off */ @@ -32,16 +30,16 @@ class extends */ public function up() { - $helper = $this->getHelperManager(); - - $iblockId = $helper->Iblock()->getIblockIdIfExists( - '', - '' - ); - - $helper->Iblock()->saveSectionsFromTree( - $iblockId, - - ); + $this->getExchangeManager() + ->IblockSectionsImport() + ->setLimit(20) + ->execute(function ($item) { + $this->getHelperManager() + ->Iblock() + ->saveSectionRecord( + $item['iblock_id'], + $item['fields'] + ); + }); } }