File: /home/xedaptot/be.naniguide.com/app/Http/Controllers/Refactor/Admin/PluginController.php
<?php
namespace App\Http\Controllers\Refactor\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Model\Plugin;
class PluginController extends Controller
{
public function index(Request $request)
{
if (!$request->user()->admin->can('read', new Plugin())) {
return $this->notAuthorized();
}
return view('refactor.pages.admin.plugins.index');
}
public function listing(Request $request)
{
if (!$request->user()->admin->can('read', new Plugin())) {
return $this->notAuthorized();
}
if (!$request->user()->admin->can('readAll', new Plugin())) {
$request->merge(['admin_id' => $request->user()->admin->id]);
}
$request->merge(['no_customer' => true]);
// Two possible statuses for a plugin from the user's point of view:
// - active = running fine (DB status='active' AND not errored at runtime)
// - inactive = not running (DB status='inactive' OR errored at runtime)
// Errors (missing folder, broken composer.json, recorded in master file) live
// outside the DB, so we resolve them once here and pass into the query.
$erroredNames = Plugin::getErroredPluginNames();
$request->merge(['_errored_names' => $erroredNames]);
$query = Plugin::search($request);
if ($request->status === 'active') {
$query->where('plugins.status', 'active');
if (!empty($erroredNames)) {
$query->whereNotIn('plugins.name', $erroredNames);
}
} elseif ($request->status === 'inactive') {
$query->where(function ($q) use ($erroredNames) {
$q->where('plugins.status', 'inactive');
if (!empty($erroredNames)) {
$q->orWhereIn('plugins.name', $erroredNames);
}
});
}
// Per-page rule: grid view = 16 (4×4 desktop, full rows at all breakpoints).
$defaultPerPage = $request->view === 'grid' ? 16 : 15;
$plugins = $query->paginate($request->per_page ?? $defaultPerPage);
// Pre-check plugins for errors and setting URLs
$settingUrls = [];
$blacklist = [];
foreach ($plugins as $plugin) {
$error = $plugin->getPluginInfo('error');
if (!is_null($error)) {
$blacklist[$plugin->name] = 'Cannot load plugin: ' . $error;
continue;
}
try {
$composerJson = $plugin->getComposerJson();
if (array_key_exists('extra', $composerJson) && array_key_exists('setting-route', $composerJson['extra'])) {
// Settings link is always shown when the plugin declares a
// setting-route — some plugins (e.g., acelle/awswhitelabel)
// require configuration BEFORE activation can succeed, so
// hiding Settings on inactive plugins would brick the flow.
// Plugins that want Settings to be reachable only after
// activation gate their own routes with a "<plugin>.active"
// middleware (e.g., console.active).
$settingUrls[$plugin->name] = action($composerJson['extra']['setting-route']);
}
} catch (\Exception $ex) {
$blacklist[$plugin->name] = 'Plugin error: ' . $ex->getMessage();
}
}
$view = $request->view === 'grid' ? '_grid' : '_list';
return view('refactor.pages.admin.plugins.' . $view, compact('plugins', 'settingUrls', 'blacklist'));
}
public function install(Request $request)
{
if (!$request->user()->admin->can('install', Plugin::class)) {
return $this->notAuthorized();
}
if ($request->isMethod('post')) {
// Catch at the controller boundary so the real reason
// surfaces in the install dialog (the JS reads `data.message`).
// Without this, an SQL/file/composer failure bubbles up as
// Laravel's generic "Server Error" and the admin has nothing
// to grep for. Past incident 2026-05-15 (AppK / snapvalid):
// `plugins.description` was varchar(191), composer description
// ran 280 chars → SQLSTATE 22001 swallowed → UI showed
// "Server Error" with zero hint.
try {
$pluginName = Plugin::upload($request);
Plugin::register($pluginName);
} catch (\Throwable $ex) {
\Log::error('Plugin install failed: ' . $ex->getMessage(), ['exception' => $ex]);
return response()->json([
'status' => 'error',
'message' => $ex->getMessage(),
], 500);
}
return response()->json([
'status' => 'success',
'message' => trans('refactor/admin_plugins.flash.installed'),
]);
}
return view('refactor.pages.admin.plugins.install');
}
public function delete(Request $request)
{
$plugin = Plugin::findByUid($request->uid);
if (!$plugin || !$request->user()->admin->can('delete', $plugin)) {
return response()->json(['status' => 'error', 'message' => 'Not authorized'], 403);
}
try {
$plugin->deleteAndCleanup();
} catch (\Exception $ex) {
return response()->json(['status' => 'error', 'message' => $ex->getMessage()], 422);
}
return response()->json([
'status' => 'success',
'message' => trans('refactor/admin_plugins.flash.deleted'),
]);
}
public function enable(Request $request)
{
$plugins = Plugin::whereIn('uid', is_array($request->uids) ? $request->uids : explode(',', $request->uids));
try {
foreach ($plugins->get() as $plugin) {
if ($request->user()->admin->can('enable', $plugin)) {
$plugin->activate();
}
}
} catch (\Exception $ex) {
return response()->json(['status' => 'error', 'message' => $ex->getMessage()], 422);
}
return response()->json(['status' => 'success', 'message' => trans('refactor/admin_plugins.flash.enabled')]);
}
public function disable(Request $request)
{
$plugins = Plugin::whereIn('uid', is_array($request->uids) ? $request->uids : explode(',', $request->uids));
try {
foreach ($plugins->get() as $plugin) {
if ($request->user()->admin->can('disable', $plugin)) {
$plugin->disable();
}
}
} catch (\Exception $ex) {
return response()->json(['status' => 'error', 'message' => $ex->getMessage()], 422);
}
return response()->json(['status' => 'success', 'message' => trans('refactor/admin_plugins.flash.disabled')]);
}
public function refresh(Request $request)
{
$plugin = Plugin::findByUid($request->uid);
if (!$plugin || !$request->user()->admin->can('update', $plugin)) {
return response()->json(['status' => 'error', 'message' => 'Not authorized'], 403);
}
try {
$plugin->refreshFromComposer();
} catch (\Exception $ex) {
return response()->json(['status' => 'error', 'message' => $ex->getMessage()], 422);
}
return response()->json([
'status' => 'success',
'message' => trans('refactor/admin_plugins.flash.refreshed'),
]);
}
}