[_id:…, cid:…, cid:…], …] */ public $rowUpdates = []; /** * New rows in the full Row::data format, including GRIDs. * Values are identified by GCIDs from previously defined, or new columns. * * @var array|null - [[_id:…, cid:…, cid:…], …] */ public $newRows = []; /** * Rows to be removed * * @var int[]|null - GRIDs */ public $removedRows = []; /** * Values changed in column specifications, such as name, title, etc. * This does not affect the table rows in any way. * * Key'd by id * * @var array[] - column specification objects, with GCIDs, key'd by CID */ public $columnUpdates = []; /** * New columns in the full format, including GCIDs * * @var array|null - [[id:…, …], …] */ public $newColumns = []; /** * When reordering columns, here is the column IDs array * in the new order. Columns meanwhile removed from the table * or added to it are to be ignored or appended at the end. * * This shall not be filled if merely editing or adding columns, * unless the order is explicitly adjusted by the user. * * @var string[]|null - GCIDs */ public $columnOrder = []; /** * Columns to be removed * * The data associated to those columns may or may not be removed from the Rows. * It does not matter, other than in regard to the table size. * * @var int[]|null - GCIDs */ public $removedColumns = []; private function walkProps() { $properties = (new ReflectionClass($this))->getProperties(); foreach ($properties as $property) { if (in_array($property->name, self::object_excluded_properties)) { continue; } yield $property->name; } } /** * Reconstruct from a object, such as one found in Proposal. * Note that the fromProposal() method should be used when the * proposal is available, as it populates additional fields. * * @param \stdClass $changes * @return Changeset */ public static function fromObject($changes) { $changeset = new Changeset(); foreach ($changeset->walkProps() as $prop) { if (isset($changes->$prop)) { $changeset->$prop = $changes->$prop; } } return $changeset; } /** * Serialize to an object format that may be stored in a Proposal. * * @return \stdClass */ public function toObject() { $object = new \stdClass(); foreach ($this->walkProps() as $prop) { $object->$prop = $this->$prop; } return $object; } /** * Check if there is any change in this changeset * * @return bool - any found */ public function hasAnyChanges() { foreach ($this->walkProps() as $prop) { if (!empty($this->$prop)) { return true; } } return false; } /** * Decorate / transform a single row for the editor view * * @param object|\DecoratedRow $row - row, must be key'd by column ids * @param bool $decorate - to add extra underscored info for the editor * @return \DecoratedRow|object|null - null if not decorating and the row was removed */ public function transformRow($row, $decorate) { if ($row instanceof Row) $row = (object)$row->getAttributes(); if ($decorate) { $row->_remove = false; $row->_changed = []; $row->_orig = []; } // Removed rows - return as null if (in_array($row->_id, $this->removedRows)) { if ($decorate) { $row->_remove = true; } else { return null; } } // if marked for removal, hide changes if (!$row->_remove) { // Changed values if (isset($this->rowUpdates[$row->_id])) { $newVals = $this->rowUpdates[$row->_id]; if ($decorate) { $row->_changed = array_keys($newVals); $row->_orig = array_only((array)$row, $row->_changed); } $row = (object)array_merge((array)$row, $newVals); } } // Drop deleted columns if (!$decorate) { foreach ($this->removedColumns as $colId) { unset($row->$colId); } } unset($row->_row_pivot); return $row; } /** * Decorate / transform columns (loaded from the source revision) * * @return Column[] */ public function fetchAndTransformColumns() { /** @var Column[] - loaded and transformed columns, cached from previous call to transformColumns() */ static $cachedColumns = []; if ($cachedColumns) return $cachedColumns; $columns = Column::columnsFromJson($this->revision->columns); // Modify columns foreach ($columns as $column) { if (isset($this->columnUpdates[$column->id])) { $column->modifyByChangeset($this->columnUpdates[$column->id]); } if (in_array($column->id, $this->removedColumns)) { $column->markForRemoval(); } } // Append new columns foreach ($this->newColumns as $newColumn) { $columns[] = $c =new Column($newColumn); $c->markAsNew(); } // Reorder $colsById = collect($columns)->keyBy('id')->all(); $newOrder = []; foreach ($this->columnOrder as $id) { $newOrder[] = $colsById[$id]; } $leftover_keys = array_diff(array_keys($colsById), $this->columnOrder); foreach ($leftover_keys as $id) { $newOrder[] = $colsById[$id]; } return $cachedColumns = $newOrder; } public function rowRemove(int $id) { $this->removedRows[] = $id; } public function rowRestore(int $id) { $this->removedRows = array_diff($this->removedRows, [$id]); } public function fetchAndTransformRow(int $id) { $r = $this->fetchRow($id); $transformed = $this->transformRow($r, true); return $transformed; } public function fetchColumns() { return Column::columnsFromJson($this->revision->columns); } public function fetchRow($id) { $r = $this->revision->rowsData($this->fetchColumns(), true, false) ->whereRaw("data->>'_id' = ?", $id)->first(); if (! $r) throw new NotExistException("No such row _id = $id in this revision."); // remove junk unset($r->pivot_revision_id); unset($r->pivot_row_id); return (object)$r->getAttributes(); } /** * Apply a row update, adding the row to the list of changes, or removing it * if all differences were undone. * * @param array|object $newVals */ public function rowUpdate($newVals) { $newVals = (object)$newVals; $_id = $newVals->_id; $origRow = $this->fetchRow($_id); /** @var Column[]|Collection $cols */ $cols = collect($this->fetchAndTransformColumns())->keyBy('id'); $updateObj = []; foreach ($newVals as $colId => $value) { if (starts_with($colId, '_')) continue; // internals $col = $cols[$colId]; $value = $col->cast($value); $origValue = $col->cast(isset($origRow->$colId) ? $origRow->$colId : null); if ($value !== $origValue) { $updateObj[$colId] = $value; } } if (!empty($updateObj)) { $this->rowUpdates[$_id] = $updateObj; } else { // remove possible old update record for this row, if nothing changes now unset($this->rowUpdates[$_id]); } } /** * Get a page of added rows for display in the editor * * @param int $perPage * @return \Illuminate\Pagination\LengthAwarePaginator|Collection|array */ public function getAddedRows(int $perPage = 25) { return collection_paginate($this->newRows, $perPage); } }