Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class Client
{
use CommonTrait;

private string $apiKey;
private string $apiSecret;
private string $baseUrl;
Expand Down Expand Up @@ -159,6 +159,41 @@ public function makeRequest(
'stream-auth-type' => 'jwt',
]);

// Check if this is an upload request (needs multipart/form-data) - like Go SDK
if ($body instanceof GeneratedModels\ImageUploadRequest || $body instanceof GeneratedModels\FileUploadRequest) {
unset($headers['Content-Type']); // Let Guzzle set multipart Content-Type

$multipart = [];
$isImage = $body instanceof GeneratedModels\ImageUploadRequest;

// File field
if ($body->file !== null) {
// Check if it's a file path that exists, otherwise treat as base64
$filePath = (string) $body->file;
$fileContent = file_exists($filePath)
? file_get_contents($filePath)
: (base64_decode($filePath, true) ?: throw new StreamException('Failed to decode base64 file data'));

$multipart[] = [
'name' => 'file',
'contents' => $fileContent,
'filename' => $isImage ? 'image.jpg' : (file_exists($filePath) ? basename($filePath) : 'file.pdf'),
];
}

// User field (JSON string)
if ($body->user !== null) {
$multipart[] = ['name' => 'user', 'contents' => json_encode($body->user)];
}

// Upload sizes (images only, JSON string)
if ($isImage && $body->uploadSizes !== null && !empty($body->uploadSizes)) {
$multipart[] = ['name' => 'upload_sizes', 'contents' => json_encode($body->uploadSizes)];
}

return $this->httpClient->request($method, $url, $headers, $multipart);
}

// Make the request
return $this->httpClient->request($method, $url, $headers, $body);
}
Expand Down
3 changes: 1 addition & 2 deletions src/Constant.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@

class Constant
{
const VERSION = '2.0.2';
public const VERSION = '2.0.2';
}

6 changes: 5 additions & 1 deletion src/Http/GuzzleHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ public function request(string $method, string $url, array $headers = [], mixed

// Add body if provided
if ($body !== null) {
if (is_array($body) || is_object($body)) {
// Check if this is multipart form data (array of arrays with 'name' and 'contents')
if (is_array($body) && !empty($body) && isset($body[0]) && is_array($body[0]) && isset($body[0]['name'])) {
// This is multipart form data
$options['multipart'] = $body;
} elseif (is_array($body) || is_object($body)) {
$options['json'] = $body;
} else {
$options['body'] = $body;
Expand Down
126 changes: 124 additions & 2 deletions tests/Integration/FeedIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,124 @@ public function test02eCreateActivityMultipleFeeds(): void
echo "✅ Created activity in multiple feeds: {$activityId}\n";
}

/**
* @throws StreamException
*
* @test
*/
public function test02fUploadImageAndFile(): void
{
echo "\n📤 Testing file and image uploads...\n";

// Use the test image file in the same directory
$testImagePath = __DIR__ . '/testupload.png';

if (!file_exists($testImagePath)) {
self::markTestSkipped("Test image file not found: {$testImagePath}");

return;
}

// Test 1: Upload Image from file path
echo "\n🖼️ Testing image upload from file...\n";

// snippet-start: UploadImage
$imageUploadRequest = new GeneratedModels\ImageUploadRequest(
file: $testImagePath,
user: new GeneratedModels\OnlyUserID(id: $this->testUserId),
uploadSizes: [
[
'width' => 100,
'height' => 100,
'resize' => 'scale',
'crop' => 'center',
],
]
);
$imageResponse = $this->client->uploadImage($imageUploadRequest);
// snippet-end: UploadImage

$this->assertResponseSuccess($imageResponse, 'upload image');

$imageData = $imageResponse->getData();
self::assertNotNull($imageData);
self::assertNotNull($imageData->file);
self::assertNotEmpty($imageData->file);

$imageUrl = $imageData->file;
echo "✅ Image uploaded successfully: {$imageUrl}\n";

// Test 2: Upload Image with base64 (alternative method)
echo "\n🖼️ Testing image upload with base64...\n";

// Read image and encode as base64
$imageContent = file_get_contents($testImagePath);
if ($imageContent === false) {
self::markTestSkipped("Could not read test image file: {$testImagePath}");

return;
}

$base64Image = base64_encode($imageContent);

$imageUploadRequest2 = new GeneratedModels\ImageUploadRequest(
file: $base64Image,
user: new GeneratedModels\OnlyUserID(id: $this->testUserId),
uploadSizes: [
[
'width' => 200,
'height' => 200,
'resize' => 'fill',
'crop' => 'center',
],
]
);
$imageResponse2 = $this->client->uploadImage($imageUploadRequest2);

$this->assertResponseSuccess($imageResponse2, 'upload image with base64');
$imageData2 = $imageResponse2->getData();
self::assertNotNull($imageData2->file);
echo "✅ Image uploaded with base64: {$imageData2->file}\n";

// Test 3: Upload File
echo "\n📄 Testing file upload...\n";

// Create a temporary text file
$tempFile = tmpfile();
if ($tempFile === false) {
self::markTestSkipped('Could not create temporary file');

return;
}

$tempFilePath = stream_get_meta_data($tempFile)['uri'];
$testContent = "This is a test file content from PHP SDK integration test\nCreated at: " . date('Y-m-d H:i:s');
file_put_contents($tempFilePath, $testContent);

// snippet-start: UploadFile
$fileUploadRequest = new GeneratedModels\FileUploadRequest(
file: $tempFilePath,
user: new GeneratedModels\OnlyUserID(id: $this->testUserId)
);
$fileResponse = $this->client->uploadFile($fileUploadRequest);
// snippet-end: UploadFile

$this->assertResponseSuccess($fileResponse, 'upload file');

$fileData = $fileResponse->getData();
self::assertNotNull($fileData);
self::assertNotNull($fileData->file);
self::assertNotEmpty($fileData->file);

$fileUrl = $fileData->file;
echo "✅ File uploaded successfully: {$fileUrl}\n";

// Cleanup temp file
fclose($tempFile);

echo "✅ All file upload tests completed\n";
}

/**
* @test
*/
Expand Down Expand Up @@ -816,6 +934,7 @@ public function test15QueryFollows(): void

/**
* @test
*
* @throws StreamApiException
*/
public function test15bGetOrCreateFeedWithActivitiesAndFollow(): void
Expand Down Expand Up @@ -876,6 +995,7 @@ public function test15bGetOrCreateFeedWithActivitiesAndFollow(): void

// Step 5: Follow user 2 from user 1
echo "\n5️⃣ Following user 2 from user 1...\n";

try {
$followResponse = $this->feedsV3Client->follow(
new GeneratedModels\FollowRequest(
Expand All @@ -887,6 +1007,7 @@ public function test15bGetOrCreateFeedWithActivitiesAndFollow(): void
echo "✅ Followed user 2\n";
} catch (StreamApiException $e) {
echo '⚠️ Follow failed: ' . $e->getMessage() . "\n";

throw $e;
}

Expand All @@ -899,15 +1020,16 @@ public function test15bGetOrCreateFeedWithActivitiesAndFollow(): void
$verifyFeedData = $verifyFeedResponse->getData();
self::assertNotNull($verifyFeedData->feed);
self::assertSame($feedData2->feed->id, $verifyFeedData->feed->id, 'Feed ID should match');

// Check if activities exist in the feed
if ($verifyFeedData->activities !== null && count($verifyFeedData->activities) > 0) {
echo "✅ Feed 2 has " . count($verifyFeedData->activities) . " activities\n";
echo '✅ Feed 2 has ' . count($verifyFeedData->activities) . " activities\n";
// Verify our activity is in the list
$foundActivity = false;
foreach ($verifyFeedData->activities as $activity) {
if ($activity->id === $activityId2) {
$foundActivity = true;

break;
}
}
Expand Down
Binary file added tests/Integration/testupload.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.