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); } }