<?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'),
];
}
}