Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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