Newer
Older
admin_crud_pkg / src / Http / Controllers / CrudController.php
<?php

namespace Istlab\AdminCrud\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
use Istlab\AdminCrud\AdminCrud;
use Istlab\AdminCrud\Support\Schema;

class CrudController extends Controller
{
    /**
     * テーブル一覧 / テーブル表示 / 行表示
     */
    public function index(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.roles', 'admin'));

        if (!$req->has('table')) {
            $tables = Schema::tables();
            $tableDataCounts = [];
            foreach ($tables as $tn) {
                $tableDataCounts[$tn] = DB::table($tn)->count();
            }

            return view('admin-crud::db', $this->viewVars(compact('tables', 'tableDataCounts')));
        }

        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);
        $coldetails = Schema::columnDetails($tableName);
        $limit = (int) config('admin-crud.limit', 100);

        $whereBy = [];
        $row = null;
        if ($req->isMethod('post')) {
            foreach ($req->all() as $fname => $fval) {
                if (str_starts_with($fname, 'whereBy__') && $fval !== null && $fval !== '') {
                    $whereBy[substr($fname, 9)] = $fval;
                }
            }
        }
        if ($req->has('row')) { // 単一行編集モード
            $whereBy['id'] = $req->input('row');
            $row = $req->input('row');
        }

        if (count($whereBy) === 0) {
            if (isset($coldetails['created_at'])) {
                $data = DB::table($tableName)->orderByDesc('created_at')->limit($limit)->get()->toArray();
            } elseif (isset($coldetails['id'])) {
                $data = DB::table($tableName)->orderBy('id')->limit($limit)->get()->toArray();
            } else {
                $data = DB::table($tableName)->limit($limit)->get()->toArray();
            }
            $numdata = DB::table($tableName)->count();
        } else {
            $query = DB::table($tableName);
            foreach ($whereBy as $fn => $fv) {
                if (!isset($coldetails[$fn])) {
                    continue;
                }
                $lowertype = strtolower($coldetails[$fn]);
                if (in_array($lowertype, ['integer', 'bigint unsigned', 'int'], true)) {
                    $query->where($fn, '=', $fv);
                } else {
                    $query->where($fn, 'LIKE', '%' . $fv . '%');
                }
            }
            if (isset($coldetails['orderint'])) {
                $query->orderBy('orderint');
            }
            $numdata = $query->count();
            $data = $query->limit($limit)->get()->toArray();
        }

        $view = $req->has('row') ? 'admin-crud::row' : 'admin-crud::table';
        [$scheduledUpdateTargetType, $scheduledUpdateCreateUrl] = $this->scheduledUpdate($tableName);
        $nextId = (int) (DB::table($tableName)->max('id') ?? 0) + 1;

        return view($view, $this->viewVars(compact(
            'tableName',
            'coldetails',
            'data',
            'whereBy',
            'numdata',
            'row',
            'scheduledUpdateTargetType',
            'scheduledUpdateCreateUrl',
            'nextId'
        )));
    }

    /**
     * セルのインライン更新 (Ajax)。whereBy__ があれば絞り込み表示へフォワード。
     */
    public function post(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.post_roles', 'admin'));

        foreach ($req->all() as $fname => $fval) {
            if (str_starts_with($fname, 'whereBy__')) {
                return $this->index($req);
            }
        }

        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);
        $field = (string) $req->input('field');
        abort_unless(array_key_exists($field, Schema::columnDetails($tableName)), 400);

        if ($req->input('dtype') === 'tinyint') {
            $currentVal = (int) DB::table($tableName)->where('id', $req->input('data_id'))->value($field);
            $newVal = 1 - $currentVal;
            DB::table($tableName)->where('id', $req->input('data_id'))->update([$field => $newVal]);

            return "TOGGLE {$newVal} {$req->input('tdid')}";
        }

        DB::table($tableName)->where('id', $req->input('data_id'))->update([$field => $req->input('val')]);

        return "OK {$req->input('tdid')}";
    }

    /**
     * 新しい行を追加 (モデルのfactoryを利用)
     */
    public function create(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.roles', 'admin'));
        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);

        $model = AdminCrud::modelFor($tableName);
        if ($model && method_exists($model, 'factory')) {
            $model::factory()->create();
        }

        return redirect(AdminCrud::route('crud', ['table' => $tableName]));
    }

    /**
     * 全行削除 (truncate)
     */
    public function truncate(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.roles', 'admin'));
        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);

        $model = AdminCrud::modelFor($tableName);
        if ($model) {
            $model::truncate();
        } elseif (in_array(DB::connection()->getDriverName(), ['sqlite', 'sqlite_testing'], true)) {
            DB::table($tableName)->delete(); // SQLiteにはTRUNCATEがない
        } else {
            DB::statement("TRUNCATE TABLE {$tableName}");
        }

        return redirect(AdminCrud::route('crud', ['table' => $tableName]));
    }

    /**
     * 行をコピー (replicate)
     */
    public function copy(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.roles', 'admin'));
        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);
        $row = $req->input('row');

        $model = AdminCrud::modelFor($tableName);
        if ($model) {
            $datum = $model::find($row);
            if (isset($datum)) {
                $newdatum = $datum->replicate();
                $newdatum->save();

                return redirect(AdminCrud::route('crud', ['table' => $tableName, 'row' => $newdatum->id]));
            }
        }

        return redirect(AdminCrud::route('crud', ['table' => $tableName]));
    }

    /**
     * 行を削除
     */
    public function delete(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.roles', 'admin'));
        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);
        $row = $req->input('row');

        $model = AdminCrud::modelFor($tableName);
        if ($model) {
            $datum = $model::find($row);
            if (isset($datum)) {
                $datum->delete();
            }
        }

        return redirect(AdminCrud::route('crud', ['table' => $tableName]));
    }

    /**
     * チェックした行を削除またはコピー
     */
    public function chkdelete(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.roles', 'admin'));
        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);

        $model = AdminCrud::modelFor($tableName);
        if ($model) {
            $dids = $req->input('did');
            if (!$dids || count($dids) === 0) {
                return redirect(AdminCrud::route('crud', ['table' => $tableName]))
                    ->with('feedback.error', '削除またはコピーする行を選択してください。');
            }
            if ($req->input('action') === 'bdelete') {
                $model::whereIn('id', $dids)->forceDelete();
            }
            if ($req->input('action') === 'bcopy') {
                foreach ($dids as $did) {
                    $datum = $model::find($did);
                    if (isset($datum)) {
                        $newdatum = $datum->replicate();
                        $newdatum->save();
                    }
                }
            }
        } else {
            info('crudchkdelete: model for ' . $tableName . ' is not found.');
        }

        return redirect(AdminCrud::route('crud', ['table' => $tableName]));
    }

    /**
     * autoincrement値を設定する
     */
    public function setautoinc(Request $req)
    {
        $this->authorizeCrud(config('admin-crud.roles', 'admin'));
        $tableName = (string) $req->input('table');
        $this->guardTable($tableName);

        $autoinc = $req->input('autoinc');
        if (!is_numeric($autoinc) || (int) $autoinc < 1) {
            return redirect(AdminCrud::route('crud', ['table' => $tableName]))
                ->with('feedback.error', 'autoincには1以上の数値を入力してください。');
        }

        if (in_array(DB::connection()->getDriverName(), ['sqlite', 'sqlite_testing'], true)) {
            DB::table('sqlite_sequence')->where('name', $tableName)->update(['seq' => (int) $autoinc - 1]);
        } else {
            DB::statement("ALTER TABLE {$tableName} AUTO_INCREMENT = " . (int) $autoinc);
        }

        return redirect(AdminCrud::route('crud', ['table' => $tableName]))
            ->with('feedback.success', "{$tableName}のautoincを{$autoinc}に設定しました。");
    }

    /**
     * Gate による認可チェック。'gate' が null ならスキップ。
     */
    protected function authorizeCrud(string $roles): void
    {
        $gate = config('admin-crud.gate');
        if (empty($gate)) {
            return;
        }
        $user = auth()->user();
        if (!$user || !$user->can($gate, $roles)) {
            abort(403);
        }
    }

    /**
     * 存在しないテーブル名への生SQL混入を防ぐため、テーブル一覧で検証する。
     */
    protected function guardTable(string $tableName): void
    {
        abort_unless(in_array($tableName, Schema::tables(), true), 404);
    }

    /**
     * 予約更新連携が有効でモデルが存在する場合のみ [モデルクラス, 作成URL] を返す。
     */
    protected function scheduledUpdate(string $tableName): array
    {
        $routeName = config('admin-crud.scheduled_update_create_route');
        $model = AdminCrud::modelFor($tableName);
        if ($routeName && $model && Route::has($routeName)) {
            return [$model, route($routeName)];
        }

        return [null, null];
    }

    /**
     * 全ビュー共通の変数を追加する。
     */
    protected function viewVars(array $vars): array
    {
        return $vars + [
            'layout' => config('admin-crud.layout_component', 'admin-crud::layout'),
            'jqueryUrl' => config('admin-crud.jquery_url'),
        ];
    }
}