|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
use App\Models\Proposal;
|
|
|
|
use App\Models\Row;
|
|
|
|
use App\Models\Table;
|
|
|
|
use App\Models\User;
|
|
|
|
use App\Tables\Changeset;
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
use Illuminate\Support\Facades\Input;
|
|
|
|
use MightyPork\Exceptions\SimpleValidationException;
|
|
|
|
use MightyPork\Utils\Utils;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Draft composing
|
|
|
|
*/
|
|
|
|
class TableEditController extends Controller
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Initialize the session-stored changeset, if not set yet
|
|
|
|
*
|
|
|
|
* @param Table $table
|
|
|
|
* @return Changeset
|
|
|
|
*/
|
|
|
|
private function getChangeset(Table $table)
|
|
|
|
{
|
|
|
|
if (Input::has('reset')) {
|
|
|
|
session()->forget($table->draftSessionKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @var Changeset $changeset */
|
|
|
|
return session()->remember($table->draftSessionKey, function () use ($table) {
|
|
|
|
$changeset = new Changeset();
|
|
|
|
$changeset->table = $table;
|
|
|
|
$changeset->revision = $table->revision;
|
|
|
|
return $changeset;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Store the changeset to session
|
|
|
|
*
|
|
|
|
* @param Changeset $changeset
|
|
|
|
*/
|
|
|
|
private function storeChangeset(Changeset $changeset)
|
|
|
|
{
|
|
|
|
session()->put($changeset->table->draftSessionKey, $changeset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Discard draft and redirect to table view
|
|
|
|
*/
|
|
|
|
public function discard(User $user, string $table)
|
|
|
|
{
|
|
|
|
/** @var Table $tableModel */
|
|
|
|
$tableModel = $user->tables()->where('name', $table)->first();
|
|
|
|
if ($tableModel === null) abort(404, "No such table.");
|
|
|
|
|
|
|
|
session()->forget($tableModel->draftSessionKey);
|
|
|
|
|
|
|
|
return redirect($tableModel->draftRoute);
|
|
|
|
}
|
|
|
|
|
|
|
|
#region Draft tabs
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show the table edit view with tabs
|
|
|
|
*
|
|
|
|
* @param User $user - table owner
|
|
|
|
* @param string $table - table name
|
|
|
|
* @param string|null $tab - page tab name, kebab-case
|
|
|
|
* @return \Illuminate\View\View;
|
|
|
|
*/
|
|
|
|
public function draft(User $user, string $table, $tab = null)
|
|
|
|
{
|
|
|
|
/** @var Table $tableModel */
|
|
|
|
$tableModel = $user->tables()->where('name', $table)->first();
|
|
|
|
if ($tableModel === null) abort(404, "No such table.");
|
|
|
|
|
|
|
|
if ($tab == null) $tab = 'edit-rows';
|
|
|
|
|
|
|
|
$method = camel_case('tab-'.$tab);
|
|
|
|
if (!method_exists($this, $method)) abort(404, "No such tab: $tab");
|
|
|
|
|
|
|
|
$changeset = $this->getChangeset($tableModel);
|
|
|
|
|
|
|
|
if (Input::has('dump')) {
|
|
|
|
dd($changeset);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->$method($changeset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @noinspection PhpUnusedPrivateMethodInspection */
|
|
|
|
private function tabEditRows(Changeset $changeset)
|
|
|
|
{
|
|
|
|
$revision = $changeset->revision;
|
|
|
|
$columns = $changeset->fetchAndTransformColumns();
|
|
|
|
$rows = $revision->rowsData($columns, true, false)
|
|
|
|
->sortByCol($changeset->fetchRevisionColumns()[0])
|
|
|
|
->paginate(25, []);
|
|
|
|
|
|
|
|
Row::disableCasts();
|
|
|
|
return view('table.propose.edit-rows', [
|
|
|
|
'changeset' => $changeset,
|
|
|
|
'table' => $changeset->table,
|
|
|
|
'columns' => collect($columns),
|
|
|
|
'rows' => $rows,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @noinspection PhpUnusedPrivateMethodInspection */
|
|
|
|
private function tabAddRows(Changeset $changeset)
|
|
|
|
{
|
|
|
|
$columns = $changeset->fetchAndTransformColumns();
|
|
|
|
$rows = $changeset->getAddedRows(25);
|
|
|
|
|
|
|
|
return view('table.propose.add-rows', [
|
|
|
|
'changeset' => $changeset,
|
|
|
|
'table' => $changeset->table,
|
|
|
|
'columns' => collect($columns),
|
|
|
|
'rows' => $rows,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @noinspection PhpUnusedPrivateMethodInspection */
|
|
|
|
private function tabAddRowsCsv(Changeset $changeset)
|
|
|
|
{
|
|
|
|
$columns = $changeset->fetchAndTransformColumns();
|
|
|
|
|
|
|
|
return view('table.propose.add-rows-csv', [
|
|
|
|
'changeset' => $changeset,
|
|
|
|
'table' => $changeset->table,
|
|
|
|
'columns' => collect($columns)->where('toRemove', false),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @noinspection PhpUnusedPrivateMethodInspection */
|
|
|
|
private function tabManageColumns(Changeset $changeset)
|
|
|
|
{
|
|
|
|
$columns = $changeset->fetchAndTransformColumns();
|
|
|
|
|
|
|
|
return view('table.propose.manage-columns', [
|
|
|
|
'changeset' => $changeset,
|
|
|
|
'table' => $changeset->table,
|
|
|
|
'columns' => collect($columns),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @noinspection PhpUnusedPrivateMethodInspection */
|
|
|
|
private function tabReview(Changeset $changeset)
|
|
|
|
{
|
|
|
|
return view('table.propose.review', [
|
|
|
|
'changeset' => $changeset,
|
|
|
|
'table' => $changeset->table,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
/**
|
|
|
|
* API hook called by AJAX or via forms.
|
|
|
|
* Generally applies modifications to the Changeset stored in session.
|
|
|
|
*
|
|
|
|
* @param Request $request
|
|
|
|
* @param User $user
|
|
|
|
* @param string $table
|
|
|
|
* @return \Symfony\Component\HttpFoundation\Response
|
|
|
|
*/
|
|
|
|
public function draftUpdate(Request $request, User $user, string $table)
|
|
|
|
{
|
|
|
|
/** @var Table $tableModel */
|
|
|
|
$tableModel = $user->tables()->where('name', $table)->first();
|
|
|
|
if ($tableModel === null) abort(404, "No such table.");
|
|
|
|
|
|
|
|
$changeset = $this->getChangeset($tableModel);
|
|
|
|
|
|
|
|
$input = objBag($request->all(), false);
|
|
|
|
|
|
|
|
$resp = null;
|
|
|
|
$code = 200;
|
|
|
|
|
|
|
|
try {
|
|
|
|
switch ($input->action) {
|
|
|
|
case 'row.update':
|
|
|
|
$data = (object)$input->data;
|
|
|
|
$resp = $changeset->rowUpdate($data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'row.update-many':
|
|
|
|
$newVals = $input->data;
|
|
|
|
$updated = [];
|
|
|
|
foreach ($newVals as $rowUpdate) {
|
|
|
|
$r = $changeset->rowUpdate($rowUpdate);
|
|
|
|
$updated[$r->_id] = $r;
|
|
|
|
}
|
|
|
|
$resp = $updated;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'row.add-csv':
|
|
|
|
$fname = 'csv-file';
|
|
|
|
if ($request->hasFile($fname)) {
|
|
|
|
try {
|
|
|
|
$file = $request->file($fname);
|
|
|
|
if ($file->isValid() && $file->isReadable()) {
|
|
|
|
$handle = $file->openFile();
|
|
|
|
$csv = Utils::csvToArray($handle);
|
|
|
|
if (empty($csv)) throw new \Exception("Failed to parse CSV file.");
|
|
|
|
|
|
|
|
$changeset->addFilledRows($csv);
|
|
|
|
$handle = null;
|
|
|
|
} else {
|
|
|
|
throw new \Exception("File not valid.");
|
|
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return $this->backWithErrors(['csv-file' => $e->getMessage()]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
$text = trim($input->data);
|
|
|
|
if (empty($text)) throw new \Exception("Empty CSV field and no file uploaded.");
|
|
|
|
$changeset->addFilledRows(Utils::csvToArray($text));
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return $this->backWithErrors(['data' => $e->getMessage()]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'row.remove':
|
|
|
|
$isNew = $changeset->isNewRow($input->id);
|
|
|
|
$changeset->rowRemove($input->id);
|
|
|
|
$resp = $isNew ? null : $changeset->fetchAndTransformRow($input->id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'row.remove-empty-new':
|
|
|
|
$changeset->removeEmptyNewRows();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'row.restore':
|
|
|
|
$changeset->rowRestore($input->id);
|
|
|
|
$resp = $changeset->fetchAndTransformRow($input->id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'rows.add':
|
|
|
|
$changeset->addBlankRows($input->count);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'col.update':
|
|
|
|
$data = (object)$input->data;
|
|
|
|
$resp = $changeset->columnUpdate($data);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'col.remove':
|
|
|
|
$isNew = $changeset->isNewColumn($input->id);
|
|
|
|
$changeset->columnRemove($input->id);
|
|
|
|
$resp = $isNew ? null : $changeset->fetchAndTransformColumn($input->id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'col.restore':
|
|
|
|
$changeset->columnRestore($input->id);
|
|
|
|
$resp = $changeset->fetchAndTransformColumn($input->id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'col.add':
|
|
|
|
$resp = $changeset->addBlankCol();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'col.sort':
|
|
|
|
$changeset->setColOrder($input->order);
|
|
|
|
$resp = !empty($changeset->columnOrder); // return flag if order is changed
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'note.set':
|
|
|
|
$changeset->note = $input->text;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'reset.col-order': // called via POST or GET
|
|
|
|
$changeset->resetColumnOrder();
|
|
|
|
$resp = $changeset->fetchAndTransformColumns(); // return all columns
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'reset.col-remove': // called via GET
|
|
|
|
$changeset->resetRemovedColumns();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'reset.col-add': // called via GET
|
|
|
|
$changeset->resetAddedColumns();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'reset.col-update': // called via GET
|
|
|
|
$changeset->resetUpdatedColumns();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'reset.row-remove': // called via GET
|
|
|
|
$changeset->resetRemovedRows();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'reset.row-add': // called via GET
|
|
|
|
$changeset->resetAddedRows();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'reset.row-update': // called via GET
|
|
|
|
$changeset->resetUpdatedRows();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
$resp = "Bad Action";
|
|
|
|
$code = 400;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (SimpleValidationException $e) {
|
|
|
|
return $this->jsonResponse(['errors' => $e->getMessageBag()->getMessages()], 400);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->storeChangeset($changeset);
|
|
|
|
|
|
|
|
if ($request->method() == 'GET') {
|
|
|
|
return back();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Redirect requested via form
|
|
|
|
if ($code == 200 && $input->has('redirect')) {
|
|
|
|
return redirect($input->redirect);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->jsonResponse($resp, $code);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Submit the form
|
|
|
|
*
|
|
|
|
* @param Request $request
|
|
|
|
* @param User $user
|
|
|
|
* @param string $table
|
|
|
|
* @return \Symfony\Component\HttpFoundation\Response
|
|
|
|
*/
|
|
|
|
public function draftSubmit(Request $request, User $user, string $table)
|
|
|
|
{
|
|
|
|
/** @var Table $tableModel */
|
|
|
|
$tableModel = $user->tables()->where('name', $table)->first();
|
|
|
|
if ($tableModel === null) abort(404, "No such table.");
|
|
|
|
|
|
|
|
$input = $this->validate($request, [
|
|
|
|
'note' => 'required|string'
|
|
|
|
]);
|
|
|
|
|
|
|
|
$changeset = $this->getChangeset($tableModel);
|
|
|
|
$changeset->note = trim($input->note);
|
|
|
|
if (empty($changeset->note)) throw new SimpleValidationException('note', 'Note is required.');
|
|
|
|
|
|
|
|
if (!$changeset->getRowChangeCounts()->any && !$changeset->getColumnChangeCounts()->any) {
|
|
|
|
flash()->error("There are no changes to submit.");
|
|
|
|
return back();
|
|
|
|
}
|
|
|
|
|
|
|
|
$proposal = Proposal::fromChangeset($changeset);
|
|
|
|
$proposal->saveOrFail();
|
|
|
|
|
|
|
|
if (\user()->ownsTable($tableModel)) {
|
|
|
|
$proposal->createRevision();
|
|
|
|
} else {
|
|
|
|
// TODO send a notification to the table owner
|
|
|
|
}
|
|
|
|
|
|
|
|
session()->forget($tableModel->draftSessionKey);
|
|
|
|
|
|
|
|
return redirect($tableModel->viewRoute);
|
|
|
|
}
|
|
|
|
}
|