Version: current

      1 <?php
      2
      3 // TODO формировать ProductCatalogDate из ДатаФормирования
      4 class ImportCatalog1C_Importer extends Base1CExchanger {
      5
      6     protected $categoryCount = 0;
      7     protected $productCount = 0;
      8     protected $offersCount = 0;
      9
     10     static $startTime;
     11
     12     private $CATEGORIES = array();
     13
     14     private static $max_version_count = 1;
     15
     16     function init() {
     17         // установить кол-во версий для товаров
     18         Versioned::$versions_ttl = self::$max_version_count;
     19
     20         // отключаем автосоздание папок каталогов и товаров - в импорте мы не копируем туда прикрепленный фото и файлы
     21         AssociatedFolderDecorator::$createFolders = false;
     22
     23         // отключаем протоколирование
     24         if (class_exists('LogItem')) {
     25             LogItem::enable(false);
     26         }
     27     }
     28
     29     function importCatalog($importFile, $log) {
     30         // $startTime = date('Y-m-d H:i:s');
     31         $startTime = self::$startTime;
     32         $this->init();
     33
     34         $log->addStatus('process', _t('ProductCatalogImport.CatalogImportStarted', 'Catalog Import started'));
     35         $log->addLog(_t('ProductCatalogImport.CatalogImportStarted', 'Catalog Import started'));
     36
     37         // Сначала импортируем каталог - рубрики и товары
     38         //$importFile = self::absolute_import_path('import.xml');
     39         $xml = simplexml_load_file($importFile);
     40         $data = array();
     41
     42         // Группы (рубрики каталога)
     43         $sc = SiteConfig::current_site_config();
     44
     45         $startPage = DataObject::get_by_id('SiteTree', (int)$sc->StartPage);
     46
     47         // FIXME а если я хочу писать в корень?
     48         if (!$startPage) {
     49             $log->addLog(_t('ProductCatalogImport.NoRoot', 'No Root'), 'warning');
     50             return false;
     51         }
     52
     53         if($xml->Классификатор->Группы) $this->insertCategory($xml->Классификатор->Группы->Группа, $startPage->ID);
     54         $msg = sprintf(_t('ProductCatalogImport.ImportedCatalogsCount', 'Imported Catalogs Count: %d'), $this->categoryCount);
     55         $log->addStatus('process', $msg);
     56         $log->addLog($msg);
     57
     58         // проверяем тип импорта - полный или инкрементный
     59         if ($xml->Каталог->attributes()->{'СодержитТолькоИзменения'} == "false") {
     60             $importType = 'full';
     61         } else {
     62             $importType = 'incremental';
     63         }
     64
     65         // Товары
     66         if ($xml->Каталог->Товары->Товар) {
     67             $log->addStatus('process', _t('ProductCatalogImport.ProductImportStarted', 'Product import started'));
     68             $log->addLog(_t('ProductCatalogImport.ProductImportStarted', 'Product import started'));
     69             $log->addLog(_t('ProductCatalogImport.ImportType', 'Import mode: ') . _t('ProductCatalogImport.ImportType_' . $importType, $importType));
     70
     71             foreach ($xml->Каталог->Товары->Товар as $product) {
     72                 $importID = (string)$product->Ид;
     73                 $newProduct = DataObject::get_one('Product', "ImportID = '{$importID}'");
     74                 if (!$newProduct) {
     75                     $newProduct = new Product();
     76                     $newProduct->ImportID = $importID;
     77                 }
     78                 $newProduct->ShowInSearch = 1;
     79                 $newProduct->Title = (string)$product->Наименование;
     80
     81                 // RNW
     82                 // 1С передает текст одним полотном, разбиваем по строкам
     83                 // хоть какое-то форматирование
     84                 // $newProduct->Content = (string)$product->Описание;
     85
     86                 if((string)$product->Описание){
     87                     $content = "";
     88                     $splitted = explode("\n",(string)$product->Описание);
     89                     foreach ($splitted as $line) {
     90                         $content .= "<p>$line</p>";
     91                     }
     92                     $newProduct->Content = $content;
     93                 }
     94
     95                 $newProduct->SKU = (string)$product->Артикул;
     96                 $newProduct->VendorCountry = (string)$product->СтранаПроисхождения;
     97
     98
     99                 if (isset($this->CATEGORIES[(string)$product->Группы->Ид])) {
    100                     $newProduct->ParentID = $this->CATEGORIES[(string)$product->Группы->Ид];
    101                 } else {
    102                     // !!!! FailSafe CategoryID !!!!
    103                     $newProduct->ParentID = 0;
    104                 }
    105
    106                 $newProduct->writeToStage('Stage');
    107                 $newProduct->publish('Stage', 'Live');
    108
    109                 // проверим все реквизиты товара
    110                 if ($product->ЗначенияРеквизитов) {
    111                     foreach ($product->ЗначенияРеквизитов->ЗначениеРеквизита as $prop) {
    112                         $newProp = DataObject::get_one('ImportCatalog1C_ProductProp', "Title = '{$prop->Наименование}'");
    113                         if (!$newProp) {
    114                             $newProp = new ImportCatalog1C_ProductProp();
    115                             $newProp->Title = (string)$prop->Наименование;
    116                             $newProp->write();
    117                         }
    118                         if ($newProp->ProductField && trim((string)$prop->Значение)) {
    119                             $newProduct->{$newProp->ProductField} = trim((string)$prop->Значение);
    120                         }
    121                     }
    122                 }
    123
    124                 // ИМПОРТИРУЕМ КАРТИНКИ
    125                 if ($product->Картинка) {
    126                     $first = true;
    127                     $oldPhotos = $newProduct->Photos();
    128                     $oldPhotosIDs = $oldPhotos->getIdList(); //сохраняем список ID прикрепленных фоток
    129                     $newProduct->Photos()->removeAll();
    130
    131                     foreach ($product->Картинка as $photo) {
    132                         if ($first) {
    133                             if ($mainPhoto = self::import_photo($newProduct->PhotoID, (string)$photo)) {
    134                                 $newProduct->PhotoID = ($mainPhoto) ? $mainPhoto->ID : 0;
    135                                 $first = false;
    136                             }
    137                         } else {
    138                             $galleryPhoto = self::import_photo(0, (string)$photo);
    139                             if ($galleryPhoto) {
    140                                 if ($mwPhoto = $oldPhotos->find('PhotoID', $galleryPhoto->ID)) {
    141                                     unset($oldPhotosIDs[$mwPhoto->ID]); //если фотка осталась, то удаляем ее ID из списка
    142                                 } else {
    143                                     $mwPhoto = new MediawebPage_Photo();
    144                                     $mwPhoto->PhotoID = $galleryPhoto->ID;
    145                                 }
    146                                 $mwPhoto->MediawebPageID = $newProduct->ID; //Цепляем к Product
    147                                 $mwPhoto->Caption = $galleryPhoto->Title;
    148                                 $mwPhoto->write();
    149                             }
    150                         }
    151                     }
    152                     if (count($oldPhotosIDs) > 0) {
    153                         foreach($oldPhotosIDs as $oldPhotosID) { // все, что осталось в списке - чистим!
    154                             if ($oldPhoto = $oldPhotos->find('ID', $oldPhotosID)) {
    155                                 $oldPhoto->delete();
    156                             }
    157                         }
    158                     }
    159                 }
    160                 $newProduct->writeToStage('Stage');
    161                 $newProduct->publish('Stage', 'Live');
    162
    163                 $this->productCount++;
    164                 if ($this->productCount % 100 == 0) {
    165                     $log->addStatus('process', sprintf(_t('ProductCatalogImport.ImportedProductsCount', 'Imported %d Products'), $this->productCount));
    166                 }
    167                 if (is_file(self::absolute_import_path('stop_import'))) {
    168                     $log->addLog(_t('ProductCatalogImport.InterruptByUser', 'Пользователь отправил запрос на прерывание импорта'));
    169                     return;
    170                 }
    171             }
    172             $msg = sprintf(_t('ProductCatalogImport.ImportedProductsCount', 'Imported %d Products'), $this->productCount);
    173             $log->addStatus('process', $msg);
    174             $log->addLog($msg);
    175         }
    176
    177         if ($importType == 'full') {
    178             $this->unpublishNotUpdated($startTime, $log);
    179         }
    180     }
    181
    182     function ImportOffers($importFile, $log) {
    183         // $startTime = date('Y-m-d H:i:s');
    184         $startTime = self::$startTime;
    185         $this->init();
    186
    187         $msg = _t('ProductCatalogImport.OffersImportStarted', 'Offers Import Started');
    188         $log->addStatus('process', $msg);
    189         $log->addLog($msg);
    190
    191         //$importFile = self::absolute_import_path('offers.xml');
    192         $xml = simplexml_load_file($importFile);
    193         $data = array();
    194
    195
    196         // проверяем тип импорта - полный или инкрементный
    197         if ($xml->ПакетПредложений->attributes()->{'СодержитТолькоИзменения'} == "false") {
    198             $importType = 'full';
    199         } else {
    200             $importType = 'incremental';
    201         }
    202         $log->addLog(_t('ProductCatalogImport.ImportType', 'Import mode: ') . _t('ProductCatalogImport.ImportType_' . $importType, $importType));
    203
    204
    205         // Импортируем Типы цен
    206         if ($xml->ПакетПредложений->ТипыЦен) {
    207             foreach ($xml->ПакетПредложений->ТипыЦен->ТипЦены as $priceType) {
    208                 $priceID = Convert::raw2sql((string)$priceType->Ид);
    209                 $newPriceType = DataObject::get_one('ImportCatalog1C_PriceType', "PriceID = '{$priceID}'");
    210                 if (!$newPriceType) {
    211                     $newPriceType = new ImportCatalog1C_PriceType();
    212                     $newPriceType->PriceID = $priceID;
    213                 }
    214                 $newPriceType->Title = Convert::raw2sql((string)$priceType->Наименование);
    215                 $newPriceType->Currency = Convert::raw2sql((string)$priceType->Валюта);
    216                 $newPriceType->write();
    217             }
    218         }
    219
    220         $sc = SiteConfig::current_site_config();
    221         if (!$sc->BasePriceTypeID || !$sc->BasePriceType()->PriceID) {
    222             $log->addLog(_t('ProductCatalogImport.BadPriceType', 'Bad Price Type'), 'error');
    223             $log->addStatus('error', _t('ProductCatalogImport.BadPriceType', 'Bad Price Type'));
    224             return false;
    225         }
    226
    227         // Импортируем цвета
    228         if ($xml->ПакетПредложений->Предложения) {
    229             foreach ($xml->ПакетПредложений->Предложения->Предложение as $offer) {
    230                 $importID = (string)$offer->Ид;
    231
    232                 if (strpos($importID, '#') !== false) {
    233 echo $importID ."\n";
    234                     $importID = substr($importID, 0, strpos($importID, '#'));
    235                 }
    236                 $newProduct = DataObject::get_one('Product', "ImportID = '{$importID}'");
    237                 if (!$newProduct) {
    238                     // echo $importID . ' ';
    239                     continue;
    240                 }
    241
    242                 // в выгрузке может не быть цен
    243                 $newProduct->Available = 0;
    244                 $newProduct->AllowPurchase = 0;
    245                 if ($offer->Цены) {
    246                     foreach ($offer->Цены->Цена as $price) {
    247                         if ($price->ИдТипаЦены == $sc->BasePriceType()->PriceID) {
    248                             $newProduct->BasePrice = (float)$price->ЦенаЗаЕдиницу; // ?? может еще испльзовать како-нить тип цены для CostPrice ???
    249                         }
    250                         if ($price->ИдТипаЦены == $sc->CostPriceType()->PriceID) {
    251                             $newProduct->CostPrice = (float)$price->ЦенаЗаЕдиницу; //  тип цены для CostPrice
    252                         }
    253                     }
    254                     $newProduct->Available = 1;
    255                     $newProduct->AllowPurchase = 1;
    256                 }
    257
    258
    259                 // Amount - на bosch2014, должно быть Quantity
    260                 $newProduct->Quantity = (int)$offer->Количество;
    261                 $newProduct->writeToStage('Stage');
    262                 // RNW
    263                 // Если товары были скрыты, то
    264                 // новым импортом должны быть опубликованы
    265                 // if ($newProduct->isPublished()) {
    266                     $newProduct->publish('Stage', 'Live');
    267                 // }
    268
    269                 $this->offersCount++;
    270                 if ($this->offersCount % 100 == 0) {
    271                     $log->addStatus('process', sprintf(_t('ProductCatalogImport.ImportedOffersCount', 'Imported %d Offers'), $this->offersCount));
    272                 }
    273
    274                 if (is_file(self::absolute_import_path('stop_import'))) {
    275                     $log->addLog(_t('ProductCatalogImport.InterruptByUser', 'Пользователь отправил запрос на прерывание импорта'));
    276                     return;
    277                 }
    278             }
    279             $log->addStatus('process', sprintf(_t('ProductCatalogImport.ImportedOffersCount', 'Imported %d Offers'), $this->offersCount));
    280             $log->addLog(sprintf(_t('ProductCatalogImport.ImportedOffersCount', 'Imported %d Offers'), $this->offersCount));
    281
    282             if ($importType == 'full') {
    283                 $this->unpublishNotUpdated($startTime, $log, true);
    284             }
    285
    286             $msg = sprintf(_t('ProductCatalogImport.ImportDone', 'Import done. Updated: %d catalogs, %d products, %d offers'), $this->categoryCount, $this->productCount, $this->offersCount);
    287             $log->addLog($msg);
    288             $log->addStatus('ok',  $msg);
    289             return true;
    290         }
    291     }
    292
    293     function unpublishNotUpdated($importStartTime, $log, $offersOnly=false) {
    294         $importStartTime = strtotime($importStartTime);
    295         $log->addLog(_t('ProductCatalogImport.HideProductsStarted', 'Find & hide old products'));
    296         $log->addStatus('process', _t('ProductCatalogImport.HideProductsStarted', 'Find & hide old products'));
    297
    298         $hideCatalogCount = 0;
    299         $hideProductCount = 0;
    300         $sc = SiteConfig::current_site_config();
    301         // Скрываем не обновленные товары
    302         $needUnpublish = DataObject::get('Product', 'ImportID IS NOT NULL AND LastEdited < \'' . date("Y-m-d H:i:s", strtotime($importStartTime)) . '\'');
    303         if ($needUnpublish) {
    304             foreach ($needUnpublish as $product) {
    305                 switch ($sc->OldItemAction) {
    306                     case 'hide':
    307                         $product->AllowPurchase = 0;
    308                         $product->writeToStage('Stage');
    309                         if ($product->isPublished()) {
    310                             $product->publish('Stage', 'Live');
    311                         }
    312                         break;
    313                     case 'unpublish':
    314                         $product->doUnpublish();
    315                         break;
    316                     case 'delete':
    317                         $product->doUnpublish();
    318                         $product->delete();
    319                         break;
    320                     default:
    321                         $product->doUnpublish();
    322                         $product->delete();
    323                         break;
    324                 }
    325                 $log->addLog("Скрыт товар {$product->Title} ({$product->ID})");
    326                 $hideProductCount++;
    327                 if ($hideProductCount % 100 == 0) {
    328                     $log->addStatus('process', sprintf(_t('ProductCatalogImport.HiddenProductsCount', 'Hide %d products'), $hideProductCount));
    329                 }
    330
    331             }
    332             $log->addStatus('process', sprintf(_t('ProductCatalogImport.HiddenProductsCount', 'Hide %d products'), $hideProductCount));
    333             $log->addLog(sprintf(_t('ProductCatalogImport.HiddenProductsCount', 'Hide %d products'), $hideProductCount));
    334         }
    335
    336         if (!$offersOnly) {
    337             // Скрываем не обновленные рубрики
    338             $log->addLog(_t('ProductCatalogImport.HideCatalogsStarted', 'Find & hide old catalogs'));
    339             $log->addStatus('process', _t('ProductCatalogImport.HideCatalogsStarted', 'Find & hide old catalogs'));
    340
    341             $needUnpublish = DataObject::get('Catalog', 'ImportID IS NOT NULL AND ClassName = \'Catalog\' AND LastEdited < \'' . date("Y-m-d H:i:s", $importStartTime) . '\'');
    342             if ($needUnpublish) {
    343                 foreach ($needUnpublish as $catalog) {
    344                     switch ($sc->OldItemAction) {
    345                         case 'hide':
    346                             $catalog->ShowInMenus = 0;
    347                             $catalog->writeToStage('Stage');
    348                             if ($catalog->isPublished()) {
    349                                 $catalog->publish('Stage', 'Live');
    350                             }
    351                             break;
    352                         case 'unpublish':
    353                             $catalog->doUnpublish();
    354                             break;
    355                         case 'delete':
    356                             $catalog->doUnpublish();
    357                             $catalog->delete();
    358                             break;
    359                         default:
    360                             $catalog->doUnpublish();
    361                             $catalog->delete();
    362                             break;
    363                     }
    364                     $hideCatalogCount++;
    365                     if ($hideCatalogCount % 100 == 0) {
    366                         $log->addStatus('process', sprintf(_t('ProductCatalogImport.HiddenCatalogsCount', 'Hide %d catalogs'), $hideCatalogCount));
    367                     }
    368                 }
    369             }
    370             $log->addStatus('process', sprintf(_t('ProductCatalogImport.HiddenCatalogsCount', 'Hide %d catalogs'), $hideCatalogCount));
    371             $log->addLog(sprintf(_t('ProductCatalogImport.HiddenCatalogsCount', 'Hide %d catalogs'), $hideCatalogCount));
    372
    373         }
    374         return true;
    375     }
    376
    377     /**
    378      * Функция добавляет корневую категорию и всех детей
    379      *
    380      * @param   SimpleXMLElement
    381      * @param   int
    382      */
    383     private function insertCategory($xml, $parent = 0) {
    384         foreach ($xml as $category){
    385             if (isset($category->Ид) && isset($category->Наименование) ){
    386                 $id =  (string)$category->Ид;
    387                 $data = array();
    388
    389                 $newCat = DataObject::get_one('Catalog', "ImportID = '{$id}'");
    390                 if (!$newCat) {
    391                     $newCat = new Catalog();
    392                     $newCat->ImportID = $id;
    393                 }
    394                 $newCat->Title = (string)$category->Наименование;
    395                 $newCat->ParentID = $parent;
    396
    397                 $newCat->writeToStage('Stage');
    398                 $newCat->publish('Stage', 'Live');
    399                 $this->CATEGORIES[$id] = $newCat->ID;
    400
    401                 $this->categoryCount++;
    402             }
    403             if ($category->Группы) $this->insertCategory($category->Группы->Группа, $newCat->ID);
    404         }
    405         unset($xml);
    406     }
    407
    408      /**
    409      * Импорт изображений
    410      *
    411      * @param int $photoID
    412      * @param string $path
    413      *
    414      * @return Image
    415      */
    416     private static function import_photo($photoID, $imagePath) {
    417         $imagePath = trim(str_replace("\\", "/", $imagePath));
    418         if ($imagePath) { // !!! $imagePath всегда true
    419             $fullImagePath = self::absolute_import_path($imagePath);
    420             $imagePath = self::import_path($imagePath);
    421
    422             // получим объект для картинки
    423             $file = false;
    424             // сначала вытащим по ID
    425             if ($photoID != 0) {
    426                 $file = DataObject::get_by_id('Image', $photoID);
    427             }
    428
    429             // не нашли по ID - поищем по пути к файлу
    430             if (!$file || $file->ID == 0) {
    431                 $file = DataObject::get_one('Image', "\"Filename\"='".Convert::raw2sql($imagePath)."'", true, 'ID DESC');
    432             }
    433
    434             // картинка физически есть на диске
    435             if (is_file($fullImagePath)) {
    436                 // не нашли по пути - создаем новую
    437                 if (!$file || $file->ID == 0) {
    438                     $file = new Image();
    439                     $file->setFilename($imagePath);
    440                     $file->Title = $file->Name;
    441                     $file->write();
    442                 }
    443
    444                 if ($file->Filename != $imagePath) {
    445                     //если поменялось имя файла, то сначала пытаемся найти имеющийся
    446                     $file = DataObject::get_one('Image', "\"Filename\"='".Convert::raw2sql($imagePath)."'", true, 'ID DESC');
    447                     //a если не находим создаем новый (старый может быть привязан к другому объекту!)
    448                     if (!$file) {
    449                         $file = new Image();
    450                         $file->setFilename($imagePath);
    451                         $file->Title = $file->Name;
    452                         $file->write();
    453                     }
    454                 }
    455
    456                 // Если поменялось имя файла (или новый файл)
    457                 if (($photoID != $file->ID)) {
    458                     if (!$file->Name) {
    459                         $file->Name = basename($imagePath);
    460                     }
    461                     if ($file->Filename && $file->Filename != $imagePath) { // для старого имени
    462                         $file->deleteFormattedImages();
    463                     }
    464                     $file->setFilename($imagePath);
    465                     $file->deleteFormattedImages(); // для нового имени
    466                     if (!$file->Title)
    467                         $file->Title = $file->Name;
    468                     $file->write();
    469                 }
    470                 elseif ($photoID != 0) {
    471                     // Если файл обновлен по ftp, а путь к нему не поменялся
    472                     if (filemtime($fullImagePath) > strtotime($file->LastEdited)) {
    473                         //!!!! не особо работает (если скопировать WinSCP, то дата файла не обновляется, может обновится при использовании ftp)
    474                         // dvp: так ты и копируешь тотже самый файл, если исходный файл измениться winscp дату изменит
    475                         $file->deleteFormattedImages();
    476                         //Чтобы LastEdited обновился )))))
    477                         $file->forceChange();
    478                         if (!$file->Title)
    479                             $file->Title = $file->Name;
    480                         $file->write();
    481                     }
    482                 }
    483                 return $file;
    484             }
    485             else {
    486                 //Если физически файла нет, а запись в БД есть
    487                 if ($file) {
    488                     $file->deleteFormattedImages();
    489                     $file->deleteDatabaseOnly(); // то удаляем запись
    490                 }
    491             }
    492         }
    493         return false;
    494     }
    495 }
    496

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -