namespace Elementor\Modules\Variables\Storage;
use Elementor\Core\Utils\Collection;
use Elementor\Modules\AtomicWidgets\Utils\Utils;
use Elementor\Modules\Variables\Storage\Entities\Variable;
use Elementor\Modules\Variables\Storage\Exceptions\DuplicatedLabel;
use Elementor\Modules\Variables\Storage\Exceptions\RecordNotFound;
use Elementor\Modules\Variables\Storage\Exceptions\VariablesLimitReached;
* TODO: a tradeoff when you want to use collection base methods they are
* performing immutable process ( creating new instances )
* we will see if we need to extend collection as time goes on
class Variables_Collection extends Collection {
const FORMAT_VERSION_V1 = 1;
const FORMAT_VERSION_V2 = 2;
const TOTAL_VARIABLES_COUNT = 100;
private function __construct( array $items = [], ?int $watermark = 0, ?int $version = null ) {
$this->watermark = $watermark;
$this->version = $version ?? self::FORMAT_VERSION_V1;
public static function hydrate( array $record ): self {
foreach ( $record['data'] ?? [] as $id => $item ) {
$data = array_merge( [ 'id' => $id ], $item );
$variables[ $id ] = Variable::from_array( $data );
$watermark = $record['watermark'];
$version = $record['version'] ?? null;
return new self( $variables, $watermark, $version );
public function serialize( bool $include_deleted_key = false ): array {
foreach ( $this->all() as $variable ) {
$var = $variable->to_array();
if ( $include_deleted_key && $variable->is_deleted() ) {
$data[ $variable->id() ] = $var;
'watermark' => $this->watermark,
'version' => $this->version,
public function set_version( $version ): void {
$this->version = $version;
public static function default(): self {
public function watermark(): int {
private function reset_watermark() {
public function increment_watermark() {
if ( PHP_INT_MAX === $this->watermark ) {
$this->reset_watermark();
public function add_variable( Variable $variable ): void {
$this->items[ $variable->id() ] = $variable;
* @throws RecordNotFound When a variable is not found.
public function find_or_fail( string $id ): Variable {
$variable = $this->get( $id );
if ( ! isset( $variable ) ) {
throw new RecordNotFound( 'Variable not found' );
* @throws DuplicatedLabel If there is a duplicate label in the database.
public function assert_label_is_unique( string $label, ?string $ignore_id = null ): void {
foreach ( $this->all() as $variable ) {
if ( $variable->is_deleted() ) {
if ( null !== $ignore_id && $variable->id() === $ignore_id ) {
if ( strcasecmp( $variable->label(), $label ) === 0 ) {
throw new DuplicatedLabel( esc_html( "Variable label '$label' already exists." ) );
* @throws VariablesLimitReached If variable limit reached.
public function assert_limit_not_reached(): void {
foreach ( $this->all() as $variable ) {
if ( ! $variable->is_deleted() ) {
if ( self::TOTAL_VARIABLES_COUNT <= $active_count ) {
throw new VariablesLimitReached( 'Total variables count limit reached' );
public function next_id(): string {
return Utils::generate_id( 'e-gv-', array_keys( $this->all() ) );
public function get_next_order(): int {
foreach ( $this->all() as $variable ) {
if ( $variable->is_deleted() ) {
if ( $variable->has_order() && $variable->order() > $highest_order ) {
$highest_order = $variable->order();
return $highest_order + 1;