加入後台選單建立機制,後台設定選項建立機制
This commit is contained in:
parent
568d481742
commit
51f9e57e6a
10
app/Exceptions/AdminMenuItemArgInvalidException.php
Normal file
10
app/Exceptions/AdminMenuItemArgInvalidException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class AdminMenuItemArgInvalidException extends Exception
|
||||
{
|
||||
}
|
||||
192
app/Http/Controllers/Admin/Menu/BaseMenuItemController.php
Normal file
192
app/Http/Controllers/Admin/Menu/BaseMenuItemController.php
Normal file
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Menu;
|
||||
|
||||
use Auth;
|
||||
use View;
|
||||
use Arr;
|
||||
use Gate;
|
||||
use Str;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* Class BaseMenuItemController
|
||||
* @package App\Http\Controllers\Admin\Menu
|
||||
*/
|
||||
abstract class BaseMenuItemController
|
||||
{
|
||||
/**
|
||||
* 顯示名稱
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $slug;
|
||||
|
||||
/**
|
||||
* 所需權限,有一個符合即可
|
||||
* 如沒有填入,代表不設限
|
||||
* 子選單預設會沿用父選單的權限,如有填入,則再加上自己的權限
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [];
|
||||
|
||||
/**
|
||||
* 選單項目的Icon類別
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $iconClasses = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $badge;
|
||||
|
||||
/**
|
||||
* Default route handler
|
||||
*/
|
||||
public function handle(Request $request)
|
||||
{
|
||||
abort(404);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return trans($this->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSlug()
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $slug
|
||||
*/
|
||||
public function setSlug($slug): void
|
||||
{
|
||||
$this->slug = $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $permissions
|
||||
*/
|
||||
public function setPermissions(array $permissions): void
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bootstrap badge arguments
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function getBadge()
|
||||
{
|
||||
if(!empty($this->badge['label']) && !empty($this->badge['type'])) {
|
||||
return Arr::add($this->badge, 'isPill', false);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bootstrap badge arguments
|
||||
*
|
||||
* @param array $badge
|
||||
*/
|
||||
public function setBadge(array $badge): void
|
||||
{
|
||||
$this->badge = $badge;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|array
|
||||
*/
|
||||
public function getIconClass(): string
|
||||
{
|
||||
if(is_array($this->iconClasses)) {
|
||||
return implode(' ', $this->iconClasses);
|
||||
} else {
|
||||
return $this->iconClasses;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $iconClasses
|
||||
*/
|
||||
public function setIconClasses($iconClasses): void
|
||||
{
|
||||
$this->iconClasses = $iconClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasIcon()
|
||||
{
|
||||
return !empty($this->getIconClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasBadge()
|
||||
{
|
||||
return !empty($this->badge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function userHasPermission()
|
||||
{
|
||||
$hasAnyPermission = false;
|
||||
foreach ($this->permissions as $permission) {
|
||||
$hasAnyPermission = $hasAnyPermission || Gate::allows('permission:' . Str::kebab($permission));
|
||||
}
|
||||
return Auth::check() &&
|
||||
((!empty($this->permissions) && $hasAnyPermission) || empty($this->permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid if both name and slug are not empty
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid()
|
||||
{
|
||||
return $this->getName() && $this->getSlug();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Menu\Options\Children;
|
||||
|
||||
use App\Http\Controllers\Admin\Menu\BaseMenuItemController;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DevelopmentMenuItemController extends BaseMenuItemController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = 'adminMenu.items.options.development';
|
||||
|
||||
$this->slug = 'development';
|
||||
|
||||
$this->permissions = ['admin manage options development'];
|
||||
|
||||
$this->iconClasses = 'nav-icon icon-settings';
|
||||
}
|
||||
|
||||
public function handle(Request $request)
|
||||
{
|
||||
return view('admin.menu.options.development');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Menu\Options\Children;
|
||||
|
||||
use App\Http\Controllers\Admin\Menu\BaseMenuItemController;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GeneralMenuItemController extends BaseMenuItemController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = 'adminMenu.items.options.general';
|
||||
|
||||
$this->slug = 'general';
|
||||
|
||||
$this->permissions = ['admin manage options general'];
|
||||
|
||||
$this->iconClasses = 'nav-icon icon-settings';
|
||||
}
|
||||
|
||||
public function handle(Request $request)
|
||||
{
|
||||
return view('admin.menu.options.general');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Menu\Options\Children;
|
||||
|
||||
use App\Http\Controllers\Admin\Menu\BaseMenuItemController;
|
||||
use Gate;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PlatformMenuItemController extends BaseMenuItemController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = 'adminMenu.items.options.platform';
|
||||
|
||||
$this->slug = 'platform';
|
||||
|
||||
$this->permissions = ['admin manage options platform'];
|
||||
|
||||
$this->iconClasses = 'nav-icon icon-settings';
|
||||
}
|
||||
|
||||
public function handle(Request $request)
|
||||
{
|
||||
return view('admin.menu.options.platform');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Menu\Options;
|
||||
|
||||
use App\Http\Controllers\Admin\Menu\BaseMenuItemController;
|
||||
|
||||
class OptionsMenuItemController extends BaseMenuItemController
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->name = 'adminMenu.items.options.options';
|
||||
|
||||
$this->slug = 'options';
|
||||
|
||||
$this->permissions = ['admin view options'];
|
||||
|
||||
$this->iconClasses = 'nav-icon icon-settings';
|
||||
}
|
||||
}
|
||||
120
app/Http/Controllers/OptionsController.php
Normal file
120
app/Http/Controllers/OptionsController.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Exceptions\AdminOptionsInvalidException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Gate;
|
||||
use Arr;
|
||||
use mysql_xdevapi\Exception;
|
||||
use Str;
|
||||
use Validator;
|
||||
|
||||
/**
|
||||
* 網站設定選項
|
||||
* Class OptionsController
|
||||
* @package App\Http\Controllers\Admin
|
||||
*/
|
||||
class OptionsController extends Controller
|
||||
{
|
||||
private $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查權限
|
||||
*
|
||||
* @param $gateName
|
||||
*/
|
||||
private function gate($gateName)
|
||||
{
|
||||
if(Gate::denies($gateName)) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 驗證欄位
|
||||
*
|
||||
* @param $options
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
private function validateOptions($options)
|
||||
{
|
||||
$rules = collect($options['fields'])
|
||||
->filter(function($item, $key){
|
||||
return isset($item['validator']);
|
||||
})
|
||||
->map(function($item, $key){
|
||||
return $item['validator'];
|
||||
});
|
||||
|
||||
$this->validate($this->request, $rules->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新欄位資料
|
||||
*
|
||||
* @param $options
|
||||
*/
|
||||
private function update($options)
|
||||
{
|
||||
$fields = $this->request->only(array_keys($options['fields']));
|
||||
|
||||
$checkboxOptions = collect($options['fields'])
|
||||
->filter(function($item, $key){
|
||||
return isset($item['type']) && $item['type'] = 'checkbox';
|
||||
});
|
||||
|
||||
foreach ($fields as $key => $value) {
|
||||
app('Option')->$key = $value;
|
||||
}
|
||||
foreach ($checkboxOptions as $key => $checkboxOption) {
|
||||
if(!$this->request->has($key)) {
|
||||
app('Option')->$key = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 處理更新選項的動作並回應
|
||||
*
|
||||
* @param $options
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
private function generateResponse($options)
|
||||
{
|
||||
$this->validateOptions($options);
|
||||
|
||||
$this->update($options);
|
||||
return back()->with(['updated' => trans('message.success')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 處理更新選項的動作
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $args
|
||||
* @return \Illuminate\Http\RedirectResponse|mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __call($method, $args = [])
|
||||
{
|
||||
$config = config('admin.options');
|
||||
if(preg_match('/^update/', $method)) {
|
||||
$page = strtolower(preg_replace('/^update/', '', $method));
|
||||
if(in_array($page, array_keys($config))) {
|
||||
if(!empty($config[$page]['permission'])) {
|
||||
$this->gate('permission:' . Str::kebab($config[$page]['permission']));
|
||||
}
|
||||
return $this->generateResponse($config[$page]);
|
||||
}
|
||||
}
|
||||
throw new \Exception("method: $method dos not exist");
|
||||
}
|
||||
}
|
||||
@ -66,6 +66,7 @@ class Kernel extends HttpKernel
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'admin.area' => \App\Http\Middleware\AdminAreaGuard::class,
|
||||
'admin.menu' => \App\Http\Middleware\CheckAdminMenuPagePermission::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
56
app/Http/Middleware/CheckAdminMenuPagePermission.php
Normal file
56
app/Http/Middleware/CheckAdminMenuPagePermission.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Route;
|
||||
use Arr;
|
||||
|
||||
/**
|
||||
* 檢查Admin選單項目的route權限
|
||||
*
|
||||
* Class CheckAdminMenuPagePermission
|
||||
* @package App\Http\Middleware
|
||||
*/
|
||||
class CheckAdminMenuPagePermission
|
||||
{
|
||||
/**
|
||||
* 處理並檢查Admin選單項目的route權限
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
//取得目前的Route
|
||||
$currentRoute = Route::current();
|
||||
//取得目前的Route Controller
|
||||
$menuController = $currentRoute->controller;
|
||||
//取得目前的Route Method
|
||||
$method = $currentRoute->getActionMethod();
|
||||
|
||||
//如果Controller為選單項目的子類別
|
||||
if(is_subclass_of($menuController, \App\Http\Controllers\Admin\Menu\BaseMenuItemController::class)) {
|
||||
//如果方法為預設的route方法
|
||||
if($method == 'handle') {
|
||||
//取得slug組成的route name
|
||||
$routeName = str_replace(config('admin.route_name_prefix') . config('admin.menu.route_name_prefix'), '', Route::currentRouteName());
|
||||
//取得Admin選單項目
|
||||
$adminMenu = app('AdminMenu')->getMenu();
|
||||
//分解Slug
|
||||
$slugs = explode('.', $routeName);
|
||||
//如果slug數量大於1,則為子選單controller
|
||||
if(sizeof($slugs) > 1) {
|
||||
$permission = Arr::get($adminMenu, "{$slugs[0]}.children.{$slugs[1]}.userHasPermission");
|
||||
} else {
|
||||
$permission = Arr::get($adminMenu, "{$slugs[0]}.userHasPermission");
|
||||
}
|
||||
if(!$permission) {
|
||||
abort(403);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
15
app/Option.php
Normal file
15
app/Option.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Option extends Model
|
||||
{
|
||||
protected $fillable = ['key', 'value'];
|
||||
|
||||
public function scopeKey($query, $key)
|
||||
{
|
||||
return $query->where('key', $key);
|
||||
}
|
||||
}
|
||||
129
app/Presenters/Admin/MainMenuItemPresenter.php
Normal file
129
app/Presenters/Admin/MainMenuItemPresenter.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Presenters\Admin;
|
||||
|
||||
use App\Presenters\Html\HtmlPresenter;
|
||||
|
||||
class MainMenuItemPresenter
|
||||
{
|
||||
private $htmlPresenter;
|
||||
|
||||
public function __construct(HtmlPresenter $htmlPresenter)
|
||||
{
|
||||
$this->htmlPresenter = $htmlPresenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查選單項目權限
|
||||
*
|
||||
* @param array $item
|
||||
* @return mixed
|
||||
*/
|
||||
private function checkMenuItemPermission($item)
|
||||
{
|
||||
return !empty($item['userHasPermission']) && $item['userHasPermission'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any child has permission
|
||||
*
|
||||
* @param $item
|
||||
* @return bool
|
||||
*/
|
||||
private function checkAnyChildPermission($item)
|
||||
{
|
||||
if(isset($item['children'])) {
|
||||
$anyChildHasPermission = false;
|
||||
foreach ($item['children'] as $childMenu) {
|
||||
$anyChildHasPermission = $anyChildHasPermission || $childMenu['userHasPermission'];
|
||||
}
|
||||
return $anyChildHasPermission;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 回傳選單項目Html
|
||||
*
|
||||
* return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$menu = app('AdminMenu')->getMenu();
|
||||
|
||||
$html = '';
|
||||
$htmlPresenter = $this->htmlPresenter;
|
||||
|
||||
foreach ($menu as $parentKey => $item) {
|
||||
switch($item['type']) {
|
||||
case 'title':
|
||||
$html .= $htmlPresenter->li([
|
||||
'class' => 'nav-title',
|
||||
'html' => trans('menu.titles.' . $item['name'])
|
||||
]);
|
||||
break;
|
||||
case 'item':
|
||||
if($this->checkMenuItemPermission($item) && $this->checkAnyChildPermission($item)) {
|
||||
$hasChildren = !empty($item['children']);
|
||||
$html .= $htmlPresenter->li([
|
||||
'class' => [
|
||||
'nav-item',
|
||||
$hasChildren ? 'nav-dropdown has-children' : '',
|
||||
"slug-{$item['slug']}"
|
||||
],
|
||||
'html' => [
|
||||
$htmlPresenter->a([
|
||||
'class' => [
|
||||
'nav-link',
|
||||
$hasChildren ? 'nav-dropdown-toggle' : '',
|
||||
],
|
||||
'href' => $item['link'],
|
||||
'html' => [
|
||||
!empty($item['iconClass']) ? $htmlPresenter->i(['class' => $item['iconClass']]) : '',
|
||||
$item['name'],
|
||||
!empty($item['badge']) ? $htmlPresenter->bs()->badge($item['badge']['label'], $item['badge']['type'], $item['badge']['isPill']) : ''
|
||||
],
|
||||
]),
|
||||
$hasChildren ? $htmlPresenter->ul([
|
||||
'class' => 'nav-dropdown-items submenu',
|
||||
'html' => function() use ($hasChildren, $item, $htmlPresenter) {
|
||||
$html = '';
|
||||
if($hasChildren) {
|
||||
foreach ($item['children'] as $childItem) {
|
||||
if($this->checkMenuItemPermission($childItem)) {
|
||||
$html .= $htmlPresenter->li([
|
||||
'class' => [
|
||||
'nav-item',
|
||||
"slug-{$childItem['slug']}",
|
||||
],
|
||||
'html' => $htmlPresenter->a([
|
||||
'class' => 'nav-link',
|
||||
'href' => $childItem['link'],
|
||||
'html' => [
|
||||
!empty($childItem['iconClass']) ? $htmlPresenter->i(['class' => $childItem['iconClass']]) : '',
|
||||
$childItem['name'],
|
||||
!empty($childItem['badge']) ? $htmlPresenter->bs()->badge($childItem['badge']['label'], $childItem['badge']['type'], $childItem['badge']['isPill']) : ''
|
||||
]
|
||||
])
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
]) : ''
|
||||
]
|
||||
]);
|
||||
}
|
||||
break;
|
||||
case 'divider':
|
||||
$html .= $htmlPresenter->li([
|
||||
'class' => 'nav-divider'
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
echo $html;
|
||||
}
|
||||
}
|
||||
168
app/Presenters/Admin/OptionFormFieldsPresenter.php
Normal file
168
app/Presenters/Admin/OptionFormFieldsPresenter.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace App\Presenters\Admin;
|
||||
|
||||
use Arr;
|
||||
|
||||
class OptionFormFieldsPresenter
|
||||
{
|
||||
public function render($page)
|
||||
{
|
||||
$options = config('admin.options.' . $page);
|
||||
$presenter = app('Html');
|
||||
$optionRepo = app('Option');
|
||||
$html = '';
|
||||
if(!empty($options['fields'])) {
|
||||
foreach($options['fields'] as $key => $option) {
|
||||
$type = !empty($option['type']) ? $option['type'] : 'text';
|
||||
$required = !empty($option['required']) && $option['required'] ? true : false;
|
||||
$label = !empty($option['label']) ? $option['label'] : $key;
|
||||
$html .= $presenter->div([
|
||||
'class' => 'form-group row',
|
||||
'html' => [
|
||||
$presenter->div([
|
||||
'class' => 'col-12 col-md-6',
|
||||
'html' => $presenter->label([
|
||||
'for' => 'option-' . $key,
|
||||
'html' => [
|
||||
trans('adminOptionLabels.' . $label),
|
||||
$required ? $presenter->span([
|
||||
'class' => 'required',
|
||||
'html' => '*'
|
||||
]) : ''
|
||||
]
|
||||
])
|
||||
]),
|
||||
$presenter->div([
|
||||
'class' => 'col-12 col-md-6',
|
||||
'html' => function() use ($key, $type, $required, $presenter, $optionRepo) {
|
||||
$html = '';
|
||||
|
||||
$bastHtmlArgs = [
|
||||
'name' => $key,
|
||||
'id' => 'option-' . $key,
|
||||
];
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
case 'password':
|
||||
case 'email':
|
||||
case 'number':
|
||||
$htmlArgs = array_merge([
|
||||
'type' => $type,
|
||||
'value' => $optionRepo->$key,
|
||||
'class' => 'form-control',
|
||||
], $bastHtmlArgs);
|
||||
|
||||
if($required)
|
||||
$htmlArgs['required'] = null;
|
||||
|
||||
$html .= $presenter->input($htmlArgs);
|
||||
break;
|
||||
case 'checkbox':
|
||||
$htmlArgs = array_merge([
|
||||
'type' => 'checkbox',
|
||||
'value' => 1,
|
||||
], $bastHtmlArgs);
|
||||
|
||||
if($optionRepo->$key) {
|
||||
$htmlArgs['checked'] = 'checked';
|
||||
}
|
||||
|
||||
$html .= $presenter->input($htmlArgs);
|
||||
break;
|
||||
case 'textarea':
|
||||
$htmlArgs = array_merge([
|
||||
'html' => $optionRepo->$key,
|
||||
'class' => 'form-control',
|
||||
], $bastHtmlArgs);
|
||||
|
||||
if($required)
|
||||
$htmlArgs['required'] = null;
|
||||
|
||||
if(!empty($option['row']))
|
||||
$htmlArgs['row'] = $option['row'];
|
||||
|
||||
$html .= $presenter->textarea($htmlArgs);
|
||||
break;
|
||||
case 'select':
|
||||
$selectOptions = !empty($option['options']) ? $option['options'] : null;
|
||||
$htmlArgs = array_merge([
|
||||
'class' => 'form-control',
|
||||
], $bastHtmlArgs);
|
||||
$currentValue = $optionRepo->$key;
|
||||
if(!empty($selectOptions)) {
|
||||
$htmlArgs['html'] = function() use ($currentValue, $selectOptions, $presenter) {
|
||||
$html = '';
|
||||
foreach ($selectOptions as $value => $label) {
|
||||
$htmlArgs = [
|
||||
'value' => $value,
|
||||
'html' => trans('form.options.' . $label),
|
||||
];
|
||||
if($currentValue == $value) {
|
||||
$htmlArgs['selected'] = 'selected';
|
||||
}
|
||||
$html .= $presenter->option($htmlArgs);
|
||||
}
|
||||
return $html;
|
||||
};
|
||||
}
|
||||
|
||||
if($required)
|
||||
$htmlArgs['required'] = null;
|
||||
|
||||
$html .= $presenter->select($htmlArgs);
|
||||
break;
|
||||
case 'radio':
|
||||
$radioOptions = !empty($option['options']) ? $option['options'] : null;
|
||||
$currentValue = $optionRepo->$key;
|
||||
$defaultValue = !empty($option['default']) ? $option['default'] : null;
|
||||
if(!empty($radioOptions)) {
|
||||
foreach ($radioOptions as $value => $label) {
|
||||
$html .= $presenter->div([
|
||||
'class' => 'form-check',
|
||||
'html' => function() use ($key, $currentValue, $defaultValue, $label, $value, $radioOptions, $presenter) {
|
||||
$html = '';
|
||||
$inputId = 'option-' . $key . '-' . $value;
|
||||
|
||||
$inputHtmlArgs = [
|
||||
'type' => 'radio',
|
||||
'class' => 'form-check-input',
|
||||
'name' => $key,
|
||||
'id' => $inputId,
|
||||
'value' => $value
|
||||
];
|
||||
|
||||
if($currentValue === null) {
|
||||
if($defaultValue && $defaultValue == $value) {
|
||||
$inputHtmlArgs['checked'] = 'checked';
|
||||
} elseif(array_key_first($radioOptions) == $value) {
|
||||
$inputHtmlArgs['checked'] = 'checked';
|
||||
}
|
||||
} elseif($currentValue == $value) {
|
||||
$inputHtmlArgs['checked'] = 'checked';
|
||||
}
|
||||
|
||||
$html .= $presenter->input($inputHtmlArgs);
|
||||
|
||||
$html .= $presenter->label([
|
||||
'class' => 'form-check-label',
|
||||
'for' => $inputId,
|
||||
'html' => $label
|
||||
]);
|
||||
return $html;
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
]),
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
128
app/Presenters/Html/BaseHtmlPresenter.php
Normal file
128
app/Presenters/Html/BaseHtmlPresenter.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Presenters\Html;
|
||||
|
||||
use Arr;
|
||||
|
||||
trait BaseHtmlPresenter
|
||||
{
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
return $this->tag($name, isset($arguments[0]) ? $arguments[0] : []);
|
||||
}
|
||||
|
||||
public function getAttribute($key, $value = null)
|
||||
{
|
||||
if($value === null) {
|
||||
return "$key";
|
||||
} else{
|
||||
return "$key=\"{$value}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public function getAttributes($fieldArgs = [])
|
||||
{
|
||||
$attributes = [];
|
||||
$multiValueAttributes = [
|
||||
'class',
|
||||
'style',
|
||||
];
|
||||
Arr::forget($fieldArgs, 'html');
|
||||
foreach ($fieldArgs as $name => $value) {
|
||||
if(in_array($name, $multiValueAttributes)) {
|
||||
if(is_array($value)) {
|
||||
$value = array_filter($value);
|
||||
$values = trim(implode(' ', $value));
|
||||
} else {
|
||||
$values = $value;
|
||||
}
|
||||
array_push($attributes, $this->getAttribute($name, $values));
|
||||
} else {
|
||||
array_push($attributes, $this->getAttribute($name, $value));
|
||||
}
|
||||
}
|
||||
return trim(implode(' ', $attributes));
|
||||
}
|
||||
|
||||
public function tag($tag, $fieldArgs = [], $echo = false)
|
||||
{
|
||||
$selfClosingTags = array(
|
||||
'area',
|
||||
'base',
|
||||
'br',
|
||||
'col',
|
||||
'command',
|
||||
'embed',
|
||||
'hr',
|
||||
'img',
|
||||
'input',
|
||||
'keygen',
|
||||
'link',
|
||||
'menuitem',
|
||||
'meta',
|
||||
'param',
|
||||
'source',
|
||||
'track',
|
||||
'wbr',
|
||||
);
|
||||
$htmlElements = !isset($fieldArgs['html']) ? '' : $fieldArgs['html'];
|
||||
$htmlContent = '';
|
||||
if(is_array($htmlElements)) {
|
||||
foreach ($htmlElements as $htmlElement) {
|
||||
if($htmlElement instanceof \Closure) {
|
||||
$htmlContent .= call_user_func($htmlElement);
|
||||
} else {
|
||||
$htmlContent .= $htmlElement;
|
||||
}
|
||||
}
|
||||
} elseif($htmlElements instanceof \Closure) {
|
||||
$htmlContent = call_user_func($htmlElements);
|
||||
} else {
|
||||
$htmlContent = $htmlElements;
|
||||
}
|
||||
|
||||
$isSelfClosing = in_array($tag, $selfClosingTags);
|
||||
|
||||
return '<' . trim(implode(' ', [$tag, $this->getAttributes($fieldArgs)])) . '>' .
|
||||
(!$isSelfClosing ? $htmlContent . '</' . $tag . '>' : '');
|
||||
}
|
||||
|
||||
public function readonly($readonly = true)
|
||||
{
|
||||
return $readonly ? $this->getAttribute('readonly') : '';
|
||||
}
|
||||
|
||||
public function disabled($disabled = true)
|
||||
{
|
||||
return $disabled ? $this->getAttribute('disabled', 'disabled') : '';
|
||||
}
|
||||
|
||||
public function required($required = true)
|
||||
{
|
||||
return $required ? $this->getAttribute('required') : '';
|
||||
}
|
||||
|
||||
public function checked($checked = true)
|
||||
{
|
||||
return $checked ? $this->getAttribute('checked', 'checked') : '';
|
||||
}
|
||||
|
||||
public function checkChecked($currentValue, $comparedValue)
|
||||
{
|
||||
if(is_array($comparedValue)) {
|
||||
return in_array($currentValue, $comparedValue) ? $this->checked() : '';
|
||||
} else {
|
||||
return $currentValue == $comparedValue ? $this->checked() : '';
|
||||
}
|
||||
}
|
||||
|
||||
public function selected($selected = true)
|
||||
{
|
||||
return $selected ? $this->getAttribute('selected', 'selected') : '';
|
||||
}
|
||||
|
||||
public function selectSelected($currentValue, $comparedValue)
|
||||
{
|
||||
return $currentValue == $comparedValue ? $this->selected() : '';
|
||||
}
|
||||
}
|
||||
158
app/Presenters/Html/Bootstrap4HtmlPresenter.php
Normal file
158
app/Presenters/Html/Bootstrap4HtmlPresenter.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Presenters\Html;
|
||||
|
||||
class Bootstrap4HtmlPresenter
|
||||
{
|
||||
use BaseHtmlPresenter;
|
||||
|
||||
/**
|
||||
* 返回Badge元件的Html
|
||||
*
|
||||
* @param string $content 內容
|
||||
* @param string $type 類型
|
||||
* @param bool $isPill 膠囊型態
|
||||
* @param bool $isLink 連結
|
||||
* @return string
|
||||
*/
|
||||
public function badge(string $content = '', string $type = 'primary', $isPill = false, string $link = '')
|
||||
{
|
||||
$allowedTypes = [
|
||||
'primary',
|
||||
'secondary',
|
||||
'success',
|
||||
'danger',
|
||||
'warning',
|
||||
'info',
|
||||
'light',
|
||||
'dark',
|
||||
];
|
||||
if(in_array(strtolower($type), $allowedTypes)) {
|
||||
$tag = $link ? 'a' : 'span';
|
||||
$htmlArgs = [
|
||||
'class' => [
|
||||
'badge',
|
||||
'badge-' . $type,
|
||||
],
|
||||
'role' => 'alert',
|
||||
'html' => $content
|
||||
];
|
||||
if($isPill)
|
||||
$htmlArgs['class'][] = 'badge-pill';
|
||||
|
||||
if($link)
|
||||
$htmlArgs['href'] = $link;
|
||||
|
||||
return $this->tag($tag, $htmlArgs);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回Alert元件的Html
|
||||
*
|
||||
* @param string $content 內容,可包含連結,連結格式為:key,再於alertLink參數使用陣列指定傳入網址
|
||||
* @param $type string 類型
|
||||
* @param bool $dismiss 是否有關閉的按鈕
|
||||
* @param array $alertLinks 文字中連結,陣列格式為 key => url
|
||||
* @return string
|
||||
*/
|
||||
public function alert(string $content = '', string $type = 'primary', $dismiss = true, array $alertLinks = [])
|
||||
{
|
||||
$allowedTypes = [
|
||||
'primary',
|
||||
'secondary',
|
||||
'success',
|
||||
'danger',
|
||||
'warning',
|
||||
'info',
|
||||
'light',
|
||||
'dark',
|
||||
];
|
||||
if(in_array(strtolower($type), $allowedTypes)) {
|
||||
if(!empty($alertLinks)) {
|
||||
foreach ($alertLinks as $key => $link) {
|
||||
$linkHtmlArgs = [
|
||||
'href' => $link['url'],
|
||||
'class' => 'alert-link',
|
||||
'html' => $link['content']
|
||||
];
|
||||
if(!empty($link['target'])) {
|
||||
$linkHtmlArgs['target'] = $link['target'];
|
||||
}
|
||||
$linkHtml = $this->tag('a', $linkHtmlArgs);
|
||||
$content = str_replace(":$key", $linkHtml, $content);
|
||||
}
|
||||
}
|
||||
$dismissButtonHtml = '';
|
||||
if($dismiss) {
|
||||
$dismissButtonHtml = $this->tag('button', [
|
||||
'type' => 'button',
|
||||
'class' => 'close',
|
||||
'data-dismiss' => 'alert',
|
||||
'aria-label' => 'Close',
|
||||
'html' => $this->tag('span', [
|
||||
'aria-hidden' => 'true',
|
||||
'html' => '×'
|
||||
])
|
||||
]);
|
||||
}
|
||||
$htmlArgs = [
|
||||
'class' => [
|
||||
'alert',
|
||||
'alert-' . $type,
|
||||
],
|
||||
'html' => $content . $dismissButtonHtml
|
||||
];
|
||||
|
||||
if($dismiss) {
|
||||
$htmlArgs['class'][] = 'alert-dismissible fade show';
|
||||
}
|
||||
|
||||
return $this->tag('div', $htmlArgs);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回Breadcrumb元件的Html
|
||||
*
|
||||
* @param array $items
|
||||
* @return string
|
||||
*/
|
||||
public function breadcrumb(array $items = [])
|
||||
{
|
||||
$html = $this->tag('nav', [
|
||||
'aria-label' => 'breadcrumb',
|
||||
'html' => $this->tag('ol', [
|
||||
'class' => 'breadcrumb',
|
||||
'html' => function() use ($items) {
|
||||
$itemsHtml = '';
|
||||
foreach ((array)$items as $item) {
|
||||
$args = [
|
||||
'class' => [
|
||||
'breadcrumb-item'
|
||||
],
|
||||
'html' => !empty($item['content']) ? $item['content'] : ''
|
||||
];
|
||||
|
||||
if(!empty($item['active'])) {
|
||||
$args['class'][] = 'active';
|
||||
$args['aria-current'] = 'page';
|
||||
$args['html'] = $this->tag('a', [
|
||||
'href' => !empty($item['href']) ? $item['href'] : '#',
|
||||
'html' => !empty($item['content']) ? $item['content'] : ''
|
||||
]);
|
||||
}
|
||||
|
||||
$itemsHtml .= $this->tag('li', $args);
|
||||
}
|
||||
return $itemsHtml;
|
||||
}
|
||||
])
|
||||
]);
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
18
app/Presenters/Html/FontAwesomeHtmlPresenter.php
Normal file
18
app/Presenters/Html/FontAwesomeHtmlPresenter.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Presenters\Html;
|
||||
|
||||
class FontAwesomeHtmlPresenter
|
||||
{
|
||||
use BaseHtmlPresenter;
|
||||
|
||||
public function icon($name = '')
|
||||
{
|
||||
return $this->tag('i', [
|
||||
'class' => [
|
||||
'fa',
|
||||
'fa-' . $name
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
47
app/Presenters/Html/HtmlPresenter.php
Normal file
47
app/Presenters/Html/HtmlPresenter.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Presenters\Html;
|
||||
|
||||
|
||||
/**
|
||||
* Class HtmlPresenter
|
||||
* @package App\Presenters\Html
|
||||
*
|
||||
* @property Bootstrap4HtmlPresenter $bootstrap
|
||||
* @property FontAwesomeHtmlPresenter $fontawesome
|
||||
*/
|
||||
class HtmlPresenter
|
||||
{
|
||||
use BaseHtmlPresenter;
|
||||
|
||||
private $bootstrap;
|
||||
|
||||
private $fontawesome;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->bootstrap = app()->make(Bootstrap4HtmlPresenter::class);
|
||||
$this->fontawesome = app()->make(FontAwesomeHtmlPresenter::class);
|
||||
}
|
||||
|
||||
public function bootstrap()
|
||||
{
|
||||
return $this->bootstrap;
|
||||
}
|
||||
|
||||
public function bs()
|
||||
{
|
||||
return $this->bootstrap();
|
||||
}
|
||||
|
||||
public function fontawesome()
|
||||
{
|
||||
return $this->fontawesome;
|
||||
}
|
||||
|
||||
public function fa()
|
||||
{
|
||||
return $this->fontawesome();
|
||||
}
|
||||
}
|
||||
113
app/Providers/AdminMenuServiceProvider.php
Normal file
113
app/Providers/AdminMenuServiceProvider.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Route;
|
||||
use Auth;
|
||||
|
||||
/**
|
||||
* 從Config檔案建立Menu Item,並註冊相關Route
|
||||
*
|
||||
* Class AdminMenuServiceProvider
|
||||
* @package App\Providers
|
||||
*/
|
||||
class AdminMenuServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected $namespace = '\\';
|
||||
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
/** 從Config建Admin Menu物件內容 */
|
||||
Route::group(['prefix' => config('admin.route') . '/main', 'middleware' => ['web', 'admin.area', 'admin.menu'], 'as' => config('admin.route_name_prefix')], function(){
|
||||
|
||||
//Config的Menu組態設定
|
||||
$menuItems = config('admin.menuItems');
|
||||
//Menu Route Name的前綴
|
||||
$menuRouteNamePrefix = config('admin.menu.route_name_prefix');
|
||||
//MenuItemContoller的Route處理函式
|
||||
$menuItemControllerHandlingMethod = 'handle';
|
||||
|
||||
/**
|
||||
* MenuItem的處理物件,為全域Singleton物件
|
||||
* @var \App\Services\Admin\MainMenuItemService $adminMenu
|
||||
*/
|
||||
$adminMenu = app('AdminMenu');
|
||||
|
||||
foreach ($menuItems as $menuItem) {
|
||||
if($menuItem['type'] == 'item') {
|
||||
if(class_exists($menuItem['controller'])) {
|
||||
/**
|
||||
* @var \App\Http\Controllers\Admin\Menu\BaseMenuItemController $parentItem
|
||||
*/
|
||||
$parentItem = new $menuItem['controller'];
|
||||
if(!$parentItem->isValid()) {
|
||||
continue;
|
||||
}
|
||||
$hasChildren = !empty($menuItem['children']);
|
||||
$routeName = false;
|
||||
$link = '#';
|
||||
if(!$hasChildren) {
|
||||
$routeName = "{$menuRouteNamePrefix}{$parentItem->getSlug()}";
|
||||
$action = "{$menuItem['controller']}@$menuItemControllerHandlingMethod";
|
||||
//註冊Route
|
||||
Route::get($parentItem->getSlug(), $action)->name($routeName);
|
||||
}
|
||||
//註冊選單項目
|
||||
$adminMenu->addItem([
|
||||
'type' => 'item',
|
||||
'parentSlug' => false,
|
||||
'routeName' => $routeName,
|
||||
], $parentItem);
|
||||
if($hasChildren) {
|
||||
foreach((array)$menuItem['children'] as $childMenuItem) {
|
||||
if(class_exists($childMenuItem)) {
|
||||
/**
|
||||
* @var \App\Http\Controllers\Admin\Menu\BaseMenuItemController $childItem
|
||||
*/
|
||||
$childItem = new $childMenuItem;
|
||||
if(!$childItem->isValid()) {
|
||||
continue;
|
||||
}
|
||||
$routeName = "{$menuRouteNamePrefix}{$parentItem->getSlug()}.{$childItem->getSlug()}";
|
||||
$action = "$childMenuItem@$menuItemControllerHandlingMethod";
|
||||
//註冊Route
|
||||
Route::get("{$parentItem->getSlug()}/{$childItem->getSlug()}", $action)->name($routeName);
|
||||
//註冊選單項目
|
||||
$adminMenu->addItem([
|
||||
'type' => 'item',
|
||||
'parentSlug' => $parentItem->getSlug(),
|
||||
'routeName' => $routeName,
|
||||
], $childItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif($menuItem['type'] == 'title') {
|
||||
$adminMenu->addItem([
|
||||
'type' => 'title',
|
||||
'name' => $menuItem['name']
|
||||
]);
|
||||
} elseif($menuItem['type'] == 'divider') {
|
||||
$adminMenu->addItem([
|
||||
'type' => 'divider',
|
||||
]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,9 @@ namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Str;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -25,6 +28,38 @@ class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
//
|
||||
//administrator不受Gate限制
|
||||
Gate::before(function($user, $ability) {
|
||||
/** @var \App\User $user */
|
||||
if($user->hasRole('administrator')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// Register all permissions as gate ability
|
||||
$allPermissions = Permission::all();
|
||||
foreach ($allPermissions as $permission) {
|
||||
/** @var Permission $permission */
|
||||
$permissionName = $permission->name;
|
||||
$gateName = 'permission:' . Str::kebab($permissionName);
|
||||
Gate::define($gateName, function ($user) use ($permissionName) {
|
||||
/** @var \App\User $user */
|
||||
return $user->hasPermissionTo($permissionName);
|
||||
});
|
||||
}
|
||||
|
||||
// Register all roles as gate ability
|
||||
$allRoles = Role::all();
|
||||
foreach ($allRoles as $role) {
|
||||
/** @var Role $permission */
|
||||
$roleName = $role->name;
|
||||
$gateName = 'role:' . Str::kebab($roleName);
|
||||
Gate::define($gateName, function ($user) use ($roleName) {
|
||||
/** @var \App\User $user */
|
||||
return $user->hasRole($roleName);
|
||||
});
|
||||
}
|
||||
} catch (\Exception $e) {}
|
||||
}
|
||||
}
|
||||
|
||||
47
app/Providers/OptionRouteServiceProvider.php
Normal file
47
app/Providers/OptionRouteServiceProvider.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Controllers\Admin\OptionsController;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Route;
|
||||
|
||||
/**
|
||||
* 建立後台設定頁面的route
|
||||
*
|
||||
* Class OptionRouteServiceProvider
|
||||
* @package App\Providers
|
||||
*/
|
||||
class OptionRouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Route::group(['prefix' => config('admin.route'), 'middleware' => ['web', 'admin.area'], 'as' => config('admin.route_name_prefix')], function(){
|
||||
$config = config('admin.options');
|
||||
if(empty($config)) return;
|
||||
$options = array_keys(config('admin.options'));
|
||||
|
||||
foreach ($options as $page) {
|
||||
$action = OptionsController::class;
|
||||
$action .= '@update' . ucfirst($page);
|
||||
Route::match(['put', 'patch'], "/options/{$page}/update", $action)
|
||||
->name("options.{$page}.update");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Option;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
/**
|
||||
@ -25,6 +24,25 @@ class SingletonServiceProvider extends ServiceProvider
|
||||
$this->app->singleton(\App\Services\SiteStateService::class, function($app){
|
||||
return new \App\Services\SiteStateService;
|
||||
});
|
||||
|
||||
$this->app->alias(\App\Services\Admin\MainMenuItemService::class, 'AdminMenu');
|
||||
$this->app->singleton(\App\Services\Admin\MainMenuItemService::class, function($app){
|
||||
return new \App\Services\Admin\MainMenuItemService;
|
||||
});
|
||||
|
||||
//Repositories
|
||||
$this->app->alias(\App\Repositories\OptionRepository::class, 'Option');
|
||||
$this->app->singleton(\App\Repositories\OptionRepository::class, function($app){
|
||||
return new \App\Repositories\OptionRepository($app->make(\App\Option::class));
|
||||
});
|
||||
|
||||
// Presenters
|
||||
$this->app->alias(\App\Presenters\Html\HtmlPresenter::class, 'Html');
|
||||
$this->app->singleton(\App\Presenters\Html\HtmlPresenter::class, function($app){
|
||||
return new \App\Presenters\Html\HtmlPresenter;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
125
app/Reposotories/OptionRepository.php
Normal file
125
app/Reposotories/OptionRepository.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Option;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
/**
|
||||
* Class OptionRepository
|
||||
* @package App\Repositories
|
||||
*
|
||||
* @property \App\Option $model
|
||||
* @method \App\Option getModel()
|
||||
*/
|
||||
class OptionRepository extends BaseRepository
|
||||
{
|
||||
private $cacheKeyPrefix = 'options_';
|
||||
|
||||
public function __construct(Option $option)
|
||||
{
|
||||
$this->setModel($option);
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->getOption($key);
|
||||
}
|
||||
|
||||
public function __set($key, $value)
|
||||
{
|
||||
$this->setOption($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增Option
|
||||
*
|
||||
* @param $key
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function addOption($key, $value)
|
||||
{
|
||||
if(is_string($key) && $key) {
|
||||
if(!is_string($value)) {
|
||||
if(is_bool($value) || is_null($value)) {
|
||||
$value = (string) $value;
|
||||
} else {
|
||||
$value = serialize($value);
|
||||
}
|
||||
}
|
||||
$this->model->updateOrInsert(['key' => $key], ['value' => $value]);
|
||||
Cache::forget($this->cacheKeyPrefix . $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新Option
|
||||
*
|
||||
* @param $key
|
||||
* @param $value
|
||||
* @return void
|
||||
*/
|
||||
public function setOption($key, $value)
|
||||
{
|
||||
$this->addOption($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得Option
|
||||
*
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOption($key)
|
||||
{
|
||||
if(Cache::has($this->cacheKeyPrefix . $key)) {
|
||||
return Cache::get($this->cacheKeyPrefix . $key);
|
||||
}
|
||||
$value = null;
|
||||
try {
|
||||
$option = $this->model->where(['key' => $key])->first();
|
||||
if($option) {
|
||||
$value = @unserialize($option->value);
|
||||
if($value === false) {
|
||||
$value = $option->value;
|
||||
}
|
||||
} else {
|
||||
$value = null;
|
||||
}
|
||||
Cache::put($this->cacheKeyPrefix . $key, $value);
|
||||
} catch(\Exception $e) {}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刪除Option
|
||||
*
|
||||
* @param $key
|
||||
* @throws \Exception
|
||||
* @return void
|
||||
*/
|
||||
public function deleteOption($key)
|
||||
{
|
||||
$option = $this->model->where('key', $key)->first();
|
||||
if($option) {
|
||||
$option->delete();
|
||||
Cache::forget($this->cacheKeyPrefix . $key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批次更新Options
|
||||
*
|
||||
* @param $options
|
||||
* @return void
|
||||
*/
|
||||
public function setOptions($options)
|
||||
{
|
||||
if(is_array($options)) {
|
||||
foreach ($options as $key => $value) {
|
||||
$this->setOption($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
149
app/Services/Admin/MainMenuItemService.php
Normal file
149
app/Services/Admin/MainMenuItemService.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Admin;
|
||||
|
||||
use App\Exceptions\AdminMenuItemArgInvalidException;
|
||||
use App\Http\Controllers\Admin\Menu\BaseMenuItemController;
|
||||
use Composer\Util\Git;
|
||||
use Validator;
|
||||
use Arr;
|
||||
use Route;
|
||||
|
||||
/**
|
||||
* 存放Admin主選單項目
|
||||
*
|
||||
* Class MainMenuItemHandler
|
||||
* @package App\Handlers\Admin
|
||||
*/
|
||||
class MainMenuItemService
|
||||
{
|
||||
private $menu = [];
|
||||
|
||||
private $allowedItemTypes = [
|
||||
'item',
|
||||
'title',
|
||||
'divider'
|
||||
];
|
||||
|
||||
private $adminRouteNamePrefix;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->adminRouteNamePrefix = config('admin.route_name_prefix');
|
||||
}
|
||||
|
||||
/**
|
||||
* 驗證新增選單項目方法參數
|
||||
*
|
||||
* @param $args
|
||||
* @throws AdminMenuItemArgInvalidException
|
||||
*/
|
||||
private function validateItemArgs($args)
|
||||
{
|
||||
$validator = Validator::make($args, [
|
||||
'type' => 'required',
|
||||
'name' => 'required_if:type,title',
|
||||
'slug' => 'required_if:type,item',
|
||||
'controller' => 'required_if:type,item',
|
||||
'routeName' => 'required_if:type,item',
|
||||
]);
|
||||
if($validator->fails()) {
|
||||
throw new AdminMenuItemArgInvalidException($validator->getMessageBag()->first());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增選單項目
|
||||
*
|
||||
* @param array $givenArgs
|
||||
* @param BaseMenuItemController $menuItemController;
|
||||
* @throws AdminMenuItemArgInvalidException
|
||||
*/
|
||||
public function addItem($givenArgs, BaseMenuItemController $menuItemController = null)
|
||||
{
|
||||
|
||||
$args = [
|
||||
'slug' => null,
|
||||
'routeName' => null,
|
||||
'link' => null,
|
||||
'iconClass' => null,
|
||||
'badge' => null,
|
||||
'controller' => null,
|
||||
'parentSlug' => null,
|
||||
];
|
||||
|
||||
if($givenArgs['type'] == 'item' && $menuItemController) {
|
||||
$givenArgs['slug'] = $menuItemController->getSlug();
|
||||
$givenArgs['iconClass'] = $menuItemController->getIconClass();
|
||||
$givenArgs['badge'] = $menuItemController->getBadge();
|
||||
$givenArgs['controller'] = $menuItemController;
|
||||
}
|
||||
|
||||
$this->validateItemArgs($givenArgs);
|
||||
|
||||
foreach ($givenArgs as $key => $value) {
|
||||
$args = Arr::add($args, $key, $value);
|
||||
}
|
||||
|
||||
if(in_array($args['type'], $this->allowedItemTypes)) {
|
||||
if($args['parentSlug']) {
|
||||
Arr::set($this->menu, "{$args['parentSlug']}.children.{$args['slug']}", $args);
|
||||
} else {
|
||||
if($args['type'] == 'item') {
|
||||
$this->menu[$args['slug']] = $args;
|
||||
} else {
|
||||
$this->menu[] = $args;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得選單項目
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMenu()
|
||||
{
|
||||
foreach ($this->menu as $parentSlug => $parentItem) {
|
||||
if($parentItem['controller']) {
|
||||
$userHasParentPermission = $parentItem['controller']->userHasPermission();
|
||||
Arr::set($this->menu, "$parentSlug.userHasPermission", $userHasParentPermission);
|
||||
|
||||
Arr::set($this->menu, "$parentSlug.name", $parentItem['controller']->getName());
|
||||
|
||||
$parentRouteName = $this->adminRouteNamePrefix . $parentItem['routeName'];
|
||||
$link = Route::has($parentRouteName) ? route($parentRouteName) : '#';
|
||||
Arr::set($this->menu, "$parentSlug.link", $link);
|
||||
|
||||
if(!empty($parentItem['children'])) {
|
||||
foreach ($parentItem['children'] as $childSlug => $childItem) {
|
||||
if($childItem['controller']) {
|
||||
Arr::set($this->menu,
|
||||
"$parentSlug.children.$childSlug.userHasPermission",
|
||||
$userHasParentPermission && $childItem['controller']->userHasPermission());
|
||||
|
||||
Arr::set($this->menu, "$parentSlug.children.$childSlug.name", $childItem['controller']->getName());
|
||||
|
||||
$childRouteName = $this->adminRouteNamePrefix . $childItem['routeName'];
|
||||
$link = Route::has($childRouteName) ? route($childRouteName) : '#';
|
||||
Arr::set($this->menu, "$parentSlug.children.$childSlug.link", $link);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* 設定選單項目
|
||||
*
|
||||
* @param $menu
|
||||
*/
|
||||
public function setMenu($menu)
|
||||
{
|
||||
$this->menu = $menu;
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,4 +6,51 @@ return [
|
||||
'route' => 'adm',
|
||||
// 後台的Route Name前綴
|
||||
'route_name_prefix' => 'admin.',
|
||||
// 後台選單項目
|
||||
'menuItems' => [
|
||||
[
|
||||
'type' => 'item',
|
||||
'controller' => Menu\Options\OptionsMenuItemController::class,
|
||||
'children' => [
|
||||
Menu\Options\Children\GeneralMenuItemController::class,
|
||||
Menu\Options\Children\PlatformMenuItemController::class,
|
||||
Menu\Options\Children\DevelopmentMenuItemController::class,
|
||||
]
|
||||
],
|
||||
],
|
||||
// 設定的項目
|
||||
'options' => [
|
||||
'general' => [
|
||||
'permission' => 'admin manage options general',
|
||||
'fields' => [
|
||||
'site_name' => [
|
||||
'label' => 'siteName',
|
||||
'validator' => 'required',
|
||||
'required' => true
|
||||
],
|
||||
'site_description' => [
|
||||
'label' => 'siteDescription'
|
||||
],
|
||||
'block_search_indexing' => [
|
||||
'label' => 'blockSearchEngineIndexing',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'google_api_key' => [
|
||||
'label' => 'googleApiKey',
|
||||
],
|
||||
'fb_app_id' => [
|
||||
'label' => 'fbAppId'
|
||||
],
|
||||
]
|
||||
],
|
||||
'platform' => [
|
||||
'permission' => 'admin manage options platform',
|
||||
'fields' => [
|
||||
]
|
||||
],
|
||||
'development' => [
|
||||
'permission' => 'admin manage options development'
|
||||
],
|
||||
]
|
||||
|
||||
];
|
||||
|
||||
@ -177,6 +177,8 @@ return [
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\SingletonServiceProvider::class,
|
||||
App\Providers\AdminMenuServiceProvider::class,
|
||||
App\Providers\OptionRouteServiceProvider::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|
||||
@ -10,8 +10,37 @@ return [
|
||||
'name' => 'admin area',
|
||||
'displayName' => 'adminArea',
|
||||
'assignTo' => [
|
||||
'administrator', 'editor'
|
||||
'editor'
|
||||
]
|
||||
],
|
||||
]
|
||||
[
|
||||
//檢視設定頁面
|
||||
'name' => 'admin view options',
|
||||
'displayName' => 'adminViewOptions',
|
||||
'assignTo' => []
|
||||
],
|
||||
[
|
||||
//管理一般設定
|
||||
'name' => 'admin manage options general',
|
||||
'displayName' => 'adminManageOptionsGeneral',
|
||||
'assignTo' => []
|
||||
],
|
||||
[
|
||||
//管理平台設定
|
||||
'name' => 'admin manage options platform',
|
||||
'displayName' => 'adminManageOptionsPlatform',
|
||||
'assignTo' => []
|
||||
],
|
||||
[
|
||||
//管理開發設定的權限
|
||||
'name' => 'admin manage options development',
|
||||
'displayName' => 'adminManageOptionsDevelopment',
|
||||
'assignTo' => []
|
||||
],
|
||||
],
|
||||
// 預設的設定值
|
||||
'options' => [
|
||||
'site_name' => config('app.name'),
|
||||
'block_search_indexing' => true
|
||||
],
|
||||
];
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateOptionsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('options', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('key')->unique()->comment('選項名稱');
|
||||
$table->longText('value')->comment('選項值');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('options');
|
||||
}
|
||||
}
|
||||
@ -12,5 +12,6 @@ class DatabaseSeeder extends Seeder
|
||||
public function run()
|
||||
{
|
||||
$this->call(RolesAndPermissionsSeeder::class);
|
||||
$this->call(OptionsSeeder::class);
|
||||
}
|
||||
}
|
||||
|
||||
21
database/seeds/Preset/OptionsSeeder.php
Normal file
21
database/seeds/Preset/OptionsSeeder.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use \App\Repositories\OptionRepository;
|
||||
|
||||
class OptionsSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$options = config('data-presets.options');
|
||||
|
||||
$optionRepo = app(OptionRepository::class);
|
||||
|
||||
$optionRepo->setOptions($options);
|
||||
}
|
||||
}
|
||||
11
resources/lang/en/adminMenu.php
Normal file
11
resources/lang/en/adminMenu.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
return array(
|
||||
'items' => [
|
||||
'options' => [
|
||||
'options' => 'Options',
|
||||
'general' => 'General',
|
||||
'development' => 'Development',
|
||||
'platform' => 'Platform',
|
||||
],
|
||||
],
|
||||
);
|
||||
12
resources/lang/en/adminOptionLabels.php
Normal file
12
resources/lang/en/adminOptionLabels.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
return [
|
||||
'siteName' => 'Site Name',
|
||||
'siteDescription' => 'Site Description',
|
||||
'blockSearchEngineIndexing' => 'Block Search Engine Indexing',
|
||||
'googleApiKey' => 'Google API Key',
|
||||
'fbAppId' => 'Facebook APP ID',
|
||||
'categoryName' => 'Category Name',
|
||||
'parentCategory' => 'Parent Category',
|
||||
'featureImage' => 'Feature Image',
|
||||
'showInFrontSearch' => 'Show In Searchbar List',
|
||||
];
|
||||
9
resources/lang/en/adminPageHeader.php
Normal file
9
resources/lang/en/adminPageHeader.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'options' => [
|
||||
'general' => 'General Settings',
|
||||
'development' => 'Development Settings',
|
||||
'platform' => 'Platform Settings',
|
||||
],
|
||||
];
|
||||
6
resources/lang/en/form.php
Normal file
6
resources/lang/en/form.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
return [
|
||||
'buttons' => [
|
||||
'update' => 'Update'
|
||||
]
|
||||
];
|
||||
4
resources/lang/en/message.php
Normal file
4
resources/lang/en/message.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
'success' => 'Success'
|
||||
];
|
||||
2
resources/views/admin/components/navItems.blade.php
Normal file
2
resources/views/admin/components/navItems.blade.php
Normal file
@ -0,0 +1,2 @@
|
||||
@inject('adminMenuItemPresenter', App\Presenters\Admin\MainMenuItemPresenter)
|
||||
{{ $adminMenuItemPresenter->render() }}
|
||||
@ -1,3 +1,9 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', 'Admin Area')
|
||||
|
||||
@section('admin-page-content')
|
||||
<pre>
|
||||
{{ print_r(app('AdminMenu')->getMenu()) }}
|
||||
</pre>
|
||||
@endsection
|
||||
|
||||
@ -51,6 +51,7 @@
|
||||
<div class="sidebar">
|
||||
<nav class="sidebar-nav">
|
||||
<ul class="nav">
|
||||
@include('admin.components.navItems')
|
||||
</ul>
|
||||
</nav>
|
||||
<button class="sidebar-minimizer brand-minimizer" type="button"></button>
|
||||
|
||||
3
resources/views/admin/menu/options/development.blade.php
Normal file
3
resources/views/admin/menu/options/development.blade.php
Normal file
@ -0,0 +1,3 @@
|
||||
@include('admin.menu.options.layout', [
|
||||
'slug' => 'development'
|
||||
])
|
||||
3
resources/views/admin/menu/options/general.blade.php
Normal file
3
resources/views/admin/menu/options/general.blade.php
Normal file
@ -0,0 +1,3 @@
|
||||
@include('admin.menu.options.layout', [
|
||||
'slug' => 'general'
|
||||
])
|
||||
37
resources/views/admin/menu/options/layout.blade.php
Normal file
37
resources/views/admin/menu/options/layout.blade.php
Normal file
@ -0,0 +1,37 @@
|
||||
@extends('admin.layouts.app')
|
||||
@section('admin-page-content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1>@lang('adminPageHeader.options.' . $slug)</h1>
|
||||
</div>
|
||||
</div>
|
||||
@if($errors->any())
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{!! app('Html')->bs()->alert($errors->first(), 'danger') !!}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if(session()->has('updated'))
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{!! app('Html')->bs()->alert(session()->get('updated'), 'success') !!}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<form action="{{ route('admin.options.' . $slug . '.update') }}" method="post">
|
||||
@method('put')
|
||||
@csrf
|
||||
@inject('html', \App\Presenters\Admin\OptionFormFieldsPresenter)
|
||||
{!! $html->render($slug) !!}
|
||||
<div class="form-group row">
|
||||
<div class="col">
|
||||
<button type="submit" class="btn btn-primary">@lang('form.buttons.update')</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
3
resources/views/admin/menu/options/platform.blade.php
Normal file
3
resources/views/admin/menu/options/platform.blade.php
Normal file
@ -0,0 +1,3 @@
|
||||
@include('admin.menu.options.layout', [
|
||||
'slug' => 'platform'
|
||||
])
|
||||
Loading…
Reference in New Issue
Block a user