PHP-библиотека предназначена для разработки серверной части виджета на своем локальном сервере. Для начала необходимо скачать библиотеку для взаимодействия с amoCRM. Файлы widget_lib.phar и .htaccess необходимо положить в корень вашего веб сервера, и его домен должен быть второго уровня. Так же, следует предварительно создать и загрузить себе в аккаунт архив разрабатываемого виджета(можно без php-части). Это необходимо для его идентификации и доступа к endpoint’ам.
Внимание! code и secret_key виджета должны быть идентичны в загруженном архиве и создаваемом далее manifest.json на сервере. После этого можно создать index файл, который будет содержать базовые настройки и подключение библиотеки.
<?php
header('Content-type: text/html;charset="utf-8"'); //отправка заголовка, сообщающего браузеру какую кодировку использовать
utf - 8
define('AMO_DOMAIN', 'amocrm.ru'); //домен CRM
define('AMO_PROTOCOL', 'https'); //протокол CRM
define('AUTO_BUILD', true); //автоматически собирать виджет при обращении, в противном случае необходимо каждый раз, после
изменения файлов, обращаться к контроллеру builder(описано ниже) .
require_once __DIR__ . '/widget_lib.phar'; //подключение самой библиотеки.
Теперь, необходимо создать папку /widget/ в которой будет происходить разработка виджета. В ней создаем всю структуру виджета и дополнительные файлы.
Файл /widget/widget.php должен содержать в себе класс Widget, который наследует системный класс \Helpers\Widgets.
<?php
class Widget extends \Helpers\Widgets
{
/*
Сюда мы добавим логику нашего виджета
*/
}
Класс Widget должен содержать в себе методы, которые выполняют роль точек входа в виджет. Эти методы должны иметь уровень доступа строже, чем public (т.е. protected и/ или private). Точка входа должна иметь название, начинающееся с префикса endpoint_, например: endpoint_get().
<?php
class Widget extends \Helpers\Widgets
{
protected function endpoint_get()
{
/*
Это точка входа "endpoint_get()"
*/
}
protected function endpoint_set()
{
/*
Это точка входа "endpoint_set()"
*/
}
protected function endpoint_SomeoneElse()
{
/*
Это точка входа "endpoint_SomeoneElse()"
*/
}
protected function endpoint_happy()
{
/*
Это точка входа "endpoint_happy()"
*/
}
}
Обращение к точке входа происходит по следующему URL: /#ACCOUNT#/#CONTROLLER#[/#METHOD#[/#ENDPOINT#]] . Где:
#ACCOUNT# – ваш аккаунт в системе
#CONTROLLER# – для обращения к точке входа виджета должен быть иметь значение loader
#METHOD# – код виджета (код должен содержать только строчные буквы!)
#ENDPOINT# – сама точка входа.
Так же, необходимо передавать get-параметры amouser и amohash. Например: /widgets/test/loader/addcontact/set?amouser=xxx&amohash=xxx
CONTROLLER так же может иметь значение builder. В этом случае METHOD и ENDPOINT указывать не нужно. Обращение к контроллеру builder соберет наш виджет для работы с контроллером loader и создаст zip архив для загрузки на amoCRM.
При первом обращении к контроллеру loader и после каждого изменения в файлах виджета необходимо вызывать контроллер builder. Или установить значение константы AUTO_BUILD как true (описано выше)
Так же, следует помнить, что для работы с сервером amoCRM при обращении к точке входа должны передаваться amouser и amohash. Их можно посмотреть в настройке профиля пользователя в системе (/settings/profile/). Поле amouser – E-Mail пользователя, amohash – ключ для авторизации в API.
Библиотека для работы с виджетами позволяет напрямую взаимодействовать с системой с помощью точек входа (смотрите выше). Для примера создадим виджет, добавляющий контакт в amoCRM.
Все запросы должны перенаправляться на ваш файл index.php в корневой директории. Если у Вас стоит веб-сервер apache и вы распаковали в его корень архив с библиотекой, то там уже имеется файл .htaccess с инструкциями для mod_rewrite (убедитесь, что он включен)
По-умолчанию в объекте вашего виджета уже имеются ссылки на объекты
– контактов – $this->contacts
– компаний – $this->company
– cделок – $this->leads
– примечаний – $this->notes
– задач – $this->tasks
– информации по аккаунту – $this->account->current()
Первые пять объектов имеют два метода get и set, параметры которых описаны в файле примере widget.php Объекты contacts и company имеют еще метод links() который соответствует подобному методу в API.
Для отправки cURL запросов на сторонний сервис можно использовать встроенный класс
\Helpers\Curl::init($url,[$post=FALSE],[$cookie=FALSE]);
где:
$url — ссылка, куда отправляется запрос,
$post — массив для передачи (если он заполнен, то запрос будет отправлен по методу POST),
$cookie — TRUE/FALSE использовать ли куки-файл или нет
Любые приходящие из GET или POST параметры нужно получать через
\Helpers\Route::param(#ELEMENT_KEY#)
Метод \Helpers\Route::param(#ELEMENT_KEY#) так же доступен как $this->param(#ELEMENT_KEY#)
Получение настроек текущего виджета в amoCRM возможно путем вызова
$this->account->current(‘widget’);
Для работы с языковыми сообщениями можно использовать встроенный класс
\Helpers\I18n::get(‘settings.enums.yes’)
Все языковые сообщения должны быть описаны в директории /widget/i18n/#lang#.json
На примере ниже мы продемонстрируем создание простой html-формы для добавления контакта.
Создадим файл с html-формой и назовём его form.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Добавление контакта</title>
</head>
<body>
<div id="wrapper">
<header>
<h1>Создание контакта</h1>
</header>
<div id="contact_form">
<form action="test/loader/addcontact/add?
amouser=test@mail.ru&amohash=63bcbb0169df507cf144320b9c9a79af443efd1e" method="post">
<div class="field">
<label for="contact_name">Имя</label><input id="contact_name" type="text" name="name">
</div>
<div class="field">
<label for="contact_company">Компания</label><input id="contact_company" type="text" name="company">
</div>
<div class="field">
<label for="contact_position">Должность</label><input id="contact_position" type="text" name="position">
</div>
<div class="field">
<label for="contact_phone">Телефон</label><input id="contact_phone" type="tel" name="phone">
</div>
<div class="field">
<label for="contact_email">E-mail</label><input id="contact_email" type="email" name="email">
</div>
<div class="field">
<label for="contact_web">Web-сайт</label><input id="contact_web" type="url" name="web">
</div>
<div class="field">
<label for="contact_jabber">Jabber</label><input id="contact_jabber" type="text" name="jabber">
</div>
<div class="field">
<label for="contact_scope">Сфера деятельности</label>
<select id="contact_scope" name="scope[]" size="5" multiple>
<option value="it">IT, телекоммуникации, связь, электроника</option>
<option value="auto">Автосервис, автобизнес</option>
<option value="bookkeeping">Бухгалтерия, аудит</option>
<option value="restaurants">Рестораны, фастфуд</option>
<option value="economy">Экономика, финансы</option>
</select>
</div>
<div>
<button type="submit">Создать контакт</button>
<button type="reset">Очистить форму</button>
</div>
</form>
</div>
</div>
</body>
</html>
Манифест виджета – это файл с описанием и настройками виджета в формате JSON. Рекомендуется название, описание и другую статичную информацию выносить в файлы локализации виджета (смотрите ниже). Внимание! code и secret_key должны соответствовать, загруженному в аккаунт виджету.
{
"widget": {
"name": "widget.name",
"description": "widget.description",
"short_description": "widget.short_description",
"code": "AddContact",
"secret_key": "305ce9d34a329cbf1d0678e348s2d30dcdb6bae6d81832add4cd6584fbce4d33",
"version": "1.0.0",
"locale": [
"ru",
"en"
],
"installation": true
}
}
Файл локализации – это файл в формате JSON, содержащий перевод статичной информации, используемой при разработке виджета. Эти файлы редактируются по мере написания логики виджета, в зависимости от необходимости ввода той или иной новой информации.
Создадим два файла локализации для нашего примера: на английском и на русском языках соответственно.
{
"widget": {
"name": "Добавление контактов и сделок",
"short_description": "Виджет добавляет контакты, сделки, после чего привязывает некоторые сделки к некоторым контактам" ,
"description": "Виджет добавляет контакты, сделки, после чего привязывает некоторые сделки к некоторым контактам"
},
"exceptions": {
"error": "Ошибка",
"name": "Не заполнено имя контакта",
"email": {
"empty": "Не заполнен E-mail контакта"
},
"custom_fields": {
"undefined": "Невозможно получить дополнительные поля",
"unknown": "В amoCRM отсутствуют следующие поля"
},
"contacts": {
"error": "Невозможно получить список контактов"
},
"user_already_exists": "Такой контакт уже существует в amoCRM"
},
"custom_fields": {
"scope": {
"name": "Сфера деятельности",
"fields": {
"it": "IT, телекоммуникации, связь, электроника",
"auto": "Автосервис, автобизнес",
"bookkeeping": "Бухгалтерия, аудит",
"restaurants": "Рестораны, фастфуд",
"economy": "Экономика, финансы"
}
}
}
}
Файл англоязычной локализации
{
"widget": {
"name": "Adding contacts and leads",
"short_description": "Widget adds contacts, leads, and then binds some leads from some contacts",
"description": "Widget adds contacts, leads, and then binds some leads from some contacts"
},
"exceptions": {
"error": "Error",
"name": "Not filled a contact name",
"email": {
"empty": "Not filled a contact email"
},
"custom_fields": {
"undefined": "Can not get the additional fields",
"unknown": "In amoCRM missing the following fields"
},
"contacts": {
"error": "Unable to get a list of contacts"
},
"user_already_exists": "This contact already exists in amoCRM"
},
"custom_fields": {
"scope": {
"name": "Sphere of activity",
"fields": {
"it": "In IT, telecommunications, communications, electronics",
"auto": "Auto Service, Autp Business",
"bookkeeping": "Accounting, Auditing",
"restaurants": "Restaurants, fast food",
"economy": "Economy, Finances"
}
}
}
}
Создадим пустой класс Widget, который наследует системный класс \Helpers\Widgets, а затем сделаем в нём точку входа, которую назовём add
class Widget extends \Helpers\Widgets
{
protected function endpoint_add()
{
/* Здесь будет наш код*/
}
}
Создадим внутренний метод get_data() (помеченный модификатором private), который будет получать данный из формы, и записывать их во внутреннее свойство $data, а затем поместим его вызов в точку входа.
class Widget extends \Helpers\Widgets
{
private
$data;
protected function endpoint_add()
{
$this->get_data();
}
private function get_data()
{
#Получаем данные из POST-запроса
$data = array(
'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
);
#Если не указано имя или e-mail контакта - уведомляем
if (empty ($data ['name']))
die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
if (empty ($data ['email']))
die (\Helpers\I18n:: get('exceptions.email'));
$this->data = $data;
}
}
Создадим внутренний метод get_custom_fields_info() для получения информации о нужных нам полях в amoCRM и сохраним его результат в переменной $custom_fields в точке входа.
class Widget extends \Helpers\Widgets
{
private
$data;
protected function endpoint_add()
{
$this->get_data();
$custom_fields = $this->get_custom_fields_info();
}
private function get_data()
{
#Получаем данные из POST-запроса
$data = array(
'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
);
#Если не указано имя или e-mail контакта - уведомляем
if (empty ($data ['name']))
die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
if (empty ($data ['email']))
die (\Helpers\I18n:: get('exceptions.email'));
$this->data = $data;
}
private function get_custom_fields_info()
{
#Получаем информацию по текущему аккаунту
$account = $this->account->current();
#Поля, ID которых нам нужно собрать
$need = array_flip(array('POSITION', 'PHONE', 'EMAIL', 'WEB', 'IM', 'SCOPE'));
if (isset ($account ['custom_fields'], $account ['custom_fields'] ['contacts']))
do {
foreach ($account ['custom_fields'] ['contacts'] as $field)
if (is_array($field) && isset ($field ['id'])) {
if (isset ($field ['code']) && isset ($need [$field ['code']]))
$fields [$field ['code']] = (int)$field ['id'];
#SCOPE - нестандартное поле, поэтому обрабатываем его отдельно
elseif (isset ($field ['name']) &&
$field ['name'] == \Helpers\I18n:: get('custom_fields.scope.name'))
$fields ['SCOPE'] = $field;
$diff = array_diff_key($need, $fields);
if (empty ($diff))
break 2;
}
if (isset ($diff))
die (\Helpers\I18n:: get('exceptions.custom_fields.unknown') . ': ' . join(', ', $diff));
else
die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
} while (false);
else
die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
return isset ($fields) ? $fields : false;
}
}
Теперь нам необходимо узнать, существует ли контакт с указанным E-mail у пользователя. Для этого создадим внутренний метод is_contact_exists() и сделаем соответствующую проверку в точке входа.
class Widget extends \Helpers\Widgets
{
private
$data;
protected function endpoint_add()
{
$this->get_data();
$custom_fields = $this->get_custom_fields_info();
if ($this->is_contact_exists())
die (\Helpers\I18n:: get('exceptions.user_already_exists'));
}
private function get_data()
{
#Получаем данные из POST-запроса
$data = array(
'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
);
#Если не указано имя или e-mail контакта - уведомляем
if (empty ($data ['name']))
die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
if (empty ($data ['email']))
die (\Helpers\I18n:: get('exceptions.email'));
$this->data = $data;
}
private function get_custom_fields_info()
{
#Получаем информацию по текущему аккаунту
$account = $this->account->current();
#Поля, ID которых нам нужно собрать
$need = array_flip(array('POSITION', 'PHONE', 'EMAIL', 'WEB', 'IM', 'SCOPE'));
if (isset ($account ['custom_fields'], $account ['custom_fields'] ['contacts']))
do {
foreach ($account ['custom_fields'] ['contacts'] as $field)
if (is_array($field) && isset ($field ['id'])) {
if (isset ($field ['code']) && isset ($need [$field ['code']]))
$fields [$field ['code']] = (int)$field ['id'];
#SCOPE - нестандартное поле, поэтому обрабатываем его отдельно
elseif (isset ($field ['name']) &&
$field ['name'] == \Helpers\I18n:: get('custom_fields.scope.name'))
$fields ['SCOPE'] = $field;
$diff = array_diff_key($need, $fields);
if (empty ($diff))
break 2;
}
if (isset ($diff))
die (\Helpers\I18n:: get('exceptions.custom_fields.unknown') . ': ' . join(', ', $diff));
else
die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
} while (false);
else
die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
return isset ($fields) ? $fields : false;
}
private function is_contact_exists()
{
$params = array(
'query' => $this->data ['email']
);
if ($contacts = $this->contacts->get($params))
return $contacts;
return false;
}
}
Наконец, можем создать контакт в amoCRM. Для этого напишем внутренний метод add_new_contact ($custom_fields), принимающий в качестве параметра массив с информацией, которую мы собрали в методе get_custom_fields_info() и вызовем его в точке входа.
class Widget extends \Helpers\Widgets
{
private
$data;
protected function endpoint_add()
{
$this->get_data();
$custom_fields = $this->get_custom_fields_info();
if ($this->is_contact_exists())
die (\Helpers\I18n:: get('exceptions.user_already_exists'));
$this->add_new_contact($custom_fields);
}
private function get_data()
{
#Получаем данные из POST-запроса
$data = array(
'name' => isset ($_POST ['name']) ? $_POST ['name'] : '',
'company' => isset ($_POST ['company']) ? $_POST ['company'] : '',
'position' => isset ($_POST ['position']) ? $_POST ['position'] : '',
'phone' => isset ($_POST ['phone']) ? $_POST ['phone'] : '',
'email' => isset ($_POST ['email']) ? $_POST ['email'] : '',
'web' => isset ($_POST ['web']) ? $_POST ['web'] : '',
'jabber' => isset ($_POST ['jabber']) ? $_POST ['jabber'] : '',
'scope' => isset ($_POST ['scope']) && is_array($_POST ['scope']) ? $_POST ['scope'] : array()
);
#Если не указано имя или e-mail контакта - уведомляем
if (empty ($data ['name']))
die (\Helpers\I18n:: get('exceptions.name')); #Данные берутся из файлов локализации (смотрите выше)
if (empty ($data ['email']))
die (\Helpers\I18n:: get('exceptions.email'));
$this->data = $data;
}
private function get_custom_fields_info()
{
#Получаем информацию по текущему аккаунту
$account = $this->account->current();
#Поля, ID которых нам нужно собрать
$need = array_flip(array('POSITION', 'PHONE', 'EMAIL', 'WEB', 'IM', 'SCOPE'));
if (isset ($account ['custom_fields'], $account ['custom_fields'] ['contacts']))
do {
foreach ($account ['custom_fields'] ['contacts'] as $field)
if (is_array($field) && isset ($field ['id'])) {
if (isset ($field ['code']) && isset ($need [$field ['code']]))
$fields [$field ['code']] = (int)$field ['id'];
#SCOPE - нестандартное поле, поэтому обрабатываем его отдельно
elseif (isset ($field ['name']) &&
$field ['name'] == \Helpers\I18n:: get('custom_fields.scope.name'))
$fields ['SCOPE'] = $field;
$diff = array_diff_key($need, $fields);
if (empty ($diff))
break 2;
}
if (isset ($diff))
die (\Helpers\I18n:: get('exceptions.custom_fields.unknown') . ': ' . join(', ', $diff));
else
die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
} while (false);
else
die (\Helpers\I18n:: get('exceptions.custom_fields.undefined'));
return isset ($fields) ? $fields : false;
}
private function is_contact_exists()
{
$params = array(
'query' => $this->data ['email']
);
if ($contacts = $this->contacts->get($params))
return $contacts;
return false;
}
private function add_new_contact($custom_fields)
{
$contact = array(
'name' => $this->data ['name'],
'custom_fields' => array(
array(
'id' => $custom_fields ['EMAIL'],
'values' => array(
array(
'value' => $this->data ['email'],
'enum' => 'WORK'
)
)
)
)
);
if (!empty ($this->data ['company']))
$contact += array('company_name' => $this->data ['company']);
if (!empty ($this->data ['position']))
$contact ['custom_fields'] [] = array(
'id' => $custom_fields ['POSITION'],
'values' => array(
array(
'value' => $this->data ['position']
)
)
);
if (!empty ($this->data ['phone']))
$contact ['custom_fields'] [] = array(
'id' => $custom_fields ['PHONE'],
'values' => array(
array(
'value' => $this->data ['phone'],
'enum' => 'OTHER'
)
)
);
if (!empty ($this->data ['web']))
$contact ['custom_fields'] [] = array(
'id' => $custom_fields ['WEB'],
'values' => array(
array(
'value' => $this->data ['web']
)
)
);
if (!empty ($this->data ['jabber']))
$contact ['custom_fields'] [] = array(
'id' => $custom_fields ['IM'],
'values' => array(
array(
'value' => $this->data ['jabber'],
'enum' => 'JABBER'
)
)
);
if (!empty ($this->data ['scope'])) {
foreach ($this->data ['scope'] as & $enum)
$enum = trim(\Helpers\I18n:: get('custom_fields.scope.fields.' . $enum));
unset ($enum);
$intersect = array_intersect($custom_fields ['SCOPE'] ['enums'], $this->data ['scope']);
foreach ($intersect as $k => $v)
$values [] = array(
'value' => $v,
'enum' => $k
);
$scope = array(
'id' => (int)$custom_fields ['SCOPE'] ['id'],
'values' => $values
);
$contact ['custom_fields'] [] = $scope;
}
$request ['add'] [] = $contact;
$this->contacts->set($request);
}
}
Теперь PHP-логика нашего виджета готова!
Заметим, что для отладки на локальных хостах, можно использовать метод \Helpers\Debug::vars($var_for_debug[, $name_of_debug_block]), который выводит свёрстанную страницу с отладочной информацией. Первым параметром передаётся переменная, которую нужно отладить, а вторым (необязательным) параметром можно передать название отладочного блока. При загрузке виджета к нам на аудирование не забывайте удалять отладочные выводы.
$str = 'Строка';
\Helpers\Debug::vars($str, 'Важная информация');
В ходе разработки своего виджета для интеграции с amoCRM, Вы можете столкнуться с числовыми кодами состояний или ошибок, возвращаемых вместе с ответом нашим API. Для того чтобы понять, что именно означает тот или иной код вы можете воспользоваться нашим справочником ответов API или использовать метод \Helpers\Curl::get_error_code($code), который возвращает сообщение ошибки по её числовому коду.
$code = 206;
echo \Helpers\Curl::get_error_code($code); // contacts set - request is empty
Если при работе вы указали значение константы AUTO_BUILD как true то в папке, где вы создавали виджет должна быть автоматически создана структура /widgets/code/ (если же этой папки нет, вам необходимо обратиться к контроллеру builder вручную) где code – код виджета. В ней содержится архив widget.zip, который необходимо загрузить на amoCRM в разделе /settings/dev/
Вы всегда можете скачать актуальную версию php-библиотеки по ссылке, расположенной ниже:
А пример, приведённый на этой странице, доступен здесь: