/**
* auction_import.php: Импорт данных.
* В настройках модуля указаны параметры подключения,
* согласно которым и осуществляется чтение файла и последующий импорт в ИБ.
*
* Author: Вадим Назаров <nazarov-vadim@mail.ru>
*
* 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('<span style="color: red;">Проверьте в настройках корректность IP сервера, пользователя и пароль.</span>');
}
$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 "<span style='color:red;'>Ошибка чтения файла XML - $this->path2file, проверьте сформированный файл.</span>";
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 "<span style='color:red;'>Ошибка чтения файла XML - $this->path2oldFile, проверьте сформированный файл.</span>";
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;
}
}