diff --git a/README.md b/README.md
index 87c4e2a..293d689 100644
--- a/README.md
+++ b/README.md
@@ -24,8 +24,6 @@ composer require pobo-builder/php-sdk
```php
use Pobo\Sdk\PoboClient;
-use Pobo\Sdk\DTO\Product;
-use Pobo\Sdk\DTO\LocalizedString;
$client = new PoboClient(
apiToken: 'your-api-token',
@@ -34,32 +32,48 @@ $client = new PoboClient(
);
```
-### Import Parameters
+## Import
-Import parameters first (no dependencies).
+### Import Order
+
+```
+1. Parameters (no dependencies)
+2. Categories (no dependencies)
+3. Products (depends on categories and parameters)
+4. Blogs (no dependencies)
+```
+
+### Import Parameters
```php
use Pobo\Sdk\DTO\Parameter;
use Pobo\Sdk\DTO\ParameterValue;
-$parameter = new Parameter(
- id: 1,
- name: 'Color',
- values: [
- new ParameterValue(id: 1, value: 'Red'),
- new ParameterValue(id: 2, value: 'Blue'),
- new ParameterValue(id: 3, value: 'Green'),
- ],
-);
+$parameters = [
+ new Parameter(
+ id: 1,
+ name: 'Color',
+ values: [
+ new ParameterValue(id: 1, value: 'Red'),
+ new ParameterValue(id: 2, value: 'Blue'),
+ ],
+ ),
+ new Parameter(
+ id: 2,
+ name: 'Size',
+ values: [
+ new ParameterValue(id: 3, value: 'S'),
+ new ParameterValue(id: 4, value: 'M'),
+ ],
+ ),
+];
-$result = $client->importParameters([$parameter]);
-echo sprintf('Values imported: %d', $result->valuesImported);
+$result = $client->importParameters($parameters);
+echo sprintf('Imported: %d, Values: %d', $result->imported, $result->valuesImported);
```
### Import Categories
-Import categories second (no dependencies).
-
```php
use Pobo\Sdk\DTO\Category;
use Pobo\Sdk\DTO\LocalizedString;
@@ -75,7 +89,9 @@ $categories = [
url: LocalizedString::create('https://example.com/electronics')
->withTranslation(Language::CS, 'https://example.com/cs/elektronika')
->withTranslation(Language::SK, 'https://example.com/sk/elektronika'),
- description: LocalizedString::create('
All electronics
'),
+ description: LocalizedString::create('All electronics
')
+ ->withTranslation(Language::CS, 'Veškerá elektronika
')
+ ->withTranslation(Language::SK, 'Všetka elektronika
'),
images: ['https://example.com/images/electronics.jpg'],
),
new Category(
@@ -96,8 +112,6 @@ echo sprintf('Imported: %d, Updated: %d', $result->imported, $result->updated);
### Import Products
-Import products last (depends on categories and parameters IDs).
-
```php
use Pobo\Sdk\DTO\Product;
use Pobo\Sdk\DTO\LocalizedString;
@@ -116,10 +130,10 @@ $products = [
shortDescription: LocalizedString::create('Latest iPhone model')
->withTranslation(Language::CS, 'Nejnovější model iPhone')
->withTranslation(Language::SK, 'Najnovší model iPhone'),
- images: [
- 'https://example.com/images/iphone-1.jpg',
- 'https://example.com/images/iphone-2.jpg',
- ],
+ description: LocalizedString::create('The best iPhone ever.
')
+ ->withTranslation(Language::CS, 'Nejlepší iPhone vůbec.
')
+ ->withTranslation(Language::SK, 'Najlepší iPhone vôbec.
'),
+ images: ['https://example.com/images/iphone-1.jpg'],
categoriesIds: ['CAT-001', 'CAT-002'],
parametersIds: [1, 2],
),
@@ -132,13 +146,7 @@ $products = [
url: LocalizedString::create('https://example.com/samsung-s24')
->withTranslation(Language::CS, 'https://example.com/cs/samsung-s24')
->withTranslation(Language::SK, 'https://example.com/sk/samsung-s24'),
- shortDescription: LocalizedString::create('Flagship Android phone')
- ->withTranslation(Language::CS, 'Vlajková loď Android')
- ->withTranslation(Language::SK, 'Vlajková loď Android'),
- images: [
- 'https://example.com/images/samsung-1.jpg',
- ],
- categoriesIds: ['CAT-001', 'CAT-002'],
+ categoriesIds: ['CAT-001'],
parametersIds: [1, 3],
),
];
@@ -147,17 +155,56 @@ $result = $client->importProducts($products);
if ($result->hasErrors() === true) {
foreach ($result->errors as $error) {
- echo sprintf('Error at index %d: %s', $error['index'], implode(', ', $error['errors']));
+ echo sprintf('Error: %s', implode(', ', $error['errors']));
}
}
+```
+
+### Import Blogs
+
+```php
+use Pobo\Sdk\DTO\Blog;
+use Pobo\Sdk\DTO\LocalizedString;
+use Pobo\Sdk\Enum\Language;
+$blogs = [
+ new Blog(
+ guid: '550e8400-e29b-41d4-a716-446655440000',
+ category: 'news',
+ isVisible: true,
+ name: LocalizedString::create('New Product Launch')
+ ->withTranslation(Language::CS, 'Uvedení nového produktu')
+ ->withTranslation(Language::SK, 'Uvedenie nového produktu'),
+ url: LocalizedString::create('https://example.com/blog/new-product')
+ ->withTranslation(Language::CS, 'https://example.com/cs/blog/novy-produkt')
+ ->withTranslation(Language::SK, 'https://example.com/sk/blog/novy-produkt'),
+ description: LocalizedString::create('We are excited to announce...
')
+ ->withTranslation(Language::CS, 'S radostí oznamujeme...
')
+ ->withTranslation(Language::SK, 'S radosťou oznamujeme...
'),
+ images: ['https://example.com/images/blog-1.jpg'],
+ ),
+ new Blog(
+ guid: '550e8400-e29b-41d4-a716-446655440001',
+ category: 'tips',
+ isVisible: true,
+ name: LocalizedString::create('How to Choose')
+ ->withTranslation(Language::CS, 'Jak vybrat')
+ ->withTranslation(Language::SK, 'Ako vybrať'),
+ url: LocalizedString::create('https://example.com/blog/how-to-choose')
+ ->withTranslation(Language::CS, 'https://example.com/cs/blog/jak-vybrat')
+ ->withTranslation(Language::SK, 'https://example.com/sk/blog/ako-vybrat'),
+ ),
+];
+
+$result = $client->importBlogs($blogs);
echo sprintf('Imported: %d, Updated: %d', $result->imported, $result->updated);
```
+## Export
+
### Export Products
```php
-// Get single page
$response = $client->getProducts(page: 1, perPage: 50);
foreach ($response->data as $product) {
@@ -182,7 +229,6 @@ $response = $client->getProducts(isEdited: true);
### Export Categories
```php
-// Get all categories
$response = $client->getCategories();
foreach ($response->data as $category) {
@@ -191,7 +237,61 @@ foreach ($response->data as $category) {
// Iterate through all categories
foreach ($client->iterateCategories() as $category) {
- // Process category
+ processCategory($category);
+}
+```
+
+### Export Blogs
+
+```php
+$response = $client->getBlogs();
+
+foreach ($response->data as $blog) {
+ echo sprintf("%d: %s\n", $blog->id, $blog->name->getDefault());
+}
+
+// Iterate through all blogs
+foreach ($client->iterateBlogs() as $blog) {
+ processBlog($blog);
+}
+```
+
+## Content (HTML/Marketplace)
+
+Products, categories, and blogs include a `content` field with generated HTML content for web and marketplace:
+
+```php
+use Pobo\Sdk\Enum\Language;
+
+foreach ($client->iterateProducts() as $product) {
+ if ($product->content !== null) {
+ // Get HTML content for web
+ $htmlCs = $product->content->getHtml(Language::CS);
+ $htmlSk = $product->content->getHtml(Language::SK);
+ $htmlEn = $product->content->getHtml(Language::EN);
+
+ // Get content for marketplace
+ $marketplaceCs = $product->content->getMarketplace(Language::CS);
+ $marketplaceSk = $product->content->getMarketplace(Language::SK);
+
+ // Get default content
+ $htmlDefault = $product->content->getHtmlDefault();
+ $marketplaceDefault = $product->content->getMarketplaceDefault();
+ }
+}
+
+// Same for categories
+foreach ($client->iterateCategories() as $category) {
+ if ($category->content !== null) {
+ echo $category->content->getHtml(Language::CS);
+ }
+}
+
+// Same for blogs
+foreach ($client->iterateBlogs() as $blog) {
+ if ($blog->content !== null) {
+ echo $blog->content->getHtml(Language::CS);
+ }
}
```
@@ -207,15 +307,8 @@ use Pobo\Sdk\Exception\WebhookException;
$handler = new WebhookHandler(webhookSecret: 'your-webhook-secret');
try {
- // Handle from global PHP variables
$payload = $handler->handleFromGlobals();
- // Or handle manually
- $payload = $handler->handle(
- payload: file_get_contents('php://input'),
- signature: $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? ''
- );
-
match ($payload->event) {
WebhookEvent::PRODUCTS_UPDATE => syncProducts($client),
WebhookEvent::CATEGORIES_UPDATE => syncCategories($client),
@@ -230,6 +323,15 @@ try {
}
```
+### Manual Handling
+
+```php
+$payload = $handler->handle(
+ payload: file_get_contents('php://input'),
+ signature: $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? ''
+);
+```
+
### Webhook Payload
```php
@@ -248,11 +350,9 @@ use Pobo\Sdk\Exception\WebhookException;
try {
$result = $client->importProducts($products);
} catch (ValidationException $e) {
- // Local validation error (e.g., too many items)
echo sprintf('Validation error: %s', $e->getMessage());
print_r($e->errors);
} catch (ApiException $e) {
- // API error (4xx, 5xx)
echo sprintf('API error (%d): %s', $e->httpCode, $e->getMessage());
print_r($e->responseBody);
}
@@ -260,8 +360,6 @@ try {
## Localized Strings
-The SDK uses `LocalizedString` for multi-language support:
-
```php
use Pobo\Sdk\DTO\LocalizedString;
use Pobo\Sdk\Enum\Language;
@@ -293,6 +391,21 @@ $name->toArray(); // ['default' => '...', 'cs' => '...', ...]
| `pl` | Polish |
| `hu` | Hungarian |
+## API Methods
+
+| Method | Description |
+|----------------------------------------------------------------------------------------|----------------------------------|
+| `importProducts(array $products)` | Bulk import products (max 100) |
+| `importCategories(array $categories)` | Bulk import categories (max 100) |
+| `importParameters(array $parameters)` | Bulk import parameters (max 100) |
+| `importBlogs(array $blogs)` | Bulk import blogs (max 100) |
+| `getProducts(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get products page |
+| `getCategories(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get categories page |
+| `getBlogs(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get blogs page |
+| `iterateProducts(?DateTime $lastUpdateFrom, ?bool $isEdited)` | Iterate all products |
+| `iterateCategories(?DateTime $lastUpdateFrom, ?bool $isEdited)` | Iterate all categories |
+| `iterateBlogs(?DateTime $lastUpdateFrom, ?bool $isEdited)` | Iterate all blogs |
+
## Limits
| Limit | Value |
diff --git a/src/DTO/Blog.php b/src/DTO/Blog.php
new file mode 100644
index 0000000..b82251e
--- /dev/null
+++ b/src/DTO/Blog.php
@@ -0,0 +1,96 @@
+ $images
+ */
+ public function __construct(
+ public readonly ?int $id = null,
+ public readonly ?string $guid = null,
+ public readonly ?string $category = null,
+ public readonly bool $isVisible = true,
+ public readonly ?LocalizedString $name = null,
+ public readonly ?LocalizedString $url = null,
+ public readonly ?LocalizedString $description = null,
+ public readonly ?LocalizedString $seoTitle = null,
+ public readonly ?LocalizedString $seoDescription = null,
+ public readonly ?Content $content = null,
+ public readonly array $images = [],
+ public readonly ?bool $isLoaded = null,
+ public readonly ?\DateTimeInterface $createdAt = null,
+ public readonly ?\DateTimeInterface $updatedAt = null,
+ ) {
+ }
+
+ /**
+ * @return array
+ */
+ public function toArray(): array
+ {
+ $data = [
+ 'is_visible' => $this->isVisible,
+ ];
+
+ if ($this->guid !== null) {
+ $data['guid'] = $this->guid;
+ }
+
+ if ($this->category !== null) {
+ $data['category'] = $this->category;
+ }
+
+ if ($this->name !== null) {
+ $data['name'] = $this->name->toArray();
+ }
+
+ if ($this->url !== null) {
+ $data['url'] = $this->url->toArray();
+ }
+
+ if ($this->description !== null) {
+ $data['description'] = $this->description->toArray();
+ }
+
+ if ($this->seoTitle !== null) {
+ $data['seo_title'] = $this->seoTitle->toArray();
+ }
+
+ if ($this->seoDescription !== null) {
+ $data['seo_description'] = $this->seoDescription->toArray();
+ }
+
+ if ($this->images !== []) {
+ $data['images'] = $this->images;
+ }
+
+ return $data;
+ }
+
+ /**
+ * @param array $data
+ */
+ public static function fromArray(array $data): self
+ {
+ return new self(
+ id: $data['id'] ?? null,
+ guid: $data['guid'] ?? null,
+ category: $data['category'] ?? null,
+ isVisible: $data['is_visible'] ?? true,
+ name: isset($data['name']) ? LocalizedString::fromArray($data['name']) : null,
+ url: isset($data['url']) ? LocalizedString::fromArray($data['url']) : null,
+ description: isset($data['description']) ? LocalizedString::fromArray($data['description']) : null,
+ seoTitle: isset($data['seo_title']) ? LocalizedString::fromArray($data['seo_title']) : null,
+ seoDescription: isset($data['seo_description']) ? LocalizedString::fromArray($data['seo_description']) : null,
+ content: isset($data['content']) ? Content::fromArray($data['content']) : null,
+ images: $data['images'] ?? [],
+ isLoaded: $data['is_loaded'] ?? null,
+ createdAt: isset($data['created_at']) ? new \DateTimeImmutable($data['created_at']) : null,
+ updatedAt: isset($data['updated_at']) ? new \DateTimeImmutable($data['updated_at']) : null,
+ );
+ }
+}
diff --git a/src/DTO/Category.php b/src/DTO/Category.php
index a1a0e57..07b3395 100644
--- a/src/DTO/Category.php
+++ b/src/DTO/Category.php
@@ -17,8 +17,10 @@ public function __construct(
public readonly ?LocalizedString $description = null,
public readonly ?LocalizedString $seoTitle = null,
public readonly ?LocalizedString $seoDescription = null,
+ public readonly ?Content $content = null,
public readonly array $images = [],
public readonly ?string $guid = null,
+ public readonly ?bool $isLoaded = null,
public readonly ?\DateTimeInterface $createdAt = null,
public readonly ?\DateTimeInterface $updatedAt = null,
) {
@@ -68,8 +70,10 @@ public static function fromArray(array $data): self
description: isset($data['description']) ? LocalizedString::fromArray($data['description']) : null,
seoTitle: isset($data['seo_title']) ? LocalizedString::fromArray($data['seo_title']) : null,
seoDescription: isset($data['seo_description']) ? LocalizedString::fromArray($data['seo_description']) : null,
+ content: isset($data['content']) ? Content::fromArray($data['content']) : null,
images: $data['images'] ?? [],
guid: $data['guid'] ?? null,
+ isLoaded: $data['is_loaded'] ?? null,
createdAt: isset($data['created_at']) ? new \DateTimeImmutable($data['created_at']) : null,
updatedAt: isset($data['updated_at']) ? new \DateTimeImmutable($data['updated_at']) : null,
);
diff --git a/src/DTO/Content.php b/src/DTO/Content.php
new file mode 100644
index 0000000..bfec4d5
--- /dev/null
+++ b/src/DTO/Content.php
@@ -0,0 +1,62 @@
+ $html
+ * @param array $marketplace
+ */
+ public function __construct(
+ public readonly array $html = [],
+ public readonly array $marketplace = [],
+ ) {
+ }
+
+ public function getHtml(Language $language): ?string
+ {
+ return $this->html[$language->value] ?? null;
+ }
+
+ public function getMarketplace(Language $language): ?string
+ {
+ return $this->marketplace[$language->value] ?? null;
+ }
+
+ public function getHtmlDefault(): ?string
+ {
+ return $this->html['default'] ?? $this->html['cs'] ?? null;
+ }
+
+ public function getMarketplaceDefault(): ?string
+ {
+ return $this->marketplace['default'] ?? $this->marketplace['cs'] ?? null;
+ }
+
+ /**
+ * @return array>
+ */
+ public function toArray(): array
+ {
+ return [
+ 'html' => $this->html,
+ 'marketplace' => $this->marketplace,
+ ];
+ }
+
+ /**
+ * @param array $data
+ */
+ public static function fromArray(array $data): self
+ {
+ return new self(
+ html: $data['html'] ?? [],
+ marketplace: $data['marketplace'] ?? [],
+ );
+ }
+}
diff --git a/src/DTO/Product.php b/src/DTO/Product.php
index 3060aa7..c3f3da3 100644
--- a/src/DTO/Product.php
+++ b/src/DTO/Product.php
@@ -21,6 +21,7 @@ public function __construct(
public readonly ?LocalizedString $description = null,
public readonly ?LocalizedString $seoTitle = null,
public readonly ?LocalizedString $seoDescription = null,
+ public readonly ?Content $content = null,
public readonly array $images = [],
public readonly array $categoriesIds = [],
public readonly array $parametersIds = [],
@@ -89,6 +90,7 @@ public static function fromArray(array $data): self
description: isset($data['description']) ? LocalizedString::fromArray($data['description']) : null,
seoTitle: isset($data['seo_title']) ? LocalizedString::fromArray($data['seo_title']) : null,
seoDescription: isset($data['seo_description']) ? LocalizedString::fromArray($data['seo_description']) : null,
+ content: isset($data['content']) ? Content::fromArray($data['content']) : null,
images: $data['images'] ?? [],
categoriesIds: $data['categories_ids'] ?? [],
parametersIds: $data['parameters_ids'] ?? [],
diff --git a/src/PoboClient.php b/src/PoboClient.php
index d206d7f..0e0819f 100644
--- a/src/PoboClient.php
+++ b/src/PoboClient.php
@@ -4,6 +4,7 @@
namespace Pobo\Sdk;
+use Pobo\Sdk\DTO\Blog;
use Pobo\Sdk\DTO\Category;
use Pobo\Sdk\DTO\ImportResult;
use Pobo\Sdk\DTO\PaginatedResponse;
@@ -26,8 +27,6 @@ public function __construct(
}
/**
- * Import products in bulk (max 100 items per request)
- *
* @param array> $products
* @throws ValidationException
* @throws ApiException
@@ -46,8 +45,6 @@ public function importProducts(array $products): ImportResult
}
/**
- * Import categories in bulk (max 100 items per request)
- *
* @param array> $categories
* @throws ValidationException
* @throws ApiException
@@ -66,8 +63,6 @@ public function importCategories(array $categories): ImportResult
}
/**
- * Import parameters in bulk (max 100 items per request)
- *
* @param array> $parameters
* @throws ValidationException
* @throws ApiException
@@ -86,8 +81,24 @@ public function importParameters(array $parameters): ImportResult
}
/**
- * Get products with pagination
- *
+ * @param array> $blogs
+ * @throws ValidationException
+ * @throws ApiException
+ */
+ public function importBlogs(array $blogs): ImportResult
+ {
+ $this->validateBulkSize($blogs);
+
+ $payload = array_map(
+ fn($blog) => $blog instanceof Blog ? $blog->toArray() : $blog,
+ $blogs
+ );
+
+ $response = $this->request('POST', '/api/v2/rest/blogs', $payload);
+ return ImportResult::fromArray($response);
+ }
+
+ /**
* @throws ApiException
*/
public function getProducts(
@@ -102,8 +113,6 @@ public function getProducts(
}
/**
- * Get categories with pagination
- *
* @throws ApiException
*/
public function getCategories(
@@ -118,8 +127,20 @@ public function getCategories(
}
/**
- * Iterate through all products (handles pagination automatically)
- *
+ * @throws ApiException
+ */
+ public function getBlogs(
+ ?int $page = null,
+ ?int $perPage = null,
+ ?\DateTimeInterface $lastUpdateFrom = null,
+ ?bool $isEdited = null,
+ ): PaginatedResponse {
+ $query = $this->buildQueryParams($page, $perPage, $lastUpdateFrom, $isEdited);
+ $response = $this->request('GET', '/api/v2/rest/blogs' . $query);
+ return PaginatedResponse::fromArray($response, Blog::class);
+ }
+
+ /**
* @return \Generator
* @throws ApiException
*/
@@ -141,8 +162,6 @@ public function iterateProducts(
}
/**
- * Iterate through all categories (handles pagination automatically)
- *
* @return \Generator
* @throws ApiException
*/
@@ -163,6 +182,27 @@ public function iterateCategories(
} while ($response->hasMorePages());
}
+ /**
+ * @return \Generator
+ * @throws ApiException
+ */
+ public function iterateBlogs(
+ ?\DateTimeInterface $lastUpdateFrom = null,
+ ?bool $isEdited = null,
+ ): \Generator {
+ $page = 1;
+
+ do {
+ $response = $this->getBlogs($page, self::MAX_BULK_ITEMS, $lastUpdateFrom, $isEdited);
+
+ foreach ($response->data as $blog) {
+ yield $blog;
+ }
+
+ $page++;
+ } while ($response->hasMorePages());
+ }
+
/**
* @throws ValidationException
*/
diff --git a/tests/BlogTest.php b/tests/BlogTest.php
new file mode 100644
index 0000000..195abec
--- /dev/null
+++ b/tests/BlogTest.php
@@ -0,0 +1,188 @@
+withTranslation(Language::CS, 'Název blogu'),
+ url: LocalizedString::create('https://example.com/blog')
+ ->withTranslation(Language::CS, 'https://example.com/cs/blog'),
+ description: LocalizedString::create('Description
'),
+ seoTitle: LocalizedString::create('SEO Title'),
+ seoDescription: LocalizedString::create('SEO Description'),
+ images: ['https://example.com/image.jpg'],
+ );
+
+ $this->assertSame(123, $blog->id);
+ $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $blog->guid);
+ $this->assertSame('news', $blog->category);
+ $this->assertTrue($blog->isVisible);
+ $this->assertSame('Blog Title', $blog->name->getDefault());
+ $this->assertSame('Název blogu', $blog->name->get(Language::CS));
+ }
+
+ public function testBlogToArray(): void
+ {
+ $blog = new Blog(
+ guid: '550e8400-e29b-41d4-a716-446655440000',
+ category: 'tips',
+ isVisible: true,
+ name: LocalizedString::create('Blog Title'),
+ url: LocalizedString::create('https://example.com/blog'),
+ description: LocalizedString::create('Description
'),
+ images: ['https://example.com/image.jpg'],
+ );
+
+ $array = $blog->toArray();
+
+ $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $array['guid']);
+ $this->assertSame('tips', $array['category']);
+ $this->assertTrue($array['is_visible']);
+ $this->assertArrayHasKey('name', $array);
+ $this->assertArrayHasKey('url', $array);
+ $this->assertArrayHasKey('description', $array);
+ $this->assertArrayHasKey('images', $array);
+ }
+
+ public function testBlogToArrayExcludesNullFields(): void
+ {
+ $blog = new Blog(
+ isVisible: true,
+ name: LocalizedString::create('Blog Title'),
+ url: LocalizedString::create('https://example.com/blog'),
+ );
+
+ $array = $blog->toArray();
+
+ $this->assertArrayNotHasKey('guid', $array);
+ $this->assertArrayNotHasKey('category', $array);
+ $this->assertArrayNotHasKey('description', $array);
+ $this->assertArrayNotHasKey('seo_title', $array);
+ $this->assertArrayNotHasKey('seo_description', $array);
+ $this->assertArrayNotHasKey('images', $array);
+ }
+
+ public function testBlogFromArray(): void
+ {
+ $data = [
+ 'id' => 456,
+ 'guid' => '550e8400-e29b-41d4-a716-446655440001',
+ 'category' => 'news',
+ 'is_visible' => true,
+ 'name' => ['default' => 'Blog Title', 'cs' => 'Název blogu'],
+ 'url' => ['default' => 'https://example.com/blog'],
+ 'description' => ['default' => 'Description
'],
+ 'seo_title' => ['default' => 'SEO Title'],
+ 'seo_description' => ['default' => 'SEO Description'],
+ 'images' => ['https://example.com/image.jpg'],
+ 'is_loaded' => false,
+ 'created_at' => '2024-01-15T10:30:00.000000Z',
+ 'updated_at' => '2024-01-16T14:20:00.000000Z',
+ ];
+
+ $blog = Blog::fromArray($data);
+
+ $this->assertSame(456, $blog->id);
+ $this->assertSame('550e8400-e29b-41d4-a716-446655440001', $blog->guid);
+ $this->assertSame('news', $blog->category);
+ $this->assertTrue($blog->isVisible);
+ $this->assertSame('Blog Title', $blog->name->getDefault());
+ $this->assertSame('Název blogu', $blog->name->get(Language::CS));
+ $this->assertFalse($blog->isLoaded);
+ $this->assertInstanceOf(\DateTimeInterface::class, $blog->createdAt);
+ $this->assertInstanceOf(\DateTimeInterface::class, $blog->updatedAt);
+ }
+
+ public function testBlogFromArrayWithContent(): void
+ {
+ $data = [
+ 'id' => 789,
+ 'is_visible' => true,
+ 'name' => ['default' => 'Blog'],
+ 'url' => ['default' => 'https://example.com'],
+ 'content' => [
+ 'html' => [
+ 'cs' => 'Czech HTML
',
+ 'sk' => 'Slovak HTML
',
+ ],
+ 'marketplace' => [
+ 'cs' => 'Czech Marketplace
',
+ ],
+ ],
+ ];
+
+ $blog = Blog::fromArray($data);
+
+ $this->assertInstanceOf(Content::class, $blog->content);
+ $this->assertSame('Czech HTML
', $blog->content->getHtml(Language::CS));
+ $this->assertSame('Slovak HTML
', $blog->content->getHtml(Language::SK));
+ $this->assertSame('Czech Marketplace
', $blog->content->getMarketplace(Language::CS));
+ }
+
+ public function testBlogFromArrayWithMinimalData(): void
+ {
+ $data = [
+ 'id' => 1,
+ 'is_visible' => false,
+ 'name' => ['default' => 'Minimal Blog'],
+ 'url' => ['default' => 'https://example.com/minimal'],
+ ];
+
+ $blog = Blog::fromArray($data);
+
+ $this->assertSame(1, $blog->id);
+ $this->assertFalse($blog->isVisible);
+ $this->assertNull($blog->guid);
+ $this->assertNull($blog->category);
+ $this->assertNull($blog->description);
+ $this->assertNull($blog->seoTitle);
+ $this->assertNull($blog->seoDescription);
+ $this->assertNull($blog->content);
+ $this->assertSame([], $blog->images);
+ }
+
+ public function testBlogDefaultIsVisible(): void
+ {
+ $blog = new Blog();
+
+ $this->assertTrue($blog->isVisible);
+ }
+
+ public function testBlogImagesArray(): void
+ {
+ $images = [
+ 'https://example.com/image1.jpg',
+ 'https://example.com/image2.jpg',
+ 'https://example.com/image3.jpg',
+ ];
+
+ $blog = new Blog(
+ isVisible: true,
+ name: LocalizedString::create('Blog'),
+ url: LocalizedString::create('https://example.com'),
+ images: $images,
+ );
+
+ $this->assertCount(3, $blog->images);
+ $this->assertSame($images, $blog->images);
+
+ $array = $blog->toArray();
+ $this->assertSame($images, $array['images']);
+ }
+}
diff --git a/tests/CategoryTest.php b/tests/CategoryTest.php
new file mode 100644
index 0000000..fd86feb
--- /dev/null
+++ b/tests/CategoryTest.php
@@ -0,0 +1,168 @@
+ 'CAT-001',
+ 'is_visible' => true,
+ 'name' => ['default' => 'Category Name', 'cs' => 'Název kategorie'],
+ 'url' => ['default' => 'https://example.com/category'],
+ 'description' => ['default' => 'Category description
'],
+ 'content' => [
+ 'html' => [
+ 'cs' => 'Czech HTML
',
+ 'sk' => 'Slovak HTML
',
+ ],
+ 'marketplace' => [
+ 'cs' => 'Czech Marketplace
',
+ ],
+ ],
+ 'images' => ['https://example.com/image.jpg'],
+ 'guid' => '550e8400-e29b-41d4-a716-446655440000',
+ 'is_loaded' => false,
+ 'created_at' => '2024-01-15T10:30:00.000000Z',
+ 'updated_at' => '2024-01-16T14:20:00.000000Z',
+ ];
+
+ $category = Category::fromArray($data);
+
+ $this->assertSame('CAT-001', $category->id);
+ $this->assertTrue($category->isVisible);
+ $this->assertSame('Category Name', $category->name->getDefault());
+ $this->assertSame('Název kategorie', $category->name->get(Language::CS));
+
+ $this->assertInstanceOf(Content::class, $category->content);
+ $this->assertSame('Czech HTML
', $category->content->getHtml(Language::CS));
+ $this->assertSame('Slovak HTML
', $category->content->getHtml(Language::SK));
+ $this->assertSame('Czech Marketplace
', $category->content->getMarketplace(Language::CS));
+
+ $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $category->guid);
+ $this->assertFalse($category->isLoaded);
+ }
+
+ public function testCategoryFromArrayWithoutContent(): void
+ {
+ $data = [
+ 'id' => 'CAT-002',
+ 'is_visible' => true,
+ 'name' => ['default' => 'Category'],
+ 'url' => ['default' => 'https://example.com'],
+ ];
+
+ $category = Category::fromArray($data);
+
+ $this->assertNull($category->content);
+ }
+
+ public function testCategoryToArrayDoesNotIncludeContent(): void
+ {
+ $category = new Category(
+ id: 'CAT-001',
+ isVisible: true,
+ name: LocalizedString::create('Category'),
+ url: LocalizedString::create('https://example.com'),
+ content: new Content(
+ html: ['cs' => 'Test
'],
+ marketplace: [],
+ ),
+ );
+
+ $array = $category->toArray();
+
+ $this->assertArrayNotHasKey('content', $array);
+ }
+
+ public function testCategoryWithAllLocalizedFields(): void
+ {
+ $category = new Category(
+ id: 'CAT-001',
+ isVisible: true,
+ name: LocalizedString::create('Category')
+ ->withTranslation(Language::CS, 'Kategorie')
+ ->withTranslation(Language::SK, 'Kategória'),
+ url: LocalizedString::create('https://example.com/category')
+ ->withTranslation(Language::CS, 'https://example.com/cs/kategorie')
+ ->withTranslation(Language::SK, 'https://example.com/sk/kategoria'),
+ description: LocalizedString::create('Description
')
+ ->withTranslation(Language::CS, 'Popis
'),
+ seoTitle: LocalizedString::create('SEO Title')
+ ->withTranslation(Language::CS, 'SEO Titulek'),
+ seoDescription: LocalizedString::create('SEO Description')
+ ->withTranslation(Language::CS, 'SEO Popis'),
+ );
+
+ $array = $category->toArray();
+
+ $this->assertArrayHasKey('description', $array);
+ $this->assertArrayHasKey('seo_title', $array);
+ $this->assertArrayHasKey('seo_description', $array);
+ }
+
+ public function testCategoryWithImages(): void
+ {
+ $images = [
+ 'https://example.com/image1.jpg',
+ 'https://example.com/image2.jpg',
+ ];
+
+ $category = new Category(
+ id: 'CAT-001',
+ isVisible: true,
+ name: LocalizedString::create('Category'),
+ url: LocalizedString::create('https://example.com'),
+ images: $images,
+ );
+
+ $array = $category->toArray();
+
+ $this->assertSame($images, $array['images']);
+ }
+
+ public function testCategoryToArrayExcludesNullFields(): void
+ {
+ $category = new Category(
+ id: 'CAT-001',
+ isVisible: true,
+ name: LocalizedString::create('Category'),
+ url: LocalizedString::create('https://example.com'),
+ );
+
+ $array = $category->toArray();
+
+ $this->assertArrayNotHasKey('description', $array);
+ $this->assertArrayNotHasKey('seo_title', $array);
+ $this->assertArrayNotHasKey('seo_description', $array);
+ $this->assertArrayNotHasKey('images', $array);
+ }
+
+ public function testCategoryTimestamps(): void
+ {
+ $data = [
+ 'id' => 'CAT-001',
+ 'is_visible' => true,
+ 'name' => ['default' => 'Category'],
+ 'url' => ['default' => 'https://example.com'],
+ 'created_at' => '2024-01-15T10:30:00.000000Z',
+ 'updated_at' => '2024-01-16T14:20:00.000000Z',
+ ];
+
+ $category = Category::fromArray($data);
+
+ $this->assertInstanceOf(\DateTimeInterface::class, $category->createdAt);
+ $this->assertInstanceOf(\DateTimeInterface::class, $category->updatedAt);
+ $this->assertSame('2024-01-15', $category->createdAt->format('Y-m-d'));
+ $this->assertSame('2024-01-16', $category->updatedAt->format('Y-m-d'));
+ }
+}
diff --git a/tests/ContentTest.php b/tests/ContentTest.php
new file mode 100644
index 0000000..f1ddb23
--- /dev/null
+++ b/tests/ContentTest.php
@@ -0,0 +1,144 @@
+ [
+ 'cs' => 'Czech HTML
',
+ 'sk' => 'Slovak HTML
',
+ 'en' => 'English HTML
',
+ ],
+ 'marketplace' => [
+ 'cs' => 'Czech Marketplace
',
+ 'sk' => 'Slovak Marketplace
',
+ ],
+ ];
+
+ $content = Content::fromArray($data);
+
+ $this->assertSame('Czech HTML
', $content->getHtml(Language::CS));
+ $this->assertSame('Slovak HTML
', $content->getHtml(Language::SK));
+ $this->assertSame('English HTML
', $content->getHtml(Language::EN));
+ $this->assertSame('Czech Marketplace
', $content->getMarketplace(Language::CS));
+ $this->assertSame('Slovak Marketplace
', $content->getMarketplace(Language::SK));
+ }
+
+ public function testGetHtmlReturnsNullForMissingLanguage(): void
+ {
+ $content = Content::fromArray([
+ 'html' => ['cs' => 'Czech
'],
+ 'marketplace' => [],
+ ]);
+
+ $this->assertNull($content->getHtml(Language::DE));
+ $this->assertNull($content->getHtml(Language::PL));
+ }
+
+ public function testGetMarketplaceReturnsNullForMissingLanguage(): void
+ {
+ $content = Content::fromArray([
+ 'html' => [],
+ 'marketplace' => ['cs' => 'Czech
'],
+ ]);
+
+ $this->assertNull($content->getMarketplace(Language::EN));
+ $this->assertNull($content->getMarketplace(Language::HU));
+ }
+
+ public function testGetHtmlDefault(): void
+ {
+ $content = Content::fromArray([
+ 'html' => [
+ 'default' => 'Default HTML
',
+ 'cs' => 'Czech HTML
',
+ ],
+ 'marketplace' => [],
+ ]);
+
+ $this->assertSame('Default HTML
', $content->getHtmlDefault());
+ }
+
+ public function testGetHtmlDefaultFallsBackToCs(): void
+ {
+ $content = Content::fromArray([
+ 'html' => [
+ 'cs' => 'Czech HTML
',
+ ],
+ 'marketplace' => [],
+ ]);
+
+ $this->assertSame('Czech HTML
', $content->getHtmlDefault());
+ }
+
+ public function testGetMarketplaceDefault(): void
+ {
+ $content = Content::fromArray([
+ 'html' => [],
+ 'marketplace' => [
+ 'default' => 'Default Marketplace
',
+ 'cs' => 'Czech Marketplace
',
+ ],
+ ]);
+
+ $this->assertSame('Default Marketplace
', $content->getMarketplaceDefault());
+ }
+
+ public function testGetMarketplaceDefaultFallsBackToCs(): void
+ {
+ $content = Content::fromArray([
+ 'html' => [],
+ 'marketplace' => [
+ 'cs' => 'Czech Marketplace
',
+ ],
+ ]);
+
+ $this->assertSame('Czech Marketplace
', $content->getMarketplaceDefault());
+ }
+
+ public function testToArray(): void
+ {
+ $content = new Content(
+ html: ['cs' => 'Czech
', 'en' => 'English
'],
+ marketplace: ['cs' => 'Czech MP
'],
+ );
+
+ $array = $content->toArray();
+
+ $this->assertArrayHasKey('html', $array);
+ $this->assertArrayHasKey('marketplace', $array);
+ $this->assertSame('Czech
', $array['html']['cs']);
+ $this->assertSame('English
', $array['html']['en']);
+ $this->assertSame('Czech MP
', $array['marketplace']['cs']);
+ }
+
+ public function testEmptyContent(): void
+ {
+ $content = Content::fromArray([]);
+
+ $this->assertSame([], $content->html);
+ $this->assertSame([], $content->marketplace);
+ $this->assertNull($content->getHtmlDefault());
+ $this->assertNull($content->getMarketplaceDefault());
+ }
+
+ public function testContentIsReadonly(): void
+ {
+ $content = new Content(
+ html: ['cs' => 'Test
'],
+ marketplace: ['cs' => 'Test MP
'],
+ );
+
+ $this->assertSame(['cs' => 'Test
'], $content->html);
+ $this->assertSame(['cs' => 'Test MP
'], $content->marketplace);
+ }
+}
diff --git a/tests/PoboClientTest.php b/tests/PoboClientTest.php
index 2445e6f..68dae3f 100644
--- a/tests/PoboClientTest.php
+++ b/tests/PoboClientTest.php
@@ -5,6 +5,7 @@
namespace Pobo\Sdk\Tests;
use PHPUnit\Framework\TestCase;
+use Pobo\Sdk\DTO\Blog;
use Pobo\Sdk\DTO\Category;
use Pobo\Sdk\DTO\LocalizedString;
use Pobo\Sdk\DTO\Parameter;
@@ -202,4 +203,47 @@ public function testMaxItemsValidationExceptionContainsErrors(): void
$this->assertStringContainsString('Maximum 100 items', $e->errors['bulk'][0]);
}
}
+
+ public function testImportBlogsThrowsExceptionForEmptyArray(): void
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Payload cannot be empty');
+
+ $this->client->importBlogs([]);
+ }
+
+ public function testImportBlogsThrowsExceptionForTooManyItems(): void
+ {
+ $blogs = [];
+ for ($i = 0; $i < 101; $i++) {
+ $blogs[] = [
+ 'guid' => sprintf('550e8400-e29b-41d4-a716-4466554400%02d', $i),
+ 'is_visible' => true,
+ 'name' => ['default' => sprintf('Blog %d', $i)],
+ 'url' => ['default' => sprintf('https://example.com/blog/%d', $i)],
+ ];
+ }
+
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessage('Too many items: 101 provided, maximum is 100');
+
+ $this->client->importBlogs($blogs);
+ }
+
+ public function testBlogDtoIsConvertedToArray(): void
+ {
+ $blog = new Blog(
+ guid: '550e8400-e29b-41d4-a716-446655440000',
+ category: 'news',
+ isVisible: true,
+ name: LocalizedString::create('Test Blog'),
+ url: LocalizedString::create('https://example.com/blog'),
+ );
+
+ $array = $blog->toArray();
+
+ $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $array['guid']);
+ $this->assertSame('news', $array['category']);
+ $this->assertTrue($array['is_visible']);
+ }
}
diff --git a/tests/ProductTest.php b/tests/ProductTest.php
new file mode 100644
index 0000000..0112a85
--- /dev/null
+++ b/tests/ProductTest.php
@@ -0,0 +1,171 @@
+ 'PROD-001',
+ 'is_visible' => true,
+ 'name' => ['default' => 'Product Name', 'cs' => 'Název produktu'],
+ 'url' => ['default' => 'https://example.com/product'],
+ 'short_description' => ['default' => 'Short description'],
+ 'description' => ['default' => 'Full description
'],
+ 'content' => [
+ 'html' => [
+ 'cs' => 'Czech HTML
',
+ 'sk' => 'Slovak HTML
',
+ 'en' => 'English HTML
',
+ ],
+ 'marketplace' => [
+ 'cs' => 'Czech Marketplace
',
+ 'sk' => 'Slovak Marketplace
',
+ ],
+ ],
+ 'images' => ['https://example.com/image.jpg'],
+ 'categories' => [
+ ['id' => 'CAT-001', 'name' => ['default' => 'Category 1']],
+ ],
+ 'created_at' => '2024-01-15T10:30:00.000000Z',
+ 'updated_at' => '2024-01-16T14:20:00.000000Z',
+ ];
+
+ $product = Product::fromArray($data);
+
+ $this->assertSame('PROD-001', $product->id);
+ $this->assertTrue($product->isVisible);
+ $this->assertSame('Product Name', $product->name->getDefault());
+ $this->assertSame('Název produktu', $product->name->get(Language::CS));
+
+ $this->assertInstanceOf(Content::class, $product->content);
+ $this->assertSame('Czech HTML
', $product->content->getHtml(Language::CS));
+ $this->assertSame('Slovak HTML
', $product->content->getHtml(Language::SK));
+ $this->assertSame('English HTML
', $product->content->getHtml(Language::EN));
+ $this->assertSame('Czech Marketplace
', $product->content->getMarketplace(Language::CS));
+ }
+
+ public function testProductFromArrayWithoutContent(): void
+ {
+ $data = [
+ 'id' => 'PROD-002',
+ 'is_visible' => true,
+ 'name' => ['default' => 'Product'],
+ 'url' => ['default' => 'https://example.com'],
+ ];
+
+ $product = Product::fromArray($data);
+
+ $this->assertNull($product->content);
+ }
+
+ public function testProductToArrayDoesNotIncludeContent(): void
+ {
+ $product = new Product(
+ id: 'PROD-001',
+ isVisible: true,
+ name: LocalizedString::create('Product'),
+ url: LocalizedString::create('https://example.com'),
+ content: new Content(
+ html: ['cs' => 'Test
'],
+ marketplace: [],
+ ),
+ );
+
+ $array = $product->toArray();
+
+ $this->assertArrayNotHasKey('content', $array);
+ }
+
+ public function testProductWithAllLocalizedFields(): void
+ {
+ $product = new Product(
+ id: 'PROD-001',
+ isVisible: true,
+ name: LocalizedString::create('Product')
+ ->withTranslation(Language::CS, 'Produkt')
+ ->withTranslation(Language::SK, 'Produkt'),
+ url: LocalizedString::create('https://example.com/product')
+ ->withTranslation(Language::CS, 'https://example.com/cs/produkt')
+ ->withTranslation(Language::SK, 'https://example.com/sk/produkt'),
+ shortDescription: LocalizedString::create('Short')
+ ->withTranslation(Language::CS, 'Krátký'),
+ description: LocalizedString::create('Full
')
+ ->withTranslation(Language::CS, 'Plný
'),
+ seoTitle: LocalizedString::create('SEO Title')
+ ->withTranslation(Language::CS, 'SEO Titulek'),
+ seoDescription: LocalizedString::create('SEO Description')
+ ->withTranslation(Language::CS, 'SEO Popis'),
+ );
+
+ $array = $product->toArray();
+
+ $this->assertArrayHasKey('short_description', $array);
+ $this->assertArrayHasKey('description', $array);
+ $this->assertArrayHasKey('seo_title', $array);
+ $this->assertArrayHasKey('seo_description', $array);
+ }
+
+ public function testProductWithCategoriesAndParameters(): void
+ {
+ $product = new Product(
+ id: 'PROD-001',
+ isVisible: true,
+ name: LocalizedString::create('Product'),
+ url: LocalizedString::create('https://example.com'),
+ categoriesIds: ['CAT-001', 'CAT-002'],
+ parametersIds: [1, 2, 3],
+ );
+
+ $array = $product->toArray();
+
+ $this->assertSame(['CAT-001', 'CAT-002'], $array['categories_ids']);
+ $this->assertSame([1, 2, 3], $array['parameters_ids']);
+ }
+
+ public function testProductWithImages(): void
+ {
+ $images = [
+ 'https://example.com/image1.jpg',
+ 'https://example.com/image2.jpg',
+ ];
+
+ $product = new Product(
+ id: 'PROD-001',
+ isVisible: true,
+ name: LocalizedString::create('Product'),
+ url: LocalizedString::create('https://example.com'),
+ images: $images,
+ );
+
+ $array = $product->toArray();
+
+ $this->assertSame($images, $array['images']);
+ }
+
+ public function testProductWithGuidAndIsLoaded(): void
+ {
+ $data = [
+ 'id' => 'PROD-001',
+ 'guid' => '550e8400-e29b-41d4-a716-446655440000',
+ 'is_visible' => true,
+ 'is_loaded' => true,
+ 'name' => ['default' => 'Product'],
+ 'url' => ['default' => 'https://example.com'],
+ ];
+
+ $product = Product::fromArray($data);
+
+ $this->assertSame('550e8400-e29b-41d4-a716-446655440000', $product->guid);
+ $this->assertTrue($product->isLoaded);
+ }
+}