Skip to content
Draft
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

## CHANGELOG
------------------------------------------------
## Version 2.4.3
###### Date: 19-May-2026
### Enhancement
- Added entry variants support with optional branch scoping via `variants()` on Entry and Query
------------------------------------------------
## Version 2.4.2
###### Date: 05-January-2026
### Security fix
Expand Down
552 changes: 337 additions & 215 deletions composer.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/Error/ErrorMessages.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class ErrorMessages
const TAGS_ARRAY = 'Tags must be an array. Convert the value to an array and try again.';
const VALUE_ARRAY = 'Value must be an array. Convert the value to an array and try again.';
const INVALID_QUERY = 'Invalid query. Update the query and try again.';
const VARIANT_UIDS_REQUIRED = 'Variant UID(s) are required. Provide a variant UID string or an array of variant UID strings and try again.';
const INVALID_VARIANT_UIDS = 'Invalid variant UID(s). Use a string for a single variant UID or an array of variant UID strings and try again.';

// helper.php error messages
const INVALID_STRING_INPUT = 'Invalid input for "%s". Use a string value and try again.';
Expand Down
45 changes: 45 additions & 0 deletions src/Stack/BaseQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,51 @@ public function includeFallback()
return $this->queryObject;
}

/**
* Scope entry requests to one or more entry variants.
*
* Pass a single variant UID (string) or multiple variant UIDs (array).
* Optionally pass a branch name to scope the request to that branch;
* when omitted, the stack-level branch (if set) is used.
*
* @param string|array $variantUidOrUids - Variant UID or list of variant UIDs
* @param string $branchName - Optional branch name for the request
*
* @example
* use Contentstack\Contentstack;
* $stack = Contentstack::Stack("API_KEY", "DELIVERY_TOKEN", "ENVIRONMENT");
* $result = $stack->ContentType('product')->Entry('ENTRY_UID')->variants('VARIANT_UID', 'branch_name')->toJSON()->fetch();
* $entries = $stack->ContentType('product')->Entry()->variants(array('variant1', 'variant2'), 'branch_name')->toJSON()->find();
*
* @return Query|Entry
*/
public function variants($variantUidOrUids = '', $branchName = '')
{
if (Utility::isEmpty($variantUidOrUids)) {
throw contentstackCreateError(ErrorMessages::VARIANT_UIDS_REQUIRED);
}
if (is_array($variantUidOrUids)) {
if (count($variantUidOrUids) === 0) {
throw contentstackCreateError(ErrorMessages::VARIANT_UIDS_REQUIRED);
}
foreach ($variantUidOrUids as $variantUid) {
if (Utility::isEmpty($variantUid) || !is_string($variantUid)) {
throw contentstackCreateError(ErrorMessages::INVALID_VARIANT_UIDS);
}
}
} elseif (!is_string($variantUidOrUids)) {
throw contentstackCreateError(ErrorMessages::INVALID_VARIANT_UIDS);
}
if (!Utility::isEmpty($branchName) && !is_string($branchName)) {
throw contentstackCreateError(ErrorMessages::INVALID_VARIANT_UIDS);
}
$this->queryObject->variantUid = $variantUidOrUids;
if (!Utility::isEmpty($branchName)) {
$this->queryObject->variantBranch = $branchName;
}
return $this->queryObject;
}

/**
* To include branch of publish content.
*
Expand Down
11 changes: 11 additions & 0 deletions src/Stack/ContentType/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ public function __construct($entryUid = '', $contentType = '')
}
}

/**
* Get all entries for the content type (used without an entry UID).
*
* @return Request
*/
public function find()
{
$this->operation = __FUNCTION__;
return Utility::contentstackRequest($this->contentType->stack, $this);
}

/**
* Fetch the specified entry
*
Expand Down
26 changes: 24 additions & 2 deletions src/Support/Utility.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,21 @@ public static function headers($query = '')
return $headers;
}

/**
* Format variant UID(s) for the x-cs-variant-uid request header.
*
* @param string|array $variantUidOrUids - Variant UID or list of variant UIDs
*
* @return string
*/
public static function formatVariantUids($variantUidOrUids = '')
{
if (is_array($variantUidOrUids)) {
return implode(', ', $variantUidOrUids);
}
return $variantUidOrUids;
}

/**
* POST formatted query for the API server
*
Expand Down Expand Up @@ -402,8 +417,15 @@ public static function contentstackRequest($stack, $queryObject = '', $type = ''
}else {
$request_headers[] = 'access_token: '.$Headers["access_token"];
}
if ($Headers["branch"] !== '' && $Headers["branch"] !== "undefined") {
$request_headers[] = 'branch: '.$Headers["branch"];
if (isset($queryObject->variantUid) && !Utility::isEmpty($queryObject->variantUid)) {
$request_headers[] = 'x-cs-variant-uid: '.Utility::formatVariantUids($queryObject->variantUid);
}
$branch = $Headers["branch"] ?? '';
if (isset($queryObject->variantBranch) && !Utility::isEmpty($queryObject->variantBranch)) {
$branch = $queryObject->variantBranch;
}
if ($branch !== '' && $branch !== "undefined") {
$request_headers[] = 'branch: '.$branch;
}

$proxy_details = $stack->proxy;
Expand Down
1 change: 1 addition & 0 deletions test/EntriesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -509,4 +509,5 @@ public function testFindSearch() {
}
$this->assertTrue($flag);
}

}
47 changes: 47 additions & 0 deletions test/VariantsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

use PHPUnit\Framework\TestCase;
use Contentstack\Support\Utility;
use Contentstack\Stack\ContentType;
use Contentstack\Stack\ContentType\Entry;
use Contentstack\Stack\ContentType\Query;

class VariantsTest extends TestCase
{
private function contentType()
{
return new ContentType('ct_uid', '');
}

public function testFormatVariantUids()
{
$this->assertEquals('uid1, uid2', Utility::formatVariantUids(array('uid1', 'uid2')));
$this->assertEquals('uid1', Utility::formatVariantUids('uid1'));
}

public function testEntryVariantsRequiresVariantUids()
{
$this->expectException(\Exception::class);
$this->contentType()->Entry()->variants('');
}

public function testQueryVariantsRejectsEmptyArray()
{
$this->expectException(\Exception::class);
$this->contentType()->Query()->variants(array());
}

public function testVariantsStoresBranchOnQueryObject()
{
$query = $this->contentType()->Query()->variants('variant_uid', 'main');
$this->assertEquals('variant_uid', $query->variantUid);
$this->assertEquals('main', $query->variantBranch);
}

public function testEntryVariantsChainsToEntry()
{
$entry = $this->contentType()->Entry('entry_uid')->variants(array('v1', 'v2'));
$this->assertInstanceOf(Entry::class, $entry);
$this->assertEquals(array('v1', 'v2'), $entry->variantUid);
}
}
Loading