From 5fc0c45506344054dfbfe8d1187209d9db1c7535 Mon Sep 17 00:00:00 2001 From: pahomov_aa Date: Mon, 26 Oct 2026 16:27:37 +0300 Subject: [PATCH] ref #108231 Support knowledge base API --- doc/structure.md | 1 + doc/usage/knowledge-base.md | 83 +++++ src/Client.php | 12 + src/Model/Entity/Files/Attachment.php | 10 + .../Files/AttachmentKnowledgeBaseDocument.php | 29 ++ .../KnowledgeBase/KnowledgeBaseDocument.php | 101 ++++++ .../KnowledgeBaseDocumentFile.php | 61 ++++ .../KnowledgeBaseDocumentHistory.php | 117 +++++++ .../KnowledgeBaseDocumentInput.php | 61 ++++ .../KnowledgeBase/KnowledgeBaseSection.php | 77 +++++ .../KnowledgeBaseSectionInput.php | 37 ++ .../KnowledgeBaseDocumentFilter.php | 77 +++++ .../KnowledgeBaseDocumentHistoryFilter.php | 53 +++ .../KnowledgeBaseDocumentByRequest.php | 40 +++ .../KnowledgeBaseDocumentsCreateRequest.php | 31 ++ .../KnowledgeBaseDocumentsEditRequest.php | 39 +++ .../KnowledgeBaseDocumentsHistoryRequest.php | 33 ++ .../KnowledgeBaseDocumentsRequest.php | 33 ++ .../KnowledgeBaseSectionsCreateRequest.php | 31 ++ .../KnowledgeBaseSectionsEditRequest.php | 31 ++ .../KnowledgeBaseDocumentResponse.php | 30 ++ .../KnowledgeBaseDocumentsHistoryResponse.php | 38 ++ .../KnowledgeBaseDocumentsResponse.php | 30 ++ .../KnowledgeBaseSectionsResponse.php | 30 ++ src/ResourceGroup/KnowledgeBase.php | 327 ++++++++++++++++++ tests/src/ResourceGroup/KnowledgeBaseTest.php | 244 +++++++++++++ 26 files changed, 1656 insertions(+) create mode 100644 doc/usage/knowledge-base.md create mode 100644 src/Model/Entity/Files/AttachmentKnowledgeBaseDocument.php create mode 100644 src/Model/Entity/KnowledgeBase/KnowledgeBaseDocument.php create mode 100644 src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentFile.php create mode 100644 src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentHistory.php create mode 100644 src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentInput.php create mode 100644 src/Model/Entity/KnowledgeBase/KnowledgeBaseSection.php create mode 100644 src/Model/Entity/KnowledgeBase/KnowledgeBaseSectionInput.php create mode 100644 src/Model/Filter/KnowledgeBase/KnowledgeBaseDocumentFilter.php create mode 100644 src/Model/Filter/KnowledgeBase/KnowledgeBaseDocumentHistoryFilter.php create mode 100644 src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentByRequest.php create mode 100644 src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsCreateRequest.php create mode 100644 src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsEditRequest.php create mode 100644 src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsHistoryRequest.php create mode 100644 src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsRequest.php create mode 100644 src/Model/Request/KnowledgeBase/KnowledgeBaseSectionsCreateRequest.php create mode 100644 src/Model/Request/KnowledgeBase/KnowledgeBaseSectionsEditRequest.php create mode 100644 src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentResponse.php create mode 100644 src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsHistoryResponse.php create mode 100644 src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php create mode 100644 src/Model/Response/KnowledgeBase/KnowledgeBaseSectionsResponse.php create mode 100644 src/ResourceGroup/KnowledgeBase.php create mode 100644 tests/src/ResourceGroup/KnowledgeBaseTest.php diff --git a/doc/structure.md b/doc/structure.md index c6250683c..ffc458ca7 100644 --- a/doc/structure.md +++ b/doc/structure.md @@ -27,6 +27,7 @@ The resource groups list into which client is separated: * `features` * `files` * `integration` +* [`knowledgeBase`](usage/knowledge-base.md) * `loyalty` * `notifications` * `orders` diff --git a/doc/usage/knowledge-base.md b/doc/usage/knowledge-base.md new file mode 100644 index 000000000..be2feade4 --- /dev/null +++ b/doc/usage/knowledge-base.md @@ -0,0 +1,83 @@ +# Knowledge base + +The `knowledgeBase` resource group manages company documents and sections through REST API v5. +The API key needs `knowledge_base_read` for reads and `knowledge_base_write` for writes. +Documents are account-wide; there is no `site` parameter. + +```php +use RetailCrm\Api\Factory\SimpleClientFactory; +use RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocumentInput; +use RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentByRequest; +use RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsCreateRequest; +use RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsEditRequest; + +$client = SimpleClientFactory::createClient('https://example.retailcrm.ru', 'apiKey'); +$sections = $client->knowledgeBase->sections()->sections; + +$request = new KnowledgeBaseDocumentsCreateRequest(); +$request->document = new KnowledgeBaseDocumentInput(); +$request->document->title = 'Shipping'; +$request->document->content = '# Shipping options'; +$request->document->externalId = 'shipping'; +$request->document->availableForCopilot = true; +$id = $client->knowledgeBase->documentsCreate($request)->id; + +$document = $client->knowledgeBase->documentsGet($id, new KnowledgeBaseDocumentByRequest('id'))->document; +// Without a by request, the identifier is treated as externalId. +$document = $client->knowledgeBase->documentsGet('shipping')->document; + +$edit = new KnowledgeBaseDocumentsEditRequest(); +$edit->by = 'id'; +$edit->document = new KnowledgeBaseDocumentInput(); +$edit->document->availableForCopilot = false; +$client->knowledgeBase->documentsEdit($id, $edit); +``` + +Only populated input fields are sent on edit. Null properties are omitted, as in other client models. +Use `sectionId = 0` to remove a document from its section, `parentId = 0` to move a section to the root, +and `content = ''` to clear text. False boolean values are preserved. +To send an explicit JSON null (for example, to release an external ID), the edit request also accepts an array: + +```php +$edit = new KnowledgeBaseDocumentsEditRequest(); +$edit->by = 'id'; +$edit->document = ['externalId' => null]; +$client->knowledgeBase->documentsEdit($id, $edit); +``` + +Sections are returned without pagination. Documents use `page`, `limit` and a +`KnowledgeBaseDocumentFilter`: `ids`, `externalIds`, `sectionId`, `directOnly`, +`availableForCopilot`, `hasFile`, `search`. A section filter includes subsections unless `directOnly` is true. +With `directOnly = true` and no section, only root documents are selected. + +`documentsHistory()` accepts `KnowledgeBaseDocumentsHistoryRequest` and +`KnowledgeBaseDocumentHistoryFilter` (`sinceId`, `documentId`, `startDate`, `endDate`). +Store the last history ID to retrieve subsequent changes. Content changes carry hashes, not document text; +fetch the document again to read the current content. Sections have no history endpoint. + +## Files + +Use the existing `files->upload()` method to upload the original file, then attach it: + +```php +use RetailCrm\Api\Model\Entity\Files\Attachment; +use RetailCrm\Api\Model\Entity\Files\AttachmentKnowledgeBaseDocument; +use RetailCrm\Api\Model\Entity\Files\File; +use RetailCrm\Api\Model\Request\Files\FilesEditRequest; + +$attachment = new Attachment(); +$attachment->knowledgeBaseDocument = new AttachmentKnowledgeBaseDocument(); +$attachment->knowledgeBaseDocument->id = $id; +$request = new FilesEditRequest(); +$request->file = new File(); +$request->file->filename = 'shipping.pdf'; +$request->file->attachment = [$attachment]; +// $fileId is the ID returned by files->upload(). +$client->files->edit($fileId, $request); +``` + +File operations require the corresponding `file_read` / `file_write` scopes. +A document supports one original file. Its download URL is returned in `document->file->url`. + +`sectionsDelete()` deletes the section **together with its subsections and their documents**. +`documentsDelete()` removes one document and accepts the same `by` request as `documentsGet()`. diff --git a/src/Client.php b/src/Client.php index 7d70962d6..abcd5e094 100644 --- a/src/Client.php +++ b/src/Client.php @@ -18,6 +18,7 @@ use RetailCrm\Api\Interfaces\ResponseTransformerInterface; use RetailCrm\Api\ResourceGroup\Api; use RetailCrm\Api\ResourceGroup\Costs; +use RetailCrm\Api\ResourceGroup\KnowledgeBase; use RetailCrm\Api\ResourceGroup\CustomerInteraction; use RetailCrm\Api\ResourceGroup\Customers; use RetailCrm\Api\ResourceGroup\CustomersCorporate; @@ -65,6 +66,9 @@ class Client /** @var \RetailCrm\Api\ResourceGroup\Costs */ public $costs; + /** @var \RetailCrm\Api\ResourceGroup\KnowledgeBase */ + public $knowledgeBase; + /** @var \RetailCrm\Api\ResourceGroup\CustomFields */ public $customFields; @@ -183,6 +187,14 @@ public function __construct( $eventDispatcher, $logger ); + $this->knowledgeBase = new KnowledgeBase( + $url, + $httpClient, + $requestTransformer, + $responseTransformer, + $eventDispatcher, + $logger + ); $this->customFields = new CustomFields( $url, $httpClient, diff --git a/src/Model/Entity/Files/Attachment.php b/src/Model/Entity/Files/Attachment.php index 09e041446..81ef1dd95 100644 --- a/src/Model/Entity/Files/Attachment.php +++ b/src/Model/Entity/Files/Attachment.php @@ -14,6 +14,8 @@ /** * Class Attachment * + * @SuppressWarnings(PHPMD.LongVariable) + * * @category Attachment * @package RetailCrm\Api\Model\Entity\Files */ @@ -34,4 +36,12 @@ class Attachment * @JMS\SerializedName("order") */ public $order; + + /** + * @var \RetailCrm\Api\Model\Entity\Files\AttachmentKnowledgeBaseDocument + * + * @JMS\Type("RetailCrm\Api\Model\Entity\Files\AttachmentKnowledgeBaseDocument") + * @JMS\SerializedName("knowledgeBaseDocument") + */ + public $knowledgeBaseDocument; } diff --git a/src/Model/Entity/Files/AttachmentKnowledgeBaseDocument.php b/src/Model/Entity/Files/AttachmentKnowledgeBaseDocument.php new file mode 100644 index 000000000..b3b2fb6a8 --- /dev/null +++ b/src/Model/Entity/Files/AttachmentKnowledgeBaseDocument.php @@ -0,0 +1,29 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var \DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("updatedAt") + */ + public $updatedAt; +} diff --git a/src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentFile.php b/src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentFile.php new file mode 100644 index 000000000..73566d640 --- /dev/null +++ b/src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentFile.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("created") + */ + public $created; + + /** + * @var bool + * + * @JMS\Type("bool") + * @JMS\SerializedName("deleted") + */ + public $deleted; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("source") + */ + public $source; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryUser + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryUser") + * @JMS\SerializedName("user") + */ + public $user; + + /** + * @var \RetailCrm\Api\Model\Entity\HistoryApiKey + * + * @JMS\Type("RetailCrm\Api\Model\Entity\HistoryApiKey") + * @JMS\SerializedName("apiKey") + */ + public $apiKey; + + /** + * @var string + * + * @JMS\Type("string") + * @JMS\SerializedName("field") + */ + public $field; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("oldValue") + */ + public $oldValue; + + /** + * @var mixed + * + * @JMS\Type("mixed") + * @JMS\SerializedName("newValue") + */ + public $newValue; + + /** + * @var \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocument + * + * @JMS\Type("RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocument") + * @JMS\SerializedName("document") + */ + public $document; + + /** + * @var \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocumentFile + * + * @JMS\Type("RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocumentFile") + * @JMS\SerializedName("file") + */ + public $file; +} diff --git a/src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentInput.php b/src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentInput.php new file mode 100644 index 000000000..97010d888 --- /dev/null +++ b/src/Model/Entity/KnowledgeBase/KnowledgeBaseDocumentInput.php @@ -0,0 +1,61 @@ +") + * @JMS\SerializedName("createdAt") + */ + public $createdAt; + + /** + * @var \DateTime + * + * @JMS\Type("DateTime<'Y-m-d H:i:s'>") + * @JMS\SerializedName("updatedAt") + */ + public $updatedAt; +} diff --git a/src/Model/Entity/KnowledgeBase/KnowledgeBaseSectionInput.php b/src/Model/Entity/KnowledgeBase/KnowledgeBaseSectionInput.php new file mode 100644 index 000000000..8594cefb2 --- /dev/null +++ b/src/Model/Entity/KnowledgeBase/KnowledgeBaseSectionInput.php @@ -0,0 +1,37 @@ +") + * @Form\SerializedName("ids") + */ + public $ids; + + /** + * @var string[] + * + * @Form\Type("array") + * @Form\SerializedName("externalIds") + */ + public $externalIds; + + /** + * @var int + * + * @Form\Type("int") + * @Form\SerializedName("sectionId") + */ + public $sectionId; + + /** + * @var bool + * + * @Form\Type("bool") + * @Form\SerializedName("directOnly") + */ + public $directOnly; + + /** + * @var bool + * + * @Form\Type("bool") + * @Form\SerializedName("availableForCopilot") + */ + public $availableForCopilot; + + /** + * @var bool + * + * @Form\Type("bool") + * @Form\SerializedName("hasFile") + */ + public $hasFile; + + /** + * @var string + * + * @Form\Type("string") + * @Form\SerializedName("search") + */ + public $search; +} diff --git a/src/Model/Filter/KnowledgeBase/KnowledgeBaseDocumentHistoryFilter.php b/src/Model/Filter/KnowledgeBase/KnowledgeBaseDocumentHistoryFilter.php new file mode 100644 index 000000000..8794a33c8 --- /dev/null +++ b/src/Model/Filter/KnowledgeBase/KnowledgeBaseDocumentHistoryFilter.php @@ -0,0 +1,53 @@ +") + * @Form\SerializedName("startDate") + */ + public $startDate; + + /** + * @var \DateTime + * + * @Form\Type("DateTime<'Y-m-d H:i:s'>") + * @Form\SerializedName("endDate") + */ + public $endDate; +} diff --git a/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentByRequest.php b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentByRequest.php new file mode 100644 index 000000000..e7bc49301 --- /dev/null +++ b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentByRequest.php @@ -0,0 +1,40 @@ +by = $by; + } + } +} diff --git a/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsCreateRequest.php b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsCreateRequest.php new file mode 100644 index 000000000..9f52ac85c --- /dev/null +++ b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsCreateRequest.php @@ -0,0 +1,31 @@ + + * + * @Form\Type("RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocumentInput") + * @Form\SerializedName("document") + * @Form\JsonField() + */ + public $document; +} diff --git a/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsHistoryRequest.php b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsHistoryRequest.php new file mode 100644 index 000000000..c43e63f29 --- /dev/null +++ b/src/Model/Request/KnowledgeBase/KnowledgeBaseDocumentsHistoryRequest.php @@ -0,0 +1,33 @@ +") + * @JMS\SerializedName("generatedAt") + */ + public $generatedAt; + + /** + * @var \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocumentHistory[] + * + * @JMS\Type("array") + * @JMS\SerializedName("history") + */ + public $history; +} diff --git a/src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php b/src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php new file mode 100644 index 000000000..fd0480308 --- /dev/null +++ b/src/Model/Response/KnowledgeBase/KnowledgeBaseDocumentsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("documents") + */ + public $documents; +} diff --git a/src/Model/Response/KnowledgeBase/KnowledgeBaseSectionsResponse.php b/src/Model/Response/KnowledgeBase/KnowledgeBaseSectionsResponse.php new file mode 100644 index 000000000..8aa34245f --- /dev/null +++ b/src/Model/Response/KnowledgeBase/KnowledgeBaseSectionsResponse.php @@ -0,0 +1,30 @@ +") + * @JMS\SerializedName("sections") + */ + public $sections; +} diff --git a/src/ResourceGroup/KnowledgeBase.php b/src/ResourceGroup/KnowledgeBase.php new file mode 100644 index 000000000..7783e323e --- /dev/null +++ b/src/ResourceGroup/KnowledgeBase.php @@ -0,0 +1,327 @@ +knowledgeBase->sections(); + * ``` + * + * + * @return KnowledgeBaseSectionsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function sections(): KnowledgeBaseSectionsResponse + { + /** @var KnowledgeBaseSectionsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'knowledge-base/sections', + null, + KnowledgeBaseSectionsResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/knowledge-base/sections/create" request. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseSectionsCreateRequest(); + * $request->section = new \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseSectionInput(); + * $request->section->name = 'Shipping'; + * $response = $client->knowledgeBase->sectionsCreate($request); + * ``` + * + * @param KnowledgeBaseSectionsCreateRequest $request + * + * @return IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function sectionsCreate(KnowledgeBaseSectionsCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'knowledge-base/sections/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/knowledge-base/sections/{id}/edit" request. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseSectionsEditRequest(); + * $request->section = new \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseSectionInput(); + * $request->section->name = 'Shipping'; + * $response = $client->knowledgeBase->sectionsEdit(123, $request); + * ``` + * + * @param int $id + * @param KnowledgeBaseSectionsEditRequest $request + * + * @return IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function sectionsEdit(int $id, KnowledgeBaseSectionsEditRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('knowledge-base/sections/%s/edit', rawurlencode((string) $id)), + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/knowledge-base/sections/{id}/delete" request. + * + * Deletes the section, all its subsections and their documents. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $response = $client->knowledgeBase->sectionsDelete(123); + * ``` + * + * @param int $id + * + * @return SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function sectionsDelete(int $id): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('knowledge-base/sections/%s/delete', rawurlencode((string) $id)), + null, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/knowledge-base/documents" request. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsRequest(); + * $response = $client->knowledgeBase->documents($request); + * ``` + * + * @param KnowledgeBaseDocumentsRequest|null $request + * + * @return KnowledgeBaseDocumentsResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function documents(?KnowledgeBaseDocumentsRequest $request = null): KnowledgeBaseDocumentsResponse + { + /** @var KnowledgeBaseDocumentsResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'knowledge-base/documents', + $request, + KnowledgeBaseDocumentsResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/knowledge-base/documents/{id}" request. + * + * Defaults to externalId; pass by=id to use the internal document ID. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentByRequest(); + * $request->by = 'id'; + * $response = $client->knowledgeBase->documentsGet(123, $request); + * ``` + * + * @param int|string $id + * @param KnowledgeBaseDocumentByRequest|null $request + * + * @return KnowledgeBaseDocumentResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function documentsGet($id, ?KnowledgeBaseDocumentByRequest $request = null): KnowledgeBaseDocumentResponse + { + /** @var KnowledgeBaseDocumentResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + sprintf('knowledge-base/documents/%s', rawurlencode((string) $id)), + $request, + KnowledgeBaseDocumentResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/knowledge-base/documents/create" request. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsCreateRequest(); + * $request->document = new \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocumentInput(); + * $request->document->title = 'Shipping'; + * $response = $client->knowledgeBase->documentsCreate($request); + * ``` + * + * @param KnowledgeBaseDocumentsCreateRequest $request + * + * @return IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function documentsCreate(KnowledgeBaseDocumentsCreateRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + 'knowledge-base/documents/create', + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/knowledge-base/documents/{id}/edit" request. + * + * Defaults to externalId; pass by=id to use the internal document ID. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsEditRequest(); + * $request->document = new \RetailCrm\Api\Model\Entity\KnowledgeBase\KnowledgeBaseDocumentInput(); + * $request->document->title = 'Shipping'; + * $request->by = 'id'; + * $response = $client->knowledgeBase->documentsEdit(123, $request); + * ``` + * + * @param int|string $id + * @param KnowledgeBaseDocumentsEditRequest $request + * + * @return IdResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function documentsEdit($id, KnowledgeBaseDocumentsEditRequest $request): IdResponse + { + /** @var IdResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('knowledge-base/documents/%s/edit', rawurlencode((string) $id)), + $request, + IdResponse::class + ); + return $response; + } + + /** + * Makes POST "/api/v5/knowledge-base/documents/{id}/delete" request. + * + * Defaults to externalId; pass by=id to use the internal document ID. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentByRequest(); + * $request->by = 'id'; + * $response = $client->knowledgeBase->documentsDelete(123, $request); + * ``` + * + * @param int|string $id + * @param KnowledgeBaseDocumentByRequest|null $request + * + * @return SuccessResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function documentsDelete($id, ?KnowledgeBaseDocumentByRequest $request = null): SuccessResponse + { + /** @var SuccessResponse $response */ + $response = $this->sendRequest( + RequestMethod::POST, + sprintf('knowledge-base/documents/%s/delete', rawurlencode((string) $id)), + $request, + SuccessResponse::class + ); + return $response; + } + + /** + * Makes GET "/api/v5/knowledge-base/documents/history" request. + * + * Example (initialize $client with SimpleClientFactory first): + * ```php + * $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsHistoryRequest(); + * $response = $client->knowledgeBase->documentsHistory($request); + * ``` + * + * @param KnowledgeBaseDocumentsHistoryRequest|null $request + * + * @return KnowledgeBaseDocumentsHistoryResponse + * @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface + * @throws \RetailCrm\Api\Interfaces\ClientExceptionInterface + */ + public function documentsHistory( + ?KnowledgeBaseDocumentsHistoryRequest $request = null + ): KnowledgeBaseDocumentsHistoryResponse { + /** @var KnowledgeBaseDocumentsHistoryResponse $response */ + $response = $this->sendRequest( + RequestMethod::GET, + 'knowledge-base/documents/history', + $request, + KnowledgeBaseDocumentsHistoryResponse::class + ); + return $response; + } +} diff --git a/tests/src/ResourceGroup/KnowledgeBaseTest.php b/tests/src/ResourceGroup/KnowledgeBaseTest.php new file mode 100644 index 000000000..aeac08814 --- /dev/null +++ b/tests/src/ResourceGroup/KnowledgeBaseTest.php @@ -0,0 +1,244 @@ +matchMethod('GET')->reply(200)->withBody($json); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertModelEqualsToResponse($json, $client->knowledgeBase->sections()); + } + + public function testDocuments(): void + { + $request = new KnowledgeBaseDocumentsRequest(); + $request->limit = 20; + $request->page = 1; + $request->filter = new KnowledgeBaseDocumentFilter(); + $request->filter->ids = [7, 8]; + $request->filter->externalIds = ['doc-7']; + $request->filter->sectionId = 1; + $request->filter->directOnly = true; + $request->filter->hasFile = false; + $request->filter->availableForCopilot = false; + $request->filter->search = 'Shipping'; + $json = '{"success":true,"pagination":{"limit":20,"currentPage":1,"totalCount":1,"totalPageCount":1},' + . '"documents":[{"id":7,"sectionId":1,"title":"Shipping","content":"# Shipping",' + . '"externalId":"doc-7","availableForCopilot":false,"contentHash":"abc",' + . '"createdAt":"2026-10-26 10:00:00","updatedAt":"2026-10-26 11:00:00"}]}'; + $mock = self::createApiMockBuilder('knowledge-base/documents'); + $mock->matchMethod('GET')->matchQuery([ + 'limit' => '20', 'page' => '1', + 'filter' => ['ids' => ['7', '8'], 'externalIds' => ['doc-7'], 'sectionId' => '1', + 'directOnly' => '1', 'availableForCopilot' => '0', 'hasFile' => '0', 'search' => 'Shipping'], + ])->reply(200)->withBody($json); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertModelEqualsToResponse($json, $client->knowledgeBase->documents($request)); + } + + public function testGetDocumentById(): void + { + $request = new KnowledgeBaseDocumentByRequest(); + $request->by = 'id'; + $json = '{"success":true,"document":{"id":7,"title":"Shipping","availableForCopilot":false,' + . '"file":{"id":9,"name":"manual.txt","type":"text/plain","size":12,"url":"https://example.com/file"}}}'; + $mock = self::createApiMockBuilder('knowledge-base/documents/7'); + $mock->matchMethod('GET')->matchQuery(['by' => 'id'])->reply(200)->withBody($json); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertModelEqualsToResponse($json, $client->knowledgeBase->documentsGet(7, $request)); + } + + public function testGetDocumentByExternalId(): void + { + $mock = self::createApiMockBuilder('knowledge-base/documents/doc-7'); + $mock->matchMethod('GET')->reply(200)->withBody('{"success":true,"document":{"id":7}}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertSame(7, $client->knowledgeBase->documentsGet('doc-7')->document->id); + } + + public function testHistory(): void + { + $request = new KnowledgeBaseDocumentsHistoryRequest(); + $request->limit = 20; + $request->filter = new KnowledgeBaseDocumentHistoryFilter(); + $request->filter->sinceId = 12; + $request->filter->documentId = 7; + $request->filter->startDate = new \DateTime('2026-10-26 10:00:00'); + $request->filter->endDate = new \DateTime('2026-10-26 11:00:00'); + $json = '{"success":true,"generatedAt":"2026-10-26 12:00:00",' + . '"pagination":{"limit":20,"currentPage":1,"totalCount":1,"totalPageCount":1},' + . '"history":[{"id":13,"createdAt":"2026-10-26 10:30:00","source":"api",' + . '"apiKey":{"current":true},"field":"section","oldValue":1,"newValue":{"id":2},' + . '"document":{"id":7,"externalId":"doc-7"}}]}'; + $mock = self::createApiMockBuilder('knowledge-base/documents/history'); + $mock->matchMethod('GET')->matchQuery(['limit' => '20', 'filter' => [ + 'documentId' => '7', 'sinceId' => '12', + 'startDate' => '2026-10-26 10:00:00', 'endDate' => '2026-10-26 11:00:00', + ]])->reply(200)->withBody($json); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertModelEqualsToResponse($json, $client->knowledgeBase->documentsHistory($request)); + } + + public function testAttachFile(): void + { + $request = new FilesEditRequest(); + $request->file = new File(); + $request->file->filename = 'manual.txt'; + $attachment = new Attachment(); + $attachment->knowledgeBaseDocument = new AttachmentKnowledgeBaseDocument(); + $attachment->knowledgeBaseDocument->id = 7; + $request->file->attachment = [$attachment]; + $mock = self::createApiMockBuilder('files/9/edit'); + $mock->matchMethod('POST')->matchJsonFormField('file', [ + 'filename' => 'manual.txt', 'attachment' => [['knowledgeBaseDocument' => ['id' => 7]]], + ])->reply(200)->withBody('{"success":true}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertTrue($client->files->edit(9, $request)->success); + } + + public function testSectionsCreate(): void + { + $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseSectionsCreateRequest(); + $request->section = new KnowledgeBaseSectionInput(); + $request->section->name = 'Shipping'; + $request->section->parentId = 0; + $mock = self::createApiMockBuilder('knowledge-base/sections/create'); + $mock->matchMethod('POST')->matchJsonFormField('section', [ + 'name' => 'Shipping', 'parentId' => 0, + ])->reply(201)->withBody('{"success":true,"id":7}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertSame(7, $client->knowledgeBase->sectionsCreate($request)->id); + } + + public function testSectionsEdit(): void + { + $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseSectionsEditRequest(); + $request->section = new KnowledgeBaseSectionInput(); + $request->section->parentId = 0; + $mock = self::createApiMockBuilder('knowledge-base/sections/7/edit'); + $mock->matchMethod('POST')->matchJsonFormField('section', [ + 'parentId' => 0, + ])->reply(200)->withBody('{"success":true,"id":7}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertSame(7, $client->knowledgeBase->sectionsEdit(7, $request)->id); + } + + public function testSectionsDelete(): void + { + $mock = self::createApiMockBuilder('knowledge-base/sections/7/delete'); + $mock->matchMethod('POST')->reply(200)->withBody('{"success":true}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertTrue($client->knowledgeBase->sectionsDelete(7)->success); + } + + public function testDocumentsCreate(): void + { + $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsCreateRequest(); + $request->document = new KnowledgeBaseDocumentInput(); + $request->document->title = 'Shipping'; + $request->document->content = ''; + $request->document->sectionId = 0; + $request->document->availableForCopilot = false; + $request->document->externalId = 'doc-7'; + $mock = self::createApiMockBuilder('knowledge-base/documents/create'); + $mock->matchMethod('POST')->matchJsonFormField('document', [ + 'title' => 'Shipping', 'content' => '', 'sectionId' => 0, 'availableForCopilot' => false, 'externalId' => 'doc-7', + ])->reply(201)->withBody('{"success":true,"id":7}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertSame(7, $client->knowledgeBase->documentsCreate($request)->id); + } + + public function testDocumentsEdit(): void + { + $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsEditRequest(); + $request->document = new KnowledgeBaseDocumentInput(); + $request->document->content = ''; + $request->document->sectionId = 0; + $request->document->availableForCopilot = false; + $request->by = 'id'; + $mock = self::createApiMockBuilder('knowledge-base/documents/7/edit'); + $mock->matchMethod('POST')->matchJsonFormField('document', [ + 'content' => '', 'sectionId' => 0, 'availableForCopilot' => false, + ])->matchFormData(['by' => 'id'])->reply(200)->withBody('{"success":true,"id":7}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertSame(7, $client->knowledgeBase->documentsEdit(7, $request)->id); + } + + public function testDocumentsDelete(): void + { + $request = new KnowledgeBaseDocumentByRequest(); + $request->by = 'id'; + $mock = self::createApiMockBuilder('knowledge-base/documents/7/delete'); + $mock->matchMethod('POST')->matchFormData(['by' => 'id'])->reply(200)->withBody('{"success":true}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertTrue($client->knowledgeBase->documentsDelete(7, $request)->success); + } + + public function testMissingDocument(): void + { + $mock = self::createApiMockBuilder('knowledge-base/documents/missing'); + $mock->matchMethod('GET')->reply(404)->withBody('{"success":false,"errorMsg":"Document not found"}'); + $client = TestClientFactory::createClient($mock->getClient()); + $this->expectException(\RetailCrm\Api\Interfaces\ApiExceptionInterface::class); + $client->knowledgeBase->documentsGet('missing'); + } + + public function testNullDocumentFields(): void + { + $mock = self::createApiMockBuilder('knowledge-base/documents/doc-7'); + $mock->matchMethod('GET')->reply(200)->withBody( + '{"success":true,"document":{"id":7,"sectionId":null,"content":null,"file":null,"externalId":null}}' + ); + $client = TestClientFactory::createClient($mock->getClient()); + $document = $client->knowledgeBase->documentsGet('doc-7')->document; + self::assertNull($document->sectionId); + self::assertNull($document->content); + self::assertNull($document->file); + self::assertNull($document->externalId); + } + + public function testClearExternalId(): void + { + $request = new \RetailCrm\Api\Model\Request\KnowledgeBase\KnowledgeBaseDocumentsEditRequest(); + $request->by = 'id'; + $request->document = ['externalId' => null]; + $mock = self::createApiMockBuilder('knowledge-base/documents/7/edit'); + $mock->matchMethod('POST')->matchJsonFormField('document', ['externalId' => null]) + ->matchFormData(['by' => 'id'])->reply(200)->withBody('{"success":true,"id":7}'); + $client = TestClientFactory::createClient($mock->getClient()); + self::assertSame(7, $client->knowledgeBase->documentsEdit(7, $request)->id); + } +}