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.
		
		
		
		
		
			
		
			
				
					
					
						
							330 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							330 lines
						
					
					
						
							11 KiB
						
					
					
				<?php
 | 
						|
 | 
						|
namespace App\Http\Controllers;
 | 
						|
 | 
						|
use App\Models\Revision;
 | 
						|
use App\Models\Table;
 | 
						|
use App\Models\User;
 | 
						|
use App\Tables\Changeset;
 | 
						|
use App\Tables\Column;
 | 
						|
use App\Tables\ColumnNumerator;
 | 
						|
use App\Tables\CStructArrayExporter;
 | 
						|
use App\Tables\CsvExporter;
 | 
						|
use App\Tables\CXMacroExporter;
 | 
						|
use App\Tables\JsonExporter;
 | 
						|
use App\Tables\PhpExporter;
 | 
						|
use App\Tables\RowNumerator;
 | 
						|
use Illuminate\Http\Request;
 | 
						|
use Illuminate\Validation\Rule;
 | 
						|
use MightyPork\Exceptions\NotApplicableException;
 | 
						|
use MightyPork\Utils\Utils;
 | 
						|
 | 
						|
class TableController extends Controller
 | 
						|
{
 | 
						|
    public function view(Request $request, User $user, string $table)
 | 
						|
    {
 | 
						|
        $input = $this->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.");
 | 
						|
 | 
						|
        // option 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);
 | 
						|
 | 
						|
        $columns = Column::columnsFromJson($revision->columns);
 | 
						|
 | 
						|
        $rows = $revision->rowsData($columns)->paginate(25, []);
 | 
						|
 | 
						|
        return view('table.view', [
 | 
						|
            'table' => $tableModel,
 | 
						|
            'revision' => $revision,
 | 
						|
            'columns' => $columns,
 | 
						|
            'rows' => $rows,
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
    public function delete(Request $request, User $user, string $table)
 | 
						|
    {
 | 
						|
        /** @var Table $tableModel */
 | 
						|
        $tableModel = $user->tables()->where('name', $table)->first();
 | 
						|
        if ($tableModel === null) abort(404, "No such table.");
 | 
						|
        $this->authorize('delete', $tableModel);
 | 
						|
 | 
						|
        if ($request->get('table-name', null) !== $table) {
 | 
						|
            return $this->backWithErrors(['table-name' => 'Fill table name']);
 | 
						|
        }
 | 
						|
 | 
						|
        $tableModel->delete();
 | 
						|
 | 
						|
        return redirect(route('profile.view', $user->name));
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * SHow a form for creating a new table
 | 
						|
     *
 | 
						|
     * @return \Illuminate\Http\Response
 | 
						|
     */
 | 
						|
    public function create()
 | 
						|
    {
 | 
						|
        $exampleData =
 | 
						|
            "Mercenaria mercenaria,hard clam,40\n" .
 | 
						|
            "Magallana gigas,pacific oyster,30\n" .
 | 
						|
            "Patella vulgata,common limpet,20";
 | 
						|
 | 
						|
        $columns = Column::columnsFromJson([
 | 
						|
            // fake 'id' to satisfy the check in Column constructor
 | 
						|
            ['id' => 1, 'name' => 'latin',    'type' => 'string', 'title' => 'Latin Name'],
 | 
						|
            ['id' => 2, 'name' => 'common',   'type' => 'string', 'title' => 'Common Name'],
 | 
						|
            ['id' => 3, 'name' => 'lifespan', 'type' => 'int',    'title' => 'Lifespan (years)']
 | 
						|
        ]);
 | 
						|
 | 
						|
        return view('table.create', [
 | 
						|
            'exampleColumns' => '',
 | 
						|
            'columns' => $columns,
 | 
						|
            'exampleData' => $exampleData,
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Show table settings edit form
 | 
						|
     */
 | 
						|
    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.");
 | 
						|
        $this->authorize('edit', $tableModel);
 | 
						|
 | 
						|
        return view('table.conf', [
 | 
						|
            'table' => $tableModel,
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Store modified table settings
 | 
						|
     */
 | 
						|
    public function storeSettings(Request $request, User $user, string $table)
 | 
						|
    {
 | 
						|
        /** @var Table $tableModel */
 | 
						|
        $tableModel = $user->tables()->where('name', $table)->first();
 | 
						|
        if ($tableModel === null) abort(404, "No such table.");
 | 
						|
        $this->authorize('edit', $tableModel);
 | 
						|
 | 
						|
        $input = $this->validate($request, [
 | 
						|
            'name' => [
 | 
						|
                'required',
 | 
						|
                VALI_NAME,
 | 
						|
                Rule::unique('tables')->ignoreModel($tableModel),
 | 
						|
            ],
 | 
						|
            'title' => ['required', VALI_LINE],
 | 
						|
            'description' => ['nullable', VALI_TEXT],
 | 
						|
            'license' => ['nullable', VALI_TEXT],
 | 
						|
            'origin' => ['nullable', VALI_TEXT],
 | 
						|
        ]);
 | 
						|
 | 
						|
        $tableModel->fill($input->all());
 | 
						|
        $tableModel->save();
 | 
						|
 | 
						|
        flash()->success('Table settings saved');
 | 
						|
 | 
						|
        return redirect($tableModel->viewRoute); // the route now changed
 | 
						|
    }
 | 
						|
 | 
						|
    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|json',
 | 
						|
            'data' => 'string|nullable',
 | 
						|
        ]);
 | 
						|
 | 
						|
        // Check if table name is unique for user
 | 
						|
        if ($u->hasTable($input->name)) {
 | 
						|
            return $this->backWithErrors([
 | 
						|
                'name' => "A table called \"$input->name\" already exists in your account.",
 | 
						|
            ]);
 | 
						|
        }
 | 
						|
 | 
						|
        // --- COLUMNS ---
 | 
						|
        // Parse and validate the columns specification
 | 
						|
        /** @var Column[] $columns */
 | 
						|
        $columns = [];
 | 
						|
        $col_names = []; // for checking duplicates
 | 
						|
        $colsArray = fromJSON($input->columns);
 | 
						|
 | 
						|
        // prevent griefing via long list of columns
 | 
						|
        if (count($colsArray) > 100) return $this->backWithErrors(['columns' => "Too many columns"]);
 | 
						|
 | 
						|
        foreach ($colsArray as $colObj) {
 | 
						|
            // ensure column has a title
 | 
						|
            if (!isset($colObj->title)) {
 | 
						|
                $colObj->title = $colObj->name;
 | 
						|
            }
 | 
						|
 | 
						|
            try {
 | 
						|
                if (in_array($colObj->name, $col_names)) {
 | 
						|
                    return $this->backWithErrors(['columns' => "Duplicate column: $colObj->name"]);
 | 
						|
                }
 | 
						|
 | 
						|
                $col_names[] = $colObj->name;
 | 
						|
                $columns[] = new Column($colObj);
 | 
						|
            } catch (\Exception $e) {
 | 
						|
                // validation errors from the Column constructor
 | 
						|
                return $this->backWithErrors(['columns' => $e->getMessage()]);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (count($columns) == 0) {
 | 
						|
            return $this->backWithErrors(['columns' => "Define at least one column"]);
 | 
						|
        }
 | 
						|
 | 
						|
        // Now assign column IDs
 | 
						|
        $columnNumerator = new ColumnNumerator(count($columns));
 | 
						|
        foreach ($columns as $column) {
 | 
						|
            $column->setID($columnNumerator->next());
 | 
						|
        }
 | 
						|
 | 
						|
        // --- DATA ---
 | 
						|
        $dataCsvLines = Utils::csvToArray($input->data);
 | 
						|
 | 
						|
        // Preparing data to insert into the Rows table
 | 
						|
        $rowsToInsert = null;
 | 
						|
        $rowNumerator = null;
 | 
						|
        try {
 | 
						|
            $rowsToInsert = Changeset::csvToRowsArray($columns, $dataCsvLines, true)->all();
 | 
						|
        } catch (\Exception $e) {
 | 
						|
            return $this->backWithErrors(['data' => $e->getMessage()]);
 | 
						|
        }
 | 
						|
 | 
						|
        // --- STORE TO DB ---
 | 
						|
 | 
						|
        /* Fields for the new Revision instance */
 | 
						|
        $revisionFields = [
 | 
						|
            'note' => "Initial revision of table $u->name/$input->name",
 | 
						|
            'columns' => $columns,
 | 
						|
            'row_count' => count($rowsToInsert),
 | 
						|
        ];
 | 
						|
 | 
						|
        /* Fields for the new Table instance */
 | 
						|
        $tableFields = [
 | 
						|
            'owner_id' => $u->id,
 | 
						|
            'name' => $input->name,
 | 
						|
            'title' => $input->title,
 | 
						|
            'description' => $input->description,
 | 
						|
            'license' => $input->license,
 | 
						|
            'origin' => $input->origin,
 | 
						|
        ];
 | 
						|
 | 
						|
        /** @var Table $table */
 | 
						|
        $table = null;
 | 
						|
        \DB::transaction(function () use ($revisionFields, $tableFields, $rowsToInsert, &$table) {
 | 
						|
            $revision = Revision::create($revisionFields);
 | 
						|
 | 
						|
            $tableFields['revision_id'] = $revision->id; // current revision (not-null constraint on this FK)
 | 
						|
            $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($rowsToInsert);
 | 
						|
        });
 | 
						|
 | 
						|
        return redirect($table->viewRoute);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 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->owner->name}_{$table->name}";
 | 
						|
        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
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Simple export via a preset
 | 
						|
     *
 | 
						|
     * @param Request $request
 | 
						|
     * @param User $user
 | 
						|
     * @param string $table
 | 
						|
     */
 | 
						|
    public function export(Request $request, User $user, string $table)
 | 
						|
    {
 | 
						|
        /** @var Table $tableModel */
 | 
						|
        $tableModel = $user->tables()->where('name', $table)->first();
 | 
						|
        if ($tableModel === null) abort(404, "No such table.");
 | 
						|
 | 
						|
        $exporter = null;
 | 
						|
 | 
						|
        switch ($request->get('format')) {
 | 
						|
            case 'json':
 | 
						|
                $exporter = new JsonExporter($tableModel);
 | 
						|
                break;
 | 
						|
 | 
						|
            case 'csv':
 | 
						|
                $exporter = new CsvExporter($tableModel);
 | 
						|
                break;
 | 
						|
 | 
						|
            case 'csv-tab':
 | 
						|
                $exporter = (new CsvExporter($tableModel))->withDelimiter("\t");
 | 
						|
                break;
 | 
						|
 | 
						|
            case 'c':
 | 
						|
                $exporter = new CStructArrayExporter($tableModel);
 | 
						|
                break;
 | 
						|
 | 
						|
            case 'xmacro':
 | 
						|
                $noq = explode(',', $request->input('noq',''));
 | 
						|
                $exporter = (new CXMacroExporter($tableModel))->noQuotesAround($noq);
 | 
						|
                break;
 | 
						|
 | 
						|
            case 'php':
 | 
						|
                $exporter = new PhpExporter($tableModel);
 | 
						|
                break;
 | 
						|
 | 
						|
            case 'js':
 | 
						|
                $exporter = (new JsonExporter($tableModel))->withJsWrapper();
 | 
						|
                break;
 | 
						|
 | 
						|
            default:
 | 
						|
                abort(400, "Unspecified or unknown format.");
 | 
						|
        }
 | 
						|
 | 
						|
        $dl = Utils::parseBool($request->get('dl', false));
 | 
						|
        $exporter->exportToBrowser($dl);
 | 
						|
    }
 | 
						|
}
 | 
						|
 |