├── .htaccess ├── README.md ├── index.php └── lib ├── BAApiDI.php ├── BAApiException.php ├── BAApiRouter.php ├── index.php └── routers ├── BAApiRouterLogin.php └── BAApiRouterResidues.php /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^([^?]*)$ /api/index.php?path=$1 [NC,L,QSA] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Веб-сервисы для работы с сайтом на Bitrix 2 | 3 | Веб-сервисы работают по протоколу `HTTP(S)`, для доступа необходима авторизация. Примеры на PHP - в приложении. 4 | 5 | ## 1. Авторизация 6 | 7 | Авторизация осуществляется по логину и паролю, как обычный пользователь. 8 | 9 | Желательно создать для работы с API отдельного пользователя. 10 | 11 | Для авторизации необходимо по URL https://site.ru/api/login/ в POST запросе передать логин и пароль, в ответ сервер вернет кукисы и результат авторизации. 12 | 13 | ``` 14 | URL: https://site.ru/api/login/ 15 | METHOD: POST 16 | POST['login'] - логин 17 | POST['password'] - пароль 18 | ``` 19 | 20 | Пример авторизации средствами curl: 21 | 22 | ``` 23 | curl -c cookies.txt https://site.ru/api/login/ --data "login=your@mail.ru&password=your-password" 24 | ``` 25 | 26 | | Результат | Ответ | Заголовок | 27 | |----------------------|----------------------------------------------------------|------------------------| 28 | | Успешная авторизация | success | HTTP/1.0 200 OK | 29 | | Ошибка авторизации | Incorrect login or password (или другое описание ошибки) | HTTP/1.0 403 Forbidden | 30 | 31 | При множественной неудачной авторизации вход блокируется, необходимо проверить логин и пароль путем ручной авторизации, используя логин и пароль от API, при необходимости ввести капчу. 32 | 33 | ## 2. API остатков по складам 34 | 35 | После авторизации следует запросить URL: https://site.ru/api/residues/json/, используя сохраненные куки 36 | 37 | В ответе будут остатки в формате JSON (описание ниже), заголовок ответа `HTTP/1.0 200 OK`. Других форматов (пока) не предусмотрено 38 | 39 | ``` 40 | URL: https://site.ru/api/residues/json/ 41 | METHOD: GET 42 | ``` 43 | 44 | Пример получения остатков: 45 | 46 | ``` 47 | curl -b cookies.txt https://site.ru/api/residues/json/ 48 | ``` 49 | 50 | ## 3. Неправильный вызов 51 | 52 | При неудачных запросах возвращается заголовок `HTTP/1.0 403 Forbidden` и сообщение об ошибке. 53 | 54 | | Текст ошибки | Описание | 55 | |-------------------------------------|-------------------------------| 56 | | No such router: BAApiRouterResidues | Вызов несуществующего роутера | 57 | | Authorization required | Обращение без авторизации | 58 | 59 | # Описание формата остатков по складам 60 | 61 | Формат выгрузки - JSON. Пример для двух позиций на трех складах: 62 | 63 | ``` 64 | { 65 | "stores": { 66 | "aeef2063-0113-b0d7-8255-00155d032904": { 67 | "name": "Москва склад", 68 | "abbr": "М" 69 | }, 70 | "47585e53-6085-11d9-11e0-00001a1a02c3": { 71 | "name": "Нижний Новгород склад", 72 | "abbr": "Н" 73 | }, 74 | "1d437496-c1e7-11e2-af7a-003048d2334c": { 75 | "name": "Рязань склад", 76 | "abbr": "Р" 77 | } 78 | }, 79 | "shopItems": [ 80 | { 81 | "sku": "MSX800-120", 82 | "residues": { 83 | "aeef2063-0113-b0d7-8255-00155d032904": "10", 84 | "47585e53-6085-11d9-11e0-00001a1a02c3": "0", 85 | "1d437496-c1e7-11e2-af7a-003048d2334c": "10" 86 | } 87 | }, 88 | { 89 | "sku": "MSX800-121", 90 | "residues": { 91 | "aeef2063-0113-b0d7-8255-00155d032904": "5", 92 | "47585e53-6085-11d9-11e0-00001a1a02c3": "10", 93 | "1d437496-c1e7-11e2-af7a-003048d2334c": "10" 94 | } 95 | } 96 | ] 97 | } 98 | ``` 99 | 100 | Поля: 101 | 102 | * `stores` - коллекция складов, объект, где ключ - `GUID` склада, с его описанием (название `name` и аббревиатура `abbr`) 103 | * `shopItems` - коллекция товаров, массив, где указан артикул (`sku`) и остатки (`residues`), где каждому складу соответствует остаток 104 | 105 | # Приложение 1. Пример получения остатков на PHP 106 | 107 | ``` 108 | initCurl(); 118 | } 119 | 120 | public function run() { 121 | try { 122 | $this->curlResp('https://site.ru/api/login/', array('login' => $this->apiLogin, 'password' => $this->apiPassword)); 123 | $this->curlResp('https://site.ru/api/residues/json/'); 124 | $fh = fopen('residues.json', 'w'); 125 | fwrite($fh, $this->response); 126 | fclose($fh); 127 | echo 'Done: residues.json'; 128 | } catch (Exception $e) { 129 | echo $e->GetMessage(); 130 | } 131 | } 132 | 133 | private $curl, $response, $httpCode; 134 | private function initCurl() { 135 | $this->curl = curl_init(); 136 | curl_setopt($this->curl, CURLOPT_HEADER, false); 137 | curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); 138 | curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, 30); 139 | curl_setopt($this->curl, CURLOPT_COOKIEFILE, ""); 140 | curl_setopt($this->curl, CURLOPT_COOKIEJAR, ""); 141 | 142 | // если появляется ошибка HTTPS, то расскоментировать 1 или 2 вариант 143 | 144 | // 1 вариант - указать цепочку сертификатов вручную 145 | // файл сертификата, как скачать цепочку описано здесь http://unitstep.net/blog/2009/05/05/using-curl-in-php-to-access-https-ssltls-protected-sites/ 146 | 147 | /* 148 | $certFile = 'site.ru.crt'; 149 | if(!file_exists($certFile)) throw new Exception("No cert file ".$certFile, 1); 150 | curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, true); 151 | curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, 2); 152 | curl_setopt($this->curl, CURLOPT_CAINFO, $certFile); 153 | */ 154 | 155 | // 2 вариант - отменить проверку сертификата полностью 156 | 157 | /* 158 | curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false); // SSL skip 159 | */ 160 | 161 | 162 | } 163 | 164 | private function curlResp($uri, $postData = false) { 165 | curl_setopt($this->curl, CURLOPT_URL, $uri); 166 | if($postData !== false) { 167 | curl_setopt($this->curl, CURLOPT_POST, true); 168 | curl_setopt($this->curl, CURLOPT_POSTFIELDS, $postData); 169 | } else { 170 | curl_setopt($this->curl, CURLOPT_POST, false); 171 | } 172 | $this->response = curl_exec($this->curl); 173 | $this->httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); 174 | 175 | $errNo = curl_errno($this->curl); 176 | if($errNo != 0) throw new Exception("Curl error: ".$errNo.", see codes here: http://php.net/manual/ru/function.curl-errno.php", $errNo); 177 | 178 | if($this->httpCode !== 200) throw new Exception($this->response, $this->httpCode); 179 | if(empty($this->response)) throw new Exception("Empty response", $this->httpCode); 180 | } 181 | 182 | } 183 | 184 | $ApiExample = new ApiExample(); 185 | $ApiExample->run(); 186 | ``` 187 | 188 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | RestartBuffer(); 15 | 16 | 17 | $BAApiRouter = new BAApiRouter(); 18 | $BAApiRouter->run(); -------------------------------------------------------------------------------- /lib/BAApiDI.php: -------------------------------------------------------------------------------- 1 | user = $USER; 11 | $this->app = $APPLICATION; 12 | } 13 | 14 | private static $instance; 15 | public function _() { 16 | if(!isset(self::$instance)) { 17 | self::$instance = new self(); 18 | } 19 | return self::$instance; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/BAApiException.php: -------------------------------------------------------------------------------- 1 | path = $_GET['path']; 12 | $this->chunks = explode('/', $this->path); 13 | $this->chunks = array_filter($this->chunks); 14 | } 15 | 16 | public function run() { 17 | try { 18 | $this->getRouter(); 19 | $this->router->run(); 20 | die(); 21 | } catch (Exception $e) { 22 | header("HTTP/1.0 403 Forbidden", true); 23 | echo $e->getMessage(); 24 | die(); 25 | } 26 | } 27 | 28 | private function getRouter() { 29 | $this->routerName = 'BAApiRouter'.ucfirst($this->chunks[0]); 30 | if($this->routerName == 'BAApiRouter') throw new Exception("API help: https://iek.ru/partner/personal/help/", 1); 31 | if(!class_exists($this->routerName)) throw new Exception("No such router: ".$this->routerName, 1); 32 | $this->router = new $this->routerName; 33 | if(!method_exists($this->router, 'run')) throw new Exception("No run function in router: ".$this->routerName, 1); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /lib/index.php: -------------------------------------------------------------------------------- 1 | '/api/lib/BAApiDI.php', 8 | 'BAApiLogin' => '/api/lib/BAApiLogin.php', 9 | 'BAApiException' => '/api/lib/BAApiException.php', 10 | 'BAApiRouter' => '/api/lib/BAApiRouter.php', 11 | 12 | 'BAApiRouterLogin' => '/api/lib/routers/BAApiRouterLogin.php', 13 | 'BAApiRouterResidues' => '/api/lib/routers/BAApiRouterResidues.php', 14 | 15 | 16 | ) 17 | ); -------------------------------------------------------------------------------- /lib/routers/BAApiRouterLogin.php: -------------------------------------------------------------------------------- 1 | user->Login($login, $password, "N", "Y"); // N - тогда md5 15 | $DI->app->arAuthResult = $arAuthResult; 16 | 17 | if($arAuthResult === true) { 18 | header("HTTP/1.0 200 OK", true); 19 | die('success'); 20 | } 21 | 22 | $err = isset($arAuthResult['MESSAGE']) ? strip_tags($arAuthResult['MESSAGE']) : 'error'; 23 | header("HTTP/1.0 403 Forbidden", true); 24 | die($err); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /lib/routers/BAApiRouterResidues.php: -------------------------------------------------------------------------------- 1 | accessGranted(); 18 | $this->response(); 19 | } catch (Exception $e) { 20 | header("HTTP/1.0 403 Forbidden", true); 21 | echo $e->getMessage(); 22 | die(); 23 | } 24 | 25 | } 26 | 27 | const PARTNER_GROUP_ID = 31; 28 | protected function accessGranted() { 29 | $user = BAApiDI::_()->user; 30 | if(!$user->IsAuthorized()) throw new Exception("Authorization required", 1); 31 | $groups = $user->GetUserGroupArray(); 32 | $isOk = in_array(self::PARTNER_GROUP_ID, $groups); 33 | if(!$isOk) throw new Exception("Access denied", 1); 34 | } 35 | 36 | private function response() { 37 | $this->getItems(); 38 | $this->responseJSON(); 39 | } 40 | 41 | // COPY PASTE FROM component 42 | // TODO 43 | 44 | private $shopItems, $SKLADI; 45 | private function getItems() { 46 | $this->shopItems = array(); 47 | $arResult["PARTNER"] = IEKDiscount::getPartnerByUserId(BAApiDI::_()->user->GetID()); 48 | $arResult["SKLADI"] = IEKDiscount::getSkladiByPartner($arResult["PARTNER"]); 49 | 50 | $arMinElemFilter = $arElemFilter = array("ACTIVE" => "Y", "SECTION_GLOBAL_ACTIVE" => "Y", "IBLOCK_ACTIVE" => "Y", "IBLOCK_ID" => DEALER_CATALOG_IBID); 51 | $dbElems = CIBlockElement::GetList(array('SORT' => 'ASC', 'NAME' => 'ASC'), $arElemFilter, false, false, 52 | array('ID', 'IBLOCK_ID', 'NAME', 'XML_ID', 'CATALOG_GROUP_2', 'CATALOG_GROUP_3')); 53 | 54 | while ($obElem = $dbElems->GetNextElement()) { 55 | list($shopItem, $shopItemProps) = IEKDiscount::getShopItemBasicInfo($obElem); 56 | 57 | // остаток 58 | $shopItem['OSTATOK_OT'] = $shopItem['OSTATOK_DO'] = 0; 59 | $shopItem['OSTATOK_SKLADI'] = array(); 60 | foreach ($shopItemProps['OT_I_DO']['DESCRIPTION'] as $k1 => $v1) { 61 | if (isset($arResult['SKLADI'][$shopItemProps['OT_I_DO']['VALUE'][$k1]])) { 62 | $arOtIDo = explode('@', $v1); 63 | $shopItem['OSTATOK_OT'] += intval($arOtIDo[0]); 64 | $shopItem['OSTATOK_DO'] += intval($arOtIDo[1]); 65 | $shopItem['OSTATOK_SKLADI'][$shopItemProps['OT_I_DO']['VALUE'][$k1]] = array( 66 | 'OT' => intval($arOtIDo[0]), 67 | 'DO' => intval($arOtIDo[1]), 68 | ); 69 | } 70 | } 71 | 72 | $shopItem['RESIDUES'] = array(); 73 | foreach ($shopItemProps['RESIDUES']['DESCRIPTION'] as $k1 => $v1) { 74 | if (isset($arResult['SKLADI'][$shopItemProps['RESIDUES']['VALUE'][$k1]])) { 75 | $shopItem['RESIDUES'][$shopItemProps['RESIDUES']['VALUE'][$k1]] = $v1; 76 | } 77 | } 78 | 79 | $this->SKLADI = $arResult["SKLADI"]; 80 | $this->shopItems[] = $shopItem; 81 | } 82 | if(count($this->shopItems) == 0) throw new Exception("No shop items!", 1); 83 | } 84 | 85 | private function responseJSON() { 86 | $resp = array('stores' => array(), 'shopItems' => array()); 87 | 88 | foreach ($this->SKLADI as $k2 => $v2) { 89 | $resp['stores'][$k2] = array( 90 | 'name' => iconv('cp1251', 'utf-8//IGNORE', $v2['NAME']), 91 | 'abbr' => iconv('cp1251', 'utf-8//IGNORE', $v2['ABBR']) 92 | ); 93 | } 94 | 95 | foreach($this->shopItems as $arItem) { 96 | $it = array(); 97 | $it['sku'] = $arItem["CML2_ARTICLE"]; 98 | 99 | foreach ($this->SKLADI as $k2 => $v2) { 100 | $count = iconv('cp1251', 'utf-8//IGNORE', IEKDiscount::formatResidue($arItem["RESIDUES"][$k2], $arItem["EXPECTED"][$k2], true)); 101 | $it['residues'][$k2] = $count; 102 | } 103 | 104 | 105 | $resp['shopItems'][] = $it; 106 | } 107 | die(json_encode($resp)); 108 | } 109 | 110 | } --------------------------------------------------------------------------------