validate($request, [ 'rev' => 'nullable|int' ]); /** @var Table $tableModel */ $tableModel = $user->tables()->withCount(['favourites', 'forks', 'revisions', 'comments', 'proposals']) ->where('name', $table)->first(); if ($tableModel === null) abort(404, "No such table."); // make it possible to show other revisions if ($input->has('rev')) { $rev = (int)$input->rev; $revision = $tableModel->revisions()->orderBy('created_at')->skip($rev - 1)->first(); if ($revision === null) abort(404, "No such revision"); } else { $revision = $tableModel->revision; } $this->countTableVisit($request, $tableModel); return view('table.view', [ 'table' => $tableModel, 'revision' => $revision, 'columns' => Column::columnsFromJson(json_decode($revision->columns)), 'rows' => $revision->rows()->paginate(25), ]); } /** * SHow a form for creating a new table * * @return \Illuminate\Http\Response */ public function create() { $exampleColumns = "latin,string,Latin Name\n" . "common,string,Common Name\n" . "lifespan,int,Lifespan (years)"; $exampleData = "Mercenaria mercenaria,hard clam,40\n" . "Magallana gigas,pacific oyster,30\n" . "Patella vulgata,common limpet,20"; return view('table.create', compact('exampleColumns', 'exampleData') ); } public function settings(Request $request, User $user, string $table) { /** @var Table $tableModel */ $tableModel = $user->tables()->where('name', $table)->first(); if ($tableModel === null) abort(404, "No such table."); return view('table.edit', [ 'table' => $tableModel, ]); } public function storeNew(Request $request) { /** @var User $u */ $u = \Auth::user(); $input = $this->validate($request, [ 'name' => [ 'required', VALI_NAME, Rule::unique('tables'), ], 'title' => ['required', VALI_LINE], 'description' => ['nullable', VALI_TEXT], 'license' => ['nullable', VALI_TEXT], 'origin' => ['nullable', VALI_TEXT], 'columns' => 'required|string', 'data' => 'string|nullable', ]); // Check if table name is unique for user if ($u->tables()->where('name', $input->name)->exists()) { return $this->backWithErrors([ 'name' => "A table called \"$input->name\" already exists in your account.", ]); } // Parse and validate the columns specification /** @var Column[] $columns */ $columns = []; $column_keys = []; // for checking duplicates $colTable = array_map('str_getcsv', explode("\n", $input->columns)); // prevent griefing via long list of columns if (count($colTable) > 100) return $this->backWithErrors(['columns' => "Too many columns"]); foreach ($colTable as $col) { $col = array_map('trim', $col); if (count($col) < 2 || strlen($col[0])==0) { return $this->backWithErrors(['columns' => "All columns must have at least name and type."]); } try { if (in_array($col[0], $column_keys)) { return $this->backWithErrors(['columns' => "Duplicate column: $col[0]"]); } $column_keys[] = $col[0]; $columns[] = new Column([ 'name' => $col[0], 'type' => $col[1], 'title' => count($col) >= 3 ? $col[2] : $col[0], // title falls back to =name if not specified, ]); } catch (\Exception $e) { return $this->backWithErrors(['columns' => $e->getMessage()]); } } if (count($columns) == 0) return $this->backWithErrors(['columns' => "Define at least one column"]); $rowTable = array_map('str_getcsv', explode("\n", $input->data)); // Preparing data to insert into the Rows table $rowsData = null; try { $rowsData = array_map(function ($row) use ($columns) { if (count($row) == 0 || count($row)==1&&$row[0]=='') return null; if (count($row) != count($columns)) { throw new NotApplicableException("All rows must have " . count($columns) . " fields."); } $parsed = []; foreach ($row as $i => $val) { $key = $columns[$i]->name; if (strlen($val) > 255) { // try to stop people inserting unstructured crap / malformed CSV throw new NotApplicableException("Value for column $key too long."); } $parsed[$key] = $columns[$i]->cast($val); } return [ 'data' => json_encode($parsed), ]; }, $rowTable); $rowsData = array_filter($rowsData); } catch (\Exception $e) { return $this->backWithErrors(['data' => $e->getMessage()]); } $revisionFields = [ 'note' => "Initial revision of table $u->name/$input->name", 'columns' => json_encode($columns), 'row_count' => count($rowsData), ]; $tableFields = [ 'owner_id' => $u->id, 'revision_id' => 0, 'name' => $input->name, 'title' => $input->title, 'description' => $input->description, 'license' => $input->license, 'origin' => $input->origin, ]; \DB::transaction(function () use ($revisionFields, $tableFields, $rowsData) { $revision = Revision::create($revisionFields); $tableFields['revision_id'] = $revision->id; // to meet the not-null constraint $table = Table::create($tableFields); // Attach the revision to the table, set as current $table->revisions()->attach($revision); $table->revision()->associate($revision); // Spawn rows, linked to the revision $revision->rows()->createMany($rowsData); }); return redirect(route('table.view', ['user' => $u, 'table' => $input->name])); } /** * Check unique visit, filter bots / scripts, and increment visits count. * * @param Request $request * @param Table $table */ private function countTableVisit(Request $request, Table $table) { $cookieName = "view_$table->id"; if (!$request->cookie($cookieName, false)) { $ua = $request->userAgent(); // Filter out suspicious user agents if (! str_contains(strtolower($ua), Controller::BOT_USER_AGENTS)) { $table->countVisit(); \Cookie::queue($cookieName, true, 24*60); // in minutes } } } }