datatable.directory codebase
				https://datatable.directory/
			
			
		
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							697 lines
						
					
					
						
							18 KiB
						
					
					
				
			
		
		
	
	
							697 lines
						
					
					
						
							18 KiB
						
					
					
				<?php
 | 
						|
 | 
						|
 | 
						|
namespace App\Tables;
 | 
						|
 | 
						|
use App\Models\Revision;
 | 
						|
use App\Models\Row;
 | 
						|
use App\Models\Table;
 | 
						|
use Illuminate\Pagination\Paginator;
 | 
						|
use Illuminate\Queue\SerializesModels;
 | 
						|
use Illuminate\Support\Collection;
 | 
						|
use MightyPork\Exceptions\NotApplicableException;
 | 
						|
use MightyPork\Exceptions\NotExistException;
 | 
						|
use ReflectionClass;
 | 
						|
 | 
						|
/**
 | 
						|
 * Object representing a set of table modifications
 | 
						|
 */
 | 
						|
class Changeset
 | 
						|
{
 | 
						|
    use SerializesModels;
 | 
						|
 | 
						|
    const object_excluded_properties = [
 | 
						|
        'revision',
 | 
						|
        'table',
 | 
						|
        'note',
 | 
						|
    ];
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var Revision - base revision this changeset belongs to
 | 
						|
     */
 | 
						|
    public $revision;
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var Table - table this changeset belongs to
 | 
						|
     */
 | 
						|
    public $table;
 | 
						|
 | 
						|
    /**
 | 
						|
     * @var string - user's note attached to this changeset (future proposal)
 | 
						|
     */
 | 
						|
    public $note = '';
 | 
						|
 | 
						|
    /**
 | 
						|
     * Rows whose content changed, identified by _id.
 | 
						|
     * Only changed values are to be filled. Columns are identified by GCIDs
 | 
						|
     *
 | 
						|
     * Key'd by _id
 | 
						|
     *
 | 
						|
     * @var array|null - [_id -> [_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 -> [_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 -> [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 = [];
 | 
						|
        }
 | 
						|
 | 
						|
        if ($decorate) {
 | 
						|
            $row->_orig = array_diff((array)$row, []);
 | 
						|
            // remove junk
 | 
						|
            unset($row->_orig['_id']);
 | 
						|
            unset($row->_orig['_new']);
 | 
						|
            unset($row->_orig['_remove']);
 | 
						|
            unset($row->_orig['_changed']);
 | 
						|
            unset($row->_orig['_orig']);
 | 
						|
        }
 | 
						|
 | 
						|
        if ($this->isNewRow($row->_id)) {
 | 
						|
            if ($decorate) {
 | 
						|
                $row->_new = true;
 | 
						|
            }
 | 
						|
            return $row;
 | 
						|
        }
 | 
						|
 | 
						|
        // 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);
 | 
						|
 | 
						|
        if ($decorate) {
 | 
						|
            $row->_loadvals = array_diff((array)$row, []);
 | 
						|
            // remove junk
 | 
						|
            unset($row->_loadvals['_id']);
 | 
						|
            unset($row->_loadvals['_new']);
 | 
						|
            unset($row->_loadvals['_remove']);
 | 
						|
            unset($row->_loadvals['_changed']);
 | 
						|
            unset($row->_loadvals['_orig']);
 | 
						|
        }
 | 
						|
 | 
						|
        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) {
 | 
						|
            if (isset($colsById[$id])) {
 | 
						|
                $newOrder[] = $colsById[$id];
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $leftover_keys = array_diff(array_keys($colsById), $this->columnOrder);
 | 
						|
        foreach ($leftover_keys as $id) {
 | 
						|
            $newOrder[] = $colsById[$id];
 | 
						|
        }
 | 
						|
 | 
						|
        return $cachedColumns = $newOrder;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param string $id
 | 
						|
     * @return Column
 | 
						|
     */
 | 
						|
    public function fetchColumn(string $id)
 | 
						|
    {
 | 
						|
        if ($this->isNewColumn($id)) {
 | 
						|
            $c = new Column($this->newColumns[$id]);
 | 
						|
            $c->markAsNew();
 | 
						|
            return $c;
 | 
						|
        } else {
 | 
						|
            $columns = collect($this->revision->columns)->keyBy('id');
 | 
						|
            return new Column($columns[$id]);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param string $id
 | 
						|
     * @return Column
 | 
						|
     */
 | 
						|
    public function fetchAndTransformColumn(string $id)
 | 
						|
    {
 | 
						|
        $column = $this->fetchColumn($id);
 | 
						|
 | 
						|
        if (isset($this->columnUpdates[$column->id])) {
 | 
						|
            $column->modifyByChangeset($this->columnUpdates[$column->id]);
 | 
						|
        }
 | 
						|
 | 
						|
        if (in_array($column->id, $this->removedColumns)) {
 | 
						|
            $column->markForRemoval();
 | 
						|
        }
 | 
						|
 | 
						|
        return $column;
 | 
						|
    }
 | 
						|
 | 
						|
    public function rowRemove(int $id)
 | 
						|
    {
 | 
						|
        if ($this->isNewRow($id)) {
 | 
						|
            unset($this->newRows[$id]);
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            $this->removedRows[] = $id;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function rowRestore(int $id)
 | 
						|
    {
 | 
						|
        $this->removedRows = array_diff($this->removedRows, [$id]);
 | 
						|
    }
 | 
						|
 | 
						|
    public function isNewRow(int $id)
 | 
						|
    {
 | 
						|
        return isset($this->newRows[$id]);
 | 
						|
    }
 | 
						|
 | 
						|
    public function isNewColumn(string $id)
 | 
						|
    {
 | 
						|
        return isset($this->newColumns[$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);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Fetch an existing row from DB, or a new row.
 | 
						|
     *
 | 
						|
     * @param $id
 | 
						|
     * @return object
 | 
						|
     */
 | 
						|
    public function fetchRow(int $id)
 | 
						|
    {
 | 
						|
        if ($this->isNewRow($id)) {
 | 
						|
            $nr = (object)$this->newRows[$id];
 | 
						|
            $nr->_new = true;
 | 
						|
            return $nr;
 | 
						|
        }
 | 
						|
 | 
						|
        $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 - values of the new row
 | 
						|
     * @return object - updated column
 | 
						|
     */
 | 
						|
    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 ($this->isNewRow($_id)) {
 | 
						|
            $this->newRows[$_id] = array_merge($this->newRows[$_id], $updateObj);
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            if (!empty($updateObj)) {
 | 
						|
                $this->rowUpdates[$_id] = $updateObj;
 | 
						|
            } else {
 | 
						|
                // remove possible old update record for this row, if nothing changes now
 | 
						|
                unset($this->rowUpdates[$_id]);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->fetchAndTransformRow($_id);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param $newVals
 | 
						|
     * @return Column
 | 
						|
     */
 | 
						|
    public function columnUpdate($newVals)
 | 
						|
    {
 | 
						|
        $id = $newVals->id;
 | 
						|
        $col = $this->fetchColumn($id);
 | 
						|
 | 
						|
        $updateObj = [];
 | 
						|
        foreach ($newVals as $field => $value) {
 | 
						|
            if (starts_with($field, '_')) continue; // internals
 | 
						|
 | 
						|
            if ($value !== $col->$field) {
 | 
						|
                $updateObj[$field] = $value;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if ($this->isNewColumn($id)) {
 | 
						|
            $this->newColumns[$id] = array_merge($this->newColumns[$id], $updateObj);
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            if (!empty($updateObj)) {
 | 
						|
                $this->columnUpdates[$id] = $updateObj;
 | 
						|
            } else {
 | 
						|
                // remove possible old update record for this row, if nothing changes now
 | 
						|
                unset($this->columnUpdates[$id]);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return $this->fetchAndTransformColumn($id);
 | 
						|
    }
 | 
						|
 | 
						|
    public function columnRemove(string $id)
 | 
						|
    {
 | 
						|
        if ($this->isNewColumn($id)) {
 | 
						|
            unset($this->newColumns[$id]);
 | 
						|
            // remove it from order
 | 
						|
            $this->columnOrder = array_values(array_diff($this->columnOrder, [$id]));
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            $this->removedColumns[] = $id;
 | 
						|
        }
 | 
						|
 | 
						|
        $this->clearColumnOrderIfUnchanged();
 | 
						|
    }
 | 
						|
 | 
						|
    public function columnRestore(string $id)
 | 
						|
    {
 | 
						|
        $this->removedColumns = array_diff($this->removedColumns, [$id]);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get a page of added rows for display in the editor
 | 
						|
     *
 | 
						|
     * @param int $perPage
 | 
						|
     * @return \Illuminate\Pagination\LengthAwarePaginator|Collection|array
 | 
						|
     */
 | 
						|
    public function getAddedRows($perPage = 25)
 | 
						|
    {
 | 
						|
        return collection_paginate($this->newRows, $perPage);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @param Column[] $columns
 | 
						|
     * @param array $csvArray
 | 
						|
     * @param bool $forTableInsert
 | 
						|
     * @return \array[][]|Collection
 | 
						|
     */
 | 
						|
    public static function csvToRowsArray($columns, $csvArray, $forTableInsert)
 | 
						|
    {
 | 
						|
        /** @var Collection|array[][] $rows */
 | 
						|
        $rows = collect($csvArray)->map(function ($row) use ($columns, $forTableInsert) {
 | 
						|
            if (count($row) == 0 || count($row) == 1 && $row[0] == '') return null; // discard empty lines
 | 
						|
            if (count($row) != count($columns)) {
 | 
						|
                throw new NotApplicableException("All rows must have " . count($columns) . " fields.");
 | 
						|
            }
 | 
						|
 | 
						|
            $data = [];
 | 
						|
 | 
						|
            foreach ($row as $i => $val) {
 | 
						|
                $col = $columns[$i];
 | 
						|
 | 
						|
                if (strlen($val) > 1000) {
 | 
						|
                    // try to stop people inserting unstructured crap / malformed CSV
 | 
						|
                    throw new NotApplicableException("Value for column {$col->name} too long.");
 | 
						|
                }
 | 
						|
                $data[$col->id] = $col->cast($val);
 | 
						|
            }
 | 
						|
 | 
						|
            if ($forTableInsert) {
 | 
						|
                return ['data' => $data];
 | 
						|
            } else {
 | 
						|
                return $data;
 | 
						|
            }
 | 
						|
        })->filter();
 | 
						|
 | 
						|
 | 
						|
        $rowNumerator = new RowNumerator(count($csvArray));
 | 
						|
 | 
						|
        if ($forTableInsert) {
 | 
						|
            return $rows->map(function ($row) use (&$rowNumerator) {
 | 
						|
                $row['data']['_id'] = $rowNumerator->next();
 | 
						|
                return $row;
 | 
						|
            });
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            return $rows->map(function ($row) use (&$rowNumerator) {
 | 
						|
                $row['_id'] = $rowNumerator->next();
 | 
						|
                return $row;
 | 
						|
            });
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function addBlankRows(int $count)
 | 
						|
    {
 | 
						|
        $numerator = new RowNumerator($count);
 | 
						|
 | 
						|
        $columns = $this->fetchAndTransformColumns();
 | 
						|
        $template = [];
 | 
						|
        foreach ($columns as $column) {
 | 
						|
            $template[$column->id] = $column->cast(null);
 | 
						|
        }
 | 
						|
 | 
						|
        foreach ($numerator->generate() as $_id) {
 | 
						|
            $row = $template;
 | 
						|
            $row['_id'] = $_id;
 | 
						|
            $this->newRows[$_id] = $row;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function addFilledRows($csvArray)
 | 
						|
    {
 | 
						|
        /** @var Column[] $columns */
 | 
						|
        $columns = array_values($this->fetchAndTransformColumns());
 | 
						|
 | 
						|
        $rows = self::csvToRowsArray($columns, $csvArray, false)
 | 
						|
            ->keyBy('_id');
 | 
						|
 | 
						|
        // using '+' to avoid renumbering
 | 
						|
        $this->newRows = $this->newRows + $rows->toArray();
 | 
						|
    }
 | 
						|
 | 
						|
    public function addBlankCol()
 | 
						|
    {
 | 
						|
        $cid = (new ColumnNumerator(1))->next();
 | 
						|
 | 
						|
        $allCols = $this->fetchAndTransformColumns();
 | 
						|
        $num = count($allCols) + 1;
 | 
						|
 | 
						|
        $col = [
 | 
						|
            'name' => "col_{$num}",
 | 
						|
            'type' => "string",
 | 
						|
            'title' => "Column {$num}",
 | 
						|
            'id' => $cid,
 | 
						|
            '_new' => true,
 | 
						|
        ];
 | 
						|
 | 
						|
        $this->newColumns[$cid] = $col;
 | 
						|
        return $col;
 | 
						|
    }
 | 
						|
 | 
						|
    public function setColOrder(array $order)
 | 
						|
    {
 | 
						|
        $allCols = $this->fetchAndTransformColumns();
 | 
						|
        $ids = collect($allCols)->pluck('id')->all();
 | 
						|
        $order = array_intersect($order, $ids);
 | 
						|
        $missing = array_diff($ids, $order);
 | 
						|
 | 
						|
        $this->columnOrder = array_values(array_merge($order, $missing));
 | 
						|
        $this->clearColumnOrderIfUnchanged();
 | 
						|
    }
 | 
						|
 | 
						|
    public function removeEmptyNewRows()
 | 
						|
    {
 | 
						|
        $cols = $this->fetchColumns();
 | 
						|
        $emptyTpl = collect($cols)->keyBy('id')->map(function(Column $c) {
 | 
						|
            return $c->cast(null);
 | 
						|
        })->all();
 | 
						|
 | 
						|
        foreach ($this->newColumns as $k => $obj) {
 | 
						|
            $cols[] = $c = new Column($obj);
 | 
						|
            $c->markAsNew();
 | 
						|
 | 
						|
            $emptyTpl[$k] = $c->cast(null);
 | 
						|
        }
 | 
						|
 | 
						|
        $this->newRows = array_filter($this->newRows, function ($r) use ($emptyTpl) {
 | 
						|
            foreach ($r as $k => $val) {
 | 
						|
                if ($k[0] == '_') continue;
 | 
						|
                if ($val != $emptyTpl[$k]) return true;
 | 
						|
            }
 | 
						|
            return false;
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    public function clearColumnOrderIfUnchanged()
 | 
						|
    {
 | 
						|
        $expected = collect($this->revision->columns)
 | 
						|
            ->pluck('id')
 | 
						|
            ->diff($this->removedColumns)
 | 
						|
            ->merge(collect($this->newColumns)->pluck('id'))
 | 
						|
            ->values()->all();
 | 
						|
 | 
						|
        $this->columnOrder = array_values($this->columnOrder);
 | 
						|
 | 
						|
        if ($expected == $this->columnOrder) {
 | 
						|
            $this->columnOrder = [];
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public function resetColumnOrder()
 | 
						|
    {
 | 
						|
        $this->columnOrder = [];
 | 
						|
    }
 | 
						|
 | 
						|
    public function resetRemovedColumns()
 | 
						|
    {
 | 
						|
        $this->removedColumns = [];
 | 
						|
    }
 | 
						|
 | 
						|
    public function resetAddedColumns()
 | 
						|
    {
 | 
						|
        $this->columnOrder = array_values(
 | 
						|
            array_diff($this->columnOrder,
 | 
						|
                collect($this->newColumns)->pluck('id')->all()
 | 
						|
            )
 | 
						|
        );
 | 
						|
 | 
						|
        $this->newColumns = [];
 | 
						|
        $this->clearColumnOrderIfUnchanged();
 | 
						|
    }
 | 
						|
 | 
						|
    public function resetUpdatedColumns()
 | 
						|
    {
 | 
						|
        $this->columnUpdates = [];
 | 
						|
    }
 | 
						|
 | 
						|
    public function resetRemovedRows()
 | 
						|
    {
 | 
						|
        $this->removedRows = [];
 | 
						|
    }
 | 
						|
 | 
						|
    public function resetAddedRows()
 | 
						|
    {
 | 
						|
        $this->newRows = [];
 | 
						|
    }
 | 
						|
 | 
						|
    public function resetUpdatedRows()
 | 
						|
    {
 | 
						|
        $this->rowUpdates = [];
 | 
						|
    }
 | 
						|
}
 | 
						|
 |