<?php
|
namespace JVBase\meta;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
/**
|
* Fluent accessor for repeater field operations
|
*
|
* Usage:
|
* $meta->repeater('gallery')->field(0, 'image');
|
* $meta->repeater('links')->addRow(['url' => '...', 'title' => '...']);
|
* $meta->repeater('gallery')->setField(0, 'caption', 'New caption');
|
*/
|
class Repeater
|
{
|
protected Meta $meta;
|
protected string $name;
|
protected array $data;
|
protected array $config;
|
|
public function __construct(Meta $meta, string $name)
|
{
|
$this->meta = $meta;
|
$this->name = $name;
|
$this->data = $meta->get($name) ?: [];
|
$this->config = $meta->config($name) ?? [];
|
|
// Ensure data is array
|
if (!is_array($this->data)) {
|
$this->data = [];
|
}
|
}
|
|
// ─────────────────────────────────────────────────────────────
|
// Read Operations
|
// ─────────────────────────────────────────────────────────────
|
|
/**
|
* Get all rows
|
*/
|
public function all(): array
|
{
|
return $this->data;
|
}
|
|
/**
|
* Get a specific row
|
*/
|
public function row(int $index): ?array
|
{
|
return $this->data[$index] ?? null;
|
}
|
|
/**
|
* Get first row
|
*/
|
public function first(): ?array
|
{
|
return $this->data[0] ?? null;
|
}
|
|
/**
|
* Get last row
|
*/
|
public function last(): ?array
|
{
|
if (empty($this->data)) {
|
return null;
|
}
|
return $this->data[array_key_last($this->data)];
|
}
|
|
/**
|
* Get field value from specific row
|
*/
|
public function field(int $index, string $field): mixed
|
{
|
return $this->data[$index][$field] ?? null;
|
}
|
|
/**
|
* Get row count
|
*/
|
public function count(): int
|
{
|
return count($this->data);
|
}
|
|
/**
|
* Check if empty
|
*/
|
public function isEmpty(): bool
|
{
|
return empty($this->data);
|
}
|
|
/**
|
* Check if row exists
|
*/
|
public function hasRow(int $index): bool
|
{
|
return isset($this->data[$index]);
|
}
|
|
// ─────────────────────────────────────────────────────────────
|
// Write Operations
|
// ─────────────────────────────────────────────────────────────
|
|
/**
|
* Set field value in specific row
|
*/
|
public function setField(int $index, string $field, mixed $value): self
|
{
|
if (!isset($this->data[$index])) {
|
$this->data[$index] = [];
|
}
|
|
$this->data[$index][$field] = $value;
|
$this->sync();
|
|
return $this;
|
}
|
|
/**
|
* Add a new row
|
*/
|
public function addRow(array $data = []): self
|
{
|
$this->data[] = $data;
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Insert row at specific index
|
*/
|
public function insertRow(int $index, array $data = []): self
|
{
|
array_splice($this->data, $index, 0, [$data]);
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Update entire row (merge with existing)
|
*/
|
public function updateRow(int $index, array $data): self
|
{
|
$this->data[$index] = array_merge($this->data[$index] ?? [], $data);
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Replace entire row
|
*/
|
public function replaceRow(int $index, array $data): self
|
{
|
$this->data[$index] = $data;
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Remove a row
|
*/
|
public function removeRow(int $index): self
|
{
|
unset($this->data[$index]);
|
$this->data = array_values($this->data); // Re-index
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Remove last row
|
*/
|
public function pop(): self
|
{
|
array_pop($this->data);
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Remove first row
|
*/
|
public function shift(): self
|
{
|
array_shift($this->data);
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Clear all rows
|
*/
|
public function clear(): self
|
{
|
$this->data = [];
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Reorder rows
|
*/
|
public function reorder(array $newOrder): self
|
{
|
$reordered = [];
|
foreach ($newOrder as $index) {
|
if (isset($this->data[$index])) {
|
$reordered[] = $this->data[$index];
|
}
|
}
|
$this->data = $reordered;
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Move row to new position
|
*/
|
public function moveRow(int $from, int $to): self
|
{
|
if (!isset($this->data[$from])) {
|
return $this;
|
}
|
|
$row = $this->data[$from];
|
unset($this->data[$from]);
|
$this->data = array_values($this->data);
|
array_splice($this->data, $to, 0, [$row]);
|
$this->sync();
|
|
return $this;
|
}
|
|
// ─────────────────────────────────────────────────────────────
|
// Query Operations
|
// ─────────────────────────────────────────────────────────────
|
|
/**
|
* Find rows where field equals value
|
*/
|
public function where(string $field, mixed $value): array
|
{
|
return array_filter($this->data, fn($row) => ($row[$field] ?? null) === $value);
|
}
|
|
/**
|
* Find first row where field equals value
|
*/
|
public function firstWhere(string $field, mixed $value): ?array
|
{
|
foreach ($this->data as $row) {
|
if (($row[$field] ?? null) === $value) {
|
return $row;
|
}
|
}
|
return null;
|
}
|
|
/**
|
* Find row index where field equals value
|
*/
|
public function findIndex(string $field, mixed $value): ?int
|
{
|
foreach ($this->data as $index => $row) {
|
if (($row[$field] ?? null) === $value) {
|
return $index;
|
}
|
}
|
return null;
|
}
|
|
/**
|
* Filter rows by callback
|
*/
|
public function filter(callable $callback): array
|
{
|
return array_filter($this->data, $callback);
|
}
|
|
/**
|
* Map rows through callback
|
*/
|
public function map(callable $callback): array
|
{
|
return array_map($callback, $this->data);
|
}
|
|
/**
|
* Pluck single field from all rows
|
*/
|
public function pluck(string $field): array
|
{
|
return array_column($this->data, $field);
|
}
|
|
/**
|
* Sort rows by field
|
*/
|
public function sortBy(string $field, string $direction = 'asc'): self
|
{
|
usort($this->data, function($a, $b) use ($field, $direction) {
|
$aVal = $a[$field] ?? null;
|
$bVal = $b[$field] ?? null;
|
|
$result = $aVal <=> $bVal;
|
return $direction === 'desc' ? -$result : $result;
|
});
|
|
$this->sync();
|
return $this;
|
}
|
|
// ─────────────────────────────────────────────────────────────
|
// Bulk Operations
|
// ─────────────────────────────────────────────────────────────
|
|
/**
|
* Replace all rows
|
*/
|
public function setAll(array $rows): self
|
{
|
$this->data = $rows;
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Append multiple rows
|
*/
|
public function addRows(array $rows): self
|
{
|
foreach ($rows as $row) {
|
$this->data[] = $row;
|
}
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Update field in all rows
|
*/
|
public function setFieldAll(string $field, mixed $value): self
|
{
|
foreach ($this->data as $index => $row) {
|
$this->data[$index][$field] = $value;
|
}
|
$this->sync();
|
return $this;
|
}
|
|
/**
|
* Remove field from all rows
|
*/
|
public function removeFieldAll(string $field): self
|
{
|
foreach ($this->data as $index => $row) {
|
unset($this->data[$index][$field]);
|
}
|
$this->sync();
|
return $this;
|
}
|
|
// ─────────────────────────────────────────────────────────────
|
// Internal
|
// ─────────────────────────────────────────────────────────────
|
|
/**
|
* Sync data back to Meta instance
|
*/
|
protected function sync(): void
|
{
|
$this->meta->set($this->name, $this->data);
|
}
|
|
/**
|
* Get field config for subfield
|
*/
|
public function fieldConfig(string $field): ?array
|
{
|
return $this->config['fields'][$field] ?? null;
|
}
|
|
/**
|
* Get row label field name
|
*/
|
public function rowLabelField(): ?string
|
{
|
return $this->config['add_label'] ?? null;
|
}
|
}
|