/** * auction_import.php: Импорт данных. * В настройках модуля указаны параметры подключения, * согласно которым и осуществляется чтение файла и последующий импорт в ИБ. * * Author: Вадим Назаров * * Version: 0.1 * * * Usage: * CAuctionImport::import2IB(); */ //error_reporting(E_ERROR | E_WARNING | E_PARSE); class CAuctionImport { private $path2file; private $path2oldFile; private $login; private $password; private $protocol; private $HOST; private $server_dir; public $log = ""; private $companies; // список активных Предприятий private $IB_COMP; private $IB_ITEMS; private $IB_COMPANIES = 3; private static $aux_sort = 1; // сортировка конкурсов private static $aux_pos_sort = 1; // сортировка позиций в конкурсе private $m_return = array("status" => false); protected static $_instance; private function __construct() { $this->path2file = COption::GetOptionString("auction_import", "file_url"); $this->path2oldFile = COption::GetOptionString("auction_import", "old_file_url"); $this->login = COption::GetOptionString("auction_import", "server_login"); $this->password = COption::GetOptionString("auction_import", "server_pass"); $this->protocol = COption::GetOptionString("auction_import", "protocol"); $this->server_dir = COption::GetOptionString("auction_import", "server_dir"); $this->HOST = COption::GetOptionString("auction_import", "HOST"); $this->IB_COMP = COption::GetOptionString("auction_import", "IB_COMP"); $this->IB_ITEMS = COption::GetOptionString("auction_import", "IB_ITEMS"); CModule::IncludeModule("iblock"); // Список предприятий $resCompanies = CIBlockElement::GetList( array("ID" => "DESC"), array("IBLOCK_ID" => $this->IB_COMPANIES), false, false, array("ID", "NAME", "IBLOCK_ID") ); while ($company = $resCompanies->GetNext()) { $name = str_replace(array('"', '«', '»'), '', $company["NAME"]); $this->companies[$name] = $company["ID"]; } $this->log .= "start: " . ConvertTimeStamp(microtime(true), "FULL") . "\n"; } public static function getInstance() { if (is_null(self::$_instance)) { self::$_instance = new CAuctionImport(); } return self::$_instance; } /** * Обращение к серверу и получение файла. * * @param string - название файла с расширением (PON_AUCTION_FOR_SITE.xml или PON_AUCTION_FOR_SITE_OLD.xml) * * @return DOMDocument - возвращает xml-файл. */ public function getContent($file) { if ($this->protocol == "sftp") { // создать директорию для download xml $d2upload = $_SERVER["DOCUMENT_ROOT"] . "/upload/auction_import"; if (!is_dir($d2upload)) { mkdir($d2upload, 0755); } $methods = array( 'kex' => 'diffie-hellman-group1-sha1', 'client_to_server' => array( 'crypt' => '3des-cbc', 'comp' => 'none' ), 'server_to_client' => array( 'crypt' => 'aes256-cbc,aes192-cbc,aes128-cbc', 'comp' => 'none' ) ); $connection = ssh2_connect($this->HOST, 22, $methods); if (ssh2_auth_password($connection, $this->login, $this->password)) { $sftp = ssh2_sftp($connection); if ($stream = fopen("ssh2.sftp://$sftp" . $this->server_dir . $file, 'r')) { $from_server = stream_get_contents($stream); fclose($stream); $handle = fopen($d2upload . "/$file", 'w+'); fwrite($handle, $from_server); fclose($handle); } else { return false; } } else { exit('Проверьте в настройках корректность IP сервера, пользователя и пароль.'); } $output = file_get_contents($d2upload . "/" . $file); } else { $ch = curl_init(); if (!empty($this->login)) { curl_setopt($ch, CURLOPT_USERPWD, $this->login . ":" . $this->password); } curl_setopt($ch, CURLOPT_URL, $file); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_HTTPHEADER, Array("Content-Type: text/xml")); if (curl_errno($ch)) { return false; } $output = curl_exec($ch); curl_close($ch); } return $output; } public function updateAgent() { global $DB; // ищем агент, предполагаем что он один для нашего модуля $resAgent = CAgent::GetList( array(), array('MODULE_ID' => 'auction_import') ); $arAgent = $resAgent->Fetch(); if (is_array($arAgent)) { // интервал обновления из настроек модуля $interval = intVal(COption::GetOptionString('auction_import', 'period')); $intervalSec = $interval * 60; $arFields = array( 'LAST_EXEC' => date($DB->DateFormatToPHP(CSite::GetDateFormat("FULL")), time()), 'NEXT_EXEC' => date($DB->DateFormatToPHP(CSite::GetDateFormat("FULL")), time() + $intervalSec), 'AGENT_INTERVAL' => $intervalSec, 'ACTIVE' => 'Y', 'IS_PERIOD' => 'Y' ); return CAgent::Update($arAgent['ID'], $arFields); } } /** * Импорт данных из файла. * * @param bool $is_agent - если агент вызывает функцию, то возвращаем имя функции * * @return array - success = true в случае успешного импорта. */ public function import2IB($is_agent = false) { global $DB; $agent = "CAuctionImport::getInstance()->import2IB(" . true . ");"; // то, что должно возвращаться агенту $ar_new_auctions = $ar_old_auctions = array(); if ($new_file_xml = $this->getContent($this->path2file)) { try { $ar_new_auctions = XML2Array::createArray($new_file_xml); } catch(Exception $e) { echo "Ошибка чтения файла XML - $this->path2file, проверьте сформированный файл."; die(); } } elseif ($is_agent) { return $agent; } else { $this->log .= "file don't exist\n"; $this->m_return["messages"][] = "Файл отсутствует или некорректные настройки импорта."; $this->updateAgent(); return $this->m_return; } if ($old_file_xml = $this->getContent($this->path2oldFile)) { try { $ar_old_auctions = XML2Array::createArray($old_file_xml); } catch(Exception $e) { echo "Ошибка чтения файла XML - $this->path2oldFile, проверьте сформированный файл."; die(); } } if (json_encode($ar_new_auctions) != json_encode($ar_old_auctions)) { $el = new CIBlockElement; // данные с XML $arXmlData = $ar_new_auctions["AuctionData"]["HeaderAuctionData"]; $LIST_ID = array(); // список ID конкурсов, кторые в xml $arSelectComp = Array("ID", "IBLOCK_ID"); $arFilterComp = Array("IBLOCK_ID" => $this->IB_COMP, "ACTIVE" => "Y"); // проход по конкурсам // на случай если в xml только одно предприятие (HeaderAuctionData) reset($arXmlData); if (!is_array(current($arXmlData))) { $arXmlData = array($arXmlData); } foreach ($arXmlData as $vval) { $arFilterComp["CODE"] = $vval["Document_Name"]; $LIST_ID[] = $vval["Document_Name"]; // выбор полей в формате Bitrix if ($auxIBFields = $this->getAUXFieldsBX($vval)) { $a_ID = false; // ID конкурса $res = CIBlockElement::GetList( Array("ID" => "DESC"), $arFilterComp, false, array("nTopCount" => 1), $arSelectComp ); if ($ob = $res->GetNextElement()) { $arAuction = $ob->GetFields(); /** обновление конкурса **/ $a_ID = $arAuction["ID"]; if ($el->Update($a_ID, $auxIBFields)) { $this->m_return["affected"]++; } else { $this->m_return["messages"][] = "Конкурс №" . $auxIBFields["Document_Name"] . " не был изменен"; } } else { /** создание конкурса **/ if ($a_ID = $el->Add($auxIBFields)) { $this->m_return["created"]++; } else { $this->m_return["messages"][] = "Конкурс №" . $vval["Document_Name"] . " не был создан"; } } // Позиции конкурса if ($a_ID) { $arSelectItems = Array("ID", "IBLOCK_ID"); $arFilterItems = Array( "IBLOCK_ID" => $this->IB_ITEMS, "ACTIVE" => "Y", "PROPERTY_COMP_ID" => $a_ID ); //Line_Number $resPos = CIBlockElement::GetList( Array("ID" => "DESC"), $arFilterItems, false, false, $arSelectItems ); while ($obPos = $resPos->GetNextElement()) { $position = $obPos->GetFields(); $DB->StartTransaction(); if (!CIBlockElement::Delete($position["ID"])) { $this->m_return["messages"][] = 'Не удалось удалить позицию: №' . $position["ID"] . " в конкурсе: " . $a_ID; $DB->Rollback(); } else { $DB->Commit(); $this->m_return["deleted_positions"]++; } } // если элемент LineAuctionData в XML один (не массив элементов) if (!isset($vval["LineAuctionData"][0])) { $positionBX = $this->getAUXPositionBX($a_ID, $vval["LineAuctionData"]); /** создание позиции **/ if ($p_ID = $el->Add($positionBX)) { $this->m_return["created_positions"]++; } else { $this->m_return["messages"][] = "Позиция '" . $positionBX["CODE"] . "' для конкурса №" . $auxIBFields["CODE"] . " не была создана"; } continue; } foreach ($vval["LineAuctionData"] as $auxPosition) { if ($positionBX = $this->getAUXPositionBX($a_ID, $auxPosition)) { /** создание позиции **/ if ($p_ID = $el->Add($positionBX)) { $this->m_return["created_positions"]++; } else { $this->m_return["messages"][] = "Позиция '" . $positionBX["CODE"] . "' для конкурса №" . $auxIBFields["CODE"] . " не была создана"; } } } } } } /** * Деактивация старых конкурсов */ if (count($LIST_ID) > 0) { $res2del = CIBlockElement::GetList( Array("ID" => "DESC"), array("IBLOCK_ID" => $this->IB_COMP, "ACTIVE" => "Y", "!CODE" => $LIST_ID), false, false, array("ID", "IBLOCK_ID") ); while ($e_del = $res2del->GetNext()) { if ($el->Update($e_del["ID"], array("ACTIVE" => "N"))) { $this->m_return["deleted_tenders"]++; } } } $this->m_return["status"] = true; $this->log .= "end: " . ConvertTimeStamp(microtime(true), "FULL") . "\n"; } elseif (!$is_agent) { $this->log .= "import don't progress, files identical\n"; $this->m_return["messages"][] = "В файле $this->path2file не обнаружено изменений, импорт не произведен."; } if ($is_agent) { return $agent; } else { $this->updateAgent(); return $this->m_return; } } /** * Получить поля конкурса с ключами полей/свойств Bitrix. * * @param array $arXmlFields - поля Конкурса из Xml * * @return array|bool */ private function getAUXFieldsBX($arXmlFields = array()) { if (empty($arXmlFields)) { return false; } $PROP = array(); if ($org_name = str_replace(array('"', "«", "»"), "", $arXmlFields["Org_Name"])) { if (isset($this->companies[$org_name]) && $this->companies[$org_name] > 0) { $PROP["ORG_NAME"] = $this->companies[$org_name]; // ID компании } else { $this->m_return["messages"][] = "Для компании " . $arXmlFields["Org_Name"] . " (конкурс:" . $arXmlFields["Document_Name"] . ") не найден соответствующий элемент"; } } $PROP["DOCUMENT_NAME"] = $arXmlFields["Document_Name"]; // Номер конкурса $PROP["NOTE_TO_BIDDERS"] = Array( "VALUE" => Array( "TEXT" => $arXmlFields["Note_To_Bidders"], "TYPE" => "text" ) ); // Комментарий к конкурсу $PROP["AUCTION_URL"] = $arXmlFields["Auction_URL"]; // Ссылка Принять участие $PROP["NEED_BY_DATE"] = $arXmlFields["Need_By_Date"]; // Дата поставки позиций конкурса $arAuction2Add = Array( "IBLOCK_SECTION_ID" => false, "IBLOCK_ID" => $this->IB_COMP, "PROPERTY_VALUES" => $PROP, "NAME" => $arXmlFields["Auction_Title"], // название конкурса "CODE" => $arXmlFields["Document_Name"], "ACTIVE" => "Y", "DATE_ACTIVE_FROM" => $arXmlFields["Open_Bidding_Date"], "DATE_ACTIVE_TO" => $arXmlFields["Close_Bidding_Date"], "SORT" => self::$aux_sort++, ); return $arAuction2Add; } /** * Позиции конкурса с полями/свойствами Bitrix * * @param array $arXmlFPosition - поля позиции конкурса из Xml * * @return array|bool */ private function getAUXPositionBX($aux_ID, $arXmlFPosition = array()) { if (empty($arXmlFPosition)) { return false; } $PROP = array(); $PROP["COMP_ID"] = $aux_ID; $PROP["QUANTITY"] = $arXmlFPosition["Quantity"]; // Количество $PROP["UOM_CODE"] = $arXmlFPosition["Uom_Code"]; // Единица измерения $arAuction2Add = Array( "IBLOCK_SECTION_ID" => false, "IBLOCK_ID" => $this->IB_ITEMS, "PROPERTY_VALUES" => $PROP, "NAME" => $arXmlFPosition["Item_Description"], // название пункта в конкурсе "CODE" => $arXmlFPosition["Line_Number"], // номер записи в конкурсе "ACTIVE" => "Y", "SORT" => self::$aux_pos_sort++, ); return $arAuction2Add; } }