From fb7aa9c2e04610c1568a0fddf3adc3600d0c8b61 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sun, 14 May 2023 20:40:35 +0500 Subject: [PATCH] files support and small fixes --- README.md | 152 ++++++++------- examples/files_actions.php | 43 +++- src/AmoCRM/Client/AmoCRMApiClient.php | 17 ++ src/AmoCRM/Client/AmoCRMApiRequest.php | 89 +++++++++ .../CustomFieldsValuesCollection.php | 1 + .../Collections/FileLinksCollection.php | 30 +++ .../Collections/FilePreviewsCollection.php | 2 +- src/AmoCRM/Collections/FilesCollection.php | 3 +- src/AmoCRM/EntitiesServices/BaseEntity.php | 3 +- .../BaseEntityTypeEntityIdEntity.php | 54 +++++ src/AmoCRM/EntitiesServices/Currencies.php | 2 +- src/AmoCRM/EntitiesServices/EntityFiles.php | 184 ++++++++++++++++++ src/AmoCRM/EntitiesServices/Files.php | 4 +- .../Enum/SuppliersCustomFieldsEnums.php | 2 +- src/AmoCRM/Models/CatalogElementModel.php | 11 +- src/AmoCRM/Models/FileLinkModel.php | 61 ++++++ src/AmoCRM/Models/Files/FileModel.php | 6 +- src/AmoCRM/Models/Files/FilePreviewModel.php | 2 +- src/AmoCRM/Models/Files/FileUploadModel.php | 2 +- src/AmoCRM/Models/LeadModel.php | 1 - 20 files changed, 576 insertions(+), 93 deletions(-) create mode 100644 src/AmoCRM/Collections/FileLinksCollection.php create mode 100644 src/AmoCRM/EntitiesServices/BaseEntityTypeEntityIdEntity.php create mode 100644 src/AmoCRM/EntitiesServices/EntityFiles.php create mode 100644 src/AmoCRM/Models/FileLinkModel.php diff --git a/README.md b/README.md index 44a4e4e3..2e7dc58d 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ $apiClient->getOAuthClient()->getOAuthButton( ] ); ``` + 2. Отправив пользователя на страницу авторизации ```php $authorizationUrl = $apiClient->getOAuthClient()->getAuthorizeUrl([ @@ -177,6 +178,8 @@ $leadsService = $apiClient->leads(); | entitySubscriptions | Подписчики сущности | | getOAuthClient | oAuth сервис | | getRequest | Голые запросы | +| files | Файлы | +| entityFiles | Связь файлов с сущностями | #### Для большинства сервисов есть базовый набор методов: @@ -227,7 +230,7 @@ $leadsService = $apiClient->leads(); 7. syncOne Синхронизировать одну модель с сервером: 1. model (BaseApiModel) - коллекция моделей создаваемой сущности 2. with (array) - массив параметров with, которые поддерживает модель сервиса - 4. Результатом выполнения будет коллекция моделей сущности + 3. Результатом выполнения будет коллекция моделей сущности ```php syncOne(BaseApiModel $model, $with = []); ``` @@ -335,7 +338,7 @@ $leadsService = $apiClient->leads(); unlink(BaseApiModel $model, $linkedEntities); ``` -#### Методы удаления доступны в сервисах ```transactions```, ```lossReasons```, ```statuses```, ```pipelines```, ```customFields```, ```customFieldsGroups```, ```roles```, ```customersStatuses```: +#### Методы удаления доступны в сервисах ```transactions```, ```lossReasons```, ```statuses```, ```pipelines```, ```customFields```, ```customFieldsGroups```, ```roles```, ```customersStatuses```, ```entityFiles```, ```files```: 1. delete 1. model (BaseApiModel) - модель сущности @@ -351,12 +354,11 @@ $leadsService = $apiClient->leads(); deleteOne(BaseApiModel $model); ``` - #### Методы доступные в сервисе ```customers```: 1. setMode Смена режима покупателей (периодические покупки или сегментация). Если покупатели выключены - то они будут включены. 1. mode (string) - тип режима (periodicity или segments) - 1. isEnabled (bool) - включен ли функционал покупателей, по-умолчанию - true - 2. Результатом выполнения является строка названия включенного режима или null в случае отключения функционала + 2. isEnabled (bool) - включен ли функционал покупателей, по-умолчанию - true + 3. Результатом выполнения является строка названия включенного режима или null в случае отключения функционала ```php setMode(string $mode, bool $isEnabled = true); ``` @@ -385,7 +387,6 @@ $leadsService = $apiClient->leads(); getByParentId(int $parentId, BaseEntityFilter $filter = null, array $with = []); ``` - #### Методы доступные в сервисе ```account``` 1. getCurrent 1. with (array) - массив параметров with, которые поддерживает модель сервиса @@ -393,7 +394,6 @@ $leadsService = $apiClient->leads(); ```php getCurrent(array $with = []); ``` - #### Методы доступные в сервисе ```unsorted``` 1. addOne Создать одну сущность: @@ -413,7 +413,7 @@ $leadsService = $apiClient->leads(); 3. link 1. model (BaseApiModel) - модель неразобранного 2. body (array) - массив дополнительной информации для привязки - 2. Результатом выполнения будет модель LinkUnsortedModel + 3. Результатом выполнения будет модель LinkUnsortedModel ```php link(BaseApiModel $unsortedModel, $body = []); ``` @@ -421,7 +421,7 @@ $leadsService = $apiClient->leads(); 4. accept 1. model (BaseApiModel) - модель неразобранного 2. body (array) - массив дополнительной информации для принятия - 2. Результатом выполнения будет модель AcceptUnsortedModel + 3. Результатом выполнения будет модель AcceptUnsortedModel ```php accept(BaseApiModel $unsortedModel, $body = []); ``` @@ -429,7 +429,7 @@ $leadsService = $apiClient->leads(); 5. decline 1. model (BaseApiModel) - модель неразобранного 2. body (array) - массив дополнительной информации для отклонения - 2. Результатом выполнения будет модель DeclineUnsortedModel + 3. Результатом выполнения будет модель DeclineUnsortedModel ```php decline(BaseApiModel $unsortedModel, $body = []); ``` @@ -440,10 +440,8 @@ $leadsService = $apiClient->leads(); ```php summary(BaseEntityFilter $filter); ``` - #### Методы доступные в сервисе ```webhooks``` - 1. subscribe 1. model (WebhookModel) - модель вебхука 2. Результатом выполнения является модель WebhookModel @@ -458,9 +456,7 @@ $leadsService = $apiClient->leads(); unsubscribe(WebhookModel $webhookModel); ``` - #### Методы доступные в сервисе ```widgets``` - 1. install 1. model (WidgetModel) - модель виджета 2. Результатом выполнения является модель WidgetModel @@ -474,9 +470,8 @@ $leadsService = $apiClient->leads(); ```php uninstall(WidgetModel $widgetModel); ``` - -#### Методы доступные в сервисе ```products``` +#### Методы доступные в сервисе ```products``` 1. settings 1. Результатом выполнения является модель ProductsSettingsModel ```php @@ -491,7 +486,6 @@ $leadsService = $apiClient->leads(); ``` #### Методы, доступные в сервисе ```talks``` - 1. close 1. model (TalkCloseActionModel) - модель для закрытия беседы 2. Результатом выполнения - является закрытие беседы или запуск NPS-бота для последующего закрытия беседы @@ -499,24 +493,33 @@ $leadsService = $apiClient->leads(); close(TalkCloseActionModel $closeAction) ``` +#### Методы, доступные в сервисе ```files``` +1. uploadOne + 1. model (FileUploadModel) - модель файла для загрузки + 2. Результатом выполнения является модель FileModel + ```php + uploadOne(FileUploadModel $model); + ``` + + ## Обработка ошибок Вызов методов библиотеки может выбрасывать ошибки типа ```AmoCRMApiException```. В данные момент доступны следующие типы ошибок, они все наследуют AmoCRMApiException: -|Тип |Условия | -|----------------------------------------------------|------------------------------------------------------------------------------------------------------| -|AmoCRM\Exceptions\AmoCRMApiConnectExceptionException|Подключение к серверу не было выполнено | -|AmoCRM\Exceptions\AmoCRMApiErrorResponseException |Сервер вернул ошибку на выполняемый запрос | -|AmoCRM\Exceptions\AmoCRMApiHttpClientException |Произошла ошибка http клиента | -|AmoCRM\Exceptions\AmoCRMApiNoContentException |Сервер вернул код 204 без результата, ничего страшного не произошло, просто нет данных на ваш запрос | -|AmoCRM\Exceptions\AmoCRMApiTooManyRedirectsException|Слишком много редиректов (в нормальном режиме не выкидывается) | -|AmoCRM\Exceptions\AmoCRMoAuthApiException |Ошибка в oAuth клиенте | -|AmoCRM\Exceptions\BadTypeException |Передан неверный тип данных | -|AmoCRM\Exceptions\InvalidArgumentException |Передан неверный аргумент | -|AmoCRM\Exceptions\NotAvailableForActionException |Метод не доступен для вызова | -|AmoCRM\Exceptions\AmoCRMApiPageNotAvailableException|Выбрасывается в случае запроса следующей или предыдущей страницы коллекции, когда страница отсутствует| -|AmoCRM\Exceptions\AmoCRMMissedTokenException |Не установлен Access Token для выполнения запроса | +| Тип | Условия | +|------------------------------------------------------|--------------------------------------------------------------------------------------------------------| +| AmoCRM\Exceptions\AmoCRMApiConnectExceptionException | Подключение к серверу не было выполнено | +| AmoCRM\Exceptions\AmoCRMApiErrorResponseException | Сервер вернул ошибку на выполняемый запрос | +| AmoCRM\Exceptions\AmoCRMApiHttpClientException | Произошла ошибка http клиента | +| AmoCRM\Exceptions\AmoCRMApiNoContentException | Сервер вернул код 204 без результата, ничего страшного не произошло, просто нет данных на ваш запрос | +| AmoCRM\Exceptions\AmoCRMApiTooManyRedirectsException | Слишком много редиректов (в нормальном режиме не выкидывается) | +| AmoCRM\Exceptions\AmoCRMoAuthApiException | Ошибка в oAuth клиенте | +| AmoCRM\Exceptions\BadTypeException | Передан неверный тип данных | +| AmoCRM\Exceptions\InvalidArgumentException | Передан неверный аргумент | +| AmoCRM\Exceptions\NotAvailableForActionException | Метод не доступен для вызова | +| AmoCRM\Exceptions\AmoCRMApiPageNotAvailableException | Выбрасывается в случае запроса следующей или предыдущей страницы коллекции, когда страница отсутствует | +| AmoCRM\Exceptions\AmoCRMMissedTokenException | Не установлен Access Token для выполнения запроса | У выброшенных Exception есть следующие методы: 1. ```getErrorCode()``` @@ -530,24 +533,24 @@ $leadsService = $apiClient->leads(); В данный момент библиотека поддерживает фильтры для следующих сервисов: -|Сервис |Фильтр |Особенности |Поддерживает ли сортировку? | -|-------------------------------------------------------------|-------------------------------------------|--------------------------------------------------------------------------------------------------|----------------------------| -|```catalogElements``` |```\AmoCRM\Filters\CatalogElementsFilter```|Доступен в ограниченном виде, в будущих версиях будет расширен |❌ | -|```companies``` |```\AmoCRM\Filters\CompaniesFilter``` |Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API |✅ | -|```contacts``` |```\AmoCRM\Filters\ContactsFilter``` |Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API |✅ | -|```customers``` |```\AmoCRM\Filters\CustomersFilter``` |Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API |✅ | -|```leads``` |```\AmoCRM\Filters\LeadsFilter``` |Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API |✅ | -|```events``` |```\AmoCRM\Filters\EventsFilter``` |Фильтр для списка событий |❌ | -|```leads```, ```contacts```, ```customers```, ```companies```|```\AmoCRM\Filters\LinksFilter``` |Фильтр для получения связей для метода `\AmoCRM\EntitiesServices\HasLinkMethodInterface::getLinks`|❌ | -|```notes``` |```\AmoCRM\Filters\NotesFilter``` |Фильтра для `\AmoCRM\EntitiesServices\EntityNotes::get` |✅ | -|```tags``` |```\AmoCRM\Filters\TagsFilter``` |Фильтр для `\AmoCRM\EntitiesServices\EntityTags::get` |❌ | -|```tasks``` |```\AmoCRM\Filters\TasksFilter``` |Фильтр для метода `\AmoCRM\EntitiesServices\Tasks::get` |✅ | -|```unsorted``` |```\AmoCRM\Filters\UnsortedFilter``` |Фильтр для метода `\AmoCRM\EntitiesServices\Unsorted::get` |✅ | -|```unsorted``` |```\AmoCRM\Filters\UnsortedSummaryFilter```|Фильтр для метода `\AmoCRM\EntitiesServices\Unsorted::summary` |❌ | -|```webhooks``` |```\AmoCRM\Filters\WebhooksFilter``` |Фильтр для метода получения хуков |❌ | -|```sources``` |```\AmoCRM\Filters\SourcesFilter``` |Фильтр для метода получения источников `\AmoCRM\EntitiesServices\Sources::get` |❌ | -|```chatTemplates``` |```\AmoCRM\Filters\Chats\TemplatesFilter```|Фильтр для метода получения шаблонов чатов `\AmoCRM\EntitiesServices\Chats\Templates::get` |❌ | -|Сервисы, где необходима постраничная навигация |```\AmoCRM\Filters\PagesFilter``` |Фильтр, который подходит для любого сервиса, где есть постраничная навигация |❌ | +| Сервис | Фильтр | Особенности | Поддерживает ли сортировку? | +|---------------------------------------------------------------|---------------------------------------------|----------------------------------------------------------------------------------------------------|-----------------------------| +| ```catalogElements``` | ```\AmoCRM\Filters\CatalogElementsFilter``` | Доступен в ограниченном виде, в будущих версиях будет расширен | ❌ | +| ```companies``` | ```\AmoCRM\Filters\CompaniesFilter``` | Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API | ✅ | +| ```contacts``` | ```\AmoCRM\Filters\ContactsFilter``` | Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API | ✅ | +| ```customers``` | ```\AmoCRM\Filters\CustomersFilter``` | Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API | ✅ | +| ```leads``` | ```\AmoCRM\Filters\LeadsFilter``` | Доступен только на аккаунтах, которые подключены к тестированию функционала фильтрации по API | ✅ | +| ```events``` | ```\AmoCRM\Filters\EventsFilter``` | Фильтр для списка событий | ❌ | +| ```leads```, ```contacts```, ```customers```, ```companies``` | ```\AmoCRM\Filters\LinksFilter``` | Фильтр для получения связей для метода `\AmoCRM\EntitiesServices\HasLinkMethodInterface::getLinks` | ❌ | +| ```notes``` | ```\AmoCRM\Filters\NotesFilter``` | Фильтра для `\AmoCRM\EntitiesServices\EntityNotes::get` | ✅ | +| ```tags``` | ```\AmoCRM\Filters\TagsFilter``` | Фильтр для `\AmoCRM\EntitiesServices\EntityTags::get` | ❌ | +| ```tasks``` | ```\AmoCRM\Filters\TasksFilter``` | Фильтр для метода `\AmoCRM\EntitiesServices\Tasks::get` | ✅ | +| ```unsorted``` | ```\AmoCRM\Filters\UnsortedFilter``` | Фильтр для метода `\AmoCRM\EntitiesServices\Unsorted::get` | ✅ | +| ```unsorted``` | ```\AmoCRM\Filters\UnsortedSummaryFilter``` | Фильтр для метода `\AmoCRM\EntitiesServices\Unsorted::summary` | ❌ | +| ```webhooks``` | ```\AmoCRM\Filters\WebhooksFilter``` | Фильтр для метода получения хуков | ❌ | +| ```sources``` | ```\AmoCRM\Filters\SourcesFilter``` | Фильтр для метода получения источников `\AmoCRM\EntitiesServices\Sources::get` | ❌ | +| ```chatTemplates``` | ```\AmoCRM\Filters\Chats\TemplatesFilter``` | Фильтр для метода получения шаблонов чатов `\AmoCRM\EntitiesServices\Chats\Templates::get` | ❌ | +| Сервисы, где необходима постраничная навигация | ```\AmoCRM\Filters\PagesFilter``` | Фильтр, который подходит для любого сервиса, где есть постраничная навигация | ❌ | ## Работа с дополнительными полями сущностей @@ -594,31 +597,33 @@ Namespace, в котором находятся коллекции моделе Namespace, в котором находятся модели дополнительных полей - ```\AmoCRM\Models\CustomFieldsValues``` -| Тип поля | Модель значения | Коллекция моделей значений | Модель доп поля | Контакт | Сделка | Компания | Покупатель | Каталог | Сегмент | -|--------------------------|------------------------------------|-----------------------------------------|-------------------------------------|:-------:|:------:|:--------:|:----------:|:-------:|:-------:| -| Текст | TextCustomFieldValueModel | TextCustomFieldValueCollection | TextCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Число | NumericCustomFieldValueModel | NumericCustomFieldValueCollection | NumericCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Флаг | CheckboxCustomFieldValueModel | CheckboxCustomFieldValueCollection | CheckboxCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Список | SelectCustomFieldValueModel | SelectCustomFieldValueCollection | SelectCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Мультисписок | MultiselectCustomFieldValueModel | MultiselectCustomFieldValueCollection | MultiSelectCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Мультитекст | MultitextCustomFieldValueModel | MultitextCustomFieldValueCollection | MultitextCustomFieldValuesModel | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | -| Дата | DateCustomFieldValueModel | DateCustomFieldValueCollection | DateCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Ссылка | UrlCustomFieldValueModel | UrlCustomFieldValueCollection | UrlCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Дата и время | DateTimeCustomFieldValueModel | DateTimeCustomFieldValueCollection | DateTimeCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Текстовая область | TextareaCustomFieldValueModel | TextareaCustomFieldValueCollection | TextareaCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Переключатель | RadiobuttonCustomFieldValueModel | RadiobuttonCustomFieldValueCollection | RadiobuttonCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Короткий адрес | StreetAddressCustomFieldValueModel | StreetAddressCustomFieldValueCollection | StreetAddressCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Адрес | SmartAddressCustomFieldValueModel | SmartAddressCustomFieldValueCollection | SmartAddressCustomFieldValuesModel | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| День рождения | BirthdayCustomFieldValueModel | BirthdayCustomFieldValueCollection | BirthdayCustomFieldValuesModel | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| Юр. лицо | LegalEntityCustomFieldValueModel | LegalEntityCustomFieldValueCollection | LegalEntityCustomFieldValuesModel | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| Цена | PriceCustomFieldValueModel | PriceCustomFieldValueCollection | PriceCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | -| Категория | CategoryCustomFieldValueModel | CategoryCustomFieldValueCollection | CategoryCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | -| Предметы | ItemsCustomFieldValueModel | ItemsCustomFieldValueCollection | ItemsCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | -| Метка | TrackingDataCustomFieldValueModel | TrackingDataCustomFieldValueCollection | TrackingDataCustomFieldValuesModel | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | -| Связь с другим элементом | LinkedEntityCustomFieldValueModel | LinkedEntityCustomFieldValueCollection | LinkedEntityCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | -| Денежное | MonetaryCustomFieldModel | MonetaryCustomFieldValueCollection | MonetaryCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | -| Каталоги и списки | ChainedListCustomFieldModel | ChainedListCustomFieldValueCollection | ChainedListCustomFieldValuesModel | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | -| Файл | FileCustomFieldModel | FileCustomFieldValueCollection | FileCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| Тип поля | Модель значения | Коллекция моделей значений | Модель доп поля | Контакт | Сделка | Компания | Покупатель | Каталог | Сегмент | +|--------------------------|------------------------------------|-----------------------------------------|-------------------------------------|:-------:|:------:|:--------:|:----------:|:------------------------:|:-------:| +| Текст | TextCustomFieldValueModel | TextCustomFieldValueCollection | TextCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Число | NumericCustomFieldValueModel | NumericCustomFieldValueCollection | NumericCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Флаг | CheckboxCustomFieldValueModel | CheckboxCustomFieldValueCollection | CheckboxCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Список | SelectCustomFieldValueModel | SelectCustomFieldValueCollection | SelectCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Мультисписок | MultiselectCustomFieldValueModel | MultiselectCustomFieldValueCollection | MultiSelectCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Мультитекст | MultitextCustomFieldValueModel | MultitextCustomFieldValueCollection | MultitextCustomFieldValuesModel | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Дата | DateCustomFieldValueModel | DateCustomFieldValueCollection | DateCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Ссылка | UrlCustomFieldValueModel | UrlCustomFieldValueCollection | UrlCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Дата и время | DateTimeCustomFieldValueModel | DateTimeCustomFieldValueCollection | DateTimeCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Текстовая область | TextareaCustomFieldValueModel | TextareaCustomFieldValueCollection | TextareaCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Переключатель | RadiobuttonCustomFieldValueModel | RadiobuttonCustomFieldValueCollection | RadiobuttonCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Короткий адрес | StreetAddressCustomFieldValueModel | StreetAddressCustomFieldValueCollection | StreetAddressCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Адрес | SmartAddressCustomFieldValueModel | SmartAddressCustomFieldValueCollection | SmartAddressCustomFieldValuesModel | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| День рождения | BirthdayCustomFieldValueModel | BirthdayCustomFieldValueCollection | BirthdayCustomFieldValuesModel | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| Юр. лицо | LegalEntityCustomFieldValueModel | LegalEntityCustomFieldValueCollection | LegalEntityCustomFieldValuesModel | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| Цена | PriceCustomFieldValueModel | PriceCustomFieldValueCollection | PriceCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | +| Категория | CategoryCustomFieldValueModel | CategoryCustomFieldValueCollection | CategoryCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | +| Предметы | ItemsCustomFieldValueModel | ItemsCustomFieldValueCollection | ItemsCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | +| Метка | TrackingDataCustomFieldValueModel | TrackingDataCustomFieldValueCollection | TrackingDataCustomFieldValuesModel | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | +| Связь с другим элементом | LinkedEntityCustomFieldValueModel | LinkedEntityCustomFieldValueCollection | LinkedEntityCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | +| Денежное | MonetaryCustomFieldModel | MonetaryCustomFieldValueCollection | MonetaryCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| Каталоги и списки | ChainedListCustomFieldModel | ChainedListCustomFieldValueCollection | ChainedListCustomFieldValuesModel | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | +| Файл | FileCustomFieldModel | FileCustomFieldValueCollection | FileCustomFieldValuesModel | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| Плательщик | PayerCustomFieldModel | PayerCustomFieldValueCollection | PayerCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ (только счета-покупки) | ❌ | +| Поставщик | SupplierCustomFieldModel | SupplierCustomFieldValueCollection | SupplierCustomFieldValuesModel | ❌ | ❌ | ❌ | ❌ | ✅ (только счета-покупки) | ❌ | Пример кода, как создать коллекцию значения полей сущности: ```php @@ -827,6 +832,7 @@ $lead->setTags((new NullTagsCollection())); 24. ```\AmoCRM\Enum\Sources\SourceServiceTypeEnum``` - типы сервисов для источников 25. ```\AmoCRM\Enum\Tags\TagColorsEnum``` - возможные цвета для тегов 26. ```\AmoCRM\Enum\Invoices\BillStatusEnumCode``` - предустановленные статусы для Счетов/Покупок +27. ```\AmoCRM\Enum\SuppliersCustomFieldsEnums``` - константы для свойств поля поставщик ## Работа в случае смены субдомена аккаунта diff --git a/examples/files_actions.php b/examples/files_actions.php index a9d7e400..782edb2b 100644 --- a/examples/files_actions.php +++ b/examples/files_actions.php @@ -1,7 +1,9 @@ entityFiles(EntityTypesInterface::LEADS, 21825653)->add( + (new FileLinksCollection()) + ->add( + (new FileLinkModel()) + ->setFileUuid($file->getUuid()) + ) + ); + var_dump($result); +} catch (AmoCRMApiException $e) { + printError($e); +} + +// Получим все файлы, связанные со сделкой 21825653 +try { + $result = $apiClient->entityFiles(EntityTypesInterface::LEADS, 21825653)->get(); + var_dump($result); +} catch (AmoCRMApiException $e) { + printError($e); +} + +//Отвяжем файл от сделки с ID 21825653 +try { + $result = $apiClient->entityFiles(EntityTypesInterface::LEADS, 21825653)->delete( + (new FileLinksCollection()) + ->add( + (new FileLinkModel()) + ->setFileUuid($file->getUuid()) + ) + ); + var_dump($result); +} catch (AmoCRMApiException $e) { + printError($e); +} diff --git a/src/AmoCRM/Client/AmoCRMApiClient.php b/src/AmoCRM/Client/AmoCRMApiClient.php index e3021879..d2718b87 100644 --- a/src/AmoCRM/Client/AmoCRMApiClient.php +++ b/src/AmoCRM/Client/AmoCRMApiClient.php @@ -16,6 +16,7 @@ use AmoCRM\EntitiesServices\Customers\Transactions; use AmoCRM\EntitiesServices\CustomFieldGroups; use AmoCRM\EntitiesServices\CustomFields; +use AmoCRM\EntitiesServices\EntityFiles; use AmoCRM\EntitiesServices\EntityNotes; use AmoCRM\EntitiesServices\Files; use AmoCRM\EntitiesServices\Sources; @@ -388,6 +389,22 @@ public function files(?string $domain = null): Files return new Files($request); } + /** + * Метод вернет объект для работы со связями файлов с сущностями + * + * @param string $entityType + * @param int $entityId + * + * @return EntityFiles + * @throws InvalidArgumentException|AmoCRMMissedTokenException + */ + public function entityFiles(string $entityType, int $entityId): EntityFiles + { + return (new EntityFiles($this->buildRequest())) + ->setEntityType($entityType) + ->setEntityId($entityId); + } + /** * Метод вернет объект ролей пользователей * diff --git a/src/AmoCRM/Client/AmoCRMApiRequest.php b/src/AmoCRM/Client/AmoCRMApiRequest.php index 46a9c683..6c003327 100644 --- a/src/AmoCRM/Client/AmoCRMApiRequest.php +++ b/src/AmoCRM/Client/AmoCRMApiRequest.php @@ -27,6 +27,7 @@ class AmoCRMApiRequest { public const POST_REQUEST = 'POST'; + public const PUT_REQUEST = 'PUT'; public const GET_REQUEST = 'GET'; public const PATCH_REQUEST = 'PATCH'; public const DELETE_REQUEST = 'DELETE'; @@ -205,6 +206,94 @@ public function post( return $response; } + /** + * @param string $method + * @param array|mixed $body + * @param array $queryParams + * @param array $headers + * @param bool $needToRefresh + * @param bool $isFullPath + * + * @return array + * @throws AmoCRMApiConnectExceptionException + * @throws AmoCRMApiException + * @throws AmoCRMApiHttpClientException + * @throws AmoCRMApiNoContentException + * @throws AmoCRMApiTooManyRedirectsException + * @throws AmoCRMoAuthApiException + */ + public function put( + string $method, + $body = [], + array $queryParams = [], + array $headers = [], + bool $needToRefresh = false, + bool $isFullPath = false + ): array { + if ($this->accessToken->hasExpired()) { + $needToRefresh = true; + } + + if ($needToRefresh) { + $this->refreshAccessToken(); + } + + $headers = array_merge($headers, $this->getBaseHeaders()); + + $this->lastHttpMethod = self::PUT_REQUEST; + $this->lastMethod = $isFullPath ? $method : $this->getUrl() . $method; + $this->lastBody = $body; + $this->lastQueryParams = $queryParams; + + $requestOptions = [ + 'connect_timeout' => self::CONNECT_TIMEOUT, + 'headers' => $headers, + 'http_errors' => false, + 'query' => $queryParams, + 'timeout' => self::REQUEST_TIMEOUT, + ]; + + if (is_array($body)) { + $requestOptions['json'] = $body; + } else { + $requestOptions['body'] = $body; + } + + try { + $response = $this->httpClient->request( + self::PUT_REQUEST, + $isFullPath ? $method : $this->getUrl() . $method, + $requestOptions + ); + } catch (ConnectException $e) { + throw new AmoCRMApiConnectExceptionException($e->getMessage(), $e->getCode(), $this->getLastRequestInfo()); + } catch (TooManyRedirectsException $e) { + throw new AmoCRMApiTooManyRedirectsException($e->getMessage(), $e->getCode(), $this->getLastRequestInfo()); + } catch (GuzzleException $e) { + throw new AmoCRMApiHttpClientException($e->getMessage(), $e->getCode(), $this->getLastRequestInfo()); + } + + if (!empty($response->getHeader('X-Request-Id'))) { + $this->lastRequestId = $response->getHeader('X-Request-Id')[0]; + } + + /** + * В случае получения ошибки авторизации, пробуем обновить токен 1 раз, + * если не получилось, то тогда уже выкидываем Exception + */ + try { + $response = $this->parseResponse($response); + } catch (AmoCRMoAuthApiException $e) { + if ($needToRefresh) { + throw $e; + } + + return $this->post($method, $body, $queryParams, $headers, true); + } + + return $response; + } + /** * @param string $method * @param array $body diff --git a/src/AmoCRM/Collections/CustomFieldsValuesCollection.php b/src/AmoCRM/Collections/CustomFieldsValuesCollection.php index 66e0f16d..c3e86b38 100644 --- a/src/AmoCRM/Collections/CustomFieldsValuesCollection.php +++ b/src/AmoCRM/Collections/CustomFieldsValuesCollection.php @@ -20,6 +20,7 @@ * @method CustomFieldsValuesCollection prepend(BaseCustomFieldValuesModel $value) * @method CustomFieldsValuesCollection add(BaseCustomFieldValuesModel $value) * @method null|BaseCustomFieldValuesModel getBy($key, $value) + * @method static CustomFieldsValuesCollection fromArray(array $value) */ class CustomFieldsValuesCollection extends BaseApiCollection { diff --git a/src/AmoCRM/Collections/FileLinksCollection.php b/src/AmoCRM/Collections/FileLinksCollection.php new file mode 100644 index 00000000..bea4f352 --- /dev/null +++ b/src/AmoCRM/Collections/FileLinksCollection.php @@ -0,0 +1,30 @@ +request->post($this->getMethod(), $collection->toApi()); - $collection = $this->processAdd($collection, $response); - return $collection; + return $this->processAdd($collection, $response); } /** diff --git a/src/AmoCRM/EntitiesServices/BaseEntityTypeEntityIdEntity.php b/src/AmoCRM/EntitiesServices/BaseEntityTypeEntityIdEntity.php new file mode 100644 index 00000000..1c97ba8b --- /dev/null +++ b/src/AmoCRM/EntitiesServices/BaseEntityTypeEntityIdEntity.php @@ -0,0 +1,54 @@ +validateEntityType($entityType); + $this->entityType = $entityType; + + return $this; + } + + /** + * @return string + */ + public function getEntityType(): string + { + return $this->entityType; + } + + /** + * @return string + */ + protected function getMethod(): string + { + return sprintf($this->method, $this->getEntityType(), $this->getEntityId()); + } +} diff --git a/src/AmoCRM/EntitiesServices/Currencies.php b/src/AmoCRM/EntitiesServices/Currencies.php index 64fdc8b1..62c33732 100644 --- a/src/AmoCRM/EntitiesServices/Currencies.php +++ b/src/AmoCRM/EntitiesServices/Currencies.php @@ -46,7 +46,7 @@ class Currencies extends BaseEntity implements HasPageMethodsInterface */ protected function getEntitiesFromResponse(array $response): array { - return (array) ($response[AmoCRMApiRequest::EMBEDDED][EntityTypesInterface::CURRENCIES] ?? []); + return (array)($response[AmoCRMApiRequest::EMBEDDED][EntityTypesInterface::CURRENCIES] ?? []); } /** diff --git a/src/AmoCRM/EntitiesServices/EntityFiles.php b/src/AmoCRM/EntitiesServices/EntityFiles.php new file mode 100644 index 00000000..3b1288d1 --- /dev/null +++ b/src/AmoCRM/EntitiesServices/EntityFiles.php @@ -0,0 +1,184 @@ +buildFilter(); + } + + $response = $this->request->get($this->getMethod(), $queryParams); + + return $this->createCollection($response); + } + + public function add(BaseApiCollection $collection): BaseApiCollection + { + $response = $this->request->put($this->getMethod(), $collection->toApi()); + + return $this->processAdd($collection, $response); + } + + public function delete(BaseApiCollection $collection): bool + { + $this->request->delete($this->getMethod(), $collection->toApi()); + + return true; + } + + /** + * @param array $response + * + * @return array + */ + protected function getEntitiesFromResponse(array $response): array + { + return $response[AmoCRMApiRequest::EMBEDDED][EntityTypesInterface::FILES] ?? []; + } + + public function getOne($id, array $with = []): ?BaseApiModel + { + throw new NotAvailableForActionException('Method not available for this entity'); + } + + public function addOne(BaseApiModel $model): BaseApiModel + { + /** @var BaseApiCollection $collection */ + $collection = new $this->collectionClass(); + $collection->add($model); + $collection = $this->add($collection); + + return $collection->first(); + } + + public function update(BaseApiCollection $collection): BaseApiCollection + { + throw new NotAvailableForActionException('Method not available for this entity'); + } + + public function updateOne(BaseApiModel $apiModel): BaseApiModel + { + throw new NotAvailableForActionException('Method not available for this entity'); + } + + public function syncOne(BaseApiModel $apiModel, $with = []): BaseApiModel + { + throw new NotAvailableForActionException('Method not available for this entity'); + } + + /** + * @param BaseApiModel|FileLinkModel $model + * + * @return bool + */ + public function deleteOne(BaseApiModel $model): bool + { + /** @var BaseApiCollection $collection */ + $collection = new $this->collectionClass(); + $collection->add($model); + + return $this->delete($collection); + } + + protected function validateEntityType(string $entityType): string + { + $availableEntities = [ + EntityTypesInterface::CONTACTS, + EntityTypesInterface::LEADS, + EntityTypesInterface::CUSTOMERS, + EntityTypesInterface::COMPANIES, + ]; + + if (!in_array($entityType, $availableEntities, true)) { + preg_match("/" . EntityTypesInterface::CATALOGS . ":(\d+)/", $entityType, $matches); + if (isset($matches[1]) && (int)$matches[1] > EntityTypesInterface::MIN_CATALOG_ID) { + $entityType = EntityTypesInterface::CATALOGS . '/' . (int)$matches[1]; + } else { + throw new InvalidArgumentException('Entity is not supported by this method'); + } + } + + return $entityType; + } + + /** + * @param BaseApiCollection $collection + * @param array $response + * + * @return BaseApiCollection + */ + protected function processAdd(BaseApiCollection $collection, array $response): BaseApiCollection + { + return $this->processAction($collection, $response); + } + + /** + * @param BaseApiCollection $collection + * @param array $response + * + * @return BaseApiCollection + */ + protected function processAction(BaseApiCollection $collection, array $response): BaseApiCollection + { + $entities = $this->getEntitiesFromResponse($response); + foreach ($entities as $entity) { + if (array_key_exists('file_uuid', $entity)) { + $initialEntity = $collection->getBy('file_uuid', $entity['file_uuid']); + if ($initialEntity !== null) { + $this->processModelAction($initialEntity, $entity); + } + } + } + + return $collection; + } + + /** + * @param BaseApiModel|FileLinkModel $apiModel + * @param array $entity + */ + protected function processModelAction(BaseApiModel $apiModel, array $entity): void + { + if (isset($entity['file_uuid'])) { + $apiModel->setFileUuid($entity['file_uuid']); + } + } +} diff --git a/src/AmoCRM/EntitiesServices/Files.php b/src/AmoCRM/EntitiesServices/Files.php index 57728960..3015505b 100644 --- a/src/AmoCRM/EntitiesServices/Files.php +++ b/src/AmoCRM/EntitiesServices/Files.php @@ -2,8 +2,8 @@ namespace AmoCRM\EntitiesServices; -use AmoCRM\AmoCRM\Models\Files\FileModel; -use AmoCRM\AmoCRM\Models\Files\FileUploadModel; +use AmoCRM\Models\Files\FileModel; +use AmoCRM\Models\Files\FileUploadModel; use AmoCRM\Client\AmoCRMApiClient; use AmoCRM\Client\AmoCRMApiRequest; use AmoCRM\Collections\BaseApiCollection; diff --git a/src/AmoCRM/Enum/SuppliersCustomFieldsEnums.php b/src/AmoCRM/Enum/SuppliersCustomFieldsEnums.php index 97ae4660..1f43ce29 100644 --- a/src/AmoCRM/Enum/SuppliersCustomFieldsEnums.php +++ b/src/AmoCRM/Enum/SuppliersCustomFieldsEnums.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace AmoCRM\AmoCRM\Enum; +namespace AmoCRM\Enum; class SuppliersCustomFieldsEnums { diff --git a/src/AmoCRM/Models/CatalogElementModel.php b/src/AmoCRM/Models/CatalogElementModel.php index 2da67a6c..5c01724f 100644 --- a/src/AmoCRM/Models/CatalogElementModel.php +++ b/src/AmoCRM/Models/CatalogElementModel.php @@ -398,7 +398,7 @@ public static function fromArray(array $catalogElement): self } if (!empty($catalogElement['custom_fields_values'])) { $valuesCollection = new CustomFieldsValuesCollection(); - $customFieldsValues = $valuesCollection->fromArray($catalogElement['custom_fields_values']); + $customFieldsValues = $valuesCollection::fromArray($catalogElement['custom_fields_values']); $catalogElementModel->setCustomFieldsValues($customFieldsValues); } @@ -428,10 +428,9 @@ public static function fromArray(array $catalogElement): self */ public function toArray(): array { - return [ + $result = [ 'id' => $this->getId(), 'name' => $this->getName(), - 'currency_id' => $this->getCurrencyId(), 'created_by' => $this->getCreatedBy(), 'updated_by' => $this->getUpdatedBy(), 'created_at' => $this->getCreatedAt(), @@ -449,6 +448,12 @@ public function toArray(): array 'price_id' => $this->getPriceId(), ] ]; + + if (!is_null($this->getCurrencyId())) { + $result['currency_id'] = $this->getCurrencyId(); + } + + return $result; } public function toApi(?string $requestId = "0"): array diff --git a/src/AmoCRM/Models/FileLinkModel.php b/src/AmoCRM/Models/FileLinkModel.php new file mode 100644 index 00000000..d5c77c2c --- /dev/null +++ b/src/AmoCRM/Models/FileLinkModel.php @@ -0,0 +1,61 @@ +fileUuid; + } + + /** + * @param null|string $fileUuid + * + * @return FileLinkModel + */ + public function setFileUuid(?string $fileUuid): FileLinkModel + { + $this->fileUuid = $fileUuid; + + return $this; + } + + /** + * @param array $link + * + * @return self + */ + public static function fromArray(array $link): self + { + $model = new self(); + + $model + ->setFileUuid($link['file_uuid'] ?? null); + + return $model; + } + + /** + * @inheritDoc + */ + public function toArray(): array + { + return [ + 'file_uuid' => $this->getFileUuid(), + ]; + } + + public function toApi(?string $requestId = "0"): array + { + return $this->toArray(); + } +} diff --git a/src/AmoCRM/Models/Files/FileModel.php b/src/AmoCRM/Models/Files/FileModel.php index 4a9444ce..8984b740 100644 --- a/src/AmoCRM/Models/Files/FileModel.php +++ b/src/AmoCRM/Models/Files/FileModel.php @@ -1,6 +1,6 @@